summaryrefslogtreecommitdiff
path: root/g_src/textures.cpp
blob: 50334bac72aa95205304bd5a083c46259c7549b9 (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
#include <cassert>

#include "enabler.h"
#include "init.h"

// Used to sort textures
struct vsize_pos {
  int h, w;
  SDL_Surface *s;
  long pos;
  // Assigned texture-catalog coordinates
  int x, y;

  bool operator< (struct vsize_pos y) const {
    // sort produces an ascending order. We want descending. Don't argue.
    if (h > y.h) return true;
    return false;
  }
};


// Check whether a particular texture can be sized to some size,
// assuming in RGBA 32-bit format
bool testTextureSize(GLuint texnum, int w, int h) {
  GLint gpu_width;
  glBindTexture(GL_TEXTURE_2D, texnum);
      printGLError();
  glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
      printGLError();
  glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &gpu_width);
      printGLError();

  if (gpu_width == w) return true;
  return false;
}

// Texture catalog implementation
void textures::upload_textures() {
  if (uploaded) return; // Don't bother
  if (!enabler.uses_opengl()) return; // No uploading
  glEnable(GL_TEXTURE_2D);
  printGLError();
  glGenTextures(1, &gl_catalog);
  printGLError();

  // First, sort the textures by vertical size. We'll want to place the large
  // ones first.
  // Since we mustn't alter the raws array, first thing is to create a new one.
  // We pretend textures are one pixel larger than they actually are in either
  // direction, to avoid border scuffles when interpolating.
  std::vector<vsize_pos> ordered;
  long pos = 0;
  for (std::vector<SDL_Surface *>::iterator it = raws.begin();
       it != raws.end(); ++it) {
    if (*it) {
      vsize_pos item;
      item.h = (*it)->h+2;
      item.w = (*it)->w+2;
      item.s = *it;
      item.pos = pos;
      ordered.push_back(item);
    }
    pos++;
  }
  sort(ordered.begin(), ordered.end());

  /* Tiling algorithm:
  **
  ** Given a particular texture width, we pack tiles from largest to smallest
  ** by reserving rows for tiles with a particular height or lower.
  ** This does lead to space wastage when a row has, say, one 32x32 tile and
  ** fifteen 16x16 tiles, but generally not very much.
  **
  ** Possible improvement: Allow for multiple rows of smaller tiles inside
  ** a row that's at least twice as high as the smaller tiles are.
   */

  // Set the initial width to the minimum possible
  int catalog_width = 0;
  for (int i=0; i < ordered.size(); i++)
    if (catalog_width < ordered[i].w) catalog_width = ordered[i].w;
  const int width_increment = 4; // For speed, not that it matters.
  int catalog_height;
  // Figure out what the optimal texture width is
  // This may not be actually be an approximately square texture, but for the
  // moment that's what we're aiming for. On GPUs without the NPOT extension,
  // rectangular textures may actulaly use less video memory.
  // However, a square one is less likely to run into dimensional limits.
  for(;;) {
    int catalog_x = 0;
    int catalog_y = 0;
    int row_height = ordered[0].h;
    catalog_height = row_height;
    for (int pos = 0; pos < ordered.size(); pos++) {
      // Check whether we must start a new row
      if (catalog_x + ordered[pos].w > catalog_width) {
	catalog_x = 0;
	catalog_y = catalog_height;
	row_height = ordered[pos].h;
	catalog_height += row_height;
      }
      // Tentatively install tile at catalog_x, catalog_y
      ordered[pos].x = catalog_x;
      ordered[pos].y = catalog_y;
      // Goto next tile
      catalog_x += ordered[pos].w;
    }
    // If we didn't just cross "square", increment width and try again.
    if (catalog_height > catalog_width)
      catalog_width += width_increment;
    else
      break; // Otherwise we're done.
   }

#ifdef DEBUG
  std::cout << "Ideal catalog size: " << catalog_width << "x" << catalog_height << "\n";
#endif
  
  // Check whether the GPU supports non-power-of-two textures
  bool npot = false;
  if (GLEW_ARB_texture_rectangle && GLEW_ARB_texture_non_power_of_two)
    npot=true;
  
  if (!npot) {
    // Use a power-of-two texture catalog
    int newx = 1, newy = 1;
    while (newx < catalog_width) newx *= 2;
    while (newy < catalog_height) newy *= 2;
    catalog_width = newx;
    catalog_height = newy;
    std::cout << "GPU does not support non-power-of-two textures, using " << catalog_width << "x" << catalog_height << " catalog.\n";
  }
  // Check whether the GPU will allow a texture of that size
  if (!testTextureSize(gl_catalog, catalog_width, catalog_height)) {
    MessageBox(NULL,"GPU unable to accommodate texture catalog. Retry without graphical tiles, update your drivers, or better yet update your GPU.", "GL error", MB_OK);
    exit(EXIT_FAILURE);
  }

  // Guess it will. Well, then, actually upload it
  glBindTexture(GL_TEXTURE_2D, gl_catalog);
      printGLError();
  char *zeroes = new char[catalog_width*catalog_height*4];
  memset(zeroes,0,sizeof(char)*catalog_width*catalog_height*4);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, catalog_width, catalog_height, 0, GL_RGBA,
	       GL_UNSIGNED_BYTE, zeroes);
  delete[] zeroes;
      printGLError();
  glBindTexture(GL_TEXTURE_2D, gl_catalog);
      printGLError();
  GLint param = (init.window.flag.has_flag(INIT_WINDOW_FLAG_TEXTURE_LINEAR) ?
    GL_LINEAR : GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,param);
       printGLError();
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,param);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
      printGLError();
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
      printGLError();
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
      printGLError();
  // Performance isn't important here. Let's make sure there are no alignment issues.
  glPixelStorei(GL_PACK_ALIGNMENT, 1);
      printGLError();
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
      printGLError();
  // While storing the positions to gl_texpos.
  if (gl_texpos) delete[] gl_texpos;
  gl_texpos = new struct gl_texpos[raws.size()];
  for (int pos = 0; pos < ordered.size(); pos++) {
    long raws_pos = ordered[pos].pos;
    SDL_Surface *s = ordered[pos].s;
    SDL_PixelFormat *f = s->format;
    SDL_LockSurface(s);
    // Make /real/ sure we get the GL format right.
    unsigned char *pixels = new unsigned char[ordered[pos].w * ordered[pos].h * 4];
    // Recall, ordered[pos].w is 2 larger than s->w because of the border.
    for (int bx=0; bx < ordered[pos].w; bx++) {
      int x = bx - 1;
      if (x == -1) x++;
      if (x == s->w) x--;
      for (int by=0; by < ordered[pos].h; by++) {
        int y = by - 1;
        if (y == -1) y++;
        if (y == s->h) y--;
        // GL textures are loaded upside-down, Y=0 at the bottom
        unsigned char *pixel_dst = &pixels[(ordered[pos].h - by - 1)*ordered[pos].w*4 + bx*4];
        unsigned char *pixel_src = &((unsigned char*)s->pixels)[y*s->w*4 + x*4];
        assert (pixel_dst < pixels + ordered[pos].w * ordered[pos].h * 4);
        assert (pixel_src < (unsigned char*)s->pixels + s->w * s->h * 4);
        // We convert all textures to RGBA format at load-time, further below
        for (int i=0; i<4; i++) {
          pixel_dst[i] = pixel_src[i];
        }
      }
    }
    // Right. Upload the texture to the catalog.
    SDL_UnlockSurface(s);
    glBindTexture(GL_TEXTURE_2D, gl_catalog);
    printGLError();
    glTexSubImage2D(GL_TEXTURE_2D, 0, ordered[pos].x, ordered[pos].y, ordered[pos].w, ordered[pos].h,
		    GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    delete[] pixels;
    printGLError();
    // Compute texture coordinates and store to gl_texpos for later output.
    // To make sure the right pixel is chosen when texturing, we must
    // pick coordinates that place us in the middle of the pixel we want.
    //
    // There's no real reason to use double instead of floats here, but
    // no reason not to either, and it just might help with precision.
    //
    // There's a one-pixel border around each tile, so we offset by 1
    gl_texpos[raws_pos].left   = ((double)ordered[pos].x+1)      / (double)catalog_width;
    gl_texpos[raws_pos].right  = ((double)ordered[pos].x+1+s->w) / (double)catalog_width;
    gl_texpos[raws_pos].top    = ((double)ordered[pos].y+1)      / (double)catalog_height;
    gl_texpos[raws_pos].bottom = ((double)ordered[pos].y+1+s->h) / (double)catalog_height;
  }
  // And that's that. Locked, loaded and ready for texturing.
  printGLError();
  uploaded=true;
}

