1 #define CLUTTER_DISABLE_DEPRECATION_WARNINGS
2 #include <clutter/clutter.h>
3 
4 typedef struct _FooActor      FooActor;
5 typedef struct _FooActorClass FooActorClass;
6 
7 struct _FooActorClass
8 {
9   ClutterActorClass parent_class;
10 };
11 
12 struct _FooActor
13 {
14   ClutterActor parent;
15 
16   guint8 last_paint_opacity;
17   int paint_count;
18 };
19 
20 typedef struct
21 {
22   ClutterActor *stage;
23   FooActor *foo_actor;
24   ClutterActor *parent_container;
25   ClutterActor *container;
26   ClutterActor *child;
27   ClutterActor *unrelated_actor;
28   gboolean was_painted;
29 } Data;
30 
31 GType foo_actor_get_type (void) G_GNUC_CONST;
32 
33 G_DEFINE_TYPE (FooActor, foo_actor, CLUTTER_TYPE_ACTOR);
34 
35 static gboolean group_has_overlaps;
36 
37 static void
foo_actor_paint(ClutterActor * actor)38 foo_actor_paint (ClutterActor *actor)
39 {
40   FooActor *foo_actor = (FooActor *) actor;
41   ClutterActorBox allocation;
42 
43   foo_actor->last_paint_opacity = clutter_actor_get_paint_opacity (actor);
44   foo_actor->paint_count++;
45 
46   clutter_actor_get_allocation_box (actor, &allocation);
47 
48   /* Paint a red rectangle with the right opacity */
49   cogl_set_source_color4ub (255,
50                             0,
51                             0,
52                             foo_actor->last_paint_opacity);
53   cogl_rectangle (allocation.x1,
54                   allocation.y1,
55                   allocation.x2,
56                   allocation.y2);
57 }
58 
59 static gboolean
foo_actor_get_paint_volume(ClutterActor * actor,ClutterPaintVolume * volume)60 foo_actor_get_paint_volume (ClutterActor *actor,
61                             ClutterPaintVolume *volume)
62 {
63   return clutter_paint_volume_set_from_allocation (volume, actor);
64 }
65 
66 static gboolean
foo_actor_has_overlaps(ClutterActor * actor)67 foo_actor_has_overlaps (ClutterActor *actor)
68 {
69   return FALSE;
70 }
71 
72 static void
foo_actor_class_init(FooActorClass * klass)73 foo_actor_class_init (FooActorClass *klass)
74 {
75   ClutterActorClass *actor_class = (ClutterActorClass *) klass;
76 
77   actor_class->paint = foo_actor_paint;
78   actor_class->get_paint_volume = foo_actor_get_paint_volume;
79   actor_class->has_overlaps = foo_actor_has_overlaps;
80 }
81 
82 static void
foo_actor_init(FooActor * self)83 foo_actor_init (FooActor *self)
84 {
85 }
86 
87 typedef struct _FooGroup      FooGroup;
88 typedef struct _FooGroupClass FooGroupClass;
89 
90 struct _FooGroupClass
91 {
92   ClutterActorClass parent_class;
93 };
94 
95 struct _FooGroup
96 {
97   ClutterActor parent;
98 };
99 
100 GType foo_group_get_type (void);
101 
G_DEFINE_TYPE(FooGroup,foo_group,CLUTTER_TYPE_ACTOR)102 G_DEFINE_TYPE (FooGroup, foo_group, CLUTTER_TYPE_ACTOR)
103 
104 static gboolean
105 foo_group_has_overlaps (ClutterActor *actor)
106 {
107   return group_has_overlaps;
108 }
109 
110 static void
foo_group_class_init(FooGroupClass * klass)111 foo_group_class_init (FooGroupClass *klass)
112 {
113   ClutterActorClass *actor_class = (ClutterActorClass *) klass;
114 
115   actor_class->has_overlaps = foo_group_has_overlaps;
116 }
117 
118 static void
foo_group_init(FooGroup * self)119 foo_group_init (FooGroup *self)
120 {
121 }
122 
123 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)124 verify_results (Data *data,
125                 guint8 expected_color_red,
126                 guint8 expected_color_green,
127                 guint8 expected_color_blue,
128                 int expected_paint_count,
129                 int expected_paint_opacity)
130 {
131   guchar *pixel;
132 
133   data->foo_actor->paint_count = 0;
134 
135   /* Read a pixel at the center of the to determine what color it
136      painted. This should cause a redraw */
137   pixel = clutter_stage_read_pixels (CLUTTER_STAGE (data->stage),
138                                      50, 50, /* x/y */
139                                      1, 1 /* width/height */);
140 
141   g_assert_cmpint (expected_paint_count, ==, data->foo_actor->paint_count);
142   g_assert_cmpint (expected_paint_opacity,
143                    ==,
144                    data->foo_actor->last_paint_opacity);
145 
146   g_assert_cmpint (ABS ((int) expected_color_red - (int) pixel[0]), <=, 2);
147   g_assert_cmpint (ABS ((int) expected_color_green - (int) pixel[1]), <=, 2);
148   g_assert_cmpint (ABS ((int) expected_color_blue - (int) pixel[2]), <=, 2);
149 
150   free (pixel);
151 }
152 
153 static void
verify_redraw(Data * data,int expected_paint_count)154 verify_redraw (Data *data, int expected_paint_count)
155 {
156   GMainLoop *main_loop = g_main_loop_new (NULL, TRUE);
157   guint paint_handler;
158 
159   paint_handler = g_signal_connect_data (data->stage,
160                                          "paint",
161                                          G_CALLBACK (g_main_loop_quit),
162                                          main_loop,
163                                          NULL,
164                                          G_CONNECT_SWAPPED | G_CONNECT_AFTER);
165 
166   /* Queue a redraw on the stage */
167   clutter_actor_queue_redraw (data->stage);
168 
169   data->foo_actor->paint_count = 0;
170 
171   /* Wait for it to paint */
172   g_main_loop_run (main_loop);
173 
174   g_signal_handler_disconnect (data->stage, paint_handler);
175 
176   g_assert_cmpint (data->foo_actor->paint_count, ==, expected_paint_count);
177 }
178 
179 static gboolean
verify_redraws(gpointer user_data)180 verify_redraws (gpointer user_data)
181 {
182   Data *data = user_data;
183 
184   /* Queueing a redraw on the actor should cause a redraw */
185   clutter_actor_queue_redraw (data->container);
186   verify_redraw (data, 1);
187 
188   /* Queueing a redraw on a child should cause a redraw */
189   clutter_actor_queue_redraw (data->child);
190   verify_redraw (data, 1);
191 
192   /* Modifying the transformation on the parent should cause a
193      redraw */
194   clutter_actor_set_anchor_point (data->parent_container, 0, 1);
195   verify_redraw (data, 1);
196 
197   /* Redrawing an unrelated actor shouldn't cause a redraw */
198   clutter_actor_set_position (data->unrelated_actor, 0, 1);
199   verify_redraw (data, 0);
200 
201   data->was_painted = TRUE;
202 
203   return G_SOURCE_REMOVE;
204 }
205 
206 static gboolean
run_verify(gpointer user_data)207 run_verify (gpointer user_data)
208 {
209   Data *data = user_data;
210 
211   group_has_overlaps = FALSE;
212 
213   /* By default the actor shouldn't be redirected so the redraw should
214      cause the actor to be painted */
215   verify_results (data,
216                   255, 0, 0,
217                   1,
218                   255);
219 
220   /* Make the actor semi-transparent and verify the paint opacity */
221   clutter_actor_set_opacity (data->container, 127);
222   verify_results (data,
223                   255, 127, 127,
224                   1,
225                   127);
226 
227   /* With automatic redirect for opacity it shouldn't redirect if
228    * has_overlaps returns FALSE; */
229   clutter_actor_set_offscreen_redirect
230     (data->container, CLUTTER_OFFSCREEN_REDIRECT_AUTOMATIC_FOR_OPACITY);
231   verify_results (data,
232                   255, 127, 127,
233                   1,
234                   127);
235 
236   /* We do a double check here to verify that the actor wasn't cached
237    * during the last check. If it was cached then this check wouldn't
238    * result in any foo-actor re-paint. */
239   verify_results (data,
240                   255, 127, 127,
241                   1,
242                   127);
243 
244   /* With automatic redirect for opacity it should redirect if
245    * has_overlaps returns TRUE.
246    * The first paint will still cause the actor to draw because
247    * it needs to fill the cache first. It should be painted with full
248    * opacity */
249   group_has_overlaps = TRUE;
250 
251   verify_results (data,
252                   255, 127, 127,
253                   1,
254                   255);
255 
256   /* The second time the actor is painted it should be cached */
257   verify_results (data,
258                   255, 127, 127,
259                   0,
260                   255);
261 
262   /* We should be able to change the opacity without causing the actor
263      to redraw */
264   clutter_actor_set_opacity (data->container, 64);
265   verify_results (data,
266                   255, 191, 191,
267                   0,
268                   255);
269 
270   /* Changing it back to fully opaque should cause it not to go
271      through the FBO so it will draw */
272   clutter_actor_set_opacity (data->container, 255);
273   verify_results (data,
274                   255, 0, 0,
275                   1,
276                   255);
277 
278   /* Tell it to always redirect through the FBO. This should cause a
279      paint of the actor because the last draw didn't go through the
280      FBO */
281   clutter_actor_set_offscreen_redirect (data->container,
282                                         CLUTTER_OFFSCREEN_REDIRECT_ALWAYS);
283   verify_results (data,
284                   255, 0, 0,
285                   1,
286                   255);
287 
288   /* We should be able to change the opacity without causing the actor
289      to redraw */
290   clutter_actor_set_opacity (data->container, 64);
291   verify_results (data,
292                   255, 191, 191,
293                   0,
294                   255);
295 
296   /* Even changing it back to fully opaque shouldn't cause a redraw */
297   clutter_actor_set_opacity (data->container, 255);
298   verify_results (data,
299                   255, 0, 0,
300                   0,
301                   255);
302 
303   /* Check redraws */
304   g_idle_add (verify_redraws, data);
305 
306   return G_SOURCE_REMOVE;
307 }
308 
309 static void
actor_offscreen_redirect(void)310 actor_offscreen_redirect (void)
311 {
312   Data data = { 0 };
313 
314   if (!cogl_features_available (COGL_FEATURE_OFFSCREEN))
315     return;
316 
317   data.stage = clutter_test_get_stage ();
318   data.parent_container = clutter_actor_new ();
319   data.container = g_object_new (foo_group_get_type (), NULL);
320   data.foo_actor = g_object_new (foo_actor_get_type (), NULL);
321   clutter_actor_set_size (CLUTTER_ACTOR (data.foo_actor), 100, 100);
322 
323   clutter_actor_add_child (data.container, CLUTTER_ACTOR (data.foo_actor));
324   clutter_actor_add_child (data.parent_container, data.container);
325   clutter_actor_add_child (data.stage, data.parent_container);
326 
327   data.child = clutter_actor_new ();
328   clutter_actor_set_size (data.child, 1, 1);
329   clutter_actor_add_child (data.container, data.child);
330 
331   data.unrelated_actor = clutter_actor_new ();
332   clutter_actor_set_size (data.child, 1, 1);
333   clutter_actor_add_child (data.stage, data.unrelated_actor);
334 
335   clutter_actor_show (data.stage);
336 
337   clutter_threads_add_repaint_func_full (CLUTTER_REPAINT_FLAGS_POST_PAINT,
338                                          run_verify,
339                                          &data,
340                                          NULL);
341 
342   while (!data.was_painted)
343     g_main_context_iteration (NULL, FALSE);
344 }
345 
346 CLUTTER_TEST_SUITE (
347   CLUTTER_TEST_UNIT ("/actor/offscreen/redirect", actor_offscreen_redirect)
348 )
349