gtk/testsuite/gdk/memorytexture.c
Benjamin Otte 9179ebb28e testsuite: Add memory test support for OpenGL up/downloads
Use a GL renderer to upload textures (and then optionally download them
via release() again). This way, we can test that the GL renderer
properly uploads textures to the right formats (not losing information
for HDR for example) and downloads them again.
2021-09-12 05:54:37 +02:00

531 lines
14 KiB
C

#include <gtk/gtk.h>
#include "gsk/ngl/gsknglrenderer.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,
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_FLOAT_PREMULTIPLIED:
return 8;
case GDK_MEMORY_R32G32B32_FLOAT:
return 12;
case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
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_FLOAT_PREMULTIPLIED:
case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
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_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_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_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);
gdk_gl_texture_release (GDK_GL_TEXTURE (texture));
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);
}
}
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" };
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), test_download_1x1, NULL);
g_free (test_name);
}
}
}
int
main (int argc, char *argv[])
{
GdkSurface *surface;
int result;
gtk_test_init (&argc, &argv, NULL);
add_test ("/memorytexture/download_1x1", test_download_1x1);
add_test ("/memorytexture/download_4x4", test_download_4x4);
surface = gdk_surface_new_toplevel (gdk_display_get_default());
gl_renderer = gsk_ngl_renderer_new ();
if (!gsk_renderer_realize (gl_renderer, surface, NULL))
{
g_clear_object (&gl_renderer);
g_clear_object (&surface);
}
result = g_test_run ();
if (gl_renderer)
{
gsk_renderer_unrealize (gl_renderer);
g_clear_object (&gl_renderer);
}
g_clear_object (&surface);
return result;
}