/*

 * Z buffer: 16 bits Z / 32 bits color
 * 
 */
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include "zbuffer.h"
#include "pnotify.h"

#ifdef DO_PSTATS
int pixel_count_white_untextured;
int pixel_count_flat_untextured;
int pixel_count_smooth_untextured;
int pixel_count_white_textured;
int pixel_count_flat_textured;
int pixel_count_smooth_textured;
int pixel_count_white_perspective;
int pixel_count_flat_perspective;
int pixel_count_smooth_perspective;
int pixel_count_smooth_multitex2;
int pixel_count_smooth_multitex3;
#endif  // DO_PSTATS

ZBuffer *
ZB_open(int xsize, int ysize, int mode,
        int nb_colors,
        unsigned char *color_indexes,
        unsigned int *color_table,
        void *frame_buffer) {
  ZBuffer *zb;
  int size;

  zb = (ZBuffer *)gl_malloc(sizeof(ZBuffer));
  if (zb == NULL)
    return NULL;
  memset(zb, 0, sizeof(ZBuffer));

  /* xsize must be a multiple of 4 */
  xsize = (xsize + 3) & ~3;

  zb->xsize = xsize;
  zb->ysize = ysize;
  zb->mode = mode;
  zb->linesize = (xsize * PSZB + 3) & ~3;

  switch (mode) {
#ifdef TGL_FEATURE_8_BITS
  case ZB_MODE_INDEX:
    ZB_initDither(zb, nb_colors, color_indexes, color_table);
    break;
#endif
#ifdef TGL_FEATURE_32_BITS
  case ZB_MODE_RGBA:
#endif
#ifdef TGL_FEATURE_24_BITS
  case ZB_MODE_RGB24:
#endif
  case ZB_MODE_5R6G5B:
    zb->nb_colors = 0;
    break;
  default:
    goto error;
  }

  size = zb->xsize * zb->ysize * sizeof(ZPOINT);

  zb->zbuf = (ZPOINT *)gl_malloc(size);
  if (zb->zbuf == NULL)
    goto error;

  if (frame_buffer == NULL) {
    zb->pbuf = (PIXEL *)gl_malloc(zb->ysize * zb->linesize);
    if (zb->pbuf == NULL) {
      gl_free(zb->zbuf);
      goto error;
    }
    zb->frame_buffer_allocated = 1;
  } else {
    zb->frame_buffer_allocated = 0;
    zb->pbuf = (PIXEL *)frame_buffer;
  }

  return zb;
 error:
  gl_free(zb);
  return NULL;
}

void
ZB_close(ZBuffer * zb) {
#ifdef TGL_FEATURE_8_BITS
  if (zb->mode == ZB_MODE_INDEX)
    ZB_closeDither(zb);
#endif

  if (zb->frame_buffer_allocated)
    gl_free(zb->pbuf);

  gl_free(zb->zbuf);
  gl_free(zb);
}

void
ZB_resize(ZBuffer * zb, void *frame_buffer, int xsize, int ysize) {
  int size;

  /* xsize must be a multiple of 4 */
  xsize = (xsize + 3) & ~3;

  zb->xsize = xsize;
  zb->ysize = ysize;
  zb->linesize = (xsize * PSZB + 3) & ~3;

  size = zb->xsize * zb->ysize * sizeof(ZPOINT);
  gl_free(zb->zbuf);
  zb->zbuf = (ZPOINT *)gl_malloc(size);

  if (zb->frame_buffer_allocated)
    gl_free(zb->pbuf);

  if (frame_buffer == NULL) {
    zb->pbuf = (PIXEL *)gl_malloc(zb->ysize * zb->linesize);
    zb->frame_buffer_allocated = 1;
  } else {
    zb->pbuf = (PIXEL *)frame_buffer;
    zb->frame_buffer_allocated = 0;
  }
}

static void 
ZB_copyBuffer(const ZBuffer * zb,
              void *buf,
              int linesize) {
  unsigned char *p1;
  PIXEL *q;
  int y, n;

  q = zb->pbuf;
  p1 = (unsigned char *)buf;
  n = zb->xsize * PSZB;
  for (y = 0; y < zb->ysize; y++) {
    memcpy(p1, q, n);
    p1 += linesize;
    q = (PIXEL *) ((char *) q + zb->linesize);
  }
}

