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 "cogl-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