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