static void
ZB_copyBufferNoAlpha(const ZBuffer * zb, void *buf, int linesize) {
  const PIXEL *q = zb->pbuf;
  PIXEL *p = (PIXEL *)buf;
  int xsize = zb->xsize;
  for (int y = 0; y < zb->ysize; ++y) {
    const PIXEL *q1 = q;
    PIXEL *p1 = p;
    PIXEL *p2 = p1 + xsize;
    while (p1 < p2) {
      // Make sure the alpha bits are set to 0xff.
#ifdef WORDS_BIGENDIAN        
      *p1 = *q1 | 0x000000ff;
#else
      *p1 = *q1 | 0xff000000;
#endif
      ++p1;
      ++q1;
    }
    p = (PIXEL *) ((char *) p + linesize);
    q = (const PIXEL *) ((const char *) q + zb->linesize);
  }
}

#define RGB32_TO_RGB16(v) \
  (((v >> 8) & 0xf800) | (((v) >> 5) & 0x07e0) | (((v) & 0xff) >> 3))

/* XXX: not optimized */
static void ZB_copyFrameBuffer5R6G5B(const ZBuffer * zb, 
                                     void *buf, int linesize) 
{
  PIXEL *q;
  unsigned short *p, *p1;
  int y, n;

  q = zb->pbuf;
  p1 = (unsigned short *) buf;

  for (y = 0; y < zb->ysize; y++) {
    p = p1;
    n = zb->xsize >> 2;
    do {
      p[0] = RGB32_TO_RGB16(q[0]);
      p[1] = RGB32_TO_RGB16(q[1]);
      p[2] = RGB32_TO_RGB16(q[2]);
      p[3] = RGB32_TO_RGB16(q[3]);
      q += 4;
      p += 4;
    } while (--n > 0);
    p1 = (unsigned short *)((char *)p1 + linesize);
  }
}

/* XXX: not optimized */
static void ZB_copyFrameBufferRGB24(const ZBuffer * zb, 
                                    void *buf, int linesize) 
{
  PIXEL *q;
  unsigned char *p, *p1;
  int y, n;

  fprintf(stderr, "copyFrameBufferRGB24\n");
    
  q = zb->pbuf;
  p1 = (unsigned char *) buf;

  for (y = 0; y < zb->ysize; y++) {
    p = p1;
    n = zb->xsize;
    do {
      p[0] = q[0];
      p[1] = q[1];
      p[2] = q[2];
      q += 4;
      p += 3;
    } while (--n > 0);
    p1 += linesize;
  }
}

void
ZB_copyFrameBuffer(const ZBuffer * zb, void *buf,
                   int linesize) {
  switch (zb->mode) {
#ifdef TGL_FEATURE_16_BITS
  case ZB_MODE_5R6G5B:
    ZB_copyFrameBuffer5R6G5B(zb, buf, linesize);
    break;
#endif
#ifdef TGL_FEATURE_24_BITS
  case ZB_MODE_RGB24:
    ZB_copyFrameBufferRGB24(zb, buf, linesize);
    break;
#endif
#ifdef TGL_FEATURE_32_BITS
  case ZB_MODE_RGBA:
    ZB_copyBuffer(zb, buf, linesize);
    break;
#endif
  default:
    assert(0);
  }
}

void
ZB_copyFrameBufferNoAlpha(const ZBuffer * zb, void *buf,
                          int linesize) {
  switch (zb->mode) {
#ifdef TGL_FEATURE_16_BITS
  case ZB_MODE_5R6G5B:
    ZB_copyFrameBuffer5R6G5B(zb, buf, linesize);
    break;
#endif
#ifdef TGL_FEATURE_24_BITS
  case ZB_MODE_RGB24:
    ZB_copyFrameBufferRGB24(zb, buf, linesize);
    break;
#endif
#ifdef TGL_FEATURE_32_BITS
  case ZB_MODE_RGBA:
    ZB_copyBufferNoAlpha(zb, buf, linesize);
    break;
#endif
  default:
    assert(0);
  }
}

