From ea76b6988ccafaa6a4d4ed90f2489d0e49e1f180 Mon Sep 17 00:00:00 2001 From: Benjamin Barenblat Date: Sat, 5 Sep 2015 09:32:30 -0400 Subject: Imported Upstream version 0.40.24 --- g_src/textures.cpp | 398 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100755 g_src/textures.cpp (limited to 'g_src/textures.cpp') 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 + +#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 ordered; + long pos = 0; + for (std::vector::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; + } +} -- cgit v1.2.3