1 /* cairo - a vector graphics library with display and print output
3 * Copyright © 2009 Eric Anholt
4 * Copyright © 2009 Chris Wilson
5 * Copyright © 2005,2010 Red Hat, Inc
7 * This library is free software; you can redistribute it and/or
8 * modify it either under the terms of the GNU Lesser General Public
9 * License version 2.1 as published by the Free Software Foundation
10 * (the "LGPL") or, at your option, under the terms of the Mozilla
11 * Public License Version 1.1 (the "MPL"). If you do not alter this
12 * notice, a recipient may use your version of this file under either
13 * the MPL or the LGPL.
15 * You should have received a copy of the LGPL along with this library
16 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
18 * You should have received a copy of the MPL along with this library
19 * in the file COPYING-MPL-1.1
21 * The contents of this file are subject to the Mozilla Public License
22 * Version 1.1 (the "License"); you may not use this file except in
23 * compliance with the License. You may obtain a copy of the License at
24 * http://www.mozilla.org/MPL/
26 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
27 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
28 * the specific language governing rights and limitations.
30 * The Original Code is the cairo graphics library.
32 * The Initial Developer of the Original Code is Red Hat, Inc.
35 * Benjamin Otte <otte@gnome.org>
36 * Carl Worth <cworth@cworth.org>
37 * Chris Wilson <chris@chris-wilson.co.uk>
38 * Eric Anholt <eric@anholt.net>
43 #include "cairo-error-private.h"
44 #include "cairo-gl-gradient-private.h"
45 #include "cairo-gl-private.h"
49 _cairo_gl_gradient_sample_width (unsigned int n_stops
,
50 const cairo_gradient_stop_t
*stops
)
56 for (n
= 1; n
< n_stops
; n
++) {
57 double dx
= stops
[n
].offset
- stops
[n
-1].offset
;
62 return 1024; /* we need to emulate an infinitely sharp step */
64 max
= fabs (stops
[n
].color
.red
- stops
[n
-1].color
.red
);
66 delta
= fabs (stops
[n
].color
.green
- stops
[n
-1].color
.green
);
70 delta
= fabs (stops
[n
].color
.blue
- stops
[n
-1].color
.blue
);
74 delta
= fabs (stops
[n
].color
.alpha
- stops
[n
-1].color
.alpha
);
78 ramp
= 128 * max
/ dx
;
83 return (width
+ 7) & -8;
86 static uint8_t premultiply(double c
, double a
)
92 static uint32_t color_stop_to_pixel(const cairo_gradient_stop_t
*stop
)
96 a
= stop
->color
.alpha_short
>> 8;
97 r
= premultiply(stop
->color
.red
, stop
->color
.alpha
);
98 g
= premultiply(stop
->color
.green
, stop
->color
.alpha
);
99 b
= premultiply(stop
->color
.blue
, stop
->color
.alpha
);
101 if (_cairo_is_little_endian ())
102 return a
<< 24 | r
<< 16 | g
<< 8 | b
<< 0;
104 return a
<< 0 | r
<< 8 | g
<< 16 | b
<< 24;
107 static cairo_status_t
108 _cairo_gl_gradient_render (const cairo_gl_context_t
*ctx
,
109 unsigned int n_stops
,
110 const cairo_gradient_stop_t
*stops
,
114 pixman_image_t
*gradient
, *image
;
115 pixman_gradient_stop_t pixman_stops_stack
[32];
116 pixman_gradient_stop_t
*pixman_stops
;
117 pixman_point_fixed_t p1
, p2
;
119 pixman_format_code_t gradient_pixman_format
;
122 * Ensure that the order of the gradient's components in memory is BGRA.
123 * This is done so that the gradient's pixel data is always suitable for
124 * texture upload using format=GL_BGRA and type=GL_UNSIGNED_BYTE.
126 if (_cairo_is_little_endian ())
127 gradient_pixman_format
= PIXMAN_a8r8g8b8
;
129 gradient_pixman_format
= PIXMAN_b8g8r8a8
;
131 pixman_stops
= pixman_stops_stack
;
132 if (unlikely (n_stops
> ARRAY_LENGTH (pixman_stops_stack
))) {
133 pixman_stops
= _cairo_malloc_ab (n_stops
,
134 sizeof (pixman_gradient_stop_t
));
135 if (unlikely (pixman_stops
== NULL
))
136 return _cairo_error (CAIRO_STATUS_NO_MEMORY
);
139 for (i
= 0; i
< n_stops
; i
++) {
140 pixman_stops
[i
].x
= _cairo_fixed_16_16_from_double (stops
[i
].offset
);
141 pixman_stops
[i
].color
.red
= stops
[i
].color
.red_short
;
142 pixman_stops
[i
].color
.green
= stops
[i
].color
.green_short
;
143 pixman_stops
[i
].color
.blue
= stops
[i
].color
.blue_short
;
144 pixman_stops
[i
].color
.alpha
= stops
[i
].color
.alpha_short
;
147 p1
.x
= _cairo_fixed_16_16_from_double (0.5);
149 p2
.x
= _cairo_fixed_16_16_from_double (width
- 0.5);
152 gradient
= pixman_image_create_linear_gradient (&p1
, &p2
,
155 if (pixman_stops
!= pixman_stops_stack
)
158 if (unlikely (gradient
== NULL
))
159 return _cairo_error (CAIRO_STATUS_NO_MEMORY
);
161 pixman_image_set_filter (gradient
, PIXMAN_FILTER_BILINEAR
, NULL
, 0);
162 pixman_image_set_repeat (gradient
, PIXMAN_REPEAT_PAD
);
164 image
= pixman_image_create_bits (gradient_pixman_format
, width
, 1,
165 bytes
, sizeof(uint32_t)*width
);
166 if (unlikely (image
== NULL
)) {
167 pixman_image_unref (gradient
);
168 return _cairo_error (CAIRO_STATUS_NO_MEMORY
);
171 pixman_image_composite32 (PIXMAN_OP_SRC
,
172 gradient
, NULL
, image
,
178 pixman_image_unref (gradient
);
179 pixman_image_unref (image
);
181 /* We need to fudge pixel 0 to hold the left-most color stop and not
182 * the neareset stop to the zeroth pixel centre in order to correctly
183 * populate the border color. For completeness, do both edges.
185 ((uint32_t*)bytes
)[0] = color_stop_to_pixel(&stops
[0]);
186 ((uint32_t*)bytes
)[width
-1] = color_stop_to_pixel(&stops
[n_stops
-1]);
188 return CAIRO_STATUS_SUCCESS
;
192 _cairo_gl_gradient_hash (unsigned int n_stops
,
193 const cairo_gradient_stop_t
*stops
)
195 return _cairo_hash_bytes (n_stops
,
197 sizeof (cairo_gradient_stop_t
) * n_stops
);
200 static cairo_gl_gradient_t
*
201 _cairo_gl_gradient_lookup (cairo_gl_context_t
*ctx
,
203 unsigned int n_stops
,
204 const cairo_gradient_stop_t
*stops
)
206 cairo_gl_gradient_t lookup
;
208 lookup
.cache_entry
.hash
= hash
,
209 lookup
.n_stops
= n_stops
;
210 lookup
.stops
= stops
;
212 return _cairo_cache_lookup (&ctx
->gradients
, &lookup
.cache_entry
);
216 _cairo_gl_gradient_equal (const void *key_a
, const void *key_b
)
218 const cairo_gl_gradient_t
*a
= key_a
;
219 const cairo_gl_gradient_t
*b
= key_b
;
221 if (a
->n_stops
!= b
->n_stops
)
224 return memcmp (a
->stops
, b
->stops
, a
->n_stops
* sizeof (cairo_gradient_stop_t
)) == 0;
228 _cairo_gl_gradient_create (cairo_gl_context_t
*ctx
,
229 unsigned int n_stops
,
230 const cairo_gradient_stop_t
*stops
,
231 cairo_gl_gradient_t
**gradient_out
)
234 cairo_gl_gradient_t
*gradient
;
235 cairo_status_t status
;
237 GLint internal_format
;
240 if ((unsigned int) ctx
->max_texture_size
/ 2 <= n_stops
)
241 return CAIRO_INT_STATUS_UNSUPPORTED
;
243 hash
= _cairo_gl_gradient_hash (n_stops
, stops
);
245 gradient
= _cairo_gl_gradient_lookup (ctx
, hash
, n_stops
, stops
);
247 *gradient_out
= _cairo_gl_gradient_reference (gradient
);
248 return CAIRO_STATUS_SUCCESS
;
251 gradient
= malloc (sizeof (cairo_gl_gradient_t
) + sizeof (cairo_gradient_stop_t
) * (n_stops
- 1));
252 if (gradient
== NULL
)
253 return _cairo_error (CAIRO_STATUS_NO_MEMORY
);
255 tex_width
= _cairo_gl_gradient_sample_width (n_stops
, stops
);
256 if (tex_width
> ctx
->max_texture_size
)
257 tex_width
= ctx
->max_texture_size
;
259 CAIRO_REFERENCE_COUNT_INIT (&gradient
->ref_count
, 2);
260 gradient
->cache_entry
.hash
= hash
;
261 gradient
->cache_entry
.size
= tex_width
;
262 gradient
->device
= &ctx
->base
;
263 gradient
->n_stops
= n_stops
;
264 gradient
->stops
= gradient
->stops_embedded
;
265 memcpy (gradient
->stops_embedded
, stops
, n_stops
* sizeof (cairo_gradient_stop_t
));
267 glGenTextures (1, &gradient
->tex
);
268 _cairo_gl_context_activate (ctx
, CAIRO_GL_TEX_TEMP
);
269 glBindTexture (ctx
->tex_target
, gradient
->tex
);
271 data
= _cairo_malloc_ab (tex_width
, sizeof (uint32_t));
272 if (unlikely (data
== NULL
)) {
273 status
= _cairo_error (CAIRO_STATUS_NO_MEMORY
);
274 goto cleanup_gradient
;
277 status
= _cairo_gl_gradient_render (ctx
, n_stops
, stops
, data
, tex_width
);
278 if (unlikely (status
))
282 * In OpenGL ES 2.0 no format conversion is allowed i.e. 'internalFormat'
283 * must match 'format' in glTexImage2D.
285 if (_cairo_gl_get_flavor () == CAIRO_GL_FLAVOR_ES
)
286 internal_format
= GL_BGRA
;
288 internal_format
= GL_RGBA
;
290 glTexImage2D (ctx
->tex_target
, 0, internal_format
, tex_width
, 1, 0,
291 GL_BGRA
, GL_UNSIGNED_BYTE
, data
);
295 /* we ignore errors here and just return an uncached gradient */
296 if (unlikely (_cairo_cache_insert (&ctx
->gradients
, &gradient
->cache_entry
)))
297 CAIRO_REFERENCE_COUNT_INIT (&gradient
->ref_count
, 1);
299 *gradient_out
= gradient
;
300 return CAIRO_STATUS_SUCCESS
;
309 cairo_gl_gradient_t
*
310 _cairo_gl_gradient_reference (cairo_gl_gradient_t
*gradient
)
312 assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&gradient
->ref_count
));
314 _cairo_reference_count_inc (&gradient
->ref_count
);
320 _cairo_gl_gradient_destroy (cairo_gl_gradient_t
*gradient
)
322 cairo_gl_context_t
*ctx
;
323 cairo_status_t ignore
;
325 assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&gradient
->ref_count
));
327 if (! _cairo_reference_count_dec_and_test (&gradient
->ref_count
))
330 if (_cairo_gl_context_acquire (gradient
->device
, &ctx
) == CAIRO_STATUS_SUCCESS
) {
331 /* The gradient my still be active in the last operation, so flush */
332 _cairo_gl_composite_flush (ctx
);
333 glDeleteTextures (1, &gradient
->tex
);
334 ignore
= _cairo_gl_context_release (ctx
, CAIRO_STATUS_SUCCESS
);