testsuite: Add offload tests

These tests come in two variants.

The first takes .node and .offload file, parses the node file,
and compares the resulting subsurface attachments to expected results.

The second variant takes two .node/.offload file pairs and a .diff
file, parses the node files, compares the resulting subsurface
attachments, and then diffs the nodes, comparing the resulting
area to the region in the .diff file.
This commit is contained in:
Matthias Clasen 2023-11-10 21:34:30 +01:00
parent d3a5aa5304
commit 44d977844b
25 changed files with 1036 additions and 0 deletions

View File

@ -0,0 +1,180 @@
/*
* Copyright 2023, Red Hat, Inc
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gskrendernodeattach.h"
#include "gsk/gsk.h"
#include "gsk/gskrendernodeprivate.h"
#include "gdk/gdksurfaceprivate.h"
#include "gdk/gdksubsurfaceprivate.h"
static GskRenderNode *
node_attach (const GskRenderNode *node,
GdkSurface *surface,
int *idx)
{
switch (GSK_RENDER_NODE_TYPE (node))
{
case GSK_CAIRO_NODE:
case GSK_COLOR_NODE:
case GSK_LINEAR_GRADIENT_NODE:
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
case GSK_RADIAL_GRADIENT_NODE:
case GSK_REPEATING_RADIAL_GRADIENT_NODE:
case GSK_CONIC_GRADIENT_NODE:
case GSK_BORDER_NODE:
case GSK_TEXTURE_NODE:
case GSK_TEXTURE_SCALE_NODE:
case GSK_INSET_SHADOW_NODE:
case GSK_OUTSET_SHADOW_NODE:
case GSK_TEXT_NODE:
return gsk_render_node_ref ((GskRenderNode *)node);
case GSK_TRANSFORM_NODE:
return gsk_transform_node_new (node_attach (gsk_transform_node_get_child (node), surface, idx),
gsk_transform_node_get_transform (node));
case GSK_OPACITY_NODE:
return gsk_opacity_node_new (node_attach (gsk_opacity_node_get_child (node), surface, idx),
gsk_opacity_node_get_opacity (node));
case GSK_COLOR_MATRIX_NODE:
return gsk_color_matrix_node_new (node_attach (gsk_color_matrix_node_get_child (node), surface, idx),
gsk_color_matrix_node_get_color_matrix (node),
gsk_color_matrix_node_get_color_offset (node));
case GSK_REPEAT_NODE:
return gsk_repeat_node_new (&node->bounds,
node_attach (gsk_repeat_node_get_child (node), surface, idx),
gsk_repeat_node_get_child_bounds (node));
case GSK_CONTAINER_NODE:
{
GskRenderNode **children = g_newa (GskRenderNode *, gsk_container_node_get_n_children (node));
for (int i = 0; i < gsk_container_node_get_n_children (node); i++)
children[i] = node_attach (gsk_container_node_get_child (node, i), surface, idx);
return gsk_container_node_new (children, gsk_container_node_get_n_children (node));
}
case GSK_CLIP_NODE:
return gsk_clip_node_new (node_attach (gsk_clip_node_get_child (node), surface, idx),
gsk_clip_node_get_clip (node));
case GSK_ROUNDED_CLIP_NODE:
return gsk_rounded_clip_node_new (node_attach (gsk_rounded_clip_node_get_child (node), surface, idx),
gsk_rounded_clip_node_get_clip (node));
case GSK_SHADOW_NODE:
{
GskShadow *shadows = g_newa (GskShadow, gsk_shadow_node_get_n_shadows (node));
for (int i = 0; i < gsk_shadow_node_get_n_shadows (node); i++)
shadows[i] = *gsk_shadow_node_get_shadow (node, i);
return gsk_shadow_node_new (node_attach (gsk_shadow_node_get_child (node), surface, idx),
shadows,
gsk_shadow_node_get_n_shadows (node));
}
case GSK_BLEND_NODE:
return gsk_blend_node_new (node_attach (gsk_blend_node_get_bottom_child (node), surface, idx),
node_attach (gsk_blend_node_get_top_child (node), surface, idx),
gsk_blend_node_get_blend_mode (node));
case GSK_CROSS_FADE_NODE:
return gsk_cross_fade_node_new (node_attach (gsk_cross_fade_node_get_start_child (node), surface, idx),
node_attach (gsk_cross_fade_node_get_end_child (node), surface, idx),
gsk_cross_fade_node_get_progress (node));
case GSK_BLUR_NODE:
return gsk_blur_node_new (node_attach (gsk_blur_node_get_child (node), surface, idx),
gsk_blur_node_get_radius (node));
case GSK_DEBUG_NODE:
return gsk_debug_node_new (node_attach (gsk_debug_node_get_child (node), surface, idx),
g_strdup (gsk_debug_node_get_message (node)));
case GSK_GL_SHADER_NODE:
{
GskRenderNode **children;
children = g_newa (GskRenderNode *, gsk_gl_shader_node_get_n_children (node));
for (int i = 0; i < gsk_gl_shader_node_get_n_children (node); i++)
children[i] = node_attach (gsk_gl_shader_node_get_child (node, i), surface, idx);
return gsk_gl_shader_node_new (gsk_gl_shader_node_get_shader (node),
&node->bounds,
gsk_gl_shader_node_get_args (node),
children,
gsk_gl_shader_node_get_n_children (node));
}
case GSK_MASK_NODE:
return gsk_mask_node_new (node_attach (gsk_mask_node_get_source (node), surface, idx),
node_attach (gsk_mask_node_get_mask (node), surface, idx),
gsk_mask_node_get_mask_mode (node));
case GSK_FILL_NODE:
return gsk_fill_node_new (node_attach (gsk_fill_node_get_child (node), surface, idx),
gsk_fill_node_get_path (node),
gsk_fill_node_get_fill_rule (node));
case GSK_STROKE_NODE:
return gsk_stroke_node_new (node_attach (gsk_stroke_node_get_child (node), surface, idx),
gsk_stroke_node_get_path (node),
gsk_stroke_node_get_stroke (node));
case GSK_SUBSURFACE_NODE:
{
GdkSubsurface *subsurface;
g_assert (gsk_subsurface_node_get_subsurface (node) == NULL);
if (*idx == -1)
subsurface = gdk_surface_create_subsurface (surface);
else
{
subsurface = gdk_surface_get_subsurface (surface, *idx);
(*idx)++;
}
return gsk_subsurface_node_new (node_attach (gsk_subsurface_node_get_child (node), surface, idx),
subsurface);
}
case GSK_NOT_A_RENDER_NODE:
default:
g_assert_not_reached ();
}
}
/* Find all the subsurface nodes in the given tree, and attach them
* to a subsurface of the given surface. If the surface already has
* subsurfaces, we assume that we are just reattaching, and that the
* nodes are still in the same order. Otherwise, we create new
* subsurfaces.
*/
GskRenderNode *
gsk_render_node_attach (const GskRenderNode *node,
GdkSurface *surface)
{
int idx;
if (gdk_surface_get_n_subsurfaces (surface) > 0)
idx = 0;
else
idx = -1;
return node_attach (node, surface, &idx);
}

