mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-26 05:31:07 +00:00
6cd6da050b
This takes a render node tree and "replays" it by using the GtkSnapshot machinery. We don't necesserily expect to get back an exactly equal render node tree back, since GtkSnapshot applies various small optimizations where possible, but the original and the replayed nodes should render to identical textures. Signed-off-by: Sergey Bugaev <bugaevc@gmail.com>
544 lines
16 KiB
C
544 lines
16 KiB
C
#include <string.h>
|
|
#include <glib/gstdio.h>
|
|
#include <gtk/gtk.h>
|
|
#include <stdlib.h>
|
|
#include "../reftests/reftest-compare.h"
|
|
|
|
static char *arg_output_dir = NULL;
|
|
static gboolean flip = FALSE;
|
|
static gboolean rotate = FALSE;
|
|
static gboolean repeat = FALSE;
|
|
static gboolean mask = FALSE;
|
|
static gboolean replay = FALSE;
|
|
|
|
extern void
|
|
replay_node (GskRenderNode *node, GtkSnapshot *snapshot);
|
|
|
|
static const char *
|
|
get_output_dir (void)
|
|
{
|
|
static const char *output_dir = NULL;
|
|
GError *error = NULL;
|
|
GFile *file;
|
|
|
|
if (output_dir)
|
|
return output_dir;
|
|
|
|
if (arg_output_dir)
|
|
{
|
|
GFile *arg_file = g_file_new_for_commandline_arg (arg_output_dir);
|
|
const char *subdir;
|
|
|
|
subdir = g_getenv ("TEST_OUTPUT_SUBDIR");
|
|
if (subdir)
|
|
{
|
|
GFile *child = g_file_get_child (arg_file, subdir);
|
|
g_object_unref (arg_file);
|
|
arg_file = child;
|
|
}
|
|
|
|
output_dir = g_file_get_path (arg_file);
|
|
g_object_unref (arg_file);
|
|
}
|
|
else
|
|
{
|
|
output_dir = g_get_tmp_dir ();
|
|
}
|
|
|
|
/* Just try to create the output directory.
|
|
* If it already exists, that's exactly what we wanted to check,
|
|
* so we can happily skip that error.
|
|
*/
|
|
file = g_file_new_for_path (output_dir);
|
|
if (!g_file_make_directory_with_parents (file, NULL, &error))
|
|
{
|
|
g_object_unref (file);
|
|
|
|
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
|
|
{
|
|
g_error ("Failed to create output dir: %s", error->message);
|
|
g_error_free (error);
|
|
return NULL;
|
|
}
|
|
g_error_free (error);
|
|
}
|
|
else
|
|
g_object_unref (file);
|
|
|
|
return output_dir;
|
|
}
|
|
|
|
static char *
|
|
file_replace_extension (const char *old_file,
|
|
const char *old_ext,
|
|
const char *new_ext)
|
|
{
|
|
GString *file = g_string_new (NULL);
|
|
|
|
if (g_str_has_suffix (old_file, old_ext))
|
|
g_string_append_len (file, old_file, strlen (old_file) - strlen (old_ext));
|
|
else
|
|
g_string_append (file, old_file);
|
|
|
|
g_string_append (file, new_ext);
|
|
|
|
return g_string_free (file, FALSE);
|
|
}
|
|
|
|
static char *
|
|
get_output_file (const char *file,
|
|
const char *orig_ext,
|
|
const char *new_ext)
|
|
{
|
|
const char *dir;
|
|
char *result, *base;
|
|
char *name;
|
|
|
|
dir = get_output_dir ();
|
|
base = g_path_get_basename (file);
|
|
name = file_replace_extension (base, orig_ext, new_ext);
|
|
|
|
result = g_strconcat (dir, G_DIR_SEPARATOR_S, name, NULL);
|
|
|
|
g_free (base);
|
|
g_free (name);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
save_image (GdkTexture *texture,
|
|
const char *test_name,
|
|
const char *extension)
|
|
{
|
|
char *filename = get_output_file (test_name, ".node", extension);
|
|
gboolean result;
|
|
|
|
g_print ("Storing test result image at %s\n", filename);
|
|
result = gdk_texture_save_to_png (texture, filename);
|
|
g_assert_true (result);
|
|
g_free (filename);
|
|
}
|
|
|
|
static void
|
|
save_node (GskRenderNode *node,
|
|
const char *test_name,
|
|
const char *extension)
|
|
{
|
|
char *filename = get_output_file (test_name, ".node", extension);
|
|
gboolean result;
|
|
|
|
g_print ("Storing modified nodes at %s\n", filename);
|
|
result = gsk_render_node_write_to_file (node, filename, NULL);
|
|
g_assert_true (result);
|
|
g_free (filename);
|
|
}
|
|
|
|
static void
|
|
deserialize_error_func (const GskParseLocation *start,
|
|
const GskParseLocation *end,
|
|
const GError *error,
|
|
gpointer user_data)
|
|
{
|
|
GString *string = g_string_new ("<data>");
|
|
|
|
g_string_append_printf (string, ":%zu:%zu",
|
|
start->lines + 1, start->line_chars + 1);
|
|
if (start->lines != end->lines || start->line_chars != end->line_chars)
|
|
{
|
|
g_string_append (string, "-");
|
|
if (start->lines != end->lines)
|
|
g_string_append_printf (string, "%zu:", end->lines + 1);
|
|
g_string_append_printf (string, "%zu", end->line_chars + 1);
|
|
}
|
|
|
|
g_warning ("Error at %s: %s", string->str, error->message);
|
|
|
|
g_string_free (string, TRUE);
|
|
}
|
|
|
|
static const GOptionEntry options[] = {
|
|
{ "output", 0, 0, G_OPTION_ARG_FILENAME, &arg_output_dir, "Directory to save image files to", "DIR" },
|
|
{ "flip", 0, 0, G_OPTION_ARG_NONE, &flip, "Do flipped test", NULL },
|
|
{ "rotate", 0, 0, G_OPTION_ARG_NONE, &rotate, "Do rotated test", NULL },
|
|
{ "repeat", 0, 0, G_OPTION_ARG_NONE, &repeat, "Do repeated test", NULL },
|
|
{ "mask", 0, 0, G_OPTION_ARG_NONE, &mask, "Do masked test", NULL },
|
|
{ "replay", 0, 0, G_OPTION_ARG_NONE, &replay, "Do replay test", NULL },
|
|
{ NULL }
|
|
};
|
|
|
|
static GskRenderNode *
|
|
load_node_file (const char *node_file)
|
|
{
|
|
GBytes *bytes;
|
|
gsize len;
|
|
char *contents;
|
|
GError *error = NULL;
|
|
GskRenderNode *node;
|
|
|
|
if (!g_file_get_contents (node_file, &contents, &len, &error))
|
|
{
|
|
g_print ("Could not open node file: %s\n", error->message);
|
|
g_clear_error (&error);
|
|
return NULL;
|
|
}
|
|
|
|
bytes = g_bytes_new_take (contents, len);
|
|
node = gsk_render_node_deserialize (bytes, deserialize_error_func, NULL);
|
|
g_bytes_unref (bytes);
|
|
|
|
g_assert_no_error (error);
|
|
g_assert_nonnull (node);
|
|
|
|
return node;
|
|
}
|
|
|
|
static GdkPixbuf *
|
|
apply_mask_to_pixbuf (GdkPixbuf *pixbuf)
|
|
{
|
|
GdkPixbuf *copy;
|
|
|
|
copy = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
|
|
for (unsigned int j = 0; j < gdk_pixbuf_get_height (copy); j++)
|
|
{
|
|
guint8 *row = gdk_pixbuf_get_pixels (copy) + j * gdk_pixbuf_get_rowstride (copy);
|
|
for (unsigned int i = 0; i < gdk_pixbuf_get_width (copy); i++)
|
|
{
|
|
guint8 *p = row + i * 4;
|
|
if ((i < 25 && j >= 25) || (i >= 25 && j < 25))
|
|
{
|
|
p[0] = p[1] = p[2] = p[3] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return copy;
|
|
}
|
|
|
|
/*
|
|
* Non-option arguments:
|
|
* 1) .node file to compare
|
|
* 2) .png file to compare the rendered .node file to
|
|
*/
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
GdkTexture *reference_texture;
|
|
GdkTexture *rendered_texture;
|
|
GskRenderer *renderer;
|
|
GdkSurface *window;
|
|
GskRenderNode *node;
|
|
const char *node_file;
|
|
const char *png_file;
|
|
gboolean success = TRUE;
|
|
GError *error = NULL;
|
|
GOptionContext *context;
|
|
GdkTexture *diff_texture;
|
|
|
|
(g_test_init) (&argc, &argv, NULL);
|
|
|
|
context = g_option_context_new ("NODE REF - run GSK node tests");
|
|
g_option_context_add_main_entries (context, options, NULL);
|
|
g_option_context_set_ignore_unknown_options (context, TRUE);
|
|
|
|
if (!g_option_context_parse (context, &argc, &argv, &error))
|
|
{
|
|
g_error ("Option parsing failed: %s\n", error->message);
|
|
return 1;
|
|
}
|
|
else if (argc != 3)
|
|
{
|
|
char *help = g_option_context_get_help (context, TRUE, NULL);
|
|
g_print ("%s", help);
|
|
return 1;
|
|
}
|
|
|
|
g_option_context_free (context);
|
|
|
|
gtk_init ();
|
|
|
|
node_file = argv[1];
|
|
png_file = argv[2];
|
|
|
|
g_print ("Node file: '%s'\n", node_file);
|
|
g_print ("PNG file: '%s'\n", png_file);
|
|
|
|
window = gdk_surface_new_toplevel (gdk_display_get_default());
|
|
renderer = gsk_renderer_new_for_surface (window);
|
|
|
|
/* Load the render node from the given .node file */
|
|
node = load_node_file (node_file);
|
|
if (!node)
|
|
return 1;
|
|
|
|
/* Render the .node file and download to cairo surface */
|
|
rendered_texture = gsk_renderer_render_texture (renderer, node, NULL);
|
|
g_assert_nonnull (rendered_texture);
|
|
|
|
save_image (rendered_texture, node_file, ".out.png");
|
|
|
|
/* Load the given reference png file */
|
|
reference_texture = gdk_texture_new_from_filename (png_file, &error);
|
|
if (reference_texture == NULL)
|
|
{
|
|
g_print ("Error loading reference surface: %s\n", error->message);
|
|
g_clear_error (&error);
|
|
save_image (rendered_texture, node_file, ".out.png");
|
|
return 0;
|
|
}
|
|
|
|
/* Now compare the two */
|
|
diff_texture = reftest_compare_textures (rendered_texture, reference_texture);
|
|
if (diff_texture)
|
|
{
|
|
save_image (diff_texture, node_file, ".diff.png");
|
|
g_object_unref (diff_texture);
|
|
success = FALSE;
|
|
}
|
|
|
|
g_clear_object (&reference_texture);
|
|
g_clear_object (&rendered_texture);
|
|
|
|
if (flip)
|
|
{
|
|
GskRenderNode *node2;
|
|
GdkPixbuf *pixbuf, *pixbuf2;
|
|
GskTransform *transform;
|
|
|
|
transform = gsk_transform_scale (NULL, -1, 1);
|
|
node2 = gsk_transform_node_new (node, transform);
|
|
gsk_transform_unref (transform);
|
|
|
|
save_node (node2, node_file, "-flipped.node");
|
|
|
|
rendered_texture = gsk_renderer_render_texture (renderer, node2, NULL);
|
|
save_image (rendered_texture, node_file, "-flipped.out.png");
|
|
|
|
pixbuf = gdk_pixbuf_new_from_file (png_file, &error);
|
|
pixbuf2 = gdk_pixbuf_flip (pixbuf, TRUE);
|
|
reference_texture = gdk_texture_new_for_pixbuf (pixbuf2);
|
|
g_object_unref (pixbuf2);
|
|
g_object_unref (pixbuf);
|
|
|
|
save_image (reference_texture, node_file, "-flipped.ref.png");
|
|
|
|
diff_texture = reftest_compare_textures (rendered_texture, reference_texture);
|
|
|
|
if (diff_texture)
|
|
{
|
|
save_image (diff_texture, node_file, "-flipped.diff.png");
|
|
g_object_unref (diff_texture);
|
|
success = FALSE;
|
|
}
|
|
|
|
g_clear_object (&rendered_texture);
|
|
g_clear_object (&reference_texture);
|
|
gsk_render_node_unref (node2);
|
|
}
|
|
|
|
if (repeat)
|
|
{
|
|
GskRenderNode *node2;
|
|
GdkPixbuf *pixbuf, *pixbuf2, *pixbuf3;
|
|
int width, height;
|
|
graphene_rect_t node_bounds;
|
|
graphene_rect_t bounds;
|
|
float offset_x, offset_y;
|
|
|
|
gsk_render_node_get_bounds (node, &node_bounds);
|
|
|
|
if (node_bounds.size.width > 32768. || node_bounds.size.height > 32768.)
|
|
{
|
|
g_print ("Avoiding repeat test that exceeds cairo image surface dimensions");
|
|
exit (77);
|
|
}
|
|
|
|
bounds.origin.x = 0.;
|
|
bounds.origin.y = 0.;
|
|
bounds.size.width = 2 * node_bounds.size.width;
|
|
bounds.size.height = 2 * node_bounds.size.height;
|
|
|
|
offset_x = floorf (fmodf (node_bounds.origin.x, node_bounds.size.width));
|
|
offset_y = floorf (fmodf (node_bounds.origin.y, node_bounds.size.height));
|
|
if (offset_x < 0)
|
|
offset_x += node_bounds.size.width;
|
|
if (offset_y < 0)
|
|
offset_y += node_bounds.size.height;
|
|
|
|
node2 = gsk_repeat_node_new (&bounds, node, &node_bounds);
|
|
save_node (node2, node_file, "-repeated.node");
|
|
|
|
rendered_texture = gsk_renderer_render_texture (renderer, node2, NULL);
|
|
save_image (rendered_texture, node_file, "-repeated.out.png");
|
|
|
|
pixbuf = gdk_pixbuf_new_from_file (png_file, &error);
|
|
|
|
width = gdk_pixbuf_get_width (pixbuf);
|
|
height = gdk_pixbuf_get_height (pixbuf);
|
|
pixbuf2 = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
|
|
gdk_pixbuf_get_has_alpha (pixbuf),
|
|
gdk_pixbuf_get_bits_per_sample (pixbuf),
|
|
width * 3,
|
|
height * 3);
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
for (int j = 0; j < 3; j++)
|
|
gdk_pixbuf_copy_area (pixbuf, 0, 0, width, height, pixbuf2, i * width, j * height);
|
|
|
|
pixbuf3 = gdk_pixbuf_new_subpixbuf (pixbuf2, width - (int) offset_x, height - (int) offset_y, 2 * width, 2 * height);
|
|
|
|
reference_texture = gdk_texture_new_for_pixbuf (pixbuf3);
|
|
|
|
g_object_unref (pixbuf3);
|
|
g_object_unref (pixbuf2);
|
|
g_object_unref (pixbuf);
|
|
|
|
save_image (reference_texture, node_file, "-repeated.ref.png");
|
|
|
|
diff_texture = reftest_compare_textures (rendered_texture, reference_texture);
|
|
|
|
if (diff_texture)
|
|
{
|
|
save_image (diff_texture, node_file, "-repeated.diff.png");
|
|
g_object_unref (diff_texture);
|
|
success = FALSE;
|
|
}
|
|
|
|
g_clear_object (&rendered_texture);
|
|
g_clear_object (&reference_texture);
|
|
gsk_render_node_unref (node2);
|
|
}
|
|
|
|
if (rotate)
|
|
{
|
|
GskRenderNode *node2;
|
|
GdkPixbuf *pixbuf, *pixbuf2;
|
|
GskTransform *transform;
|
|
|
|
transform = gsk_transform_rotate (NULL, 90);
|
|
node2 = gsk_transform_node_new (node, transform);
|
|
gsk_transform_unref (transform);
|
|
|
|
save_node (node2, node_file, "-rotated.node");
|
|
|
|
rendered_texture = gsk_renderer_render_texture (renderer, node2, NULL);
|
|
save_image (rendered_texture, node_file, "-rotated.out.png");
|
|
|
|
pixbuf = gdk_pixbuf_new_from_file (png_file, &error);
|
|
pixbuf2 = gdk_pixbuf_rotate_simple (pixbuf, GDK_PIXBUF_ROTATE_CLOCKWISE);
|
|
reference_texture = gdk_texture_new_for_pixbuf (pixbuf2);
|
|
g_object_unref (pixbuf2);
|
|
g_object_unref (pixbuf);
|
|
|
|
save_image (reference_texture, node_file, "-rotated.ref.png");
|
|
|
|
diff_texture = reftest_compare_textures (rendered_texture, reference_texture);
|
|
|
|
if (diff_texture)
|
|
{
|
|
save_image (diff_texture, node_file, "-rotated.diff.png");
|
|
g_object_unref (diff_texture);
|
|
success = FALSE;
|
|
}
|
|
|
|
g_clear_object (&rendered_texture);
|
|
g_clear_object (&reference_texture);
|
|
gsk_render_node_unref (node2);
|
|
}
|
|
|
|
if (mask)
|
|
{
|
|
GskRenderNode *node2;
|
|
GdkPixbuf *pixbuf, *pixbuf2;
|
|
graphene_rect_t bounds;
|
|
GskRenderNode *mask_node;
|
|
GskRenderNode *nodes[2];
|
|
|
|
gsk_render_node_get_bounds (node, &bounds);
|
|
nodes[0] = gsk_color_node_new (&(GdkRGBA){ 0, 0, 0, 1},
|
|
&GRAPHENE_RECT_INIT (bounds.origin.x, bounds.origin.y, 25, 25));
|
|
nodes[1] = gsk_color_node_new (&(GdkRGBA){ 0, 0, 0, 1},
|
|
&GRAPHENE_RECT_INIT (bounds.origin.x + 25, bounds.origin.y + 25, bounds.size.width - 25, bounds.size.height - 25));
|
|
|
|
mask_node = gsk_container_node_new (nodes, G_N_ELEMENTS (nodes));
|
|
node2 = gsk_mask_node_new (node, mask_node, GSK_MASK_MODE_ALPHA);
|
|
gsk_render_node_unref (mask_node);
|
|
gsk_render_node_unref (nodes[0]);
|
|
gsk_render_node_unref (nodes[1]);
|
|
save_node (node2, node_file, "-masked.node");
|
|
|
|
rendered_texture = gsk_renderer_render_texture (renderer, node2, NULL);
|
|
save_image (rendered_texture, node_file, "-masked.out.png");
|
|
|
|
pixbuf = gdk_pixbuf_new_from_file (png_file, &error);
|
|
pixbuf2 = apply_mask_to_pixbuf (pixbuf);
|
|
reference_texture = gdk_texture_new_for_pixbuf (pixbuf2);
|
|
g_object_unref (pixbuf2);
|
|
g_object_unref (pixbuf);
|
|
|
|
save_image (reference_texture, node_file, "-masked.ref.png");
|
|
|
|
diff_texture = reftest_compare_textures (rendered_texture, reference_texture);
|
|
|
|
if (diff_texture)
|
|
{
|
|
save_image (diff_texture, node_file, "-masked.diff.png");
|
|
g_object_unref (diff_texture);
|
|
success = FALSE;
|
|
}
|
|
|
|
g_clear_object (&rendered_texture);
|
|
g_clear_object (&reference_texture);
|
|
gsk_render_node_unref (node2);
|
|
}
|
|
|
|
if (replay)
|
|
{
|
|
GskRenderNode *node2;
|
|
GdkTexture *rendered_texture2;
|
|
graphene_rect_t node_bounds, node2_bounds;
|
|
GtkSnapshot *snapshot = gtk_snapshot_new ();
|
|
|
|
replay_node (node, snapshot);
|
|
node2 = gtk_snapshot_free_to_node (snapshot);
|
|
/* If the whole render node tree got eliminated, make sure we have
|
|
something to work with nevertheless. */
|
|
if (!node2)
|
|
node2 = gsk_container_node_new (NULL, 0);
|
|
|
|
gsk_render_node_get_bounds (node, &node_bounds);
|
|
gsk_render_node_get_bounds (node2, &node2_bounds);
|
|
/* Check that the node didn't grow. */
|
|
success = success && graphene_rect_contains_rect (&node_bounds, &node2_bounds);
|
|
|
|
rendered_texture = gsk_renderer_render_texture (renderer, node, &node_bounds);
|
|
rendered_texture2 = gsk_renderer_render_texture (renderer, node2, &node_bounds);
|
|
g_assert_nonnull (rendered_texture);
|
|
g_assert_nonnull (rendered_texture2);
|
|
|
|
diff_texture = reftest_compare_textures (rendered_texture, rendered_texture2);
|
|
|
|
if (diff_texture)
|
|
{
|
|
save_image (diff_texture, node_file, "-replayed.diff.png");
|
|
save_node (node2, node_file, "-replayed.node");
|
|
g_object_unref (diff_texture);
|
|
success = FALSE;
|
|
}
|
|
|
|
g_clear_object (&rendered_texture);
|
|
g_clear_object (&rendered_texture2);
|
|
gsk_render_node_unref (node2);
|
|
}
|
|
|
|
gsk_render_node_unref (node);
|
|
|
|
gsk_renderer_unrealize (renderer);
|
|
g_object_unref (renderer);
|
|
gdk_surface_destroy (window);
|
|
|
|
gdk_display_close (gdk_display_get_default ());
|
|
|
|
return success ? 0 : 1;
|
|
}
|