// Copy from (source_xmin,source_ymin)+(source_xsize,source_ysize) to 
//  (dest_xmin,dest_ymin)+(dest_xsize,dest_ysize).
void ZB_zoomFrameBuffer(ZBuffer *dest, int dest_xmin, int dest_ymin, int dest_xsize, int dest_ysize,
                        const ZBuffer *source, int source_xmin, int source_ymin, int source_xsize, int source_ysize) {
  int tyinc = dest->linesize / PSZB;
  int fyinc = source->linesize / PSZB;
  
  int fyt = 0;
  for (int ty = 0; ty < dest_ysize; ++ty) {
    int fy = fyt / dest_ysize;
    fyt += source_ysize;
    
    PIXEL *tp = dest->pbuf + dest_xmin + (dest_ymin + ty) * tyinc;
    PIXEL *fp = source->pbuf + source_xmin + (source_ymin + fy) * fyinc;
    ZPOINT *tz = dest->zbuf + dest_xmin + (dest_ymin + ty) * dest->xsize;
    ZPOINT *fz = source->zbuf + source_xmin + (source_ymin + fy) * source->xsize;
    int fxt = 0;
    for (int tx = 0; tx < dest_xsize; ++tx) {
      int fx = fxt / dest_xsize;
      fxt += source_xsize;
      
      tp[tx] = fp[fx];
      tz[tx] = fz[fx];
    }
  }
}


/*
 * adr must be aligned on an 'int'
 */
void
memset_s(void *adr, int val, int count) {
  int i, n, v;
  unsigned int *p;
  unsigned short *q;

  p = (unsigned int *)adr;
  v = val | (val << 16);

  n = count >> 3;
  for (i = 0; i < n; i++) {
    p[0] = v;
    p[1] = v;
    p[2] = v;
    p[3] = v;
    p += 4;
  }

  q = (unsigned short *) p;
  n = count & 7;
  for (i = 0; i < n; i++)
    *q++ = val;
}

void
memset_l(void *adr, int val, int count) {
  int i, n, v;
  unsigned int *p;

  p = (unsigned int *)adr;
  v = val;
  n = count >> 2;
  for (i = 0; i < n; i++) {
    p[0] = v;
    p[1] = v;
    p[2] = v;
    p[3] = v;
    p += 4;
  }

  n = count & 3;
  for (i = 0; i < n; i++)
    *p++ = val;
}

/* count must be a multiple of 4 and >= 4 */
void
memset_RGB24(void *adr,int r, int v, int b,long count) {
  long i, n;
  register long v1,v2,v3,*pt=(long *)(adr);
  unsigned char *p,R=(unsigned char)r,V=(unsigned char)v,B=(unsigned char)b;

  p=(unsigned char *)adr;
  *p++=R;
  *p++=V;
  *p++=B;
  *p++=R;
  *p++=V;
  *p++=B;
  *p++=R;
  *p++=V;
  *p++=B;
  *p++=R;
  *p++=V;
  *p++=B;
  v1=*pt++;
  v2=*pt++;
  v3=*pt++;
  n = count >> 2;
  for(i=1;i<n;i++) {
    *pt++=v1;
    *pt++=v2;
    *pt++=v3;
  }
}

void
ZB_clear(ZBuffer * zb, int clear_z, ZPOINT z,
         int clear_color, unsigned int r, unsigned int g, unsigned int b, unsigned int a) {
  unsigned int color;
  int y;
  PIXEL *pp;
  
  if (clear_z) {
    memset(zb->zbuf, 0, zb->xsize * zb->ysize * sizeof(ZPOINT));
  }
  if (clear_color) {
    color = RGBA_TO_PIXEL(r, g, b, a);
    pp = zb->pbuf;
    for (y = 0; y < zb->ysize; y++) {
      memset_l(pp, color, zb->xsize);
      pp = (PIXEL *) ((char *) pp + zb->linesize);
    }
  }
}

void
ZB_clear_viewport(ZBuffer * zb, int clear_z, ZPOINT z,
                  int clear_color, unsigned int r, unsigned int g, unsigned int b, unsigned int a,
                  int xmin, int ymin, int xsize, int ysize) {
  unsigned int color;
  int y;
  PIXEL *pp;
  ZPOINT *zz;

  nassertv(xmin >= 0 && xmin < zb->xsize && 
           ymin >= 0 && ymin < zb->ysize &&
           xmin + xsize >= 0 && xmin + xsize <= zb->xsize &&
           ymin + ysize >= 0 && ymin + ysize <= zb->ysize);

  if (clear_z) {
    zz = zb->zbuf + xmin + ymin * zb->xsize;
    for (y = 0; y < ysize; ++y) {
      memset(zz, 0, xsize * sizeof(ZPOINT));
      zz += zb->xsize;
    }
  }
  if (clear_color) {
    color = RGBA_TO_PIXEL(r, g, b, a);
    pp = zb->pbuf + xmin + ymin * (zb->linesize / PSZB);
    for (y = 0; y < ysize; ++y) {
      memset_l(pp, color, xsize);
      pp += zb->xsize;
    }
  }
}