View File

@ -0,0 +1,6 @@
#pragma once
#include "gsk/gsk.h"
GskRenderNode * gsk_render_node_attach (const GskRenderNode *node,
GdkSurface *surface);

View File

@ -371,11 +371,47 @@ foreach test : node_parser_tests
'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir())
],
protocol: 'exitcode',
suite: 'gsk',
)
endif
endforeach
# offload does not work outside of linux
if os_linux
offload = executable('offload', 'offload.c', 'gskrendernodeattach.c',
dependencies : libgtk_static_dep,
c_args: common_cflags,
)
offload_tests = [
'simple.node',
'transforms.node',
'deep.node',
'move.node',
'start_offloading.node',
'stop_offloading.node',
]
foreach test : offload_tests
if test.endswith('.node') and not test.endswith('.out')
test('parser ' + test, offload,
args: [
join_paths(meson.current_source_dir(), 'offload', test)
],
env: [
'GSK_RENDERER=opengl',
'GTK_A11Y=test',
'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir())
],
protocol: 'exitcode',
suite: 'gsk',
)
endif
endforeach
endif
tests = [
['transform'],
['shader'],

614
testsuite/gsk/offload.c Normal file
View File

@ -0,0 +1,614 @@
/*
* Copyright (C) 2023 Red Hat Inc.
*
* Author:
* Matthias Clasen <mclasen@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gtk/gtk.h>
#include <gdk/gdksurfaceprivate.h>
#include <gdk/gdksubsurfaceprivate.h>
#include <gsk/gskrendernodeparserprivate.h>
#include <gsk/gskrendernodeprivate.h>
#include <gsk/gskoffloadprivate.h>
#include "gskrendernodeattach.h"
static char *
test_get_sibling_file (const char *node_file,
const char *old_ext,
const char *new_ext)
{
GString *file = g_string_new (NULL);
if (g_str_has_suffix (node_file, old_ext))
g_string_append_len (file, node_file, strlen (node_file) - 5);
else
g_string_append (file, node_file);
g_string_append (file, new_ext);
if (!g_file_test (file->str, G_FILE_TEST_EXISTS))
{
g_string_free (file, TRUE);
return NULL;
}
return g_string_free (file, FALSE);
}
static GBytes *
diff_with_file (const char *file1,
GBytes *input,
GError **error)
{
char *buffer;
gsize len;
static const char msg[] = "The output is not as expected";
g_file_get_contents (file1, &buffer, &len, NULL);
if (strcmp (buffer, (char *) g_bytes_get_data (input, NULL)) == 0)
{
g_free (buffer);
return NULL;
}
g_free (buffer);
return g_bytes_new_static (msg, strlen (msg) + 1);
}
static void
append_error_value (GString *string,
GType enum_type,
guint value)
{
GEnumClass *enum_class;
GEnumValue *enum_value;
enum_class = g_type_class_ref (enum_type);
enum_value = g_enum_get_value (enum_class, value);
g_string_append (string, enum_value->value_name);
g_type_class_unref (enum_class);
}
static void
deserialize_error_func (const GskParseLocation *start,
const GskParseLocation *end,
const GError *error,
gpointer user_data)
{
GString *errors = 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_string_append_printf (errors, "%s: error: ", string->str);
g_string_free (string, TRUE);
if (error->domain == GTK_CSS_PARSER_ERROR)
append_error_value (errors, GTK_TYPE_CSS_PARSER_ERROR, error->code);
else if (error->domain == GTK_CSS_PARSER_WARNING)
append_error_value (errors, GTK_TYPE_CSS_PARSER_WARNING, error->code);
else
g_string_append_printf (errors,
"%s %u\n",
g_quark_to_string (error->domain),
error->code);
g_string_append_c (errors, '\n');
}
static GskRenderNode *
node_from_file (GFile *file)
{
GBytes *bytes;
GError *error = NULL;
GString *errors = NULL;
GskRenderNode *node = NULL;
bytes = g_file_load_bytes (file, NULL, NULL, &error);
if (error)
{
g_print ("Error loading file: %s\n", error->message);
g_clear_error (&error);
return NULL;
}
errors = g_string_new ("");
node = gsk_render_node_deserialize (bytes,
deserialize_error_func,
errors);
if (errors->len > 0)
{
g_print ("Error loading file: %s\n", errors->str);
g_string_free (errors, TRUE);
g_bytes_unref (bytes);
return NULL;
}
g_string_free (errors, TRUE);
g_bytes_unref (bytes);
return node;
}
static GskRenderNode *
node_from_path (const char *path)
{
GFile *file = g_file_new_for_path (path);
GskRenderNode *node = node_from_file (file);
g_object_unref (file);
return node;
}
static GBytes *
collect_offload_info (GdkSurface *surface,
GskOffload *offload)
{
GString *s;
GBytes *bytes;
s = g_string_new ("");
for (unsigned int i = 0; i < gdk_surface_get_n_subsurfaces (surface); i++)
{
GdkSubsurface *subsurface = gdk_surface_get_subsurface (surface, i);
g_object_set_data (G_OBJECT (subsurface), "pos", GUINT_TO_POINTER (i));
}
for (unsigned int i = 0; i < gdk_surface_get_n_subsurfaces (surface); i++)
{
GdkSubsurface *subsurface;
GskOffloadInfo *info;
char above[20];
subsurface = gdk_surface_get_subsurface (surface, i);
info = gsk_offload_get_subsurface_info (offload, subsurface);
if (info->place_above)
g_snprintf (above, sizeof (above), "%d",
GPOINTER_TO_INT (g_object_get_data (G_OBJECT (subsurface), "pos")));
else
g_snprintf (above, sizeof (above), "-");
if (info->can_offload)
{
g_string_append_printf (s, "%u: offloaded, %s%sabove: %s, ",
i,
info->was_offloaded ? "was offloaded, " : "",
gdk_subsurface_is_above_parent (subsurface) ? "raised, " : "",
above);
g_string_append_printf (s, "texture: %dx%d, ",
gdk_texture_get_width (info->texture),
gdk_texture_get_height (info->texture));
g_string_append_printf (s, "rect: %g %g %g %g\n",
info->rect.origin.x, info->rect.origin.y,
info->rect.size.width, info->rect.size.height);
}
else
g_string_append_printf (s, "%u: %snot offloaded\n",
i,
info->was_offloaded ? "was offloaded, " : "");
}
bytes = g_bytes_new (s->str, s->len + 1);
g_string_free (s, TRUE);
return bytes;
}
static char *
region_to_string (cairo_region_t *region)
{
GString *s = g_string_new ("");
for (int i = 0; i < cairo_region_num_rectangles (region); i++)
{
cairo_rectangle_int_t r;
cairo_region_get_rectangle (region, i, &r);
g_string_append_printf (s, "%d %d %d %d\n", r.x, r.y, r.width, r.height);
}
return g_string_free (s, FALSE);
}
static gboolean
region_contains_region (cairo_region_t *region1,
cairo_region_t *region2)
{
for (int i = 0; i < cairo_region_num_rectangles (region2); i++)
{
cairo_rectangle_int_t r;
cairo_region_get_rectangle (region2, i, &r);
if (cairo_region_contains_rectangle (region1, &r) != CAIRO_REGION_OVERLAP_IN)
return FALSE;
}
return TRUE;
}
static cairo_region_t *
region_from_string (const char *str)
{
cairo_region_t *region;
char **s;
int len;
region = cairo_region_create ();
s = g_strsplit (str, "\n", 0);
len = g_strv_length (s);
for (int i = 0; i < len; i++)
{
cairo_rectangle_int_t r;
if (s[i][0] == '\0')
continue;
if (sscanf (s[i], "%d %d %d %d", &r.x, &r.y, &r.width, &r.height) == 4)
cairo_region_union_rectangle (region, &r);
else
g_error ("failed to parse region");
}
g_strfreev (s);
return region;
}
static cairo_region_t *
region_from_file (const char *path)
{
char *buffer;
gsize len;
GError *error = NULL;
cairo_region_t *region;
if (!g_file_get_contents (path, &buffer, &len, &error))
{
g_error ("Failed to read region file: %s", error->message);
g_error_free (error);
return NULL;
}
region = region_from_string (buffer);
g_free (buffer);
return region;
}
static void
notify_width (GdkSurface *surface,
GParamSpec *pspec,
gpointer data)
{
gboolean *done = data;
*done = TRUE;
}
static void
compute_size (GdkToplevel *toplevel,
GdkToplevelSize *size,
gpointer data)
{
g_signal_connect (toplevel, "notify::width",
G_CALLBACK (notify_width), data);
gdk_toplevel_size_set_size (size, 800, 600);
}
static GdkSurface *
make_toplevel (void)
{
GdkSurface *surface;
GdkToplevelLayout *layout;
gboolean done;
surface = gdk_surface_new_toplevel (gdk_display_get_default ());
done = FALSE;
g_signal_connect (surface, "compute-size", G_CALLBACK (compute_size), &done);
layout = gdk_toplevel_layout_new ();
gdk_toplevel_present (GDK_TOPLEVEL (surface), layout);
gdk_toplevel_layout_unref (layout);
while (!done)
g_main_context_iteration (NULL, TRUE);
return surface;
}
static gboolean
parse_node_file (GFile *file, const char *generate)
{
char *reference_file;
GdkSurface *surface;
GdkSubsurface *subsurface;
GskOffload *offload;
GskRenderNode *node, *tmp;
GBytes *offload_state, *diff;
GError *error = NULL;
gboolean result = TRUE;
cairo_region_t *clip, *region;
char *path;
GskRenderNode *node2;
const char *generate_values[] = { "offload", "offload2", "diff", NULL };
if (generate && !g_strv_contains (generate_values, generate))
{
g_print ("Allowed --generate values are: ");
for (int i = 0; generate_values[i]; i++)
g_print ("%s ", generate_values[i]);
g_print ("\n");
return FALSE;
}
surface = make_toplevel ();
subsurface = gdk_surface_create_subsurface (surface);
if (subsurface == NULL)
exit (77); /* subsurfaces aren't supported, skip these tests */
g_clear_object (&subsurface);
node = node_from_file (file);
if (node == NULL)
{
return FALSE;
}
tmp = gsk_render_node_attach (node, surface);
gsk_render_node_unref (node);
node = tmp;
offload = gsk_offload_new (surface, node);
offload_state = collect_offload_info (surface, offload);
gsk_offload_free (offload);
if (g_strcmp0 (generate, "offload") == 0)
{
g_print ("%s", (char *)g_bytes_get_data (offload_state, NULL));
g_bytes_unref (offload_state);
return TRUE;
}
reference_file = test_get_sibling_file (g_file_peek_path (file), ".node", ".offload");
if (reference_file == NULL)
return FALSE;
diff = diff_with_file (reference_file, offload_state, &error);
g_assert_no_error (error);
if (diff && g_bytes_get_size (diff) > 0)
{
g_print ("Resulting .offload file doesn't match reference:\n%s\n",
(const char *) g_bytes_get_data (diff, NULL));
result = FALSE;
}
g_clear_pointer (&offload_state, g_bytes_unref);
g_clear_pointer (&diff, g_bytes_unref);
g_clear_pointer (&reference_file, g_free);
path = test_get_sibling_file (g_file_peek_path (file), ".node", ".node2");
if (path)
{
node2 = node_from_path (path);
tmp = gsk_render_node_attach (node2, surface);
gsk_render_node_unref (node2);
node2 = tmp;
offload = gsk_offload_new (surface, node2);
offload_state = collect_offload_info (surface, offload);
if (g_strcmp0 (generate, "offload2") == 0)
{
g_print ("%s", (char *)g_bytes_get_data (offload_state, NULL));
g_bytes_unref (offload_state);
return TRUE;
}
reference_file = test_get_sibling_file (g_file_peek_path (file), ".node", ".offload2");
if (reference_file == NULL)
return FALSE;
diff = diff_with_file (reference_file, offload_state, &error);
g_assert_no_error (error);
if (diff && g_bytes_get_size (diff) > 0)
{
g_print ("Resulting .offload2 file doesn't match reference:\n%s\n",
(const char *) g_bytes_get_data (diff, NULL));
result = FALSE;
}
g_clear_pointer (&offload_state, g_bytes_unref);
g_clear_pointer (&diff, g_bytes_unref);
g_clear_pointer (&reference_file, g_free);
clip = cairo_region_create ();
gsk_render_node_diff (node, node2, clip, offload);
if (g_strcmp0 (generate, "diff") == 0)
{
char *out = region_to_string (clip);
g_print ("%s", out);
cairo_region_destroy (clip);
g_free (out);
return TRUE;
}
reference_file = test_get_sibling_file (g_file_peek_path (file), ".node", ".diff");
region = region_from_file (reference_file);
if (!region_contains_region (clip, region))
{
g_print ("Resulting region doesn't include reference:\n");
g_print ("%s\n", region_to_string (clip));
result = FALSE;
}
g_clear_pointer (&region, cairo_region_destroy);
g_clear_pointer (&reference_file, g_free);
g_clear_pointer (&clip, cairo_region_destroy);
g_clear_pointer (&node2, gsk_render_node_unref);
}
g_clear_pointer (&node, gsk_render_node_unref);
gdk_surface_destroy (surface);
return result;
}
static gboolean
test_file (GFile *file)
{
return parse_node_file (file, FALSE);
}
static int
compare_files (gconstpointer a, gconstpointer b)
{
GFile *file1 = G_FILE (a);
GFile *file2 = G_FILE (b);
char *path1, *path2;
int result;
path1 = g_file_get_path (file1);
path2 = g_file_get_path (file2);
result = strcmp (path1, path2);
g_free (path1);
g_free (path2);
return result;
}
static gboolean
test_files_in_directory (GFile *dir)
{
GFileEnumerator *enumerator;
GFileInfo *info;
GList *l, *files;
GError *error = NULL;
gboolean result = TRUE;
enumerator = g_file_enumerate_children (dir, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, &error);
g_assert_no_error (error);
files = NULL;
while ((info = g_file_enumerator_next_file (enumerator, NULL, &error)))
{
const char *filename;
filename = g_file_info_get_name (info);
if (!g_str_has_suffix (filename, ".node"))
{
g_object_unref (info);
continue;
}
files = g_list_prepend (files, g_file_get_child (dir, filename));
g_object_unref (info);
}
g_assert_no_error (error);
g_object_unref (enumerator);
files = g_list_sort (files, compare_files);
for (l = files; l; l = l->next)
{
result &= test_file (l->data);
}
g_list_free_full (files, g_object_unref);
return result;
}
int
main (int argc, char **argv)
{
gboolean success;
if (argc < 2)
{
const char *basedir;
GFile *dir;
gtk_test_init (&argc, &argv);
basedir = g_test_get_dir (G_TEST_DIST);
dir = g_file_new_for_path (basedir);
success = test_files_in_directory (dir);
g_object_unref (dir);
}
else if (g_str_has_prefix (argv[1], "--generate="))
{
/* We have up to 3 different result files, the extra
* argument determines which one is generated. Possible
* values are offload/offload2/diff.
*/
if (argc >= 3)
{
const char *generate = argv[1] + strlen ("--generate=");
GFile *file = g_file_new_for_commandline_arg (argv[2]);
gtk_init ();
success = parse_node_file (file, generate);
g_object_unref (file);
}
else
success = FALSE;
}
else
{
guint i;
gtk_test_init (&argc, &argv);
success = TRUE;
for (i = 1; i < argc; i++)
{
GFile *file = g_file_new_for_commandline_arg (argv[i]);
success &= test_file (file);
g_object_unref (file);
}
}
return success ? 0 : 1;
}