void textures::remove_uploaded_textures() {
  if (!uploaded) return; // Nothing to do
  glDeleteTextures(1, &gl_catalog);
  uploaded=false;
}

SDL_Surface *textures::get_texture_data(long pos) {
  if (raws.size() > pos) {
    return raws[pos];
  } else {
    std::cerr << "Asked for nonexistent texture data\n";
    SDL_Surface *surf = SDL_CreateRGBSurface(SDL_SWSURFACE, 8, 8, 32, 0, 0, 0, 0);
    SDL_FillRect(surf, NULL, SDL_MapRGB(surf->format, 255, 0, 255));
    raws.resize(pos+1);
    raws[pos] = surf;
    return raws[pos];
  }
}

long textures::clone_texture(long src) {
  long tx;
	
  if (raws.size() > src && raws[src]) {
    SDL_Surface *dst = SDL_ConvertSurface(raws[src], raws[src]->format, SDL_SWSURFACE);
    tx=add_texture(dst);
  }
  else {
  // Okay, we've been asked to clone a nonexistent texture. Riight...
  // Fortunately add_texture doesn't look at the pointer it gets at all.
  std::cerr << "Asked to clone nonexistent texture!\n";
  tx=add_texture(NULL);
  }

  enabler.reset_textures();

  return tx;
}

