diff --git a/tests/conform/Makefile.am b/tests/conform/Makefile.am index 7a5b6f971..442800d63 100644 --- a/tests/conform/Makefile.am +++ b/tests/conform/Makefile.am @@ -35,6 +35,7 @@ test_conformance_SOURCES = \ test-materials.c \ test-group.c \ test-actor-size.c \ + test-texture-fbo.c \ $(NULL) # For convenience, this provides a way to easily run individual unit tests: diff --git a/tests/conform/test-conform-main.c b/tests/conform/test-conform-main.c index 2b2624434..3379fbabf 100644 --- a/tests/conform/test-conform-main.c +++ b/tests/conform/test-conform-main.c @@ -147,6 +147,7 @@ main (int argc, char **argv) TEST_CONFORM_SIMPLE ("/texture", test_backface_culling); TEST_CONFORM_SIMPLE ("/texture", test_npot_texture); TEST_CONFORM_SIMPLE ("/texture", test_premult); + TEST_CONFORM_SIMPLE ("/texture", test_texture_fbo); TEST_CONFORM_SIMPLE ("/path", test_path); diff --git a/tests/conform/test-texture-fbo.c b/tests/conform/test-texture-fbo.c new file mode 100644 index 000000000..b11bde080 --- /dev/null +++ b/tests/conform/test-texture-fbo.c @@ -0,0 +1,255 @@ + +#include <clutter/clutter.h> +#include <cogl/cogl.h> + +#include "test-conform-common.h" + +#define SOURCE_SIZE 32 +#define SOURCE_DIVISIONS_X 2 +#define SOURCE_DIVISIONS_Y 2 +#define DIVISION_WIDTH (SOURCE_SIZE / SOURCE_DIVISIONS_X) +#define DIVISION_HEIGHT (SOURCE_SIZE / SOURCE_DIVISIONS_Y) + +static const ClutterColor +corner_colors[SOURCE_DIVISIONS_X * SOURCE_DIVISIONS_Y] = + { + { 0xff, 0x00, 0x00, 0xff }, + { 0x00, 0xff, 0x00, 0xff }, + { 0x00, 0x00, 0xff, 0xff }, + { 0xff, 0x00, 0xff, 0xff } + }; + +static const ClutterColor stage_color = { 0x0, 0x0, 0x0, 0xff }; + +typedef struct _TestState +{ + ClutterActor *stage; + guint frame; +} TestState; + +static ClutterActor * +create_source (void) +{ + int x, y; + ClutterActor *group = clutter_group_new (); + + /* Create a group with a different coloured rectangle at each + corner */ + for (y = 0; y < SOURCE_DIVISIONS_Y; y++) + for (x = 0; x < SOURCE_DIVISIONS_X; x++) + { + ClutterActor *rect = clutter_rectangle_new (); + clutter_actor_set_size (rect, DIVISION_WIDTH, DIVISION_HEIGHT); + clutter_actor_set_position (rect, + DIVISION_WIDTH * x, + DIVISION_HEIGHT * y); + clutter_rectangle_set_color (CLUTTER_RECTANGLE (rect), + corner_colors + + (y * SOURCE_DIVISIONS_X + x)); + clutter_container_add (CLUTTER_CONTAINER (group), rect, NULL); + } + + return group; +} + +static void +pre_paint_clip_cb (void) +{ + /* Generate a clip path that clips out the top left division */ + cogl_path_move_to (DIVISION_WIDTH, 0); + cogl_path_line_to (SOURCE_SIZE, 0); + cogl_path_line_to (SOURCE_SIZE, SOURCE_SIZE); + cogl_path_line_to (0, SOURCE_SIZE); + cogl_path_line_to (0, DIVISION_HEIGHT); + cogl_path_line_to (DIVISION_WIDTH, DIVISION_HEIGHT); + cogl_path_close (); + cogl_clip_push_from_path (); +} + +static void +post_paint_clip_cb (void) +{ + cogl_clip_pop (); +} + +static gboolean +validate_part (TestState *state, + int xpos, int ypos, + int clip_flags) +{ + int x, y; + gboolean pass = TRUE; + + /* Check whether the center of each division is the right color */ + for (y = 0; y < SOURCE_DIVISIONS_Y; y++) + for (x = 0; x < SOURCE_DIVISIONS_X; x++) + { + guchar *pixels; + const ClutterColor *correct_color; + + /* Read the center pixels of this division */ + pixels = clutter_stage_read_pixels (CLUTTER_STAGE (state->stage), + x * DIVISION_WIDTH + + DIVISION_WIDTH / 2 + xpos, + y * DIVISION_HEIGHT + + DIVISION_HEIGHT / 2 + ypos, + 1, 1); + + /* If this division is clipped then it should be the stage + color */ + if ((clip_flags & (1 << ((y * SOURCE_DIVISIONS_X) + x)))) + correct_color = &stage_color; + else + /* Otherwise it should be the color for this division */ + correct_color = corner_colors + (y * SOURCE_DIVISIONS_X) + x; + + if (pixels == NULL || + pixels[0] != correct_color->red || + pixels[1] != correct_color->green || + pixels[2] != correct_color->blue) + pass = FALSE; + + g_free (pixels); + } + + return pass; +} + +static void +validate_result (TestState *state) +{ + int ypos = 0; + + if (g_test_verbose ()) + g_print ("Testing onscreen clone...\n"); + g_assert (validate_part (state, SOURCE_SIZE, ypos * SOURCE_SIZE, 0)); + ypos++; + +#if 0 /* this doesn't work */ + if (g_test_verbose ()) + g_print ("Testing offscreen clone...\n"); + g_assert (validate_part (state, SOURCE_SIZE, ypos * SOURCE_SIZE, 0)); +#endif + ypos++; + + if (g_test_verbose ()) + g_print ("Testing onscreen clone with rectangular clip...\n"); + g_assert (validate_part (state, SOURCE_SIZE, ypos * SOURCE_SIZE, ~1)); + ypos++; + + if (g_test_verbose ()) + g_print ("Testing onscreen clone with path clip...\n"); + g_assert (validate_part (state, SOURCE_SIZE, ypos * SOURCE_SIZE, 1)); + ypos++; + + /* Comment this out if you want visual feedback of what this test + * paints. + */ + clutter_main_quit (); +} + +static void +on_paint (ClutterActor *actor, TestState *state) +{ + int frame_num; + + /* XXX: Experiments have shown that for some buggy drivers, when using + * glReadPixels there is some kind of race, so we delay our test for a + * few frames and a few seconds: + */ + /* Need to increment frame first because clutter_stage_read_pixels + fires a redraw */ + frame_num = state->frame++; + if (frame_num == 2) + validate_result (state); + else if (frame_num < 2) + g_usleep (G_USEC_PER_SEC); +} + +static gboolean +queue_redraw (gpointer stage) +{ + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); + + return TRUE; +} + +void +test_texture_fbo (TestConformSimpleFixture *fixture, + gconstpointer data) +{ + TestState state; + guint idle_source; + ClutterActor *actor; + int ypos = 0; + + state.frame = 0; + + state.stage = clutter_stage_get_default (); + + clutter_stage_set_color (CLUTTER_STAGE (state.stage), &stage_color); + + /* Onscreen source with clone next to it */ + actor = create_source (); + clutter_container_add (CLUTTER_CONTAINER (state.stage), actor, NULL); + clutter_actor_set_position (actor, 0, ypos * SOURCE_SIZE); + actor = clutter_texture_new_from_actor (actor); + clutter_actor_set_position (actor, SOURCE_SIZE, ypos * SOURCE_SIZE); + clutter_container_add (CLUTTER_CONTAINER (state.stage), actor, NULL); + ypos++; + + /* Offscreen source with clone */ +#if 0 /* this doesn't work */ + actor = create_source (); + actor = clutter_texture_new_from_actor (actor); + clutter_actor_set_position (actor, SOURCE_SIZE, ypos * SOURCE_SIZE); + clutter_container_add (CLUTTER_CONTAINER (state.stage), actor, NULL); +#endif + ypos++; + + /* Source clipped to the top left division */ + actor = create_source (); + clutter_container_add (CLUTTER_CONTAINER (state.stage), actor, NULL); + clutter_actor_set_position (actor, 0, ypos * SOURCE_SIZE); + clutter_actor_set_clip (actor, 0, 0, DIVISION_WIDTH, DIVISION_HEIGHT); + actor = clutter_texture_new_from_actor (actor); + clutter_actor_set_position (actor, SOURCE_SIZE, ypos * SOURCE_SIZE); + clutter_container_add (CLUTTER_CONTAINER (state.stage), actor, NULL); + ypos++; + + /* Source clipped to everything but top left division using a + path */ + actor = create_source (); + clutter_container_add (CLUTTER_CONTAINER (state.stage), actor, NULL); + clutter_actor_set_position (actor, 0, ypos * SOURCE_SIZE); + g_signal_connect (actor, "paint", + G_CALLBACK (pre_paint_clip_cb), NULL); + g_signal_connect_after (actor, "paint", + G_CALLBACK (post_paint_clip_cb), NULL); + actor = clutter_texture_new_from_actor (actor); + clutter_actor_set_position (actor, SOURCE_SIZE, ypos * SOURCE_SIZE); + clutter_container_add (CLUTTER_CONTAINER (state.stage), actor, NULL); + ypos++; + + /* We force continuous redrawing of the stage, since we need to skip + * the first few frames, and we wont be doing anything else that + * will trigger redrawing. */ + idle_source = g_idle_add (queue_redraw, state.stage); + + g_signal_connect_after (state.stage, "paint", G_CALLBACK (on_paint), &state); + + clutter_actor_show_all (state.stage); + + clutter_main (); + + g_source_remove (idle_source); + + /* Remove all of the actors from the stage */ + clutter_container_foreach (CLUTTER_CONTAINER (state.stage), + (ClutterCallback) clutter_actor_destroy, + NULL); + + if (g_test_verbose ()) + g_print ("OK\n"); +} +