View File

@ -0,0 +1,58 @@
subsurface {
child: debug {
message: "A node that is deep in, but still found";
child: debug {
message: "Affine transforms, debug nodes and single-child containers are ok";
child: transform {
transform: translate(10, 10) scale(5, 10);
child: container {
texture { }
}
}
}
}
}
subsurface {
child: debug {
message: "Multi-child containers are not ok";
child: container {
color { }
texture { }
}
}
}
subsurface {
child: debug {
message: "other nodes, such as cross-fade, are not ok";
child: cross-fade {
start: texture { }
end: texture { }
progress: 0.5;
}
}
}
subsurface {
child: debug {
message: "Other nodes, such as cross-fade, are not ok";
child: cross-fade {
start: texture { }
end: texture { }
progress: 0.5;
}
}
}
subsurface {
child: debug {
message: "Clips (regardless how large) are not ok";
child: clip {
clip: 0 0 400 400;
child: texture { }
}
}
}
subsurface {
child: debug {
message: "Can't offload non-textures";
child: linear-gradient { }
}
}

View File

@ -0,0 +1,6 @@
0: offloaded, above: -, texture: 10x10, rect: 10 10 250 500
1: not offloaded
2: not offloaded
3: not offloaded
4: not offloaded
5: not offloaded

View File

