summaryrefslogtreecommitdiff
path: root/cogl/cogl-meta-texture.c
blob: 554aaad6e013aafa9359bc11e8e773781e22d90e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
/*
 * Cogl
 *
 * A Low Level GPU Graphics and Utilities API
 *
 * Copyright (C) 2011 Intel Corporation.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *
 * Authors:
 *  Robert Bragg   <robert@linux.intel.com>
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "cogl-texture.h"
#include "cogl-matrix.h"
#include "cogl-spans.h"
#include "cogl-meta-texture.h"
#include "cogl-texture-rectangle-private.h"

#include <string.h>
#include <math.h>

typedef struct _ForeachData
{
  float meta_region_coords[4];
  CoglPipelineWrapMode wrap_s;
  CoglPipelineWrapMode wrap_t;
  CoglMetaTextureCallback callback;
  void *user_data;

  int width;
  int height;

  CoglTexture *padded_textures[9];
  const float *grid_slice_texture_coords;
  float slice_offset_s;
  float slice_offset_t;
  float slice_range_s;
  float slice_range_t;
} ForeachData;

static void
padded_grid_repeat_cb (CoglTexture *slice_texture,
                       const float *slice_texture_coords,
                       const float *meta_coords,
                       void *user_data)
{
  ForeachData *data;
  float mapped_coords[4];

  /* Ignore padding slices for the current grid */
  if (!slice_texture)
    return;

  data = user_data;

  /* NB: the slice_texture_coords[] we get here will always be
   * normalized.
   *
   * We now need to map the normalized slice_texture_coords[] we have
   * here back to the real slice coordinates we saved in the previous
   * stage...
   */
  mapped_coords[0] =
    slice_texture_coords[0] * data->slice_range_s + data->slice_offset_s;
  mapped_coords[1] =
    slice_texture_coords[1] * data->slice_range_t + data->slice_offset_t;
  mapped_coords[2] =
    slice_texture_coords[2] * data->slice_range_s + data->slice_offset_s;
  mapped_coords[3] =
    slice_texture_coords[3] * data->slice_range_t + data->slice_offset_t;

  data->callback (slice_texture,
                  mapped_coords, meta_coords, data->user_data);
}

static int
setup_padded_spans (CoglSpan *spans,
                    float start,
                    float end,
                    float range,
                    int *real_index)
{
  int span_index = 0;

  if (start > 0)
    {
      spans[0].start = 0;
      spans[0].size = start;
      spans[0].waste = 0;
      span_index++;
      spans[1].start = spans[0].size;
    }
  else
    spans[span_index].start = 0;

  spans[span_index].size = end - start;
  spans[span_index].waste = 0;
  *real_index = span_index;
  span_index++;

  if (end < range)
    {
      spans[span_index].start =
        spans[span_index - 1].start + spans[span_index - 1].size;
      spans[span_index].size = range - end;
      spans[span_index].waste = 0;
      span_index++;
    }

  return span_index;
}

/* This handles each sub-texture within the range [0,1] of our
 * original meta texture and repeats each one separately across the
 * users requested virtual texture coordinates.
 *
 * A notable advantage of this approach is that we will batch
 * together callbacks corresponding to the same underlying slice
 * together.
 */
static void
create_grid_and_repeat_cb (CoglTexture *slice_texture,
                           const float *slice_texture_coords,
                           const float *meta_coords,
                           void *user_data)
{
  ForeachData *data = user_data;
  CoglSpan x_spans[3];
  int n_x_spans;
  int x_real_index;
  CoglSpan y_spans[3];
  int n_y_spans;
  int y_real_index;

  /* NB: This callback is called for each slice of the meta-texture
   * in the range [0,1].
   *
   * We define a "padded grid" for each slice of the meta-texture in
   * the range [0,1]. The x axis and y axis grid lines are defined
   * using CoglSpans.
   *
   * The padded grid maps over the meta-texture coordinates in the
   * range [0,1] but only contains one valid cell that corresponds to
   * current slice being iterated and all the surrounding cells just
   * provide padding.
   *
   * Once we've defined our padded grid we then repeat that across
   * the user's original region, calling their callback whenever
   * we see our current slice - ignoring padding.
   *
   * NB: we can assume meta_coords[] are normalized at this point
   * since TextureRectangles aren't iterated with this code-path.
   *
   * NB: spans are always defined using non-normalized coordinates
   */
  n_x_spans = setup_padded_spans (x_spans,
                                  meta_coords[0] * data->width,
                                  meta_coords[2] * data->width,
                                  data->width,
                                  &x_real_index);
  n_y_spans = setup_padded_spans (y_spans,
                                  meta_coords[1] * data->height,
                                  meta_coords[3] * data->height,
                                  data->height,
                                  &y_real_index);

  data->padded_textures[n_x_spans * y_real_index + x_real_index] =
    slice_texture;

  /* Our callback is going to be passed normalized slice texture
   * coordinates, and we will need to map the range [0,1] to the real
   * slice_texture_coords we have here... */
  data->grid_slice_texture_coords = slice_texture_coords;
  data->slice_range_s = fabs (data->grid_slice_texture_coords[2] -
                              data->grid_slice_texture_coords[0]);
  data->slice_range_t = fabs (data->grid_slice_texture_coords[3] -
                              data->grid_slice_texture_coords[1]);
  data->slice_offset_s = MIN (data->grid_slice_texture_coords[0],
                              data->grid_slice_texture_coords[2]);
  data->slice_offset_t = MIN (data->grid_slice_texture_coords[1],
                              data->grid_slice_texture_coords[3]);

  /* Now actually iterate the region the user originally requested
   * using the current padded grid */
  _cogl_texture_spans_foreach_in_region (x_spans,
                                         n_x_spans,
                                         y_spans,
                                         n_y_spans,
                                         data->padded_textures,
                                         data->meta_region_coords,
                                         data->width,
                                         data->height,
                                         data->wrap_s,
                                         data->wrap_t,
                                         padded_grid_repeat_cb,
                                         data);

  /* Clear the padded_textures ready for the next iteration */
  data->padded_textures[n_x_spans * y_real_index + x_real_index] = NULL;
}

