Learning_GTK4_tree/testsuite/gdk/memorytexture.c

1472 lines
41 KiB
C
Raw Permalink Normal View History

2023-12-12 11:36:42 +01:00
#include <gtk/gtk.h>
#include "gsk/gl/gskglrenderer.h"
#ifdef GDK_RENDERING_VULKAN
#include "gsk/vulkan/gskvulkanrenderer.h"
#endif
#include <epoxy/gl.h>
#define N 10
static GdkGLContext *gl_context = NULL;
static GskRenderer *gl_renderer = NULL;
static GskRenderer *vulkan_renderer = NULL;
typedef struct _TextureBuilder TextureBuilder;
typedef enum {
TEXTURE_METHOD_LOCAL,
TEXTURE_METHOD_GL,
TEXTURE_METHOD_GL_RELEASED,
TEXTURE_METHOD_GL_NATIVE,
TEXTURE_METHOD_VULKAN,
TEXTURE_METHOD_PNG,
TEXTURE_METHOD_PNG_PIXBUF,
TEXTURE_METHOD_TIFF,
TEXTURE_METHOD_TIFF_PIXBUF,
N_TEXTURE_METHODS
} TextureMethod;
typedef enum {
CHANNEL_UINT_8,
CHANNEL_UINT_16,
CHANNEL_FLOAT_16,
CHANNEL_FLOAT_32,
} ChannelType;
struct _TextureBuilder
{
GdkMemoryFormat format;
int width;
int height;
guchar *pixels;
gsize stride;
gsize offset;
};
static inline guint
as_uint (const float x)
{
return *(guint*)&x;
}
static inline float
as_float (const guint x)
{
return *(float*)&x;
}
// IEEE-754 16-bit floating-point format (without infinity): 1-5-10
//
static inline float
half_to_float (const guint16 x)
{
const guint e = (x&0x7C00)>>10; // exponent
const guint m = (x&0x03FF)<<13; // mantissa
const guint v = as_uint((float)m)>>23;
return as_float((x&0x8000)<<16 | (e!=0)*((e+112)<<23|m) | ((e==0)&(m!=0))*((v-37)<<23|((m<<(150-v))&0x007FE000)));
}
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 gsize
gdk_memory_format_bytes_per_pixel (GdkMemoryFormat format)
{
switch (format)
{
case GDK_MEMORY_G8:
case GDK_MEMORY_A8:
return 1;
case GDK_MEMORY_G8A8_PREMULTIPLIED:
case GDK_MEMORY_G8A8:
case GDK_MEMORY_G16:
case GDK_MEMORY_A16:
case GDK_MEMORY_A16_FLOAT:
return 2;
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_A8B8G8R8_PREMULTIPLIED:
case GDK_MEMORY_B8G8R8A8:
case GDK_MEMORY_A8R8G8B8:
case GDK_MEMORY_R8G8B8A8:
case GDK_MEMORY_A8B8G8R8:
case GDK_MEMORY_B8G8R8X8:
case GDK_MEMORY_X8R8G8B8:
case GDK_MEMORY_R8G8B8X8:
case GDK_MEMORY_X8B8G8R8:
case GDK_MEMORY_G16A16_PREMULTIPLIED:
case GDK_MEMORY_G16A16:
case GDK_MEMORY_A32_FLOAT:
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 ChannelType
gdk_memory_format_get_channel_type (GdkMemoryFormat format)
{
switch (format)
{
case GDK_MEMORY_R8G8B8:
case GDK_MEMORY_B8G8R8:
case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED:
case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED:
case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED:
case GDK_MEMORY_A8B8G8R8_PREMULTIPLIED:
case GDK_MEMORY_B8G8R8A8:
case GDK_MEMORY_A8R8G8B8:
case GDK_MEMORY_R8G8B8A8:
case GDK_MEMORY_A8B8G8R8:
case GDK_MEMORY_B8G8R8X8:
case GDK_MEMORY_X8R8G8B8:
case GDK_MEMORY_R8G8B8X8:
case GDK_MEMORY_X8B8G8R8:
case GDK_MEMORY_G8:
case GDK_MEMORY_G8A8:
case GDK_MEMORY_G8A8_PREMULTIPLIED:
case GDK_MEMORY_A8:
return CHANNEL_UINT_8;
case GDK_MEMORY_R16G16B16:
case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED:
case GDK_MEMORY_R16G16B16A16:
case GDK_MEMORY_G16:
case GDK_MEMORY_G16A16:
case GDK_MEMORY_G16A16_PREMULTIPLIED:
case GDK_MEMORY_A16:
return CHANNEL_UINT_16;
case GDK_MEMORY_R16G16B16_FLOAT:
case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED:
case GDK_MEMORY_R16G16B16A16_FLOAT:
case GDK_MEMORY_A16_FLOAT:
return CHANNEL_FLOAT_16;
case GDK_MEMORY_R32G32B32_FLOAT:
case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
case GDK_MEMORY_R32G32B32A32_FLOAT:
case GDK_MEMORY_A32_FLOAT:
return CHANNEL_FLOAT_32;
case GDK_MEMORY_N_FORMATS:
default:
g_assert_not_reached ();
return CHANNEL_UINT_8;
}
}
/* return the number of color channels, ignoring alpha */
static guint
gdk_memory_format_n_colors (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:
case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED:
case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED:
case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED:
case GDK_MEMORY_A8B8G8R8_PREMULTIPLIED:
case GDK_MEMORY_B8G8R8A8:
case GDK_MEMORY_A8R8G8B8:
case GDK_MEMORY_R8G8B8A8:
case GDK_MEMORY_A8B8G8R8:
case GDK_MEMORY_B8G8R8X8:
case GDK_MEMORY_X8R8G8B8:
case GDK_MEMORY_R8G8B8X8:
case GDK_MEMORY_X8B8G8R8:
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 3;
case GDK_MEMORY_G8:
case GDK_MEMORY_G16:
case GDK_MEMORY_G8A8_PREMULTIPLIED:
case GDK_MEMORY_G8A8:
case GDK_MEMORY_G16A16_PREMULTIPLIED:
case GDK_MEMORY_G16A16:
return 1;
case GDK_MEMORY_A8:
case GDK_MEMORY_A16:
case GDK_MEMORY_A16_FLOAT:
case GDK_MEMORY_A32_FLOAT:
return 0;
case GDK_MEMORY_N_FORMATS:
default:
g_assert_not_reached ();
return TRUE;
}
}
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:
case GDK_MEMORY_G8:
case GDK_MEMORY_G16:
case GDK_MEMORY_B8G8R8X8:
case GDK_MEMORY_X8R8G8B8:
case GDK_MEMORY_R8G8B8X8:
case GDK_MEMORY_X8B8G8R8:
return FALSE;
case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED:
case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED:
case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED:
case GDK_MEMORY_A8B8G8R8_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:
case GDK_MEMORY_G8A8_PREMULTIPLIED:
case GDK_MEMORY_G8A8:
case GDK_MEMORY_G16A16_PREMULTIPLIED:
case GDK_MEMORY_G16A16:
case GDK_MEMORY_A8:
case GDK_MEMORY_A16:
case GDK_MEMORY_A16_FLOAT:
case GDK_MEMORY_A32_FLOAT:
return TRUE;
case GDK_MEMORY_N_FORMATS:
default:
g_assert_not_reached ();
return TRUE;
}
}
static gboolean
gdk_memory_format_is_premultiplied (GdkMemoryFormat format)
{
switch (format)
{
case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED:
case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED:
case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED:
case GDK_MEMORY_A8B8G8R8_PREMULTIPLIED:
case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED:
case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED:
case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
case GDK_MEMORY_G8A8_PREMULTIPLIED:
case GDK_MEMORY_G16A16_PREMULTIPLIED:
case GDK_MEMORY_A8:
case GDK_MEMORY_A16:
case GDK_MEMORY_A16_FLOAT:
case GDK_MEMORY_A32_FLOAT:
return TRUE;
case GDK_MEMORY_R8G8B8:
case GDK_MEMORY_B8G8R8:
case GDK_MEMORY_R16G16B16:
case GDK_MEMORY_R16G16B16_FLOAT:
case GDK_MEMORY_R32G32B32_FLOAT:
case GDK_MEMORY_B8G8R8A8:
case GDK_MEMORY_A8R8G8B8:
case GDK_MEMORY_R8G8B8A8:
case GDK_MEMORY_A8B8G8R8:
case GDK_MEMORY_B8G8R8X8:
case GDK_MEMORY_X8R8G8B8:
case GDK_MEMORY_R8G8B8X8:
case GDK_MEMORY_X8B8G8R8:
case GDK_MEMORY_R16G16B16A16:
case GDK_MEMORY_R16G16B16A16_FLOAT:
case GDK_MEMORY_R32G32B32A32_FLOAT:
case GDK_MEMORY_G8:
case GDK_MEMORY_G8A8:
case GDK_MEMORY_G16:
case GDK_MEMORY_G16A16:
return FALSE;
case GDK_MEMORY_N_FORMATS:
default:
g_assert_not_reached ();
return FALSE;
}
}
static gboolean
gdk_memory_format_is_deep (GdkMemoryFormat format)
{
return gdk_memory_format_get_channel_type (format) != CHANNEL_UINT_8;
}
static gboolean
gdk_memory_format_pixel_equal (GdkMemoryFormat format,
gboolean accurate,
const guchar *pixel1,
const guchar *pixel2)
{
switch (format)
{
case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED:
case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED:
case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED:
case GDK_MEMORY_A8B8G8R8_PREMULTIPLIED:
case GDK_MEMORY_R8G8B8:
case GDK_MEMORY_B8G8R8:
case GDK_MEMORY_B8G8R8A8:
case GDK_MEMORY_A8R8G8B8:
case GDK_MEMORY_R8G8B8A8:
case GDK_MEMORY_A8B8G8R8:
case GDK_MEMORY_A8:
case GDK_MEMORY_G8:
case GDK_MEMORY_G8A8:
case GDK_MEMORY_G8A8_PREMULTIPLIED:
return memcmp (pixel1, pixel2, gdk_memory_format_bytes_per_pixel (format)) == 0;
case GDK_MEMORY_B8G8R8X8:
case GDK_MEMORY_R8G8B8X8:
return memcmp (pixel1, pixel2, 3) == 0;
case GDK_MEMORY_X8R8G8B8:
case GDK_MEMORY_X8B8G8R8:
return memcmp (pixel1 + 1, pixel2 + 1, 3) == 0;
case GDK_MEMORY_R16G16B16:
case GDK_MEMORY_R16G16B16A16:
case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED:
case GDK_MEMORY_G16:
case GDK_MEMORY_G16A16:
case GDK_MEMORY_G16A16_PREMULTIPLIED:
case GDK_MEMORY_A16:
{
const guint16 *u1 = (const guint16 *) pixel1;
const guint16 *u2 = (const guint16 *) pixel2;
guint i;
for (i = 0; i < gdk_memory_format_bytes_per_pixel (format) / sizeof (guint16); i++)
{
if (!G_APPROX_VALUE (u1[i], u2[i], accurate ? 1 : 256))
return FALSE;
}
}
return TRUE;
case GDK_MEMORY_R16G16B16_FLOAT:
case GDK_MEMORY_R16G16B16A16_FLOAT:
case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED:
case GDK_MEMORY_A16_FLOAT:
{
guint i;
for (i = 0; i < gdk_memory_format_bytes_per_pixel (format) / sizeof (guint16); i++)
{
float f1 = half_to_float (((guint16 *) pixel1)[i]);
float f2 = half_to_float (((guint16 *) pixel2)[i]);
if (!G_APPROX_VALUE (f1, f2, accurate ? 1./65535 : 1./255))
return FALSE;
}
}
return TRUE;
case GDK_MEMORY_R32G32B32_FLOAT:
case GDK_MEMORY_R32G32B32A32_FLOAT:
case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
case GDK_MEMORY_A32_FLOAT:
{
const float *f1 = (const float *) pixel1;
const float *f2 = (const float *) pixel2;
guint i;
for (i = 0; i < gdk_memory_format_bytes_per_pixel (format) / sizeof (float); i++)
{
if (!G_APPROX_VALUE (f1[i], f2[i], accurate ? 1./65535 : 1./255))
return FALSE;
}
}
return TRUE;
case GDK_MEMORY_N_FORMATS:
default:
g_assert_not_reached ();
return FALSE;
}
}
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 gpointer
encode_two_formats (GdkMemoryFormat format1,
GdkMemoryFormat format2)
{
return GSIZE_TO_POINTER (format1 * GDK_MEMORY_N_FORMATS + format2);
}
static void
decode_two_formats (gconstpointer data,
GdkMemoryFormat *format1,
GdkMemoryFormat *format2)
{
gsize value = GPOINTER_TO_SIZE (data);
*format2 = value % GDK_MEMORY_N_FORMATS;
value /= GDK_MEMORY_N_FORMATS;
*format1 = 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 * 255.f + 0.5f, 0.f, 255.f);
if (premultiply)
{
data[r] = CLAMP (color->red * color->alpha * 255.f + 0.5f, 0.f, 255.f);
data[g] = CLAMP (color->green * color->alpha * 255.f + 0.5f, 0.f, 255.f);
data[b] = CLAMP (color->blue * color->alpha * 255.f + 0.5f, 0.f, 255.f);
}
else
{
data[r] = CLAMP (color->red * 255.f + 0.5f, 0.f, 255.f);
data[g] = CLAMP (color->green * 255.f + 0.5f, 0.f, 255.f);
data[b] = CLAMP (color->blue * 255.f + 0.5f, 0.f, 255.f);
}
}
static float
color_gray (const GdkRGBA *color)
{
return 1/3.f * (color->red + color->green + color->blue);
}
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_A8B8G8R8_PREMULTIPLIED:
set_pixel_u8 (data, 3, 2, 1, 0, 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_B8G8R8X8:
set_pixel_u8 (data, 2, 1, 0, -1, TRUE, color);
break;
case GDK_MEMORY_X8R8G8B8:
set_pixel_u8 (data, 1, 2, 3, -1, TRUE, color);
break;
case GDK_MEMORY_R8G8B8X8:
set_pixel_u8 (data, 0, 1, 2, -1, TRUE, color);
break;
case GDK_MEMORY_X8B8G8R8:
set_pixel_u8 (data, 3, 2, 1, -1, TRUE, 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 * 65535.f + 0.5f, 0, 65535.f),
CLAMP (color->green * color->alpha * 65535.f + 0.5f, 0, 65535.f),
CLAMP (color->blue * color->alpha * 65535.f + 0.5f, 0, 65535.f),
};
memcpy (data, pixels, 3 * sizeof (guint16));
}
break;
case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED:
{
guint16 pixels[4] = {
CLAMP (color->red * color->alpha * 65535.f + 0.5f, 0, 65535.f),
CLAMP (color->green * color->alpha * 65535.f + 0.5f, 0, 65535.f),
CLAMP (color->blue * color->alpha * 65535.f + 0.5f, 0, 65535.f),
CLAMP (color->alpha * 65535.f + 0.5f, 0, 65535.f),
};
memcpy (data, pixels, 4 * sizeof (guint16));
}
break;
case GDK_MEMORY_R16G16B16A16:
{
guint16 pixels[4] = {
CLAMP (color->red * 65535.f + 0.5f, 0, 65535.f),
CLAMP (color->green * 65535.f + 0.5f, 0, 65535.f),
CLAMP (color->blue * 65535.f + 0.5f, 0, 65535.f),
CLAMP (color->alpha * 65535.f + 0.5f, 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_G8A8_PREMULTIPLIED:
{
data[0] = CLAMP (color_gray (color) * color->alpha * 255.f + 0.5f, 0.f, 255.f);
data[1] = CLAMP (color->alpha * 255.f + 0.5f, 0.f, 255.f);
}
break;
case GDK_MEMORY_G8A8:
{
data[0] = CLAMP (color_gray (color) * 255.f + 0.5f, 0.f, 255.f);
data[1] = CLAMP (color->alpha * 255.f + 0.5f, 0.f, 255.f);
}
break;
case GDK_MEMORY_G8:
{
*data = CLAMP (color_gray (color) * color->alpha * 255.f + 0.5f, 0.f, 255.f);
}
break;
case GDK_MEMORY_G16A16_PREMULTIPLIED:
{
guint16 pixels[2] = {
CLAMP (color_gray (color) * color->alpha * 65535.f + 0.5f, 0.f, 65535.f),
CLAMP (color->alpha * 65535.f + 0.5f, 0.f, 65535.f),
};
memcpy (data, pixels, 2 * sizeof (guint16));
}
break;
case GDK_MEMORY_G16A16:
{
guint16 pixels[2] = {
CLAMP (color_gray (color) * 65535.f + 0.5f, 0.f, 65535.f),
CLAMP (color->alpha * 65535.f + 0.5f, 0.f, 65535.f),
};
memcpy (data, pixels, 2 * sizeof (guint16));
}
break;
case GDK_MEMORY_G16:
{
guint16 pixel = CLAMP (color_gray (color) * color->alpha * 65535.f + 0.5f, 0.f, 65535.f);
memcpy (data, &pixel, sizeof (guint16));
}
break;
case GDK_MEMORY_A8:
{
*data = CLAMP (color->alpha * 255.f + 0.5f, 0.f, 255.f);
}
break;
case GDK_MEMORY_A16:
{
guint16 pixel = CLAMP (color->alpha * 65535.f, 0.f, 65535.f);
memcpy (data, &pixel, sizeof (guint16));
}
break;
case GDK_MEMORY_A16_FLOAT:
{
guint16 pixel = float_to_half (color->alpha);
memcpy (data, &pixel, sizeof (guint16));
}
break;
case GDK_MEMORY_A32_FLOAT:
{
memcpy (data, &color->alpha, 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 *texture1,
GdkTexture *texture2,
gboolean accurate_compare)
{
GdkTextureDownloader *downloader1, *downloader2;
GBytes *bytes1, *bytes2;
gsize stride1, stride2, bpp;
const guchar *data1, *data2;
int width, height, x, y;
GdkMemoryFormat format;
g_assert_cmpint (gdk_texture_get_width (texture1), ==, gdk_texture_get_width (texture2));
g_assert_cmpint (gdk_texture_get_height (texture1), ==, gdk_texture_get_height (texture2));
g_assert_cmpint (gdk_texture_get_format (texture1), ==, gdk_texture_get_format (texture2));
format = gdk_texture_get_format (texture1);
bpp = gdk_memory_format_bytes_per_pixel (format);
width = gdk_texture_get_width (texture1);
height = gdk_texture_get_height (texture1);
downloader1 = gdk_texture_downloader_new (texture1);
gdk_texture_downloader_set_format (downloader1, format);
bytes1 = gdk_texture_downloader_download_bytes (downloader1, &stride1);
g_assert_cmpint (stride1, >=, bpp * width);
g_assert_nonnull (bytes1);
gdk_texture_downloader_free (downloader1);
downloader2 = gdk_texture_downloader_new (texture2);
gdk_texture_downloader_set_format (downloader2, format);
bytes2 = gdk_texture_downloader_download_bytes (downloader2, &stride2);
g_assert_cmpint (stride2, >=, bpp * width);
g_assert_nonnull (bytes2);
gdk_texture_downloader_free (downloader2);
data1 = g_bytes_get_data (bytes1, NULL);
data2 = g_bytes_get_data (bytes2, NULL);
for (y = 0; y < height; y++)
{
for (x = 0; x < width; x++)
{
g_assert_true (gdk_memory_format_pixel_equal (format, accurate_compare, data1 + bpp * x, data2 + bpp * x));
}
data1 += stride1;
data2 += stride2;
}
g_bytes_unref (bytes2);
g_bytes_unref (bytes1);
}
static GdkTexture *
upload_to_renderer (GdkTexture *texture,
GskRenderer *renderer)
{
GskRenderNode *node;
GdkTexture *result;
if (renderer == NULL)
{
g_test_skip ("renderer not supported");
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 (renderer, node, NULL);
gsk_render_node_unref (node);
g_object_unref (texture);
return result;
}
static void
release_texture (gpointer data)
{
unsigned int id = GPOINTER_TO_UINT (data);
gdk_gl_context_make_current (gl_context);
glDeleteTextures (1, &id);
}
static GdkTexture *
upload_to_gl_native (GdkTexture *texture)
{
struct {
GdkMemoryFormat format;
unsigned int bpp;
int gl_internalformat;
int gl_format;
int gl_type;
int swizzle[4];
} formats[] = {
{ GDK_MEMORY_R8G8B8, 3, GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE, { GL_RED, GL_GREEN, GL_BLUE, GL_ONE } },
{ GDK_MEMORY_R16G16B16A16_PREMULTIPLIED, 8, GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT, { GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA } },
{ GDK_MEMORY_G8, 1, GL_R8, GL_RED, GL_UNSIGNED_BYTE, { GL_RED, GL_RED, GL_RED, GL_ONE } },
{ GDK_MEMORY_G16, 2, GL_R16, GL_RED, GL_UNSIGNED_SHORT, { GL_RED, GL_RED, GL_RED, GL_ONE } },
{ GDK_MEMORY_G8A8_PREMULTIPLIED, 2, GL_RG8, GL_RG, GL_UNSIGNED_BYTE, { GL_RED, GL_RED, GL_RED, GL_GREEN } },
{ GDK_MEMORY_G16A16_PREMULTIPLIED, 4, GL_RG16, GL_RG, GL_UNSIGNED_SHORT, { GL_RED, GL_RED, GL_RED, GL_GREEN } },
{ GDK_MEMORY_G8A8, 2, GL_RG8, GL_RG, GL_UNSIGNED_BYTE, { GL_RED, GL_RED, GL_RED, GL_GREEN } },
{ GDK_MEMORY_G16A16, 4, GL_RG16, GL_RG, GL_UNSIGNED_SHORT, { GL_RED, GL_RED, GL_RED, GL_GREEN } },
{ GDK_MEMORY_A8, 1, GL_R8, GL_RED, GL_UNSIGNED_BYTE, { GL_ONE, GL_ONE, GL_ONE, GL_RED } },
{ GDK_MEMORY_A16, 2, GL_R16, GL_RED, GL_UNSIGNED_SHORT, { GL_ONE, GL_ONE, GL_ONE, GL_RED } },
};
if (gl_context == NULL)
return texture;
gdk_gl_context_make_current (gl_context);
for (unsigned int i = 0; i < G_N_ELEMENTS (formats); i++)
{
unsigned int id;
guchar *data;
int width, height;
gsize stride;
GdkTextureDownloader *d;
GdkGLTextureBuilder *b;
GdkTexture *result;
if (formats[i].format != gdk_texture_get_format (texture))
continue;
width = gdk_texture_get_width (texture);
height = gdk_texture_get_height (texture);
data = g_malloc (width * height * formats[i].bpp);
stride = width * formats[i].bpp;
d = gdk_texture_downloader_new (texture);
gdk_texture_downloader_set_format (d, formats[i].format);
gdk_texture_downloader_download_into (d, data, stride);
gdk_texture_downloader_free (d);
glGenTextures (1, &id);
glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_2D, id);
glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
glTexImage2D (GL_TEXTURE_2D, 0, formats[i].gl_internalformat, width, height, 0, formats[i].gl_format, formats[i].gl_type, data);
glTexParameteriv (GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, formats[i].swizzle);
g_free (data);
b = gdk_gl_texture_builder_new ();
gdk_gl_texture_builder_set_context (b, gl_context);
gdk_gl_texture_builder_set_id (b, id);
gdk_gl_texture_builder_set_width (b, width);
gdk_gl_texture_builder_set_height (b, height);
gdk_gl_texture_builder_set_format (b, formats[i].format);
result = gdk_gl_texture_builder_build (b, release_texture, GUINT_TO_POINTER (id));
g_object_unref (b);
g_object_unref (texture);
return result;
}
return upload_to_renderer (texture, gl_renderer);
}
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_renderer (texture, gl_renderer);
break;
case TEXTURE_METHOD_GL_RELEASED:
texture = upload_to_renderer (texture, gl_renderer);
if (GDK_IS_GL_TEXTURE (texture))
gdk_gl_texture_release (GDK_GL_TEXTURE (texture));
break;
case TEXTURE_METHOD_GL_NATIVE:
texture = upload_to_gl_native (texture);
break;
case TEXTURE_METHOD_VULKAN:
texture = upload_to_renderer (texture, vulkan_renderer);
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_tiff_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 gboolean
texture_method_is_accurate (TextureMethod method)
{
switch (method)
{
case TEXTURE_METHOD_LOCAL:
case TEXTURE_METHOD_TIFF:
return TRUE;
case TEXTURE_METHOD_GL:
case TEXTURE_METHOD_GL_RELEASED:
case TEXTURE_METHOD_GL_NATIVE:
case TEXTURE_METHOD_VULKAN:
case TEXTURE_METHOD_PNG:
case TEXTURE_METHOD_PNG_PIXBUF:
case TEXTURE_METHOD_TIFF_PIXBUF:
return FALSE;
case N_TEXTURE_METHODS:
default:
g_assert_not_reached ();
return FALSE;
}
}
static GdkTexture *
ensure_texture_format (GdkTexture *texture,
GdkMemoryFormat format)
{
GdkTextureDownloader *downloader;
GdkTexture *result;
GBytes *bytes;
gsize stride;
if (gdk_texture_get_format (texture) == format)
return texture;
downloader = gdk_texture_downloader_new (texture);
gdk_texture_downloader_set_format (downloader, format);
bytes = gdk_texture_downloader_download_bytes (downloader, &stride);
gdk_texture_downloader_free (downloader);
result = gdk_memory_texture_new (gdk_texture_get_width (texture),
gdk_texture_get_height (texture),
format,
bytes,
stride);
g_bytes_unref (bytes);
g_object_unref (texture);
return result;
}
static void
color_make_opaque (GdkRGBA *result,
const GdkRGBA *color)
{
result->red *= color->alpha;
result->green *= color->alpha;
result->blue *= color->alpha;
result->alpha = 1.0f;
}
static void
color_make_gray (GdkRGBA *result,
const GdkRGBA *color)
{
result->red = (color->red + color->green + color->blue) / 3.0f;
result->green = result->red;
result->blue = result->red;
result->alpha = color->alpha;
}
static void
color_make_white (GdkRGBA *result,
const GdkRGBA *color)
{
result->red = 1.0f;
result->green = 1.0f;
result->blue = 1.0f;
result->alpha = color->alpha;
}
/* Generate colors so that premultiplying will result in values in steps of 1/15th.
* Also make sure that an averaged gray value fits in that range. */
static void
create_random_color (GdkRGBA *color)
{
int r, g, b;
do
{
r = g_test_rand_int_range (0, 6);
g = g_test_rand_int_range (0, 6);
b = g_test_rand_int_range (0, 6);
}
while ((r + g + b) % 3 != 0);
color->red = r / 5.f;
color->green = g / 5.f;
color->blue = b / 5.f;
color->alpha = g_test_rand_int_range (0, 4) / 3.f;
}
static gboolean
should_skip_download_test (GdkMemoryFormat format,
TextureMethod method)
{
switch (method)
{
case TEXTURE_METHOD_LOCAL:
case TEXTURE_METHOD_PNG:
case TEXTURE_METHOD_PNG_PIXBUF:
case TEXTURE_METHOD_TIFF:
return FALSE;
case TEXTURE_METHOD_GL:
case TEXTURE_METHOD_GL_RELEASED:
if (gl_renderer == NULL)
{
g_test_skip ("OpenGL renderer is not supported");
return TRUE;
}
G_GNUC_FALLTHROUGH;
case TEXTURE_METHOD_GL_NATIVE:
{
int major, minor;
if (gl_context == NULL)
{
g_test_skip ("OpenGL is not supported");
return TRUE;
}
gdk_gl_context_get_version (gl_context, &major, &minor);
if ((method == TEXTURE_METHOD_GL ||
method == TEXTURE_METHOD_GL_RELEASED ||
method == TEXTURE_METHOD_GL_NATIVE) &&
gdk_gl_context_get_use_es (gl_context) &&
(major < 3 || (major == 3 && minor < 1)) &&
gdk_memory_format_is_deep (format))
{
g_test_skip ("GLES < 3.1 can't handle 16bit non-RGBA formats");
return TRUE;
}
return FALSE;
}
case TEXTURE_METHOD_VULKAN:
if (vulkan_renderer == NULL)
{
g_test_skip ("Vulkan is not supported");
return TRUE;
}
return FALSE;
case TEXTURE_METHOD_TIFF_PIXBUF:
g_test_skip ("the pixbuf tiff loader is broken (gdk-pixbuf#100)");
return TRUE;
case N_TEXTURE_METHODS:
default:
g_assert_not_reached ();
return TRUE;
}
}
static void
test_download (gconstpointer data,
unsigned int width,
unsigned int height,
gsize n_runs)
{
GdkMemoryFormat format;
TextureMethod method;
GdkTexture *expected, *test;
gsize i;
decode (data, &format, &method);
if (should_skip_download_test (format, method))
return;
for (i = 0; i < n_runs; i++)
{
GdkRGBA color;
create_random_color (&color);
/* these methods may premultiply during operation */
if (color.alpha == 0.f &&
!gdk_memory_format_is_premultiplied (format) &&
gdk_memory_format_has_alpha (format) &&
(method == TEXTURE_METHOD_GL || method == TEXTURE_METHOD_GL_RELEASED ||
method == TEXTURE_METHOD_GL_NATIVE || method == TEXTURE_METHOD_VULKAN))
color = (GdkRGBA) { 0, 0, 0, 0 };
expected = create_texture (format, TEXTURE_METHOD_LOCAL, width, height, &color);
test = create_texture (format, method, width, height, &color);
test = ensure_texture_format (test, format);
compare_textures (expected, test, texture_method_is_accurate (method));
g_object_unref (expected);
g_object_unref (test);
}
}
static void
test_download_1x1 (gconstpointer data)
{
test_download (data, 1, 1, N);
}
static void
test_download_random (gconstpointer data)
{
int width, height;
/* Make sure the maximum is:
* - larger than what GSK puts into the icon cache
* - larger than the small max-texture-size we test against
*/
do
{
width = g_test_rand_int_range (1, 40) * g_test_rand_int_range (1, 40);
height = g_test_rand_int_range (1, 40) * g_test_rand_int_range (1, 40);
}
while (width * height >= 1024 * 1024);
test_download (data, width, height, 1);
}
static void
test_conversion (gconstpointer data,
int size)
{
GdkMemoryFormat format1, format2;
GdkTexture *test1, *test2;
GdkRGBA color1, color2;
gboolean accurate;
gsize i;
decode_two_formats (data, &format1, &format2);
if (gdk_memory_format_get_channel_type (format1) == CHANNEL_FLOAT_16)
accurate = FALSE;
else
accurate = TRUE;
for (i = 0; i < N; i++)
{
/* non-premultiplied can represet GdkRGBA (1, 1, 1, 0)
* but premultiplied cannot.
* Premultiplied will always represent this as (0, 0, 0, 0)
*/
do
{
create_random_color (&color1);
}
while (color1.alpha == 0 &&
gdk_memory_format_is_premultiplied (format1) !=
gdk_memory_format_is_premultiplied (format2));
/* If the source can't handle alpha, make sure
* the target uses with the opaque version of the color.
*/
color2 = color1;
if (!gdk_memory_format_has_alpha (format1) &&
gdk_memory_format_has_alpha (format2))
color_make_opaque (&color2, &color2);
/* If the source has fewer color channels than the
* target, make sure the colors get adjusted.
*/
if (gdk_memory_format_n_colors (format1) <
gdk_memory_format_n_colors (format2))
{
if (gdk_memory_format_n_colors (format1) == 1)
color_make_gray (&color2, &color2);
else
color_make_white (&color2, &color2);
}
test1 = create_texture (format1, TEXTURE_METHOD_LOCAL, 1, 1, &color1);
test2 = create_texture (format2, TEXTURE_METHOD_LOCAL, 1, 1, &color2);
/* Convert the first one to the format of the 2nd */
test1 = ensure_texture_format (test1, format2);
compare_textures (test1, test2, accurate);
g_object_unref (test2);
g_object_unref (test1);
}
}
static void
test_conversion_1x1 (gconstpointer data)
{
test_conversion (data, 1);
}
static void
test_conversion_random (gconstpointer data)
{
test_conversion (data, g_test_rand_int_range (2, 18));
}
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", "gl-native", "vulkan", "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);
}
}
}
static void
add_conversion_test (const char *name,
GTestDataFunc func)
{
GdkMemoryFormat format1, format2;
GEnumClass *enum_class;
enum_class = g_type_class_ref (GDK_TYPE_MEMORY_FORMAT);
for (format1 = 0; format1 < GDK_MEMORY_N_FORMATS; format1++)
{
for (format2 = 0; format2 < GDK_MEMORY_N_FORMATS; format2++)
{
char *test_name = g_strdup_printf ("%s/%s/%s",
name,
g_enum_get_value (enum_class, format1)->value_nick,
g_enum_get_value (enum_class, format2)->value_nick);
g_test_add_data_func_full (test_name, encode_two_formats (format1, format2), 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_random", test_download_random);
add_conversion_test ("/memorytexture/conversion_1x1", test_conversion_1x1);
add_conversion_test ("/memorytexture/conversion_random", test_conversion_random);
gl_context = gdk_display_create_gl_context (gdk_display_get_default (), NULL);
if (gl_context == NULL || !gdk_gl_context_realize (gl_context, NULL))
{
g_clear_object (&gl_context);
}
gl_renderer = gsk_gl_renderer_new ();
if (!gsk_renderer_realize (gl_renderer, NULL, NULL))
{
g_clear_object (&gl_renderer);
}
#ifdef GDK_RENDERING_VULKAN
vulkan_renderer = gsk_vulkan_renderer_new ();
if (!gsk_renderer_realize (vulkan_renderer, NULL, NULL))
{
g_clear_object (&vulkan_renderer);
}
#endif
result = g_test_run ();
#ifdef GDK_RENDERING_VULKAN
if (vulkan_renderer)
{
gsk_renderer_unrealize (vulkan_renderer);
g_clear_object (&vulkan_renderer);
}
#endif
if (gl_renderer)
{
gsk_renderer_unrealize (gl_renderer);
g_clear_object (&gl_renderer);
}
gdk_gl_context_clear_current ();
g_clear_object (&gl_context);
return result;
}