void textures::grayscale_texture(long pos) {
 SDL_Surface *s = get_texture_data(pos);
 if (!s) return;
 SDL_LockSurface(s);
 SDL_PixelFormat *f = s->format;
 Uint32 *pixels = (Uint32*)(s->pixels);
 if (f->BytesPerPixel != 4) {
   std::cerr << "grayscale_texture ran into mysteriously uncanonicalized texture\n";
   goto cleanup;
 }
 for (int i=0; i < s->w*s->h; i++) { // For each pixel
   int r = (pixels[i] & f->Rmask) >> f->Rshift;
   int g = (pixels[i] & f->Gmask) >> f->Gshift;
   int b = (pixels[i] & f->Bmask) >> f->Bshift;
   int alpha = (pixels[i] & f->Amask) >> f->Ashift;
int luminosity=(int)((float)r*0.30f+(float)g*0.59f+(float)b*0.11f);
if(luminosity<0)luminosity=0;
if(luminosity>255)luminosity=255;
   pixels[i] = (luminosity << f->Rshift) |
     (luminosity << f->Gshift) |
     (luminosity << f->Bshift) |
     (alpha << f->Ashift);
 }

 cleanup:
 SDL_UnlockSurface(s);

 enabler.reset_textures();
}

// Converts an arbitrary Surface to something like the display format
// (32-bit RGBA), and converts magenta to transparency if convert_magenta is set
// and the source surface didn't already have an alpha channel.
// It also deletes the source surface.
//
// It uses the same pixel format (RGBA, R at lowest address) regardless of
// hardware.
SDL_Surface *canonicalize_format(SDL_Surface *src, bool convert_magenta) {
  SDL_PixelFormat fmt;
  fmt.palette = NULL;
  fmt.BitsPerPixel = 32;
  fmt.BytesPerPixel = 4;
  fmt.Rloss = fmt.Gloss = fmt.Bloss = fmt.Aloss = 0;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
  fmt.Rshift = 24; fmt.Gshift = 16; fmt.Bshift = 8; fmt.Ashift = 0;
#else
  fmt.Rshift = 0; fmt.Gshift = 8; fmt.Bshift = 16; fmt.Ashift = 24;
#endif
  fmt.Rmask = 255 << fmt.Rshift;
  fmt.Gmask = 255 << fmt.Gshift;
  fmt.Bmask = 255 << fmt.Bshift;
  fmt.Amask = 255 << fmt.Ashift;
  fmt.colorkey = 0;
  fmt.alpha = 255;

  if (src->format->Amask == 0 && convert_magenta) { // No alpha
    SDL_SetColorKey(src, SDL_SRCCOLORKEY,
		    SDL_MapRGB(src->format, 255, 0, 255));
  }
  SDL_Surface *tgt = SDL_ConvertSurface(src, &fmt, SDL_SWSURFACE);
  SDL_FreeSurface(src);
  return tgt;
}

