#include #include "gsk/gl/gskglrenderer.h" #define N 20 static GskRenderer *gl_renderer = NULL; typedef struct _TextureBuilder TextureBuilder; typedef enum { TEXTURE_METHOD_LOCAL, TEXTURE_METHOD_GL, TEXTURE_METHOD_GL_RELEASED, TEXTURE_METHOD_PNG, TEXTURE_METHOD_PNG_PIXBUF, TEXTURE_METHOD_TIFF, TEXTURE_METHOD_TIFF_PIXBUF, N_TEXTURE_METHODS } TextureMethod; struct _TextureBuilder { GdkMemoryFormat format; int width; int height; guchar *pixels; gsize stride; gsize offset; }; static gsize gdk_memory_format_bytes_per_pixel (GdkMemoryFormat format) { switch (format) { case GDK_MEMORY_R8G8B8: case GDK_MEMORY_B8G8R8: return 3; case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED: case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED: case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED: case GDK_MEMORY_B8G8R8A8: case GDK_MEMORY_A8R8G8B8: case GDK_MEMORY_R8G8B8A8: case GDK_MEMORY_A8B8G8R8: return 4; case GDK_MEMORY_R16G16B16: case GDK_MEMORY_R16G16B16_FLOAT: return 6; case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED: case GDK_MEMORY_R16G16B16A16: case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED: case GDK_MEMORY_R16G16B16A16_FLOAT: return 8; case GDK_MEMORY_R32G32B32_FLOAT: return 12; case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED: case GDK_MEMORY_R32G32B32A32_FLOAT: return 16; case GDK_MEMORY_N_FORMATS: default: g_assert_not_reached (); return 4; } } static gboolean gdk_memory_format_has_alpha (GdkMemoryFormat format) { switch (format) { case GDK_MEMORY_R8G8B8: case GDK_MEMORY_B8G8R8: case GDK_MEMORY_R16G16B16: case GDK_MEMORY_R16G16B16_FLOAT: case GDK_MEMORY_R32G32B32_FLOAT: return FALSE; case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED: case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED: case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED: case GDK_MEMORY_B8G8R8A8: case GDK_MEMORY_A8R8G8B8: case GDK_MEMORY_R8G8B8A8: case GDK_MEMORY_A8B8G8R8: case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED: case GDK_MEMORY_R16G16B16A16: case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED: case GDK_MEMORY_R16G16B16A16_FLOAT: case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED: case GDK_MEMORY_R32G32B32A32_FLOAT: return TRUE; case GDK_MEMORY_N_FORMATS: default: g_assert_not_reached (); return TRUE; } } static gpointer encode (GdkMemoryFormat format, TextureMethod method) { return GSIZE_TO_POINTER (method * GDK_MEMORY_N_FORMATS + format); } static void decode (gconstpointer data, GdkMemoryFormat *format, TextureMethod *method) { gsize value = GPOINTER_TO_SIZE (data); *format = value % GDK_MEMORY_N_FORMATS; value /= GDK_MEMORY_N_FORMATS; *method = value; } static void texture_builder_init (TextureBuilder *builder, GdkMemoryFormat format, int width, int height) { gsize extra_stride; builder->format = format; builder->width = width; builder->height = height; extra_stride = g_test_rand_bit() ? g_test_rand_int_range (0, 16) : 0; builder->offset = g_test_rand_bit() ? g_test_rand_int_range (0, 128) : 0; builder->stride = width * gdk_memory_format_bytes_per_pixel (format) + extra_stride; builder->pixels = g_malloc0 (builder->offset + builder->stride * height); } static GdkTexture * texture_builder_finish (TextureBuilder *builder) { GBytes *bytes; GdkTexture *texture; bytes = g_bytes_new_with_free_func (builder->pixels + builder->offset, builder->height * builder->stride, g_free, builder->pixels); texture = gdk_memory_texture_new (builder->width, builder->height, builder->format, bytes, builder->stride); g_bytes_unref (bytes); return texture; } static inline void set_pixel_u8 (guchar *data, int r, int g, int b, int a, gboolean premultiply, const GdkRGBA *color) { if (a >= 0) data[a] = CLAMP (color->alpha * 256.f, 0.f, 255.f); if (premultiply) { data[r] = CLAMP (color->red * color->alpha * 256.f, 0.f, 255.f); data[g] = CLAMP (color->green * color->alpha * 256.f, 0.f, 255.f); data[b] = CLAMP (color->blue * color->alpha * 256.f, 0.f, 255.f); } else { data[r] = CLAMP (color->red * 256.f, 0.f, 255.f); data[g] = CLAMP (color->green * 256.f, 0.f, 255.f); data[b] = CLAMP (color->blue * 256.f, 0.f, 255.f); } } static inline guint16 float_to_half (const float x) { const guint b = *(guint*)&x+0x00001000; // round-to-nearest-even const guint e = (b&0x7F800000)>>23; // exponent const guint m = b&0x007FFFFF; // mantissa 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 } static void texture_builder_set_pixel (TextureBuilder *builder, int x, int y, const GdkRGBA *color) { guchar *data; g_assert_cmpint (x, >=, 0); g_assert_cmpint (x, <, builder->width); g_assert_cmpint (y, >=, 0); g_assert_cmpint (y, <, builder->height); data = builder->pixels + builder->offset + y * builder->stride + x * gdk_memory_format_bytes_per_pixel (builder->format); switch (builder->format) { case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED: set_pixel_u8 (data, 2, 1, 0, 3, TRUE, color); break; case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED: set_pixel_u8 (data, 1, 2, 3, 0, TRUE, color); break; case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED: set_pixel_u8 (data, 0, 1, 2, 3, TRUE, color); break; case GDK_MEMORY_B8G8R8A8: set_pixel_u8 (data, 2, 1, 0, 3, FALSE, color); break; case GDK_MEMORY_A8R8G8B8: set_pixel_u8 (data, 1, 2, 3, 0, FALSE, color); break; case GDK_MEMORY_R8G8B8A8: set_pixel_u8 (data, 0, 1, 2, 3, FALSE, color); break; case GDK_MEMORY_A8B8G8R8: set_pixel_u8 (data, 3, 2, 1, 0, FALSE, color); break; case GDK_MEMORY_R8G8B8: set_pixel_u8 (data, 0, 1, 2, -1, TRUE, color); break; case GDK_MEMORY_B8G8R8: set_pixel_u8 (data, 2, 1, 0, -1, TRUE, color); break; case GDK_MEMORY_R16G16B16: { guint16 pixels[3] = { CLAMP (color->red * color->alpha * 65536.f, 0, 65535.f), CLAMP (color->green * color->alpha * 65536.f, 0, 65535.f), CLAMP (color->blue * color->alpha * 65536.f, 0, 65535.f), }; memcpy (data, pixels, 3 * sizeof (guint16)); } break; case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED: { guint16 pixels[4] = { CLAMP (color->red * color->alpha * 65536.f, 0, 65535.f), CLAMP (color->green * color->alpha * 65536.f, 0, 65535.f), CLAMP (color->blue * color->alpha * 65536.f, 0, 65535.f), CLAMP (color->alpha * 65536.f, 0, 65535.f), }; memcpy (data, pixels, 4 * sizeof (guint16)); } break; case GDK_MEMORY_R16G16B16A16: { guint16 pixels[4] = { CLAMP (color->red * 65536.f, 0, 65535.f), CLAMP (color->green * 65536.f, 0, 65535.f), CLAMP (color->blue * 65536.f, 0, 65535.f), CLAMP (color->alpha * 65536.f, 0, 65535.f), }; memcpy (data, pixels, 4 * sizeof (guint16)); } break; case GDK_MEMORY_R16G16B16_FLOAT: { guint16 pixels[3] = { float_to_half (color->red * color->alpha), float_to_half (color->green * color->alpha), float_to_half (color->blue * color->alpha) }; memcpy (data, pixels, 3 * sizeof (guint16)); } break; case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED: { guint16 pixels[4] = { float_to_half (color->red * color->alpha), float_to_half (color->green * color->alpha), float_to_half (color->blue * color->alpha), float_to_half (color->alpha) }; memcpy (data, pixels, 4 * sizeof (guint16)); } break; case GDK_MEMORY_R16G16B16A16_FLOAT: { guint16 pixels[4] = { float_to_half (color->red), float_to_half (color->green), float_to_half (color->blue), float_to_half (color->alpha) }; memcpy (data, pixels, 4 * sizeof (guint16)); } break; case GDK_MEMORY_R32G32B32_FLOAT: { float pixels[3] = { color->red * color->alpha, color->green * color->alpha, color->blue * color->alpha }; memcpy (data, pixels, 3 * sizeof (float)); } break; case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED: { float pixels[4] = { color->red * color->alpha, color->green * color->alpha, color->blue * color->alpha, color->alpha }; memcpy (data, pixels, 4 * sizeof (float)); } break; case GDK_MEMORY_R32G32B32A32_FLOAT: { float pixels[4] = { color->red, color->green, color->blue, color->alpha }; memcpy (data, pixels, 4 * sizeof (float)); } break; case GDK_MEMORY_N_FORMATS: default: g_assert_not_reached (); break; } } static void texture_builder_fill (TextureBuilder *builder, const GdkRGBA *color) { int x, y; for (y = 0; y < builder->height; y++) for (x = 0; x < builder->width; x++) texture_builder_set_pixel (builder, x, y, color); } static void compare_textures (GdkTexture *expected, GdkTexture *test, gboolean has_alpha) { guint32 *expected_data, *test_data; int width, height; int x, y; g_assert_cmpint (gdk_texture_get_width (expected), ==, gdk_texture_get_width (test)); g_assert_cmpint (gdk_texture_get_height (expected), ==, gdk_texture_get_height (test)); width = gdk_texture_get_width (expected); height = gdk_texture_get_height (expected); expected_data = g_new (guint32, width * height); gdk_texture_download (expected, (guchar *) expected_data, width * 4); test_data = g_new (guint32, width * height); gdk_texture_download (test, (guchar *) test_data, width * 4); for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { if (has_alpha) g_assert_cmphex (expected_data[y * width + x], ==, test_data[y * width + x]); else g_assert_cmphex (expected_data[y * width + x] | 0xFF000000, ==, test_data[y * width + x]); } } g_free (expected_data); g_free (test_data); } static GdkTexture * upload_to_gl (GdkTexture *texture) { GskRenderNode *node; GdkTexture *result; if (gl_renderer == NULL) return texture; node = gsk_texture_node_new (texture, &GRAPHENE_RECT_INIT( 0, 0, gdk_texture_get_width (texture), gdk_texture_get_height (texture) )); result = gsk_renderer_render_texture (gl_renderer, node, NULL); gsk_render_node_unref (node); g_object_unref (texture); return result; } static GdkTexture * create_texture (GdkMemoryFormat format, TextureMethod method, int width, int height, const GdkRGBA *color) { TextureBuilder builder; GdkTexture *texture; texture_builder_init (&builder, format, width, height); texture_builder_fill (&builder, color); texture = texture_builder_finish (&builder); switch (method) { case TEXTURE_METHOD_LOCAL: break; case TEXTURE_METHOD_GL: texture = upload_to_gl (texture); break; case TEXTURE_METHOD_GL_RELEASED: texture = upload_to_gl (texture); if (GDK_IS_GL_TEXTURE (texture)) gdk_gl_texture_release (GDK_GL_TEXTURE (texture)); break; case TEXTURE_METHOD_PNG: { GBytes *bytes = gdk_texture_save_to_png_bytes (texture); g_assert (bytes); g_object_unref (texture); texture = gdk_texture_new_from_bytes (bytes, NULL); g_assert (texture); g_bytes_unref (bytes); } break; case TEXTURE_METHOD_PNG_PIXBUF: { GInputStream *stream; GdkPixbuf *pixbuf; GBytes *bytes; bytes = gdk_texture_save_to_png_bytes (texture); g_assert (bytes); g_object_unref (texture); stream = g_memory_input_stream_new_from_bytes (bytes); pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, NULL); g_object_unref (stream); g_assert (pixbuf); texture = gdk_texture_new_for_pixbuf (pixbuf); g_assert (texture); g_object_unref (pixbuf); g_bytes_unref (bytes); } break; case TEXTURE_METHOD_TIFF: { GBytes *bytes = gdk_texture_save_to_tiff_bytes (texture); g_assert (bytes); g_object_unref (texture); texture = gdk_texture_new_from_bytes (bytes, NULL); g_assert (texture); g_bytes_unref (bytes); } break; case TEXTURE_METHOD_TIFF_PIXBUF: { GInputStream *stream; GdkPixbuf *pixbuf; GBytes *bytes; bytes = gdk_texture_save_to_png_bytes (texture); g_assert (bytes); g_object_unref (texture); stream = g_memory_input_stream_new_from_bytes (bytes); pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, NULL); g_object_unref (stream); g_assert (pixbuf); texture = gdk_texture_new_for_pixbuf (pixbuf); g_assert (texture); g_object_unref (pixbuf); g_bytes_unref (bytes); } break; case N_TEXTURE_METHODS: default: g_assert_not_reached (); break; } return texture; } static void create_random_color (GdkRGBA *color) { /* Generate colors so that premultiplying will result in values in steps of 1/15th */ color->red = g_test_rand_int_range (0, 6) / 5.f; color->green = g_test_rand_int_range (0, 6) / 5.f; color->blue = g_test_rand_int_range (0, 6) / 5.f; color->alpha = g_test_rand_int_range (0, 4) / 3.f; } static void test_download_1x1 (gconstpointer data) { GdkMemoryFormat format; TextureMethod method; GdkTexture *expected, *test; gsize i; decode (data, &format, &method); for (i = 0; i < N; i++) { GdkRGBA color; create_random_color (&color); expected = create_texture (GDK_MEMORY_DEFAULT, TEXTURE_METHOD_LOCAL, 1, 1, &color); test = create_texture (format, method, 1, 1, &color); compare_textures (expected, test, gdk_memory_format_has_alpha (format)); g_object_unref (expected); g_object_unref (test); } } static void test_download_4x4 (gconstpointer data) { GdkMemoryFormat format; TextureMethod method; GdkTexture *expected, *test; gsize i; decode (data, &format, &method); for (i = 0; i < N; i++) { GdkRGBA color; create_random_color (&color); expected = create_texture (GDK_MEMORY_DEFAULT, TEXTURE_METHOD_LOCAL, 4, 4, &color); test = create_texture (format, method, 4, 4, &color); compare_textures (expected, test, gdk_memory_format_has_alpha (format)); g_object_unref (expected); g_object_unref (test); } } /* larger than what NGL puts into the icon cache */ static void test_download_192x192 (gconstpointer data) { GdkMemoryFormat format; TextureMethod method; GdkTexture *expected, *test; GdkRGBA color; decode (data, &format, &method); create_random_color (&color); expected = create_texture (GDK_MEMORY_DEFAULT, TEXTURE_METHOD_LOCAL, 192, 192, &color); test = create_texture (format, method, 192, 192, &color); compare_textures (expected, test, gdk_memory_format_has_alpha (format)); g_object_unref (expected); g_object_unref (test); } static void add_test (const char *name, GTestDataFunc func) { GdkMemoryFormat format; TextureMethod method; GEnumClass *enum_class; enum_class = g_type_class_ref (GDK_TYPE_MEMORY_FORMAT); for (format = 0; format < GDK_MEMORY_N_FORMATS; format++) { for (method = 0; method < N_TEXTURE_METHODS; method++) { const char *method_names[N_TEXTURE_METHODS] = { "local", "gl", "gl-released", "png", "png-pixbuf", "tiff", "tiff-pixbuf" }; char *test_name = g_strdup_printf ("%s/%s/%s", name, g_enum_get_value (enum_class, format)->value_nick, method_names[method]); g_test_add_data_func_full (test_name, encode (format, method), func, NULL); g_free (test_name); } } } int main (int argc, char *argv[]) { int result; gtk_test_init (&argc, &argv, NULL); add_test ("/memorytexture/download_1x1", test_download_1x1); add_test ("/memorytexture/download_4x4", test_download_4x4); add_test ("/memorytexture/download_192x192", test_download_192x192); gl_renderer = gsk_gl_renderer_new (); if (!gsk_renderer_realize (gl_renderer, NULL, NULL)) { g_clear_object (&gl_renderer); } result = g_test_run (); if (gl_renderer) { gsk_renderer_unrealize (gl_renderer); g_clear_object (&gl_renderer); } gdk_gl_context_clear_current (); return result; }