#define SWAP(A,B) do { float tmp = B; B = A; A = tmp; } while (0)

typedef struct _ClampData
{
  float start;
  float end;
  CoglBool s_flipped;
  CoglBool t_flipped;
  CoglMetaTextureCallback callback;
  void *user_data;
} ClampData;

static void
clamp_s_cb (CoglTexture *sub_texture,
            const float *sub_texture_coords,
            const float *meta_coords,
            void *user_data)
{
  ClampData *clamp_data = user_data;
  float mapped_meta_coords[4] = {
    clamp_data->start,
    meta_coords[1],
    clamp_data->end,
    meta_coords[3]
  };
  if (clamp_data->s_flipped)
    SWAP (mapped_meta_coords[0], mapped_meta_coords[2]);
  /* NB: we never need to flip the t coords when dealing with
   * s-axis clamping so no need to check if ->t_flipped */
  clamp_data->callback (sub_texture,
                        sub_texture_coords, mapped_meta_coords,
                        clamp_data->user_data);
}

static void
clamp_t_cb (CoglTexture *sub_texture,
            const float *sub_texture_coords,
            const float *meta_coords,
            void *user_data)
{
  ClampData *clamp_data = user_data;
  float mapped_meta_coords[4] = {
    meta_coords[0],
    clamp_data->start,
    meta_coords[2],
    clamp_data->end,
  };
  if (clamp_data->s_flipped)
    SWAP (mapped_meta_coords[0], mapped_meta_coords[2]);
  if (clamp_data->t_flipped)
    SWAP (mapped_meta_coords[1], mapped_meta_coords[3]);
  clamp_data->callback (sub_texture,
                        sub_texture_coords, mapped_meta_coords,
                        clamp_data->user_data);
}

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)
{
  float width = cogl_texture_get_width (COGL_TEXTURE (meta_texture));
  ClampData clamp_data;

  /* Consider that *tx_1 may be > *tx_2 and to simplify things
   * we just flip them around if that's the case and keep a note
   * of the fact that they are flipped. */
  if (*tx_1 > *tx_2)
    {
      SWAP (*tx_1, *tx_2);
      clamp_data.s_flipped = TRUE;
    }
  else
    clamp_data.s_flipped = FALSE;

  /* The same goes for ty_1 and ty_2... */
  if (*ty_1 > *ty_2)
    {
      SWAP (*ty_1, *ty_2);
      clamp_data.t_flipped = TRUE;
    }
  else
    clamp_data.t_flipped = FALSE;

  clamp_data.callback = callback;
  clamp_data.user_data = user_data;

  if (wrap_s == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
    {
      float max_s_coord;
      float half_texel_width;

      /* Consider that rectangle textures have non-normalized
       * coordinates... */
      if (cogl_is_texture_rectangle (meta_texture))
        max_s_coord = width;
      else
        max_s_coord = 1.0;

      half_texel_width = max_s_coord / (width * 2);

      /* Handle any left clamped region */
      if (*tx_1 < 0)
        {
          clamp_data.start = *tx_1;
          clamp_data.end = MIN (0, *tx_2);;
          cogl_meta_texture_foreach_in_region (meta_texture,
                                               half_texel_width, *ty_1,
                                               half_texel_width, *ty_2,
                                               COGL_PIPELINE_WRAP_MODE_REPEAT,
                                               wrap_t,
                                               clamp_s_cb,
                                               &clamp_data);
          /* Have we handled everything? */
          if (*tx_2 <= 0)
            return TRUE;

          /* clamp tx_1 since we've handled everything with x < 0 */
          *tx_1 = 0;
        }

      /* Handle any right clamped region - including the corners */
      if (*tx_2 > max_s_coord)
        {
          clamp_data.start = MAX (max_s_coord, *tx_1);
          clamp_data.end = *tx_2;
          cogl_meta_texture_foreach_in_region (meta_texture,
                                               max_s_coord - half_texel_width,
                                               *ty_1,
                                               max_s_coord - half_texel_width,
                                               *ty_2,
                                               COGL_PIPELINE_WRAP_MODE_REPEAT,
                                               wrap_t,
                                               clamp_s_cb,
                                               &clamp_data);
          /* Have we handled everything? */
          if (*tx_1 >= max_s_coord)
            return TRUE;

          /* clamp tx_2 since we've handled everything with x >
           * max_s_coord */
          *tx_2 = max_s_coord;
        }
    }

  if (wrap_t == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
    {
      float height = cogl_texture_get_height (COGL_TEXTURE (meta_texture));
      float max_t_coord;
      float half_texel_height;

      /* Consider that rectangle textures have non-normalized
       * coordinates... */
      if (cogl_is_texture_rectangle (meta_texture))
        max_t_coord = height;
      else
        max_t_coord = 1.0;

      half_texel_height = max_t_coord / (height * 2);

      /* Handle any top clamped region */
      if (*ty_1 < 0)
        {
          clamp_data.start = *ty_1;
          clamp_data.end = MIN (0, *ty_2);;
          cogl_meta_texture_foreach_in_region (meta_texture,
                                               *tx_1, half_texel_height,
                                               *tx_2, half_texel_height,
                                               wrap_s,
                                               COGL_PIPELINE_WRAP_MODE_REPEAT,
                                               clamp_t_cb,
                                               &clamp_data);
          /* Have we handled everything? */
          if (*tx_2 <= 0)
            return TRUE;

          /* clamp ty_1 since we've handled everything with y < 0 */
          *ty_1 = 0;
        }

      /* Handle any bottom clamped region */
      if (*ty_2 > max_t_coord)
        {
          clamp_data.start = MAX (max_t_coord, *ty_1);;
          clamp_data.end = *ty_2;
          cogl_meta_texture_foreach_in_region (meta_texture,
                                               *tx_1,
                                               max_t_coord - half_texel_height,
                                               *tx_2,
                                               max_t_coord - half_texel_height,
                                               wrap_s,
                                               COGL_PIPELINE_WRAP_MODE_REPEAT,
                                               clamp_t_cb,
                                               &clamp_data);
          /* Have we handled everything? */
          if (*ty_1 >= max_t_coord)
            return TRUE;

          /* clamp ty_2 since we've handled everything with y >
           * max_t_coord */
          *ty_2 = max_t_coord;
        }
    }

  if (clamp_data.s_flipped)
    SWAP (*tx_1, *tx_2);
  if (clamp_data.t_flipped)
    SWAP (*ty_1, *ty_2);

  return FALSE;
}

typedef struct _NormalizeData
{
  CoglMetaTextureCallback callback;
  void *user_data;
  float s_normalize_factor;
  float t_normalize_factor;
} NormalizeData;

static void
normalize_meta_coords_cb (CoglTexture *slice_texture,
                          const float *slice_coords,
                          const float *meta_coords,
                          void *user_data)
{
  NormalizeData *data = user_data;
  float normalized_meta_coords[4] = {
      meta_coords[0] * data->s_normalize_factor,
      meta_coords[1] * data->t_normalize_factor,
      meta_coords[2] * data->s_normalize_factor,
      meta_coords[3] * data->t_normalize_factor
  };

  data->callback (slice_texture,
                  slice_coords, normalized_meta_coords,
                  data->user_data);
}

typedef struct _UnNormalizeData
{
  CoglMetaTextureCallback callback;
  void *user_data;
  float width;
  float height;
} UnNormalizeData;

static void
un_normalize_slice_coords_cb (CoglTexture *slice_texture,
                              const float *slice_coords,
                              const float *meta_coords,
                              void *user_data)
{
  UnNormalizeData *data = user_data;
  float un_normalized_slice_coords[4] = {
    slice_coords[0] * data->width,
    slice_coords[1] * data->height,
    slice_coords[2] * data->width,
    slice_coords[3] * data->height
  };

  data->callback (slice_texture,
                  un_normalized_slice_coords, meta_coords,
                  data->user_data);
}

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)
{
  CoglTexture *texture = COGL_TEXTURE (meta_texture);
  float width = cogl_texture_get_width (texture);
  float height = cogl_texture_get_height (texture);
  NormalizeData normalize_data;

  if (wrap_s == COGL_PIPELINE_WRAP_MODE_AUTOMATIC)
    wrap_s = COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE;
  if (wrap_t == COGL_PIPELINE_WRAP_MODE_AUTOMATIC)
    wrap_t = COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE;

  if (wrap_s == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE ||
      wrap_t == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
    {
      CoglBool finished = foreach_clamped_region (meta_texture,
                                                  &tx_1, &ty_1, &tx_2, &ty_2,
                                                  wrap_s, wrap_t,
                                                  callback,
                                                  user_data);
      if (finished)
        return;

      /* Since clamping has been handled we now want to normalize our
       * wrap modes we se can assume from this point on we don't
       * need to consider CLAMP_TO_EDGE. (NB: The spans code will
       * assert that CLAMP_TO_EDGE isn't requested) */
      if (wrap_s == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
        wrap_s = COGL_PIPELINE_WRAP_MODE_REPEAT;
      if (wrap_t == COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE)
        wrap_t = COGL_PIPELINE_WRAP_MODE_REPEAT;
    }

  /* It makes things simpler to deal with non-normalized region
   * coordinates beyond this point and only re-normalize just before
   * calling the user's callback... */

  if (!cogl_is_texture_rectangle (COGL_TEXTURE (meta_texture)))
    {
      normalize_data.callback = callback;
      normalize_data.user_data = user_data;
      normalize_data.s_normalize_factor = 1.0f / width;
      normalize_data.t_normalize_factor = 1.0f / height;
      callback = normalize_meta_coords_cb;
      user_data = &normalize_data;
      tx_1 *= width;
      ty_1 *= height;
      tx_2 *= width;
      ty_2 *= height;
    }

  /* XXX: at some point this wont be routed through the CoglTexture
   * vtable, instead there will be a separate CoglMetaTexture
   * interface vtable. */

  if (texture->vtable->foreach_sub_texture_in_region)
    {
      ForeachData data;

      data.meta_region_coords[0] = tx_1;
      data.meta_region_coords[1] = ty_1;
      data.meta_region_coords[2] = tx_2;
      data.meta_region_coords[3] = ty_2;
      data.wrap_s = wrap_s;
      data.wrap_t = wrap_t;
      data.callback = callback;
      data.user_data = user_data;

      data.width = width;
      data.height = height;

      memset (data.padded_textures, 0, sizeof (data.padded_textures));

      /*
       * 1) We iterate all the slices of the meta-texture only within
       *    the range [0,1].
       *
       * 2) We define a "padded grid" for each slice of the
       *    meta-texture in the range [0,1].
       *
       *    The padded grid maps over the meta-texture coordinates in
       *    the range [0,1] but only contains one valid cell that
       *    corresponds to current slice being iterated and all the
       *    surrounding cells just provide padding.
       *
       * 3) Once we've defined our padded grid we then repeat that
       *    across the user's original region, calling their callback
       *    whenever we see our current slice - ignoring padding.
       *
       * A notable benefit of this design is that repeating a texture
       * made of multiple slices will result in us repeating each
       * slice in-turn so the user gets repeat callbacks for the same
       * texture batched together. For manual emulation of texture
       * repeats done by drawing geometry this makes it more likely
       * that we can batch geometry.
       */

      texture->vtable->foreach_sub_texture_in_region (texture,
                                                      0, 0, 1, 1,
                                                      create_grid_and_repeat_cb,
                                                      &data);
    }
  else
    {
      CoglSpan x_span = { 0, width, 0 };
      CoglSpan y_span = { 0, height, 0 };
      float meta_region_coords[4] = { tx_1, ty_1, tx_2, ty_2 };
      UnNormalizeData un_normalize_data;

      /* If we are dealing with a CoglTextureRectangle then we need a shim
       * callback that un_normalizes the slice coordinates we get from
       * _cogl_texture_spans_foreach_in_region before passing them to
       * the user's callback. */
      if (cogl_is_texture_rectangle (meta_texture))
        {
          un_normalize_data.callback = callback;
          un_normalize_data.user_data = user_data;
          un_normalize_data.width = width;
          un_normalize_data.height = height;
          callback = un_normalize_slice_coords_cb;
          user_data = &un_normalize_data;
        }

      _cogl_texture_spans_foreach_in_region (&x_span, 1,
                                             &y_span, 1,
                                             &texture,
                                             meta_region_coords,
                                             width,
                                             height,
                                             wrap_s,
                                             wrap_t,
                                             callback,
                                             user_data);
    }
}
#undef SWAP