@ -0,0 +1,2 @@
20 20 16 16
40 40 20 20

View File

@ -0,0 +1,15 @@
container {
color {
bounds: 0 0 800 600;
color: red;
}
transform {
transform: translate(20,20);
child: subsurface {
child: texture {
texture: url('data:image/svg+xml;utf-8,<svg width="16" height="16"></svg>');
bounds: 0 0 16 16;
}
}
}
}

View File

@ -0,0 +1,15 @@
container {
color {
bounds: 0 0 800 600;
color: red;
}
transform {
transform: translate(40,40);
child: subsurface {
child: texture {
texture: url('data:image/svg+xml;utf-8,<svg width="16" height="16"></svg>');
bounds: 0 0 20 20;
}
}
}
}

View File

@ -0,0 +1 @@
0: offloaded, raised, above: -, texture: 16x16, rect: 20 20 16 16

View File

@ -0,0 +1 @@
0: offloaded, raised, above: -, texture: 16x16, rect: 40 40 20 20

View File

@ -0,0 +1,26 @@
container {
transform {
transform: translate(20,20);
child: subsurface {
child: texture {
texture: url('data:image/svg+xml;utf-8,<svg width="13" height="17"></svg>');
}
}
}
transform {
transform: translate(0,100) scale(10, 10);
child: subsurface {
child: texture {
texture: url('data:image/svg+xml;utf-8,<svg width="10" height="21"></svg>');
}
}
}
opacity {
opacity: 0.5;
child: subsurface {
child: texture {
texture: url('data:image/svg+xml;utf-8,<svg width="16" height="16"></svg>');
}
}
}
}