// Finds or creates a free spot in the texture array, and inserts
// surface in that spot, then returns the location.
long textures::add_texture(SDL_Surface *surface) {
  long sz = raws.size();
  // Look for a free spot
  for (long pos=0; pos < sz; pos++) {
    if (raws[pos] == NULL) {
      raws[pos] = surface;
      return pos;
    }
  }

  // No free spot, make one
  raws.push_back(surface);
  return sz;
}

void textures::load_multi_pdim(const string &filename, long *tex_pos, long dimx,
			       long dimy, bool convert_magenta,
			       long *disp_x, long *disp_y) {
  SDL_Surface *raw = IMG_Load(filename.c_str());
  if (!raw) {
    MessageBox(NULL, ("Not found: " + filename).c_str(), "Tileset not found", MB_OK);
    exit(1);
  }
  SDL_Surface *src = canonicalize_format(raw, convert_magenta);
  SDL_SetAlpha(src, 0, 255);
  *disp_x = src->w / dimx;
  *disp_y = src->h / dimy;
  long idx = 0;
  for (int y=0; y < dimy; y++) {
    for (int x=0; x < dimx; x++) {
      SDL_Surface *tile = SDL_CreateRGBSurface(SDL_SWSURFACE, *disp_x, *disp_y,
					       32, src->format->Rmask,
					       src->format->Gmask,
					       src->format->Bmask,
					       src->format->Amask);
      SDL_SetAlpha(tile, 0,255);
      SDL_Rect pos_src;
      pos_src.x = *disp_x * x;
      pos_src.y = *disp_y * y;
      pos_src.w =  *disp_x;
      pos_src.h =  *disp_y;
      SDL_BlitSurface(src, &pos_src, tile, NULL);
      tex_pos[idx++] = add_texture(tile);
    }
  }
  SDL_FreeSurface(src);
  // Re-upload textures if necessary
  enabler.reset_textures();
}

long textures::load(const string &filename, bool convert_magenta) {
  SDL_Surface *raw = IMG_Load(filename.c_str());
  if (!raw) {
    MessageBox(NULL, ("Not found: " + filename).c_str(), "Image not found", MB_OK);
    exit(1);
  }
  SDL_Surface *tex = canonicalize_format(raw, convert_magenta);
  long pos = add_texture(tex);
  // Re-upload if necessary
  enabler.reset_textures();
  return pos;
}

void textures::delete_texture(long pos) {
  // We can't actually resize the array, as
  // (a) it'd be slow, and
  // (b) it'd change all the positions. Bad stuff would happen.
  if (raws[pos]) {
    SDL_FreeSurface(raws[pos]);
    raws[pos] = NULL;
  }
}