summaryrefslogtreecommitdiff
path: root/g_src/textures.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'g_src/textures.cpp')
-rwxr-xr-xg_src/textures.cpp398
1 files changed, 398 insertions, 0 deletions
diff --git a/g_src/textures.cpp b/g_src/textures.cpp
new file mode 100755
index 0000000..50334ba
--- /dev/null
+++ b/g_src/textures.cpp
@@ -0,0 +1,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;
+ }
+}