#define ZB_ST_FRAC_HIGH (1 << ZB_POINT_ST_FRAC_BITS)
#define ZB_ST_FRAC_MASK (ZB_ST_FRAC_HIGH - 1)

#define LINEAR_FILTER_BITSIZE(c1, c2, f, bitsize) \
  ((((c2) * (f)) >> bitsize) + (((c1) * ((1 << bitsize) - (f))) >> bitsize))

#define LINEAR_FILTER(c1, c2, f) \
  LINEAR_FILTER_BITSIZE(c1, c2, f, ZB_POINT_ST_FRAC_BITS)

#define BILINEAR_FILTER(c1, c2, c3, c4, sf, tf) \
  (LINEAR_FILTER(LINEAR_FILTER(c1, c2, sf), LINEAR_FILTER(c3, c4, sf), tf))

// Grab the nearest texel from the base level.  This is also
// implemented inline as ZB_LOOKUP_TEXTURE_NEAREST.
PIXEL
lookup_texture_nearest(ZTextureDef *texture_def, int s, int t, unsigned int level, unsigned int level_dx) {
  return ZB_LOOKUP_TEXTURE_NEAREST(texture_def, s, t);
}

// Bilinear filter four texels in the base level.
PIXEL
lookup_texture_bilinear(ZTextureDef *texture_def, int s, int t, unsigned int level, unsigned int level_dx) {
  PIXEL p1, p2, p3, p4;
  int sf, tf;
  int r, g, b, a;

  p1 = ZB_LOOKUP_TEXTURE_NEAREST(texture_def, s - ZB_ST_FRAC_HIGH, t - ZB_ST_FRAC_HIGH);
  p2 = ZB_LOOKUP_TEXTURE_NEAREST(texture_def, s, t - ZB_ST_FRAC_HIGH);
  sf = s & ZB_ST_FRAC_MASK;

  p3 = ZB_LOOKUP_TEXTURE_NEAREST(texture_def, s - ZB_ST_FRAC_HIGH, t);
  p4 = ZB_LOOKUP_TEXTURE_NEAREST(texture_def, s, t);
  tf = t & ZB_ST_FRAC_MASK;

  r = BILINEAR_FILTER(PIXEL_R(p1), PIXEL_R(p2), PIXEL_R(p3), PIXEL_R(p4), sf, tf);
  g = BILINEAR_FILTER(PIXEL_G(p1), PIXEL_G(p2), PIXEL_G(p3), PIXEL_G(p4), sf, tf);
  b = BILINEAR_FILTER(PIXEL_B(p1), PIXEL_B(p2), PIXEL_B(p3), PIXEL_B(p4), sf, tf);
  a = BILINEAR_FILTER(PIXEL_A(p1), PIXEL_A(p2), PIXEL_A(p3), PIXEL_A(p4), sf, tf);

  return RGBA_TO_PIXEL(r, g, b, a);
}

// Grab the nearest texel from the nearest mipmap level.  This is also
// implemented inline as ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST.
PIXEL
lookup_texture_mipmap_nearest(ZTextureDef *texture_def, int s, int t, unsigned int level, unsigned int level_dx) {
  return ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s, t, level);
}

// Linear filter the two texels from the two nearest mipmap levels.
PIXEL
lookup_texture_mipmap_linear(ZTextureDef *texture_def, int s, int t, unsigned int level, unsigned int level_dx) {
  PIXEL p1, p2;
  int r, g, b, a;

  p1 = ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s, t, level - 1);
  p2 = ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s, t, level);

  unsigned int bitsize = (level - 1) + ZB_POINT_ST_FRAC_BITS;
  r = LINEAR_FILTER_BITSIZE(PIXEL_R(p1), PIXEL_R(p2), level_dx, bitsize);
  g = LINEAR_FILTER_BITSIZE(PIXEL_G(p1), PIXEL_G(p2), level_dx, bitsize);
  b = LINEAR_FILTER_BITSIZE(PIXEL_B(p1), PIXEL_B(p2), level_dx, bitsize);
  a = LINEAR_FILTER_BITSIZE(PIXEL_A(p1), PIXEL_A(p2), level_dx, bitsize); 

  return RGBA_TO_PIXEL(r, g, b, a);
}

