diff --git a/tests/conform/Makefile.am b/tests/conform/Makefile.am index fa00dc8b9..0a57ad0d6 100644 --- a/tests/conform/Makefile.am +++ b/tests/conform/Makefile.am @@ -69,6 +69,7 @@ units_sources += \ test-clutter-text.c \ test-clutter-texture.c \ test-group.c \ + test-offscreen-redirect.c \ test-path.c \ test-paint-opacity.c \ test-pick.c \ diff --git a/tests/conform/test-conform-main.c b/tests/conform/test-conform-main.c index 7fa1e8d96..1cc7d5d36 100644 --- a/tests/conform/test-conform-main.c +++ b/tests/conform/test-conform-main.c @@ -133,6 +133,7 @@ main (int argc, char **argv) TEST_CONFORM_SIMPLE ("/actor", actor_picking); TEST_CONFORM_SIMPLE ("/actor", actor_fixed_size); TEST_CONFORM_SIMPLE ("/actor", actor_preferred_size); + TEST_CONFORM_SIMPLE ("/actor", test_offscreen_redirect); TEST_CONFORM_SIMPLE ("/invariants", test_initial_state); TEST_CONFORM_SIMPLE ("/invariants", test_shown_not_parented); diff --git a/tests/conform/test-offscreen-redirect.c b/tests/conform/test-offscreen-redirect.c new file mode 100644 index 000000000..04da6033c --- /dev/null +++ b/tests/conform/test-offscreen-redirect.c @@ -0,0 +1,283 @@ +#include + +#include "test-conform-common.h" + +typedef struct _FooActor FooActor; +typedef struct _FooActorClass FooActorClass; + +struct _FooActorClass +{ + ClutterActorClass parent_class; +}; + +struct _FooActor +{ + ClutterActor parent; + + guint8 last_paint_opacity; + int paint_count; +}; + +typedef struct +{ + ClutterActor *stage; + FooActor *foo_actor; + ClutterActor *parent_container; + ClutterActor *container; + ClutterActor *child; + ClutterActor *unrelated_actor; +} Data; + +GType foo_actor_get_type (void) G_GNUC_CONST; + +G_DEFINE_TYPE (FooActor, foo_actor, CLUTTER_TYPE_ACTOR); + +static void +foo_actor_class_paint (ClutterActor *actor) +{ + FooActor *foo_actor = (FooActor *) actor; + ClutterActorBox allocation; + + foo_actor->last_paint_opacity = clutter_actor_get_paint_opacity (actor); + foo_actor->paint_count++; + + clutter_actor_get_allocation_box (actor, &allocation); + + /* Paint a red rectangle with the right opacity */ + cogl_set_source_color4ub (255, + 0, + 0, + foo_actor->last_paint_opacity); + cogl_rectangle (allocation.x1, + allocation.y1, + allocation.x2, + allocation.y2); +} + +static gboolean +foo_actor_class_get_paint_volume (ClutterActor *actor, + ClutterPaintVolume *volume) +{ + return clutter_paint_volume_set_from_allocation (volume, actor); +} + +static void +foo_actor_class_init (FooActorClass *klass) +{ + ClutterActorClass *actor_class = (ClutterActorClass *) klass; + + actor_class->paint = foo_actor_class_paint; + actor_class->get_paint_volume = foo_actor_class_get_paint_volume; +} + +static void +foo_actor_init (FooActor *self) +{ +} + +static void +verify_results (Data *data, + guint8 expected_color_red, + guint8 expected_color_green, + guint8 expected_color_blue, + int expected_paint_count, + int expected_paint_opacity) +{ + guchar *pixel; + + data->foo_actor->paint_count = 0; + + /* Read a pixel at the center of the to determine what color it + painted. This should cause a redraw */ + pixel = clutter_stage_read_pixels (CLUTTER_STAGE (data->stage), + 50, 50, /* x/y */ + 1, 1 /* width/height */); + + g_assert_cmpint (expected_paint_count, ==, data->foo_actor->paint_count); + g_assert_cmpint (expected_paint_opacity, + ==, + data->foo_actor->last_paint_opacity); + + g_assert_cmpint (ABS ((int) expected_color_red - (int) pixel[0]), <=, 2); + g_assert_cmpint (ABS ((int) expected_color_green - (int) pixel[1]), <=, 2); + g_assert_cmpint (ABS ((int) expected_color_blue - (int) pixel[2]), <=, 2); + + g_free (pixel); +} + +static void +verify_redraw (Data *data, int expected_paint_count) +{ + GMainLoop *main_loop = g_main_loop_new (NULL, TRUE); + guint paint_handler; + + paint_handler = g_signal_connect_data (data->stage, + "paint", + G_CALLBACK (g_main_loop_quit), + main_loop, + NULL, + G_CONNECT_SWAPPED | G_CONNECT_AFTER); + + /* Queue a redraw on the stage */ + clutter_actor_queue_redraw (data->stage); + + data->foo_actor->paint_count = 0; + + /* Wait for it to paint */ + g_main_loop_run (main_loop); + + g_signal_handler_disconnect (data->stage, paint_handler); + + g_assert_cmpint (data->foo_actor->paint_count, ==, expected_paint_count); +} + +static gboolean +timeout_cb (gpointer user_data) +{ + Data *data = user_data; + + /* By default the actor shouldn't be redirected so the redraw should + cause the actor to be painted */ + verify_results (data, + 255, 0, 0, + 1, + 255); + + /* Make the actor semi-transparent and verify the paint opacity */ + clutter_actor_set_opacity (data->container, 127); + verify_results (data, + 255, 127, 127, + 1, + 127); + + /* Enable offscreen for opacity so it should now paint through the + fbo. The first paint will still cause the actor to draw because + it needs to fill the cache first. It should be painted with full + opacity */ + clutter_actor_set_offscreen_redirect + (data->container, CLUTTER_OFFSCREEN_REDIRECT_OPACITY_ONLY); + verify_results (data, + 255, 127, 127, + 1, + 255); + + /* The second time the actor is painted it should be cached */ + verify_results (data, + 255, 127, 127, + 0, + 255); + + /* We should be able to change the opacity without causing the actor + to redraw */ + clutter_actor_set_opacity (data->container, 64); + verify_results (data, + 255, 191, 191, + 0, + 255); + + /* Changing it back to fully opaque should cause it not to go + through the FBO so it will draw */ + clutter_actor_set_opacity (data->container, 255); + verify_results (data, + 255, 0, 0, + 1, + 255); + + /* Tell it to always redirect through the FBO. This should cause a + paint of the actor because the last draw didn't go through the + FBO */ + clutter_actor_set_offscreen_redirect (data->container, + CLUTTER_OFFSCREEN_REDIRECT_ALWAYS); + verify_results (data, + 255, 0, 0, + 1, + 255); + + /* We should be able to change the opacity without causing the actor + to redraw */ + clutter_actor_set_opacity (data->container, 64); + verify_results (data, + 255, 191, 191, + 0, + 255); + + /* Even changing it back to fully opaque shouldn't cause a redraw */ + clutter_actor_set_opacity (data->container, 255); + verify_results (data, + 255, 0, 0, + 0, + 255); + + /* Queueing a redraw on the actor should cause a redraw */ + clutter_actor_queue_redraw (data->container); + verify_redraw (data, 1); + + /* Queueing a redraw on a child should cause a redraw */ + clutter_actor_queue_redraw (data->child); + verify_redraw (data, 1); + + /* Modifying the transformation on the parent should cause a + redraw */ + clutter_actor_set_anchor_point (data->parent_container, 0, 1); + verify_redraw (data, 1); + + /* Redrawing an unrelated actor shouldn't cause a redraw */ + clutter_actor_set_position (data->unrelated_actor, 0, 1); + verify_redraw (data, 0); + + clutter_main_quit (); + + return FALSE; +} + +void +test_offscreen_redirect (TestConformSimpleFixture *fixture, + gconstpointer test_data) +{ + if (cogl_features_available (COGL_FEATURE_OFFSCREEN)) + { + Data data; + + data.stage = clutter_stage_get_default (); + + data.parent_container = clutter_group_new (); + + data.container = clutter_group_new (); + + data.foo_actor = g_object_new (foo_actor_get_type (), NULL); + clutter_actor_set_size (CLUTTER_ACTOR (data.foo_actor), 100, 100); + + clutter_container_add_actor (CLUTTER_CONTAINER (data.container), + CLUTTER_ACTOR (data.foo_actor)); + + clutter_container_add_actor (CLUTTER_CONTAINER (data.parent_container), + data.container); + + clutter_container_add_actor (CLUTTER_CONTAINER (data.stage), + data.parent_container); + + data.child = clutter_rectangle_new (); + clutter_actor_set_size (data.child, 1, 1); + clutter_container_add_actor (CLUTTER_CONTAINER (data.container), + data.child); + + data.unrelated_actor = clutter_rectangle_new (); + clutter_actor_set_size (data.child, 1, 1); + clutter_container_add_actor (CLUTTER_CONTAINER (data.stage), + data.unrelated_actor); + + clutter_actor_show (data.stage); + + /* Start the test after a short delay to allow the stage to + render its initial frames without affecting the results */ + g_timeout_add_full (G_PRIORITY_LOW, 250, timeout_cb, &data, NULL); + + clutter_main (); + + if (g_test_verbose ()) + g_print ("OK\n"); + } + else if (g_test_verbose ()) + g_print ("Skipping\n"); +} +