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 #include "cogl-config.h"
34 
35 #include "cogl-texture.h"
36 #include "cogl-spans.h"
37 #include "cogl-meta-texture.h"
38 #include "cogl-texture-private.h"
39 
40 #include <string.h>
41 #include <math.h>
42 
43 typedef struct _ForeachData
44 {
45   float meta_region_coords[4];
46   CoglPipelineWrapMode wrap_s;
47   CoglPipelineWrapMode wrap_t;
48   CoglMetaTextureCallback callback;
49   void *user_data;
50 
51   int width;
52   int height;
53 
54   CoglTexture *padded_textures[9];
55   const float *grid_slice_texture_coords;
56   float slice_offset_s;
57   float slice_offset_t;
58   float slice_range_s;
59   float slice_range_t;
60 } ForeachData;
61 
62 static void
padded_grid_repeat_cb(CoglTexture * slice_texture,const float * slice_texture_coords,const float * meta_coords,void * user_data)63 padded_grid_repeat_cb (CoglTexture *slice_texture,
64                        const float *slice_texture_coords,
65                        const float *meta_coords,
66                        void *user_data)
67 {
68   ForeachData *data;
69   float mapped_coords[4];
70 
71   /* Ignore padding slices for the current grid */
72   if (!slice_texture)
73     return;
74 
75   data = user_data;
76 
77   /* NB: the slice_texture_coords[] we get here will always be
78    * normalized.
79    *
80    * We now need to map the normalized slice_texture_coords[] we have
81    * here back to the real slice coordinates we saved in the previous
82    * stage...
83    */
84   mapped_coords[0] =
85     slice_texture_coords[0] * data->slice_range_s + data->slice_offset_s;
86   mapped_coords[1] =
87     slice_texture_coords[1] * data->slice_range_t + data->slice_offset_t;
88   mapped_coords[2] =
89     slice_texture_coords[2] * data->slice_range_s + data->slice_offset_s;
90   mapped_coords[3] =
91     slice_texture_coords[3] * data->slice_range_t + data->slice_offset_t;
92 
93   data->callback (slice_texture,
94                   mapped_coords, meta_coords, data->user_data);
95 }
96 
97 static int
setup_padded_spans(CoglSpan * spans,float start,float end,float range,int * real_index)98 setup_padded_spans (CoglSpan *spans,
99                     float start,
100                     float end,
101                     float range,
102                     int *real_index)
103 {
104   int span_index = 0;
105 
106   if (start > 0)
107     {
108       spans[0].start = 0;
109       spans[0].size = start;
110       spans[0].waste = 0;
111       span_index++;
112       spans[1].start = spans[0].size;
113     }
114   else
115     spans[span_index].start = 0;
116 
117   spans[span_index].size = end - start;
118   spans[span_index].waste = 0;
119   *real_index = span_index;
120   span_index++;
121 
122   if (end < range)
123     {
124       spans[span_index].start =
125         spans[span_index - 1].start + spans[span_index - 1].size;
126       spans[span_index].size = range - end;
127       spans[span_index].waste = 0;
128       span_index++;
129     }
130 
131   return span_index;
132 }
133 
134 /* This handles each sub-texture within the range [0,1] of our
135  * original meta texture and repeats each one separately across the
136  * users requested virtual texture coordinates.
137  *
138  * A notable advantage of this approach is that we will batch
139  * together callbacks corresponding to the same underlying slice
140  * together.
141  */
142 static void
create_grid_and_repeat_cb(CoglTexture * slice_texture,const float * slice_texture_coords,const float * meta_coords,void * user_data)143 create_grid_and_repeat_cb (CoglTexture *slice_texture,
144                            const float *slice_texture_coords,
145                            const float *meta_coords,
146                            void *user_data)
147 {
148   ForeachData *data = user_data;
149   CoglSpan x_spans[3];
150   int n_x_spans;
151   int x_real_index;
152   CoglSpan y_spans[3];
153   int n_y_spans;
154   int y_real_index;
155 
156   /* NB: This callback is called for each slice of the meta-texture
157    * in the range [0,1].
158    *
159    * We define a "padded grid" for each slice of the meta-texture in
160    * the range [0,1]. The x axis and y axis grid lines are defined
161    * using CoglSpans.
162    *
163    * The padded grid maps over the meta-texture coordinates in the
164    * range [0,1] but only contains one valid cell that corresponds to
165    * current slice being iterated and all the surrounding cells just
166    * provide padding.
167    *
168    * Once we've defined our padded grid we then repeat that across
169    * the user's original region, calling their callback whenever
170    * we see our current slice - ignoring padding.
171    *
172    * NB: we can assume meta_coords[] are normalized at this point
173    * since TextureRectangles aren't iterated with this code-path.
174    *
175    * NB: spans are always defined using non-normalized coordinates
176    */
177   n_x_spans = setup_padded_spans (x_spans,
178                                   meta_coords[0] * data->width,
179                                   meta_coords[2] * data->width,
180                                   data->width,
181                                   &x_real_index);
182   n_y_spans = setup_padded_spans (y_spans,
183                                   meta_coords[1] * data->height,
184                                   meta_coords[3] * data->height,
185                                   data->height,
186                                   &y_real_index);
187 
188   data->padded_textures[n_x_spans * y_real_index + x_real_index] =
189     slice_texture;
190 
191   /* Our callback is going to be passed normalized slice texture
192    * coordinates, and we will need to map the range [0,1] to the real
193    * slice_texture_coords we have here... */
194   data->grid_slice_texture_coords = slice_texture_coords;
195   data->slice_range_s = fabs (data->grid_slice_texture_coords[2] -
196                               data->grid_slice_texture_coords[0]);
197   data->slice_range_t = fabs (data->grid_slice_texture_coords[3] -
198                               data->grid_slice_texture_coords[1]);
199   data->slice_offset_s = MIN (data->grid_slice_texture_coords[0],
200                               data->grid_slice_texture_coords[2]);
201   data->slice_offset_t = MIN (data->grid_slice_texture_coords[1],
202                               data->grid_slice_texture_coords[3]);
203 
204   /* Now actually iterate the region the user originally requested
205    * using the current padded grid */
206   _cogl_texture_spans_foreach_in_region (x_spans,
207                                          n_x_spans,
208                                          y_spans,
209                                          n_y_spans,
210                                          data->padded_textures,
211                                          data->meta_region_coords,
212                                          data->width,
213                                          data->height,
214                                          data->wrap_s,
215                                          data->wrap_t,
216                                          padded_grid_repeat_cb,
217                                          data);
218 
219   /* Clear the padded_textures ready for the next iteration */
220   data->padded_textures[n_x_spans * y_real_index + x_real_index] = NULL;
221 }
222 
223 #define SWAP(A,B) do { float tmp = B; B = A; A = tmp; } while (0)
224 
225 typedef struct _ClampData
226 {
227   float start;
228   float end;
229   gboolean s_flipped;
230   gboolean t_flipped;
231   CoglMetaTextureCallback callback;
232   void *user_data;
233 } ClampData;
234 
235 static void
clamp_s_cb(CoglTexture * sub_texture,const float * sub_texture_coords,const float * meta_coords,void * user_data)236 clamp_s_cb (CoglTexture *sub_texture,
237             const float *sub_texture_coords,
238             const float *meta_coords,
239             void *user_data)
240 {
241   ClampData *clamp_data = user_data;
242   float mapped_meta_coords[4] = {
243     clamp_data->start,
244     meta_coords[1],
245     clamp_data->end,
246     meta_coords[3]
247   };
248   if (clamp_data->s_flipped)
249     SWAP (mapped_meta_coords[0], mapped_meta_coords[2]);
250   /* NB: we never need to flip the t coords when dealing with
251    * s-axis clamping so no need to check if ->t_flipped */
252   clamp_data->callback (sub_texture,
253                         sub_texture_coords, mapped_meta_coords,
254                         clamp_data->user_data);
255 }
256 
257 static void
clamp_t_cb(CoglTexture * sub_texture,const float * sub_texture_coords,const float * meta_coords,void * user_data)258 clamp_t_cb (CoglTexture *sub_texture,
259             const float *sub_texture_coords,
260             const float *meta_coords,
261             void *user_data)
262 {
263   ClampData *clamp_data = user_data;
264   float mapped_meta_coords[4] = {
265     meta_coords[0],
266     clamp_data->start,
267     meta_coords[2],
268     clamp_data->end,
269   };
270   if (clamp_data->s_flipped)
271     SWAP (mapped_meta_coords[0], mapped_meta_coords[2]);
272   if (clamp_data->t_flipped)
273     SWAP (mapped_meta_coords[1], mapped_meta_coords[3]);
274   clamp_data->callback (sub_texture,
275                         sub_texture_coords, mapped_meta_coords,
276                         clamp_data->user_data);
277 }
278 
279 static gboolean
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)280 foreach_clamped_region (CoglMetaTexture *meta_texture,
281                         float *tx_1,
282                         float *ty_1,
283                         float *tx_2,
284                         float *ty_2,
285                         CoglPipelineWrapMode wrap_s,
286                         CoglPipelineWrapMode wrap_t,
287                         CoglMetaTextureCallback callback,
288                         void *user_data)
289 {
290   float width = cogl_texture_get_width (COGL_TEXTURE (meta_texture));
291   ClampData clamp_data;
292 
293   /* Consider that *tx_1 may be > *tx_2 and to simplify things
294    * we just flip them around if that's the case and keep a note
295    * of the fact that they are flipped. */
296   if (*tx_1 > *tx_2)
297     {
298       SWAP (*tx_1, *tx_2);
299       clamp_data.s_flipped = TRUE;
300     }
301   else
302     clamp_data.s_flipped = FALSE;
303 
304   /* The same goes for ty_1 and ty_2... */
305   if (*ty_1 > *ty_2)
306     {
307       SWAP (*ty_1, *ty_2);
308       clamp_data.t_flipped = TRUE;
309     }
310   else
311     clamp_data.t_flipped = FALSE;
312 
313   clamp_data.callback = callback;
314   clamp_data.user_data = user_data;
315 
316   if (wrap_s == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
317     {
318       float max_s_coord = 1.0;
319       float half_texel_width;
320 
321       half_texel_width = max_s_coord / (width * 2);
322 
323       /* Handle any left clamped region */
324       if (*tx_1 < 0)
325         {
326           clamp_data.start = *tx_1;
327           clamp_data.end = MIN (0, *tx_2);
328           cogl_meta_texture_foreach_in_region (meta_texture,
329                                                half_texel_width, *ty_1,
330                                                half_texel_width, *ty_2,
331                                                COGL_PIPELINE_WRAP_MODE_REPEAT,
332                                                wrap_t,
333                                                clamp_s_cb,
334                                                &clamp_data);
335           /* Have we handled everything? */
336           if (*tx_2 <= 0)
337             return TRUE;
338 
339           /* clamp tx_1 since we've handled everything with x < 0 */
340           *tx_1 = 0;
341         }
342 
343       /* Handle any right clamped region - including the corners */
344       if (*tx_2 > max_s_coord)
345         {
346           clamp_data.start = MAX (max_s_coord, *tx_1);
347           clamp_data.end = *tx_2;
348           cogl_meta_texture_foreach_in_region (meta_texture,
349                                                max_s_coord - half_texel_width,
350                                                *ty_1,
351                                                max_s_coord - half_texel_width,
352                                                *ty_2,
353                                                COGL_PIPELINE_WRAP_MODE_REPEAT,
354                                                wrap_t,
355                                                clamp_s_cb,
356                                                &clamp_data);
357           /* Have we handled everything? */
358           if (*tx_1 >= max_s_coord)
359             return TRUE;
360 
361           /* clamp tx_2 since we've handled everything with x >
362            * max_s_coord */
363           *tx_2 = max_s_coord;
364         }
365     }
366 
367   if (wrap_t == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
368     {
369       float height = cogl_texture_get_height (COGL_TEXTURE (meta_texture));
370       float max_t_coord = 1.0;
371       float half_texel_height;
372 
373       half_texel_height = max_t_coord / (height * 2);
374 
375       /* Handle any top clamped region */
376       if (*ty_1 < 0)
377         {
378           clamp_data.start = *ty_1;
379           clamp_data.end = MIN (0, *ty_2);
380           cogl_meta_texture_foreach_in_region (meta_texture,
381                                                *tx_1, half_texel_height,
382                                                *tx_2, half_texel_height,
383                                                wrap_s,
384                                                COGL_PIPELINE_WRAP_MODE_REPEAT,
385                                                clamp_t_cb,
386                                                &clamp_data);
387           /* Have we handled everything? */
388           if (*tx_2 <= 0)
389             return TRUE;
390 
391           /* clamp ty_1 since we've handled everything with y < 0 */
392           *ty_1 = 0;
393         }
394 
395       /* Handle any bottom clamped region */
396       if (*ty_2 > max_t_coord)
397         {
398           clamp_data.start = MAX (max_t_coord, *ty_1);
399           clamp_data.end = *ty_2;
400           cogl_meta_texture_foreach_in_region (meta_texture,
401                                                *tx_1,
402                                                max_t_coord - half_texel_height,
403                                                *tx_2,
404                                                max_t_coord - half_texel_height,
405                                                wrap_s,
406                                                COGL_PIPELINE_WRAP_MODE_REPEAT,
407                                                clamp_t_cb,
408                                                &clamp_data);
409           /* Have we handled everything? */
410           if (*ty_1 >= max_t_coord)
411             return TRUE;
412 
413           /* clamp ty_2 since we've handled everything with y >
414            * max_t_coord */
415           *ty_2 = max_t_coord;
416         }
417     }
418 
419   if (clamp_data.s_flipped)
420     SWAP (*tx_1, *tx_2);
421   if (clamp_data.t_flipped)
422     SWAP (*ty_1, *ty_2);
423 
424   return FALSE;
425 }
426 
427 typedef struct _NormalizeData
428 {
429   CoglMetaTextureCallback callback;
430   void *user_data;
431   float s_normalize_factor;
432   float t_normalize_factor;
433 } NormalizeData;
434 
435 static void
normalize_meta_coords_cb(CoglTexture * slice_texture,const float * slice_coords,const float * meta_coords,void * user_data)436 normalize_meta_coords_cb (CoglTexture *slice_texture,
437                           const float *slice_coords,
438                           const float *meta_coords,
439                           void *user_data)
440 {
441   NormalizeData *data = user_data;
442   float normalized_meta_coords[4] = {
443       meta_coords[0] * data->s_normalize_factor,
444       meta_coords[1] * data->t_normalize_factor,
445       meta_coords[2] * data->s_normalize_factor,
446       meta_coords[3] * data->t_normalize_factor
447   };
448 
449   data->callback (slice_texture,
450                   slice_coords, normalized_meta_coords,
451                   data->user_data);
452 }
453 
454 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)455 cogl_meta_texture_foreach_in_region (CoglMetaTexture *meta_texture,
456                                      float tx_1,
457                                      float ty_1,
458                                      float tx_2,
459                                      float ty_2,
460                                      CoglPipelineWrapMode wrap_s,
461                                      CoglPipelineWrapMode wrap_t,
462                                      CoglMetaTextureCallback callback,
463                                      void *user_data)
464 {
465   CoglTexture *texture = COGL_TEXTURE (meta_texture);
466   float width = cogl_texture_get_width (texture);
467   float height = cogl_texture_get_height (texture);
468   NormalizeData normalize_data;
469 
470   if (wrap_s == COGL_PIPELINE_WRAP_MODE_AUTOMATIC)
471     wrap_s = COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE;
472   if (wrap_t == COGL_PIPELINE_WRAP_MODE_AUTOMATIC)
473     wrap_t = COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE;
474 
475   if (wrap_s == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE ||
476       wrap_t == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
477     {
478       gboolean finished = foreach_clamped_region (meta_texture,
479                                                   &tx_1, &ty_1, &tx_2, &ty_2,
480                                                   wrap_s, wrap_t,
481                                                   callback,
482                                                   user_data);
483       if (finished)
484         return;
485 
486       /* Since clamping has been handled we now want to normalize our
487        * wrap modes we se can assume from this point on we don't
488        * need to consider CLAMP_TO_EDGE. (NB: The spans code will
489        * assert that CLAMP_TO_EDGE isn't requested) */
490       if (wrap_s == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
491         wrap_s = COGL_PIPELINE_WRAP_MODE_REPEAT;
492       if (wrap_t == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
493         wrap_t = COGL_PIPELINE_WRAP_MODE_REPEAT;
494     }
495 
496   /* It makes things simpler to deal with non-normalized region
497    * coordinates beyond this point and only re-normalize just before
498    * calling the user's callback... */
499 
500   normalize_data.callback = callback;
501   normalize_data.user_data = user_data;
502   normalize_data.s_normalize_factor = 1.0f / width;
503   normalize_data.t_normalize_factor = 1.0f / height;
504   callback = normalize_meta_coords_cb;
505   user_data = &normalize_data;
506   tx_1 *= width;
507   ty_1 *= height;
508   tx_2 *= width;
509   ty_2 *= height;
510 
511   /* XXX: at some point this won't be routed through the CoglTexture
512    * vtable, instead there will be a separate CoglMetaTexture
513    * interface vtable. */
514 
515   if (texture->vtable->foreach_sub_texture_in_region)
516     {
517       ForeachData data;
518 
519       data.meta_region_coords[0] = tx_1;
520       data.meta_region_coords[1] = ty_1;
521       data.meta_region_coords[2] = tx_2;
522       data.meta_region_coords[3] = ty_2;
523       data.wrap_s = wrap_s;
524       data.wrap_t = wrap_t;
525       data.callback = callback;
526       data.user_data = user_data;
527 
528       data.width = width;
529       data.height = height;
530 
531       memset (data.padded_textures, 0, sizeof (data.padded_textures));
532 
533       /*
534        * 1) We iterate all the slices of the meta-texture only within
535        *    the range [0,1].
536        *
537        * 2) We define a "padded grid" for each slice of the
538        *    meta-texture in the range [0,1].
539        *
540        *    The padded grid maps over the meta-texture coordinates in
541        *    the range [0,1] but only contains one valid cell that
542        *    corresponds to current slice being iterated and all the
543        *    surrounding cells just provide padding.
544        *
545        * 3) Once we've defined our padded grid we then repeat that
546        *    across the user's original region, calling their callback
547        *    whenever we see our current slice - ignoring padding.
548        *
549        * A notable benefit of this design is that repeating a texture
550        * made of multiple slices will result in us repeating each
551        * slice in-turn so the user gets repeat callbacks for the same
552        * texture batched together. For manual emulation of texture
553        * repeats done by drawing geometry this makes it more likely
554        * that we can batch geometry.
555        */
556 
557       texture->vtable->foreach_sub_texture_in_region (texture,
558                                                       0, 0, 1, 1,
559                                                       create_grid_and_repeat_cb,
560                                                       &data);
561     }
562   else
563     {
564       CoglSpan x_span = { 0, width, 0 };
565       CoglSpan y_span = { 0, height, 0 };
566       float meta_region_coords[4] = { tx_1, ty_1, tx_2, ty_2 };
567 
568       _cogl_texture_spans_foreach_in_region (&x_span, 1,
569                                              &y_span, 1,
570                                              &texture,
571                                              meta_region_coords,
572                                              width,
573                                              height,
574                                              wrap_s,
575                                              wrap_t,
576                                              callback,
577                                              user_data);
578     }
579 }
580 #undef SWAP
581