1 /*
2  * Cogl
3  *
4  * A Low Level GPU Graphics and Utilities API
5  *
6  * Copyright (C) 2011 Intel Corporation.
7  *
8  * Permission is hereby granted, free of charge, to any person
9  * obtaining a copy of this software and associated documentation
10  * files (the "Software"), to deal in the Software without
11  * restriction, including without limitation the rights to use, copy,
12  * modify, merge, publish, distribute, sublicense, and/or sell copies
13  * of the Software, and to permit persons to whom the Software is
14  * furnished to do so, subject to the following conditions:
15  *
16  * The above copyright notice and this permission notice shall be
17  * included in all copies or substantial portions of the Software.
18  *
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
23  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
24  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26  * SOFTWARE.
27  *
28  *
29  * Authors:
30  *  Robert Bragg   <robert@linux.intel.com>
31  */
32 
33 #ifdef HAVE_CONFIG_H
34 #include "config.h"
35 #endif
36 
37 #include "cogl-texture.h"
38 #include "cogl-matrix.h"
39 #include "cogl-spans.h"
40 #include "cogl-meta-texture.h"
41 #include "cogl-texture-rectangle-private.h"
42 
43 #include <string.h>
44 #include <math.h>
45 
46 typedef struct _ForeachData
47 {
48   float meta_region_coords[4];
49   CoglPipelineWrapMode wrap_s;
50   CoglPipelineWrapMode wrap_t;
51   CoglMetaTextureCallback callback;
52   void *user_data;
53 
54   int width;
55   int height;
56 
57   CoglTexture *padded_textures[9];
58   const float *grid_slice_texture_coords;
59   float slice_offset_s;
60   float slice_offset_t;
61   float slice_range_s;
62   float slice_range_t;
63 } ForeachData;
64 
65 static void
padded_grid_repeat_cb(CoglTexture * slice_texture,const float * slice_texture_coords,const float * meta_coords,void * user_data)66 padded_grid_repeat_cb (CoglTexture *slice_texture,
67                        const float *slice_texture_coords,
68                        const float *meta_coords,
69                        void *user_data)
70 {
71   ForeachData *data;
72   float mapped_coords[4];
73 
74   /* Ignore padding slices for the current grid */
75   if (!slice_texture)
76     return;
77 
78   data = user_data;
79 
80   /* NB: the slice_texture_coords[] we get here will always be
81    * normalized.
82    *
83    * We now need to map the normalized slice_texture_coords[] we have
84    * here back to the real slice coordinates we saved in the previous
85    * stage...
86    */
87   mapped_coords[0] =
88     slice_texture_coords[0] * data->slice_range_s + data->slice_offset_s;
89   mapped_coords[1] =
90     slice_texture_coords[1] * data->slice_range_t + data->slice_offset_t;
91   mapped_coords[2] =
92     slice_texture_coords[2] * data->slice_range_s + data->slice_offset_s;
93   mapped_coords[3] =
94     slice_texture_coords[3] * data->slice_range_t + data->slice_offset_t;
95 
96   data->callback (slice_texture,
97                   mapped_coords, meta_coords, data->user_data);
98 }
99 
100 static int
setup_padded_spans(CoglSpan * spans,float start,float end,float range,int * real_index)101 setup_padded_spans (CoglSpan *spans,
102                     float start,
103                     float end,
104                     float range,
105                     int *real_index)
106 {
107   int span_index = 0;
108 
109   if (start > 0)
110     {
111       spans[0].start = 0;
112       spans[0].size = start;
113       spans[0].waste = 0;
114       span_index++;
115       spans[1].start = spans[0].size;
116     }
117   else
118     spans[span_index].start = 0;
119 
120   spans[span_index].size = end - start;
121   spans[span_index].waste = 0;
122   *real_index = span_index;
123   span_index++;
124 
125   if (end < range)
126     {
127       spans[span_index].start =
128         spans[span_index - 1].start + spans[span_index - 1].size;
129       spans[span_index].size = range - end;
130       spans[span_index].waste = 0;
131       span_index++;
132     }
133 
134   return span_index;
135 }
136 
137 /* This handles each sub-texture within the range [0,1] of our
138  * original meta texture and repeats each one separately across the
139  * users requested virtual texture coordinates.
140  *
141  * A notable advantage of this approach is that we will batch
142  * together callbacks corresponding to the same underlying slice
143  * together.
144  */
145 static void
create_grid_and_repeat_cb(CoglTexture * slice_texture,const float * slice_texture_coords,const float * meta_coords,void * user_data)146 create_grid_and_repeat_cb (CoglTexture *slice_texture,
147                            const float *slice_texture_coords,
148                            const float *meta_coords,
149                            void *user_data)
150 {
151   ForeachData *data = user_data;
152   CoglSpan x_spans[3];
153   int n_x_spans;
154   int x_real_index;
155   CoglSpan y_spans[3];
156   int n_y_spans;
157   int y_real_index;
158 
159   /* NB: This callback is called for each slice of the meta-texture
160    * in the range [0,1].
161    *
162    * We define a "padded grid" for each slice of the meta-texture in
163    * the range [0,1]. The x axis and y axis grid lines are defined
164    * using CoglSpans.
165    *
166    * The padded grid maps over the meta-texture coordinates in the
167    * range [0,1] but only contains one valid cell that corresponds to
168    * current slice being iterated and all the surrounding cells just
169    * provide padding.
170    *
171    * Once we've defined our padded grid we then repeat that across
172    * the user's original region, calling their callback whenever
173    * we see our current slice - ignoring padding.
174    *
175    * NB: we can assume meta_coords[] are normalized at this point
176    * since TextureRectangles aren't iterated with this code-path.
177    *
178    * NB: spans are always defined using non-normalized coordinates
179    */
180   n_x_spans = setup_padded_spans (x_spans,
181                                   meta_coords[0] * data->width,
182                                   meta_coords[2] * data->width,
183                                   data->width,
184                                   &x_real_index);
185   n_y_spans = setup_padded_spans (y_spans,
186                                   meta_coords[1] * data->height,
187                                   meta_coords[3] * data->height,
188                                   data->height,
189                                   &y_real_index);
190 
191   data->padded_textures[n_x_spans * y_real_index + x_real_index] =
192     slice_texture;
193 
194   /* Our callback is going to be passed normalized slice texture
195    * coordinates, and we will need to map the range [0,1] to the real
196    * slice_texture_coords we have here... */
197   data->grid_slice_texture_coords = slice_texture_coords;
198   data->slice_range_s = fabs (data->grid_slice_texture_coords[2] -
199                               data->grid_slice_texture_coords[0]);
200   data->slice_range_t = fabs (data->grid_slice_texture_coords[3] -
201                               data->grid_slice_texture_coords[1]);
202   data->slice_offset_s = MIN (data->grid_slice_texture_coords[0],
203                               data->grid_slice_texture_coords[2]);
204   data->slice_offset_t = MIN (data->grid_slice_texture_coords[1],
205                               data->grid_slice_texture_coords[3]);
206 
207   /* Now actually iterate the region the user originally requested
208    * using the current padded grid */
209   _cogl_texture_spans_foreach_in_region (x_spans,
210                                          n_x_spans,
211                                          y_spans,
212                                          n_y_spans,
213                                          data->padded_textures,
214                                          data->meta_region_coords,
215                                          data->width,
216                                          data->height,
217                                          data->wrap_s,
218                                          data->wrap_t,
219                                          padded_grid_repeat_cb,
220                                          data);
221 
222   /* Clear the padded_textures ready for the next iteration */
223   data->padded_textures[n_x_spans * y_real_index + x_real_index] = NULL;
224 }
225 
226 #define SWAP(A,B) do { float tmp = B; B = A; A = tmp; } while (0)
227 
228 typedef struct _ClampData
229 {
230   float start;
231   float end;
232   CoglBool s_flipped;
233   CoglBool t_flipped;
234   CoglMetaTextureCallback callback;
235   void *user_data;
236 } ClampData;
237 
238 static void
clamp_s_cb(CoglTexture * sub_texture,const float * sub_texture_coords,const float * meta_coords,void * user_data)239 clamp_s_cb (CoglTexture *sub_texture,
240             const float *sub_texture_coords,
241             const float *meta_coords,
242             void *user_data)
243 {
244   ClampData *clamp_data = user_data;
245   float mapped_meta_coords[4] = {
246     clamp_data->start,
247     meta_coords[1],
248     clamp_data->end,
249     meta_coords[3]
250   };
251   if (clamp_data->s_flipped)
252     SWAP (mapped_meta_coords[0], mapped_meta_coords[2]);
253   /* NB: we never need to flip the t coords when dealing with
254    * s-axis clamping so no need to check if ->t_flipped */
255   clamp_data->callback (sub_texture,
256                         sub_texture_coords, mapped_meta_coords,
257                         clamp_data->user_data);
258 }
259 
260 static void
clamp_t_cb(CoglTexture * sub_texture,const float * sub_texture_coords,const float * meta_coords,void * user_data)261 clamp_t_cb (CoglTexture *sub_texture,
262             const float *sub_texture_coords,
263             const float *meta_coords,
264             void *user_data)
265 {
266   ClampData *clamp_data = user_data;
267   float mapped_meta_coords[4] = {
268     meta_coords[0],
269     clamp_data->start,
270     meta_coords[2],
271     clamp_data->end,
272   };
273   if (clamp_data->s_flipped)
274     SWAP (mapped_meta_coords[0], mapped_meta_coords[2]);
275   if (clamp_data->t_flipped)
276     SWAP (mapped_meta_coords[1], mapped_meta_coords[3]);
277   clamp_data->callback (sub_texture,
278                         sub_texture_coords, mapped_meta_coords,
279                         clamp_data->user_data);
280 }
281 
282 static CoglBool
foreach_clamped_region(CoglMetaTexture * meta_texture,float * tx_1,float * ty_1,float * tx_2,float * ty_2,CoglPipelineWrapMode wrap_s,CoglPipelineWrapMode wrap_t,CoglMetaTextureCallback callback,void * user_data)283 foreach_clamped_region (CoglMetaTexture *meta_texture,
284                         float *tx_1,
285                         float *ty_1,
286                         float *tx_2,
287                         float *ty_2,
288                         CoglPipelineWrapMode wrap_s,
289                         CoglPipelineWrapMode wrap_t,
290                         CoglMetaTextureCallback callback,
291                         void *user_data)
292 {
293   float width = cogl_texture_get_width (COGL_TEXTURE (meta_texture));
294   ClampData clamp_data;
295 
296   /* Consider that *tx_1 may be > *tx_2 and to simplify things
297    * we just flip them around if that's the case and keep a note
298    * of the fact that they are flipped. */
299   if (*tx_1 > *tx_2)
300     {
301       SWAP (*tx_1, *tx_2);
302       clamp_data.s_flipped = TRUE;
303     }
304   else
305     clamp_data.s_flipped = FALSE;
306 
307   /* The same goes for ty_1 and ty_2... */
308   if (*ty_1 > *ty_2)
309     {
310       SWAP (*ty_1, *ty_2);
311       clamp_data.t_flipped = TRUE;
312     }
313   else
314     clamp_data.t_flipped = FALSE;
315 
316   clamp_data.callback = callback;
317   clamp_data.user_data = user_data;
318 
319   if (wrap_s == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
320     {
321       float max_s_coord;
322       float half_texel_width;
323 
324       /* Consider that rectangle textures have non-normalized
325        * coordinates... */
326       if (cogl_is_texture_rectangle (meta_texture))
327         max_s_coord = width;
328       else
329         max_s_coord = 1.0;
330 
331       half_texel_width = max_s_coord / (width * 2);
332 
333       /* Handle any left clamped region */
334       if (*tx_1 < 0)
335         {
336           clamp_data.start = *tx_1;
337           clamp_data.end = MIN (0, *tx_2);;
338           cogl_meta_texture_foreach_in_region (meta_texture,
339                                                half_texel_width, *ty_1,
340                                                half_texel_width, *ty_2,
341                                                COGL_PIPELINE_WRAP_MODE_REPEAT,
342                                                wrap_t,
343                                                clamp_s_cb,
344                                                &clamp_data);
345           /* Have we handled everything? */
346           if (*tx_2 <= 0)
347             return TRUE;
348 
349           /* clamp tx_1 since we've handled everything with x < 0 */
350           *tx_1 = 0;
351         }
352 
353       /* Handle any right clamped region - including the corners */
354       if (*tx_2 > max_s_coord)
355         {
356           clamp_data.start = MAX (max_s_coord, *tx_1);
357           clamp_data.end = *tx_2;
358           cogl_meta_texture_foreach_in_region (meta_texture,
359                                                max_s_coord - half_texel_width,
360                                                *ty_1,
361                                                max_s_coord - half_texel_width,
362                                                *ty_2,
363                                                COGL_PIPELINE_WRAP_MODE_REPEAT,
364                                                wrap_t,
365                                                clamp_s_cb,
366                                                &clamp_data);
367           /* Have we handled everything? */
368           if (*tx_1 >= max_s_coord)
369             return TRUE;
370 
371           /* clamp tx_2 since we've handled everything with x >
372            * max_s_coord */
373           *tx_2 = max_s_coord;
374         }
375     }
376 
377   if (wrap_t == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
378     {
379       float height = cogl_texture_get_height (COGL_TEXTURE (meta_texture));
380       float max_t_coord;
381       float half_texel_height;
382 
383       /* Consider that rectangle textures have non-normalized
384        * coordinates... */
385       if (cogl_is_texture_rectangle (meta_texture))
386         max_t_coord = height;
387       else
388         max_t_coord = 1.0;
389 
390       half_texel_height = max_t_coord / (height * 2);
391 
392       /* Handle any top clamped region */
393       if (*ty_1 < 0)
394         {
395           clamp_data.start = *ty_1;
396           clamp_data.end = MIN (0, *ty_2);;
397           cogl_meta_texture_foreach_in_region (meta_texture,
398                                                *tx_1, half_texel_height,
399                                                *tx_2, half_texel_height,
400                                                wrap_s,
401                                                COGL_PIPELINE_WRAP_MODE_REPEAT,
402                                                clamp_t_cb,
403                                                &clamp_data);
404           /* Have we handled everything? */
405           if (*tx_2 <= 0)
406             return TRUE;
407 
408           /* clamp ty_1 since we've handled everything with y < 0 */
409           *ty_1 = 0;
410         }
411 
412       /* Handle any bottom clamped region */
413       if (*ty_2 > max_t_coord)
414         {
415           clamp_data.start = MAX (max_t_coord, *ty_1);;
416           clamp_data.end = *ty_2;
417           cogl_meta_texture_foreach_in_region (meta_texture,
418                                                *tx_1,
419                                                max_t_coord - half_texel_height,
420                                                *tx_2,
421                                                max_t_coord - half_texel_height,
422                                                wrap_s,
423                                                COGL_PIPELINE_WRAP_MODE_REPEAT,
424                                                clamp_t_cb,
425                                                &clamp_data);
426           /* Have we handled everything? */
427           if (*ty_1 >= max_t_coord)
428             return TRUE;
429 
430           /* clamp ty_2 since we've handled everything with y >
431            * max_t_coord */
432           *ty_2 = max_t_coord;
433         }
434     }
435 
436   if (clamp_data.s_flipped)
437     SWAP (*tx_1, *tx_2);
438   if (clamp_data.t_flipped)
439     SWAP (*ty_1, *ty_2);
440 
441   return FALSE;
442 }
443 
444 typedef struct _NormalizeData
445 {
446   CoglMetaTextureCallback callback;
447   void *user_data;
448   float s_normalize_factor;
449   float t_normalize_factor;
450 } NormalizeData;
451 
452 static void
normalize_meta_coords_cb(CoglTexture * slice_texture,const float * slice_coords,const float * meta_coords,void * user_data)453 normalize_meta_coords_cb (CoglTexture *slice_texture,
454                           const float *slice_coords,
455                           const float *meta_coords,
456                           void *user_data)
457 {
458   NormalizeData *data = user_data;
459   float normalized_meta_coords[4] = {
460       meta_coords[0] * data->s_normalize_factor,
461       meta_coords[1] * data->t_normalize_factor,
462       meta_coords[2] * data->s_normalize_factor,
463       meta_coords[3] * data->t_normalize_factor
464   };
465 
466   data->callback (slice_texture,
467                   slice_coords, normalized_meta_coords,
468                   data->user_data);
469 }
470 
471 typedef struct _UnNormalizeData
472 {
473   CoglMetaTextureCallback callback;
474   void *user_data;
475   float width;
476   float height;
477 } UnNormalizeData;
478 
479 static void
un_normalize_slice_coords_cb(CoglTexture * slice_texture,const float * slice_coords,const float * meta_coords,void * user_data)480 un_normalize_slice_coords_cb (CoglTexture *slice_texture,
481                               const float *slice_coords,
482                               const float *meta_coords,
483                               void *user_data)
484 {
485   UnNormalizeData *data = user_data;
486   float un_normalized_slice_coords[4] = {
487     slice_coords[0] * data->width,
488     slice_coords[1] * data->height,
489     slice_coords[2] * data->width,
490     slice_coords[3] * data->height
491   };
492 
493   data->callback (slice_texture,
494                   un_normalized_slice_coords, meta_coords,
495                   data->user_data);
496 }
497 
498 void
cogl_meta_texture_foreach_in_region(CoglMetaTexture * meta_texture,float tx_1,float ty_1,float tx_2,float ty_2,CoglPipelineWrapMode wrap_s,CoglPipelineWrapMode wrap_t,CoglMetaTextureCallback callback,void * user_data)499 cogl_meta_texture_foreach_in_region (CoglMetaTexture *meta_texture,
500                                      float tx_1,
501                                      float ty_1,
502                                      float tx_2,
503                                      float ty_2,
504                                      CoglPipelineWrapMode wrap_s,
505                                      CoglPipelineWrapMode wrap_t,
506                                      CoglMetaTextureCallback callback,
507                                      void *user_data)
508 {
509   CoglTexture *texture = COGL_TEXTURE (meta_texture);
510   float width = cogl_texture_get_width (texture);
511   float height = cogl_texture_get_height (texture);
512   NormalizeData normalize_data;
513 
514   if (wrap_s == COGL_PIPELINE_WRAP_MODE_AUTOMATIC)
515     wrap_s = COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE;
516   if (wrap_t == COGL_PIPELINE_WRAP_MODE_AUTOMATIC)
517     wrap_t = COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE;
518 
519   if (wrap_s == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE ||
520       wrap_t == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
521     {
522       CoglBool finished = foreach_clamped_region (meta_texture,
523                                                   &tx_1, &ty_1, &tx_2, &ty_2,
524                                                   wrap_s, wrap_t,
525                                                   callback,
526                                                   user_data);
527       if (finished)
528         return;
529 
530       /* Since clamping has been handled we now want to normalize our
531        * wrap modes we se can assume from this point on we don't
532        * need to consider CLAMP_TO_EDGE. (NB: The spans code will
533        * assert that CLAMP_TO_EDGE isn't requested) */
534       if (wrap_s == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
535         wrap_s = COGL_PIPELINE_WRAP_MODE_REPEAT;
536       if (wrap_t == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
537         wrap_t = COGL_PIPELINE_WRAP_MODE_REPEAT;
538     }
539 
540   /* It makes things simpler to deal with non-normalized region
541    * coordinates beyond this point and only re-normalize just before
542    * calling the user's callback... */
543 
544   if (!cogl_is_texture_rectangle (COGL_TEXTURE (meta_texture)))
545     {
546       normalize_data.callback = callback;
547       normalize_data.user_data = user_data;
548       normalize_data.s_normalize_factor = 1.0f / width;
549       normalize_data.t_normalize_factor = 1.0f / height;
550       callback = normalize_meta_coords_cb;
551       user_data = &normalize_data;
552       tx_1 *= width;
553       ty_1 *= height;
554       tx_2 *= width;
555       ty_2 *= height;
556     }
557 
558   /* XXX: at some point this wont be routed through the CoglTexture
559    * vtable, instead there will be a separate CoglMetaTexture
560    * interface vtable. */
561 
562   if (texture->vtable->foreach_sub_texture_in_region)
563     {
564       ForeachData data;
565 
566       data.meta_region_coords[0] = tx_1;
567       data.meta_region_coords[1] = ty_1;
568       data.meta_region_coords[2] = tx_2;
569       data.meta_region_coords[3] = ty_2;
570       data.wrap_s = wrap_s;
571       data.wrap_t = wrap_t;
572       data.callback = callback;
573       data.user_data = user_data;
574 
575       data.width = width;
576       data.height = height;
577 
578       memset (data.padded_textures, 0, sizeof (data.padded_textures));
579 
580       /*
581        * 1) We iterate all the slices of the meta-texture only within
582        *    the range [0,1].
583        *
584        * 2) We define a "padded grid" for each slice of the
585        *    meta-texture in the range [0,1].
586        *
587        *    The padded grid maps over the meta-texture coordinates in
588        *    the range [0,1] but only contains one valid cell that
589        *    corresponds to current slice being iterated and all the
590        *    surrounding cells just provide padding.
591        *
592        * 3) Once we've defined our padded grid we then repeat that
593        *    across the user's original region, calling their callback
594        *    whenever we see our current slice - ignoring padding.
595        *
596        * A notable benefit of this design is that repeating a texture
597        * made of multiple slices will result in us repeating each
598        * slice in-turn so the user gets repeat callbacks for the same
599        * texture batched together. For manual emulation of texture
600        * repeats done by drawing geometry this makes it more likely
601        * that we can batch geometry.
602        */
603 
604       texture->vtable->foreach_sub_texture_in_region (texture,
605                                                       0, 0, 1, 1,
606                                                       create_grid_and_repeat_cb,
607                                                       &data);
608     }
609   else
610     {
611       CoglSpan x_span = { 0, width, 0 };
612       CoglSpan y_span = { 0, height, 0 };
613       float meta_region_coords[4] = { tx_1, ty_1, tx_2, ty_2 };
614       UnNormalizeData un_normalize_data;
615 
616       /* If we are dealing with a CoglTextureRectangle then we need a shim
617        * callback that un_normalizes the slice coordinates we get from
618        * _cogl_texture_spans_foreach_in_region before passing them to
619        * the user's callback. */
620       if (cogl_is_texture_rectangle (meta_texture))
621         {
622           un_normalize_data.callback = callback;
623           un_normalize_data.user_data = user_data;
624           un_normalize_data.width = width;
625           un_normalize_data.height = height;
626           callback = un_normalize_slice_coords_cb;
627           user_data = &un_normalize_data;
628         }
629 
630       _cogl_texture_spans_foreach_in_region (&x_span, 1,
631                                              &y_span, 1,
632                                              &texture,
633                                              meta_region_coords,
634                                              width,
635                                              height,
636                                              wrap_s,
637                                              wrap_t,
638                                              callback,
639                                              user_data);
640     }
641 }
642 #undef SWAP
643