// Bilinear filter four texels in the nearest mipmap level.
PIXEL
lookup_texture_mipmap_bilinear(ZTextureDef *texture_def, int s, int t, unsigned int level, unsigned int level_dx) {
  PIXEL p1, p2, p3, p4;
  int sf, tf;
  int r, g, b, a;

  p1 = ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s - ZB_ST_FRAC_HIGH, t - ZB_ST_FRAC_HIGH, level);
  p2 = ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s, t - ZB_ST_FRAC_HIGH, level);
  sf = (s >> level) & ZB_ST_FRAC_MASK;

  p3 = ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s - ZB_ST_FRAC_HIGH, t, level);
  p4 = ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s, t, level);
  tf = (t >> level) & ZB_ST_FRAC_MASK;

  r = BILINEAR_FILTER(PIXEL_R(p1), PIXEL_R(p2), PIXEL_R(p3), PIXEL_R(p4), sf, tf);
  g = BILINEAR_FILTER(PIXEL_G(p1), PIXEL_G(p2), PIXEL_G(p3), PIXEL_G(p4), sf, tf);
  b = BILINEAR_FILTER(PIXEL_B(p1), PIXEL_B(p2), PIXEL_B(p3), PIXEL_B(p4), sf, tf);
  a = BILINEAR_FILTER(PIXEL_A(p1), PIXEL_A(p2), PIXEL_A(p3), PIXEL_A(p4), sf, tf);

  return RGBA_TO_PIXEL(r, g, b, a);
}

// Bilinear filter four texels in each of the nearest two mipmap
// levels, then linear filter them together.
PIXEL
lookup_texture_mipmap_trilinear(ZTextureDef *texture_def, int s, int t, unsigned int level, unsigned int level_dx) {
  PIXEL p1a, p2a;

  {
    PIXEL p1, p2, p3, p4;
    int sf, tf;
    int r, g, b, a;

    p1 = ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s - ZB_ST_FRAC_HIGH, t - ZB_ST_FRAC_HIGH, level - 1);
    p2 = ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s, t - ZB_ST_FRAC_HIGH, level - 1);
    sf = (s >> (level - 1)) & ZB_ST_FRAC_MASK;
    
    p3 = ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s - ZB_ST_FRAC_HIGH, t, level - 1);
    p4 = ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s, t, level - 1);
    tf = (t >> (level - 1)) & ZB_ST_FRAC_MASK;
    
    r = BILINEAR_FILTER(PIXEL_R(p1), PIXEL_R(p2), PIXEL_R(p3), PIXEL_R(p4), sf, tf);
    g = BILINEAR_FILTER(PIXEL_G(p1), PIXEL_G(p2), PIXEL_G(p3), PIXEL_G(p4), sf, tf);
    b = BILINEAR_FILTER(PIXEL_B(p1), PIXEL_B(p2), PIXEL_B(p3), PIXEL_B(p4), sf, tf);
    a = BILINEAR_FILTER(PIXEL_A(p1), PIXEL_A(p2), PIXEL_A(p3), PIXEL_A(p4), sf, tf);
    p1a = RGBA_TO_PIXEL(r, g, b, a);
  }

  {
    PIXEL p1, p2, p3, p4;
    int sf, tf;
    int r, g, b, a;

    p1 = ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s - ZB_ST_FRAC_HIGH, t - ZB_ST_FRAC_HIGH, level);
    p2 = ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s, t - ZB_ST_FRAC_HIGH, level);
    sf = (s >> level) & ZB_ST_FRAC_MASK;
    
    p3 = ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s - ZB_ST_FRAC_HIGH, t, level);
    p4 = ZB_LOOKUP_TEXTURE_MIPMAP_NEAREST(texture_def, s, t, level);
    tf = (t >> level) & ZB_ST_FRAC_MASK;
    
    r = BILINEAR_FILTER(PIXEL_R(p1), PIXEL_R(p2), PIXEL_R(p3), PIXEL_R(p4), sf, tf);
    g = BILINEAR_FILTER(PIXEL_G(p1), PIXEL_G(p2), PIXEL_G(p3), PIXEL_G(p4), sf, tf);
    b = BILINEAR_FILTER(PIXEL_B(p1), PIXEL_B(p2), PIXEL_B(p3), PIXEL_B(p4), sf, tf);
    a = BILINEAR_FILTER(PIXEL_A(p1), PIXEL_A(p2), PIXEL_A(p3), PIXEL_A(p4), sf, tf);
    p2a = RGBA_TO_PIXEL(r, g, b, a);
  }

  int r, g, b, a;
  unsigned int bitsize = (level - 1) + ZB_POINT_ST_FRAC_BITS;
  r = LINEAR_FILTER_BITSIZE(PIXEL_R(p1a), PIXEL_R(p2a), level_dx, bitsize);
  g = LINEAR_FILTER_BITSIZE(PIXEL_G(p1a), PIXEL_G(p2a), level_dx, bitsize);
  b = LINEAR_FILTER_BITSIZE(PIXEL_B(p1a), PIXEL_B(p2a), level_dx, bitsize);
  a = LINEAR_FILTER_BITSIZE(PIXEL_A(p1a), PIXEL_A(p2a), level_dx, bitsize); 

  return RGBA_TO_PIXEL(r, g, b, a);
}


