1#include <gtk/gtk.h>
2
3#include "gsk/gl/gskglrenderer.h"
4
5#define N 20
6
7static GskRenderer *gl_renderer = NULL;
8
9typedef struct _TextureBuilder TextureBuilder;
10
11typedef enum {
12 TEXTURE_METHOD_LOCAL,
13 TEXTURE_METHOD_GL,
14 TEXTURE_METHOD_GL_RELEASED,
15 TEXTURE_METHOD_PNG,
16 TEXTURE_METHOD_PNG_PIXBUF,
17 TEXTURE_METHOD_TIFF,
18 TEXTURE_METHOD_TIFF_PIXBUF,
19
20 N_TEXTURE_METHODS
21} TextureMethod;
22
23struct _TextureBuilder
24{
25 GdkMemoryFormat format;
26 int width;
27 int height;
28
29 guchar *pixels;
30 gsize stride;
31 gsize offset;
32};
33
34static gsize
35gdk_memory_format_bytes_per_pixel (GdkMemoryFormat format)
36{
37 switch (format)
38 {
39 case GDK_MEMORY_R8G8B8:
40 case GDK_MEMORY_B8G8R8:
41 return 3;
42
43 case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED:
44 case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED:
45 case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED:
46 case GDK_MEMORY_B8G8R8A8:
47 case GDK_MEMORY_A8R8G8B8:
48 case GDK_MEMORY_R8G8B8A8:
49 case GDK_MEMORY_A8B8G8R8:
50 return 4;
51
52 case GDK_MEMORY_R16G16B16:
53 case GDK_MEMORY_R16G16B16_FLOAT:
54 return 6;
55
56 case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED:
57 case GDK_MEMORY_R16G16B16A16:
58 case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED:
59 case GDK_MEMORY_R16G16B16A16_FLOAT:
60 return 8;
61
62 case GDK_MEMORY_R32G32B32_FLOAT:
63 return 12;
64
65 case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
66 case GDK_MEMORY_R32G32B32A32_FLOAT:
67 return 16;
68
69 case GDK_MEMORY_N_FORMATS:
70 default:
71 g_assert_not_reached ();
72 return 4;
73 }
74}
75
76static gboolean
77gdk_memory_format_has_alpha (GdkMemoryFormat format)
78{
79 switch (format)
80 {
81 case GDK_MEMORY_R8G8B8:
82 case GDK_MEMORY_B8G8R8:
83 case GDK_MEMORY_R16G16B16:
84 case GDK_MEMORY_R16G16B16_FLOAT:
85 case GDK_MEMORY_R32G32B32_FLOAT:
86 return FALSE;
87
88 case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED:
89 case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED:
90 case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED:
91 case GDK_MEMORY_B8G8R8A8:
92 case GDK_MEMORY_A8R8G8B8:
93 case GDK_MEMORY_R8G8B8A8:
94 case GDK_MEMORY_A8B8G8R8:
95 case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED:
96 case GDK_MEMORY_R16G16B16A16:
97 case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED:
98 case GDK_MEMORY_R16G16B16A16_FLOAT:
99 case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
100 case GDK_MEMORY_R32G32B32A32_FLOAT:
101 return TRUE;
102
103 case GDK_MEMORY_N_FORMATS:
104 default:
105 g_assert_not_reached ();
106 return TRUE;
107 }
108}
109
110static gpointer
111encode (GdkMemoryFormat format,
112 TextureMethod method)
113{
114 return GSIZE_TO_POINTER (method * GDK_MEMORY_N_FORMATS + format);
115}
116
117static void
118decode (gconstpointer data,
119 GdkMemoryFormat *format,
120 TextureMethod *method)
121{
122 gsize value = GPOINTER_TO_SIZE (data);
123
124 *format = value % GDK_MEMORY_N_FORMATS;
125 value /= GDK_MEMORY_N_FORMATS;
126
127 *method = value;
128}
129
130static void
131texture_builder_init (TextureBuilder *builder,
132 GdkMemoryFormat format,
133 int width,
134 int height)
135{
136 gsize extra_stride;
137
138 builder->format = format;
139 builder->width = width;
140 builder->height = height;
141
142 extra_stride = g_test_rand_bit() ? g_test_rand_int_range (begin: 0, end: 16) : 0;
143 builder->offset = g_test_rand_bit() ? g_test_rand_int_range (begin: 0, end: 128) : 0;
144 builder->stride = width * gdk_memory_format_bytes_per_pixel (format) + extra_stride;
145 builder->pixels = g_malloc0 (n_bytes: builder->offset + builder->stride * height);
146}
147
148static GdkTexture *
149texture_builder_finish (TextureBuilder *builder)
150{
151 GBytes *bytes;
152 GdkTexture *texture;
153
154 bytes = g_bytes_new_with_free_func (data: builder->pixels + builder->offset,
155 size: builder->height * builder->stride,
156 free_func: g_free,
157 user_data: builder->pixels);
158 texture = gdk_memory_texture_new (width: builder->width,
159 height: builder->height,
160 format: builder->format,
161 bytes,
162 stride: builder->stride);
163 g_bytes_unref (bytes);
164
165 return texture;
166}
167
168static inline void
169set_pixel_u8 (guchar *data,
170 int r,
171 int g,
172 int b,
173 int a,
174 gboolean premultiply,
175 const GdkRGBA *color)
176{
177 if (a >= 0)
178 data[a] = CLAMP (color->alpha * 256.f, 0.f, 255.f);
179 if (premultiply)
180 {
181 data[r] = CLAMP (color->red * color->alpha * 256.f, 0.f, 255.f);
182 data[g] = CLAMP (color->green * color->alpha * 256.f, 0.f, 255.f);
183 data[b] = CLAMP (color->blue * color->alpha * 256.f, 0.f, 255.f);
184 }
185 else
186 {
187 data[r] = CLAMP (color->red * 256.f, 0.f, 255.f);
188 data[g] = CLAMP (color->green * 256.f, 0.f, 255.f);
189 data[b] = CLAMP (color->blue * 256.f, 0.f, 255.f);
190 }
191}
192
193static inline guint16
194float_to_half (const float x)
195{
196 const guint b = *(guint*)&x+0x00001000; // round-to-nearest-even
197 const guint e = (b&0x7F800000)>>23; // exponent
198 const guint m = b&0x007FFFFF; // mantissa
199 return (b&0x80000000)>>16 | (e>112)*((((e-112)<<10)&0x7C00)|m>>13) | ((e<113)&(e>101))*((((0x007FF000+m)>>(125-e))+1)>>1) | (e>143)*0x7FFF; // sign : normalized : denormalized : saturate
200}
201
202static void
203texture_builder_set_pixel (TextureBuilder *builder,
204 int x,
205 int y,
206 const GdkRGBA *color)
207{
208 guchar *data;
209
210 g_assert_cmpint (x, >=, 0);
211 g_assert_cmpint (x, <, builder->width);
212 g_assert_cmpint (y, >=, 0);
213 g_assert_cmpint (y, <, builder->height);
214
215 data = builder->pixels
216 + builder->offset
217 + y * builder->stride
218 + x * gdk_memory_format_bytes_per_pixel (format: builder->format);
219
220 switch (builder->format)
221 {
222 case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED:
223 set_pixel_u8 (data, r: 2, g: 1, b: 0, a: 3, TRUE, color);
224 break;
225 case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED:
226 set_pixel_u8 (data, r: 1, g: 2, b: 3, a: 0, TRUE, color);
227 break;
228 case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED:
229 set_pixel_u8 (data, r: 0, g: 1, b: 2, a: 3, TRUE, color);
230 break;
231 case GDK_MEMORY_B8G8R8A8:
232 set_pixel_u8 (data, r: 2, g: 1, b: 0, a: 3, FALSE, color);
233 break;
234 case GDK_MEMORY_A8R8G8B8:
235 set_pixel_u8 (data, r: 1, g: 2, b: 3, a: 0, FALSE, color);
236 break;
237 case GDK_MEMORY_R8G8B8A8:
238 set_pixel_u8 (data, r: 0, g: 1, b: 2, a: 3, FALSE, color);
239 break;
240 case GDK_MEMORY_A8B8G8R8:
241 set_pixel_u8 (data, r: 3, g: 2, b: 1, a: 0, FALSE, color);
242 break;
243 case GDK_MEMORY_R8G8B8:
244 set_pixel_u8 (data, r: 0, g: 1, b: 2, a: -1, TRUE, color);
245 break;
246 case GDK_MEMORY_B8G8R8:
247 set_pixel_u8 (data, r: 2, g: 1, b: 0, a: -1, TRUE, color);
248 break;
249 case GDK_MEMORY_R16G16B16:
250 {
251 guint16 pixels[3] = {
252 CLAMP (color->red * color->alpha * 65536.f, 0, 65535.f),
253 CLAMP (color->green * color->alpha * 65536.f, 0, 65535.f),
254 CLAMP (color->blue * color->alpha * 65536.f, 0, 65535.f),
255 };
256 memcpy (dest: data, src: pixels, n: 3 * sizeof (guint16));
257 }
258 break;
259 case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED:
260 {
261 guint16 pixels[4] = {
262 CLAMP (color->red * color->alpha * 65536.f, 0, 65535.f),
263 CLAMP (color->green * color->alpha * 65536.f, 0, 65535.f),
264 CLAMP (color->blue * color->alpha * 65536.f, 0, 65535.f),
265 CLAMP (color->alpha * 65536.f, 0, 65535.f),
266 };
267 memcpy (dest: data, src: pixels, n: 4 * sizeof (guint16));
268 }
269 break;
270 case GDK_MEMORY_R16G16B16A16:
271 {
272 guint16 pixels[4] = {
273 CLAMP (color->red * 65536.f, 0, 65535.f),
274 CLAMP (color->green * 65536.f, 0, 65535.f),
275 CLAMP (color->blue * 65536.f, 0, 65535.f),
276 CLAMP (color->alpha * 65536.f, 0, 65535.f),
277 };
278 memcpy (dest: data, src: pixels, n: 4 * sizeof (guint16));
279 }
280 break;
281 case GDK_MEMORY_R16G16B16_FLOAT:
282 {
283 guint16 pixels[3] = {
284 float_to_half (x: color->red * color->alpha),
285 float_to_half (x: color->green * color->alpha),
286 float_to_half (x: color->blue * color->alpha)
287 };
288 memcpy (dest: data, src: pixels, n: 3 * sizeof (guint16));
289 }
290 break;
291 case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED:
292 {
293 guint16 pixels[4] = {
294 float_to_half (x: color->red * color->alpha),
295 float_to_half (x: color->green * color->alpha),
296 float_to_half (x: color->blue * color->alpha),
297 float_to_half (x: color->alpha)
298 };
299 memcpy (dest: data, src: pixels, n: 4 * sizeof (guint16));
300 }
301 break;
302 case GDK_MEMORY_R16G16B16A16_FLOAT:
303 {
304 guint16 pixels[4] = {
305 float_to_half (x: color->red),
306 float_to_half (x: color->green),
307 float_to_half (x: color->blue),
308 float_to_half (x: color->alpha)
309 };
310 memcpy (dest: data, src: pixels, n: 4 * sizeof (guint16));
311 }
312 break;
313 case GDK_MEMORY_R32G32B32_FLOAT:
314 {
315 float pixels[3] = {
316 color->red * color->alpha,
317 color->green * color->alpha,
318 color->blue * color->alpha
319 };
320 memcpy (dest: data, src: pixels, n: 3 * sizeof (float));
321 }
322 break;
323 case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
324 {
325 float pixels[4] = {
326 color->red * color->alpha,
327 color->green * color->alpha,
328 color->blue * color->alpha,
329 color->alpha
330 };
331 memcpy (dest: data, src: pixels, n: 4 * sizeof (float));
332 }
333 break;
334 case GDK_MEMORY_R32G32B32A32_FLOAT:
335 {
336 float pixels[4] = {
337 color->red,
338 color->green,
339 color->blue,
340 color->alpha
341 };
342 memcpy (dest: data, src: pixels, n: 4 * sizeof (float));
343 }
344 break;
345 case GDK_MEMORY_N_FORMATS:
346 default:
347 g_assert_not_reached ();
348 break;
349 }
350}
351
352static void
353texture_builder_fill (TextureBuilder *builder,
354 const GdkRGBA *color)
355{
356 int x, y;
357
358 for (y = 0; y < builder->height; y++)
359 for (x = 0; x < builder->width; x++)
360 texture_builder_set_pixel (builder, x, y, color);
361}
362
363static void
364compare_textures (GdkTexture *expected,
365 GdkTexture *test,
366 gboolean has_alpha)
367{
368 guint32 *expected_data, *test_data;
369 int width, height;
370 int x, y;
371
372 g_assert_cmpint (gdk_texture_get_width (expected), ==, gdk_texture_get_width (test));
373 g_assert_cmpint (gdk_texture_get_height (expected), ==, gdk_texture_get_height (test));
374
375 width = gdk_texture_get_width (texture: expected);
376 height = gdk_texture_get_height (texture: expected);
377
378 expected_data = g_new (guint32, width * height);
379 gdk_texture_download (texture: expected, data: (guchar *) expected_data, stride: width * 4);
380
381 test_data = g_new (guint32, width * height);
382 gdk_texture_download (texture: test, data: (guchar *) test_data, stride: width * 4);
383
384 for (y = 0; y < height; y++)
385 {
386 for (x = 0; x < width; x++)
387 {
388 if (has_alpha)
389 g_assert_cmphex (expected_data[y * width + x], ==, test_data[y * width + x]);
390 else
391 g_assert_cmphex (expected_data[y * width + x] | 0xFF000000, ==, test_data[y * width + x]);
392 }
393 }
394
395 g_free (mem: expected_data);
396 g_free (mem: test_data);
397}
398
399static GdkTexture *
400upload_to_gl (GdkTexture *texture)
401{
402 GskRenderNode *node;
403 GdkTexture *result;
404
405 if (gl_renderer == NULL)
406 return texture;
407
408 node = gsk_texture_node_new (texture,
409 bounds: &GRAPHENE_RECT_INIT(
410 0, 0,
411 gdk_texture_get_width (texture),
412 gdk_texture_get_height (texture)
413 ));
414 result = gsk_renderer_render_texture (renderer: gl_renderer, root: node, NULL);
415 gsk_render_node_unref (node);
416 g_object_unref (object: texture);
417
418 return result;
419}
420
421static GdkTexture *
422create_texture (GdkMemoryFormat format,
423 TextureMethod method,
424 int width,
425 int height,
426 const GdkRGBA *color)
427{
428 TextureBuilder builder;
429 GdkTexture *texture;
430
431 texture_builder_init (builder: &builder, format, width, height);
432 texture_builder_fill (builder: &builder, color);
433
434 texture = texture_builder_finish (builder: &builder);
435
436 switch (method)
437 {
438 case TEXTURE_METHOD_LOCAL:
439 break;
440
441 case TEXTURE_METHOD_GL:
442 texture = upload_to_gl (texture);
443 break;
444
445 case TEXTURE_METHOD_GL_RELEASED:
446 texture = upload_to_gl (texture);
447 if (GDK_IS_GL_TEXTURE (texture))
448 gdk_gl_texture_release (GDK_GL_TEXTURE (texture));
449 break;
450
451 case TEXTURE_METHOD_PNG:
452 {
453 GBytes *bytes = gdk_texture_save_to_png_bytes (texture);
454 g_assert (bytes);
455 g_object_unref (object: texture);
456 texture = gdk_texture_new_from_bytes (bytes, NULL);
457 g_assert (texture);
458 g_bytes_unref (bytes);
459 }
460 break;
461
462 case TEXTURE_METHOD_PNG_PIXBUF:
463 {
464 GInputStream *stream;
465 GdkPixbuf *pixbuf;
466 GBytes *bytes;
467
468 bytes = gdk_texture_save_to_png_bytes (texture);
469 g_assert (bytes);
470 g_object_unref (object: texture);
471 stream = g_memory_input_stream_new_from_bytes (bytes);
472 pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, NULL);
473 g_object_unref (object: stream);
474 g_assert (pixbuf);
475 texture = gdk_texture_new_for_pixbuf (pixbuf);
476 g_assert (texture);
477 g_object_unref (object: pixbuf);
478 g_bytes_unref (bytes);
479 }
480 break;
481
482 case TEXTURE_METHOD_TIFF:
483 {
484 GBytes *bytes = gdk_texture_save_to_tiff_bytes (texture);
485 g_assert (bytes);
486 g_object_unref (object: texture);
487 texture = gdk_texture_new_from_bytes (bytes, NULL);
488 g_assert (texture);
489 g_bytes_unref (bytes);
490 }
491 break;
492
493 case TEXTURE_METHOD_TIFF_PIXBUF:
494 {
495 GInputStream *stream;
496 GdkPixbuf *pixbuf;
497 GBytes *bytes;
498
499 bytes = gdk_texture_save_to_png_bytes (texture);
500 g_assert (bytes);
501 g_object_unref (object: texture);
502 stream = g_memory_input_stream_new_from_bytes (bytes);
503 pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, NULL);
504 g_object_unref (object: stream);
505 g_assert (pixbuf);
506 texture = gdk_texture_new_for_pixbuf (pixbuf);
507 g_assert (texture);
508 g_object_unref (object: pixbuf);
509 g_bytes_unref (bytes);
510 }
511 break;
512
513 case N_TEXTURE_METHODS:
514 default:
515 g_assert_not_reached ();
516 break;
517 }
518
519 return texture;
520}
521
522static void
523create_random_color (GdkRGBA *color)
524{
525 /* Generate colors so that premultiplying will result in values in steps of 1/15th */
526 color->red = g_test_rand_int_range (begin: 0, end: 6) / 5.f;
527 color->green = g_test_rand_int_range (begin: 0, end: 6) / 5.f;
528 color->blue = g_test_rand_int_range (begin: 0, end: 6) / 5.f;
529 color->alpha = g_test_rand_int_range (begin: 0, end: 4) / 3.f;
530}
531
532static void
533test_download_1x1 (gconstpointer data)
534{
535 GdkMemoryFormat format;
536 TextureMethod method;
537 GdkTexture *expected, *test;
538 gsize i;
539
540 decode (data, format: &format, method: &method);
541
542 for (i = 0; i < N; i++)
543 {
544 GdkRGBA color;
545
546 create_random_color (color: &color);
547 expected = create_texture (GDK_MEMORY_DEFAULT, method: TEXTURE_METHOD_LOCAL, width: 1, height: 1, color: &color);
548 test = create_texture (format, method, width: 1, height: 1, color: &color);
549
550 compare_textures (expected, test, has_alpha: gdk_memory_format_has_alpha (format));
551
552 g_object_unref (object: expected);
553 g_object_unref (object: test);
554 }
555}
556
557static void
558test_download_4x4 (gconstpointer data)
559{
560 GdkMemoryFormat format;
561 TextureMethod method;
562 GdkTexture *expected, *test;
563 gsize i;
564
565 decode (data, format: &format, method: &method);
566
567 for (i = 0; i < N; i++)
568 {
569 GdkRGBA color;
570
571 create_random_color (color: &color);
572 expected = create_texture (GDK_MEMORY_DEFAULT, method: TEXTURE_METHOD_LOCAL, width: 4, height: 4, color: &color);
573 test = create_texture (format, method, width: 4, height: 4, color: &color);
574
575 compare_textures (expected, test, has_alpha: gdk_memory_format_has_alpha (format));
576
577 g_object_unref (object: expected);
578 g_object_unref (object: test);
579 }
580}
581
582/* larger than what NGL puts into the icon cache */
583static void
584test_download_192x192 (gconstpointer data)
585{
586 GdkMemoryFormat format;
587 TextureMethod method;
588 GdkTexture *expected, *test;
589 GdkRGBA color;
590
591 decode (data, format: &format, method: &method);
592
593 create_random_color (color: &color);
594 expected = create_texture (GDK_MEMORY_DEFAULT, method: TEXTURE_METHOD_LOCAL, width: 192, height: 192, color: &color);
595 test = create_texture (format, method, width: 192, height: 192, color: &color);
596
597 compare_textures (expected, test, has_alpha: gdk_memory_format_has_alpha (format));
598
599 g_object_unref (object: expected);
600 g_object_unref (object: test);
601}
602
603static void
604add_test (const char *name,
605 GTestDataFunc func)
606{
607 GdkMemoryFormat format;
608 TextureMethod method;
609 GEnumClass *enum_class;
610
611 enum_class = g_type_class_ref (type: GDK_TYPE_MEMORY_FORMAT);
612
613 for (format = 0; format < GDK_MEMORY_N_FORMATS; format++)
614 {
615 for (method = 0; method < N_TEXTURE_METHODS; method++)
616 {
617 const char *method_names[N_TEXTURE_METHODS] = { "local", "gl", "gl-released", "png", "png-pixbuf", "tiff", "tiff-pixbuf" };
618 char *test_name = g_strdup_printf (format: "%s/%s/%s",
619 name,
620 g_enum_get_value (enum_class, value: format)->value_nick,
621 method_names[method]);
622 g_test_add_data_func_full (testpath: test_name, test_data: encode (format, method), test_func: func, NULL);
623 g_free (mem: test_name);
624 }
625 }
626}
627
628int
629main (int argc, char *argv[])
630{
631 int result;
632
633 gtk_test_init (argcp: &argc, argvp: &argv, NULL);
634
635 add_test (name: "/memorytexture/download_1x1", func: test_download_1x1);
636 add_test (name: "/memorytexture/download_4x4", func: test_download_4x4);
637 add_test (name: "/memorytexture/download_192x192", func: test_download_192x192);
638
639 gl_renderer = gsk_gl_renderer_new ();
640 if (!gsk_renderer_realize (renderer: gl_renderer, NULL, NULL))
641 {
642 g_clear_object (&gl_renderer);
643 }
644
645 result = g_test_run ();
646
647 if (gl_renderer)
648 {
649 gsk_renderer_unrealize (renderer: gl_renderer);
650 g_clear_object (&gl_renderer);
651 }
652 gdk_gl_context_clear_current ();
653
654 return result;
655}
656

source code of gtk/testsuite/gdk/memorytexture.c