mirror of
https://github.com/brl/mutter.git
synced 2024-12-25 12:32:05 +00:00
4c49f83056
cogl_meta_texture_foreach_in_region() now directly supports CLAMP_TO_EDGE wrap modes. This means the cogl_rectangle code will be able to build on this and makes the logic accessible to anyone developing custom primitives that need to support meta textures. Reviewed-by: Neil Roberts <neil@linux.intel.com>
637 lines
21 KiB
C
637 lines
21 KiB
C
/*
|
|
* Cogl
|
|
*
|
|
* An object oriented GL/GLES Abstraction/Utility Layer
|
|
*
|
|
* Copyright (C) 2011 Intel Corporation.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
*
|
|
* 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.
|
|
*/
|
|
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_y_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_y_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;
|
|
gboolean s_flipped;
|
|
gboolean 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 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)
|
|
{
|
|
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)
|
|
{
|
|
gboolean 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
|