X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fyoink;a=blobdiff_plain;f=src%2Ftexture.cc;h=6d3e912925b8f1376018b9cc612fde8db1d4677a;hp=b91d23ac7456349e85db9cca2cb55e16925f1d1f;hb=838bc00015eb7f583c7cf4b3b1007697bf047da1;hpb=79b5f738f2e38acb60cda7e09f54802933a17105 diff --git a/src/texture.cc b/src/texture.cc index b91d23a..6d3e912 100644 --- a/src/texture.cc +++ b/src/texture.cc @@ -26,7 +26,6 @@ *******************************************************************************/ -#include #include #include @@ -34,6 +33,8 @@ #include #include +#include "mippleton.hh" + #include "dispatcher.hh" #include "opengl.hh" @@ -43,43 +44,47 @@ namespace dc { -class texture_impl +/** + * The texture implementation just contains all the information about the image + * which is worth having in memory. The image data itself is not worth keeping + * in memory if the texture has been loaded to GL, but the name of the resource + * is retained so that it can be reloaded if necessary. The implementation is a + * mippleton so that multiple texture objects can share the same internal + * objects and avoid having duplicate textures loaded to GL. + */ + +class texture::texture_impl : public mippleton { + + /** + * Delete the texture (if it is loaded) from GL. + */ + void unloadFromGL() { - if (object) + if (object_) { - glDeleteTextures(1, &object); - object = 0; + glDeleteTextures(1, &object_); + object_ = 0; } } + /** + * If the GL context was recreated, we probably need to reload the texture. + * This may involve reading it from disk again, but hopefully the OS was + * smart enough to cache it if the client has plenty of RAM. + */ + void contextRecreated(const notification& note) { unloadFromGL(); uploadToGL(); } -public: - texture_impl(const std::string& filePath, bool keepInMemory) - : object(0), imageData(0), imagePath(filePath), keepData(keepInMemory) - { - dispatcher::instance().addHandler("video.context_recreated", - boost::bind(&texture_impl::contextRecreated, this, _1), - this); - } - - ~texture_impl() - { - dispatcher::instance().removeHandler(this); - - if (imageData) - { - SDL_FreeSurface(imageData); - } - unloadFromGL(); - } - + /** + * This is a helper method used by some of the texture loading code. It + * returns the first power of two which is greater than the input value. + */ static int powerOfTwo(int input) { @@ -92,20 +97,62 @@ public: return value; } - // Adapted from some public domain code. This code is common enough that it - // really should be included in SDL_image... +public: + + /** + * Construction is initialization. + */ + + explicit texture_impl(const std::string& name) : + mippleton(name), + width_(0), + height_(0), + mode_(0), + minFilter_(GL_NEAREST), + maxFilter_(GL_NEAREST), + wrapU_(GL_CLAMP), + wrapV_(GL_CLAMP), + object_(0) + { + uploadToGL(); + + // we want to know when the GL context is recreated + dispatcher::instance().addHandler("video.context_recreated", + boost::bind(&texture_impl::contextRecreated, this, _1), this); + } + + ~texture_impl() + { + unloadFromGL(); + + dispatcher::instance().removeHandler(this); + } + + + /** + * Adapted from some public domain code. This stuff is common enough that + * it really should be included in SDL_image... We need this because images + * loaded with SDL_image aren't exactly GL-ready right out of the box. This + * method makes them ready. + */ + static SDL_Surface* prepareImageForGL(SDL_Surface* surface) { - /* Use the surface width and height expanded to powers of 2 */ int w = powerOfTwo(surface->w); int h = powerOfTwo(surface->h); + // 1. OpenGL images must (generally) have dimensions of a power-of-two. + // If this one doesn't, we can at least be more friendly by expanding + // the dimensions so that they are, though there will be some empty + // space within the range of normal texture coordinates. It's better of + // textures are the right size to begin with. + SDL_Surface* image = SDL_CreateRGBSurface ( SDL_SWSURFACE, w, h, 32, -#if SDL_BYTEORDER == SDL_LIL_ENDIAN /* OpenGL RGBA masks */ +#if SDL_BYTEORDER == SDL_LIL_ENDIAN 0x000000FF, 0x0000FF00, 0x00FF0000, @@ -123,7 +170,6 @@ public: return 0; } - // Save the alpha blending attributes. Uint32 savedFlags = surface->flags&(SDL_SRCALPHA|SDL_RLEACCELOK); Uint8 savedAlpha = surface->format->alpha; if (savedFlags & SDL_SRCALPHA) @@ -132,177 +178,227 @@ public: } SDL_Rect srcArea, destArea; - /* Copy the surface into the GL texture image */ srcArea.x = 0; destArea.x = 0; - /* Copy it in at the bottom, because we're going to flip - this image upside-down in a moment - */ srcArea.y = 0; destArea.y = h - surface->h; srcArea.w = surface->w; srcArea.h = surface->h; SDL_BlitSurface(surface, &srcArea, image, &destArea); - /* Restore the alpha blending attributes */ if (savedFlags & SDL_SRCALPHA) { SDL_SetAlpha(surface, savedFlags, savedAlpha); } - /* Turn the image upside-down, because OpenGL textures - start at the bottom-left, instead of the top-left - */ + // 2. OpenGL textures make more sense within the coordinate system when + // they are "upside down," so let's flip it. + Uint8 line[image->pitch]; - /* These two make the following more readable */ Uint8 *pixels = static_cast(image->pixels); Uint16 pitch = image->pitch; int ybegin = 0; int yend = image->h - 1; - // TODO: consider if this lock is legal/appropriate - if (SDL_MUSTLOCK(image)) { SDL_LockSurface(image); } + if (SDL_MUSTLOCK(image)) SDL_LockSurface(image); while (ybegin < yend) { - memcpy(line, pixels + pitch*ybegin, pitch); - memcpy(pixels + pitch*ybegin, pixels + pitch*yend, pitch); - memcpy(pixels + pitch*yend, line, pitch); + memcpy(line, pixels + pitch * ybegin, pitch); + memcpy(pixels + pitch * ybegin, pixels + pitch * yend, pitch); + memcpy(pixels + pitch * yend, line, pitch); ybegin++; yend--; } - if (SDL_MUSTLOCK(image)) { SDL_UnlockSurface(image); } + if (SDL_MUSTLOCK(image)) SDL_UnlockSurface(image); return image; } + /** + * Use SDL_image to load images from file. A surface with the image data is + * returned. + * @return Image data. + */ - void loadImageData() + SDL_Surface* loadImageData() { SDL_Surface* surface; - surface = IMG_Load(imagePath.c_str()); + surface = IMG_Load(texture::getPathToResource(getName()).c_str()); if (!surface) { - throw std::runtime_error("could not load image data from file"); + throw texture::exception("loading failed"); } - imageData = prepareImageForGL(surface); + SDL_Surface* temp = prepareImageForGL(surface); SDL_FreeSurface(surface); - if (!imageData) + if (!temp) { - throw std::runtime_error("error in preparing image data for GL"); + throw texture::exception("image couldn't be prepared for GL"); } - if (imageData->format->BytesPerPixel == 3) + if (temp->format->BytesPerPixel == 3) { - mode = GL_RGB; + mode_ = GL_RGB; } - else if (imageData->format->BytesPerPixel == 4) + else if (temp->format->BytesPerPixel == 4) { - mode = GL_RGBA; + mode_ = GL_RGBA; } else { - SDL_FreeSurface(imageData); - throw std::runtime_error("image must be 24 or 32 bpp"); + SDL_FreeSurface(temp); + throw texture::exception("image is not the required 24 or 32 bpp"); } - width = imageData->w; - height = imageData->h; + width_ = temp->w; + height_ = temp->h; + + return temp; } + /** + * Upload the image to GL so that it will be accessible by a much more + * manageable handle and hopefully reside in video memory. + */ + void uploadToGL() { - if (object) + if (object_) { // Already loaded. return; } - if (!imageData) - { - loadImageData(); - } + SDL_Surface* imageData = loadImageData(); - glGenTextures(1, &object); - - glBindTexture(GL_TEXTURE_2D, object); + glGenTextures(1, &object_); + glBindTexture(GL_TEXTURE_2D, object_); glTexImage2D ( GL_TEXTURE_2D, 0, - mode, + mode_, imageData->w, imageData->h, 0, - mode, + mode_, GL_UNSIGNED_BYTE, imageData->pixels ); - // These default filters can be changed later... - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + setProperties(); - if (!keepData) - { - SDL_FreeSurface(imageData); - imageData = 0; - } + SDL_FreeSurface(imageData); + } + + + /** + * Sets some texture properties such as the filters and external coordinate + * behavior. + */ + + void setProperties() + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, maxFilter_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapU_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapV_); } - unsigned width; - unsigned height; - int mode; - GLuint object; + unsigned width_; ///< Horizontal dimension of the image. + unsigned height_; ///< Vertical dimension. - std::string imagePath; - bool keepData; - SDL_Surface* imageData; + GLuint mode_; ///< Depth of the image, GL_RGB or GL_RGBA. + GLuint minFilter_; ///< Filter. + GLuint maxFilter_; ///< Filter. + GLuint wrapU_; ///< Wrapping behavior horizontally. + GLuint wrapV_; ///< Wrapping behavior vertically. + GLuint object_; ///< GL texture handle. }; -texture::texture(const std::string& filePath, bool keepInMemory) - : impl(new texture_impl(filePath, keepInMemory)) {} +texture::texture(const std::string& name) : + // pass through + impl(texture::texture_impl::retain(name), &texture::texture_impl::release) +{} + +/** + * Bind the GL texture for mapping, etc. + */ -const std::string& texture::filePath() +void texture::bind() { - return impl->imagePath; + glBindTexture(GL_TEXTURE_2D, getObject()); } -void texture::bind() +/** + * Get the texture object, for the curious. + */ + +GLuint texture::getObject() +{ + // pass through + return impl->object_; +} + + +unsigned texture::getWidth() { - glBindTexture(GL_TEXTURE_2D, object()); + // pass through + return impl->width_; } -GLuint texture::object() +unsigned texture::getHeight() { - if (!impl->object) - { - impl->uploadToGL(); - } + // pass through + return impl->height_; +} + + +void texture::setMinFilter(GLuint filter) +{ + impl->minFilter_ = filter; +} - return impl->object; +void texture::setMaxFilter(GLuint filter) +{ + impl->maxFilter_ = filter; } +void texture::setWrapU(GLuint wrap) +{ + impl->wrapU_ = wrap; +} -unsigned texture::width() +void texture::setWrapV(GLuint wrap) { - return impl->width; + impl->wrapV_ = wrap; } -unsigned texture::height() + +void texture::applyChanges() { - return impl->height; + bind(); + impl->setProperties(); } +std::string texture::getPathToResource(const std::string& name) +{ + // TODO since this is a generic library class, more than PNG should be + // supported + return resource::getPathToResource("textures/" + name + ".png"); +} + } // namespace dc +/** vim: set ts=4 sw=4 tw=80: *************************************************/ +