View File

@ -0,0 +1,3 @@
0: offloaded, above: -, texture: 13x17, rect: 20 20 50 50
1: offloaded, raised, above: 1, texture: 10x21, rect: 0 100 500 500
2: not offloaded

View File

@ -0,0 +1,2 @@
0 0 16 16
16 16 16 16

View File

@ -0,0 +1,12 @@
subsurface {
child: clip {
clip: 0 0 200 200;
child: transform {
transform: translate(16, 16);
child: texture {
texture: url('data:image/svg+xml;utf-8,<svg width="16" height="16"></svg>');
bounds: 0 0 16 16;
}
}
}
}

View File

@ -0,0 +1,6 @@
subsurface {
child: texture {
texture: url('data:image/svg+xml;utf-8,<svg width="16" height="16"></svg>');
bounds: 0 0 16 16;
}
}

View File

@ -0,0 +1 @@
0: not offloaded

View File

@ -0,0 +1 @@
0: offloaded, raised, above: -, texture: 16x16, rect: 0 0 16 16

View File

@ -0,0 +1,2 @@
0 0 16 16
16 16 16 16

View File

@ -0,0 +1,6 @@
subsurface {
child: texture {
texture: url('data:image/svg+xml;utf-8,<svg width="16" height="16"></svg>');
bounds: 0 0 16 16;
}
}