// Apply the wrap mode to s and t coordinates by calling the generic
// wrap mode function.
PIXEL
apply_wrap_general_minfilter(ZTextureDef *texture_def, int s, int t, unsigned int level, unsigned int level_dx) {
  s = (*texture_def->tex_wrap_u_func)(s, texture_def->s_max);
  t = (*texture_def->tex_wrap_v_func)(t, texture_def->t_max);
  return (*texture_def->tex_minfilter_func_impl)(texture_def, s, t, level, level_dx);
}

PIXEL
apply_wrap_general_magfilter(ZTextureDef *texture_def, int s, int t, unsigned int level, unsigned int level_dx) {
  s = (*texture_def->tex_wrap_u_func)(s, texture_def->s_max);
  t = (*texture_def->tex_wrap_v_func)(t, texture_def->t_max);
  return (*texture_def->tex_magfilter_func_impl)(texture_def, s, t, level, level_dx);
}

// Outside the legal range of s and t, return just the texture's
// border color.
PIXEL
apply_wrap_border_color_minfilter(ZTextureDef *texture_def, int s, int t, unsigned int level, unsigned int level_dx) {
  if (s < 0 || t < 0 || s > texture_def->s_max || t > texture_def->t_max) {
    return texture_def->border_color;
  }
  return (*texture_def->tex_minfilter_func_impl)(texture_def, s, t, level, level_dx);
}

PIXEL
apply_wrap_border_color_magfilter(ZTextureDef *texture_def, int s, int t, unsigned int level, unsigned int level_dx) {
  if (s < 0 || t < 0 || s > texture_def->s_max || t > texture_def->t_max) {
    return texture_def->border_color;
  }
  return (*texture_def->tex_magfilter_func_impl)(texture_def, s, t, level, level_dx);
}

// Outside the legal range of s and t, clamp s and t to the edge.
// This is also duplicated by texcoord_clamp(), but using these
// functions instead saves two additional function calls per pixel.
PIXEL
apply_wrap_clamp_minfilter(ZTextureDef *texture_def, int s, int t, unsigned int level, unsigned int level_dx) {
  s = min(max(s, 0), texture_def->s_max);
  t = min(max(t, 0), texture_def->t_max);
  return (*texture_def->tex_minfilter_func_impl)(texture_def, s, t, level, level_dx);
}

PIXEL
apply_wrap_clamp_magfilter(ZTextureDef *texture_def, int s, int t, unsigned int level, unsigned int level_dx) {
  s = min(max(s, 0), texture_def->s_max);
  t = min(max(t, 0), texture_def->t_max);
  return (*texture_def->tex_magfilter_func_impl)(texture_def, s, t, level, level_dx);
}

int
texcoord_clamp(int coord, int max_coord) {
  return min(max(coord, 0), max_coord);
}

int
texcoord_repeat(int coord, int max_coord) {
  return coord;
}

int
texcoord_mirror(int coord, int max_coord) {
  if ((coord & ((max_coord << 1) - 1)) > max_coord) {
    coord = (max_coord << 1) - coord;
  }
  return coord;
}

int
texcoord_mirror_once(int coord, int max_coord) {
  if (coord > max_coord) {
    coord = (max_coord << 1) - coord;
  }
  return max(coord, 0);
}