View File

@ -0,0 +1,12 @@
subsurface {
child: clip {
clip: 0 0 200 200;
child: transform {
transform: translate(16, 16);
child: texture {
texture: url('data:image/svg+xml;utf-8,<svg width="16" height="16"></svg>');
bounds: 0 0 16 16;
}
}
}
}

View File

@ -0,0 +1 @@
0: offloaded, raised, above: -, texture: 16x16, rect: 0 0 16 16

View File

@ -0,0 +1 @@
0: not offloaded

View File

@ -0,0 +1,26 @@
container {
transform {
transform: rotate(45);
child: subsurface {
child: texture {
texture: url('data:image/svg+xml;utf-8,<svg width="13" height="17"></svg>');
}
}
}
transform {
transform: translate3d(0, 1, 2);
child: subsurface {
child: texture {
texture: url('data:image/svg+xml;utf-8,<svg width="10" height="21"></svg>');
}
}
}
transform {
transform: translate(1, 2);
child: subsurface {
child: texture {
texture: url('data:image/svg+xml;utf-8,<svg width="16" height="16"></svg>');
}
}
}
}

View File

@ -0,0 +1,3 @@
0: not offloaded
1: not offloaded
2: offloaded, raised, above: -, texture: 16x16, rect: 1 2 50 50