/******************************************************************************* Copyright (c) 2009, Charles McGarvey All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include // memcpy #include #include #include #include "Dispatcher.hh" #include "Library.hh" #include "Log.hh" #include "OpenGL.hh" #include "Texture.hh" namespace Mf { /** * 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::Impl : public Library { /** * Delete the texture (if it is loaded) from GL. */ void unloadFromGL() { if (mObject) { if (mObject == globalObject_) { globalObject_ = 0; } glDeleteTextures(1, &mObject); mObject = 0; } } /** * If the GL context was recreated, we 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) { mObject = globalObject_ = 0; uploadToGL(); } /** * 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) { int value = 1; while (value < input) { value <<= 1; } return value; } static void flipSurface(SDL_Surface* image) { unsigned char* pixels = (Uint8*)(image->pixels); unsigned pitch = image->pitch; unsigned char line[pitch]; int yBegin = 0; int yEnd = image->h - 1; 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); yBegin++; yEnd--; } if (SDL_MUSTLOCK(image)) SDL_UnlockSurface(image); } public: /** * Construction is initialization. */ explicit Impl(const std::string& name) : Library(name), mContext(0), mWidth(0), mHeight(0), mMode(0), mMinFilter(GL_NEAREST), mMagFilter(GL_NEAREST), mWrapS(GL_CLAMP), mWrapT(GL_CLAMP), mObject(0) { loadFromFile(); // we want to know when the GL context is recreated Dispatcher::getInstance().addHandler("video.context_recreated", boost::bind(&Impl::contextRecreated, this, _1), this); } ~Impl() { if (mContext) { SDL_FreeSurface(mContext); } unloadFromGL(); Dispatcher::getInstance().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) { int w = powerOfTwo(surface->w); int h = powerOfTwo(surface->h); // 2. OpenGL textures make more sense within the coordinate system when // they are "upside down," so let's flip it. flipSurface(surface); // 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 if // textures are the right size to begin with. SDL_Surface* image = SDL_CreateRGBSurface ( SDL_SWSURFACE, w, h, 32, #if SDL_BYTEORDER == SDL_LIL_ENDIAN 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 #else 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF #endif ); if (!image) { return 0; } Uint32 savedFlags = surface->flags&(SDL_SRCALPHA|SDL_RLEACCELOK); Uint8 savedAlpha = surface->format->alpha; if (savedFlags & SDL_SRCALPHA) { SDL_SetAlpha(surface, 0, 0); } SDL_Rect srcArea, destArea; srcArea.x = 0; destArea.x = 0; srcArea.y = 0; destArea.y = h - surface->h; srcArea.w = surface->w; srcArea.h = surface->h; SDL_BlitSurface(surface, &srcArea, image, &destArea); if (savedFlags & SDL_SRCALPHA) { SDL_SetAlpha(surface, savedFlags, savedAlpha); } return image; } /** * Use SDL_image to load images from file. A surface with the image data is * returned. * @return Image data. */ void loadFromFile() { SDL_Surface* surface; surface = IMG_Load(Texture::getPath(getName()).c_str()); if (!surface) { logWarning("texture not found: %s", getName().c_str()); throw Exception(Exception::FILE_NOT_FOUND); } SDL_Surface* temp = prepareImageForGL(surface); SDL_FreeSurface(surface); if (!temp) { throw Exception(Exception::OPENGL_ERROR); } if (temp->format->BytesPerPixel == 3) { mMode = GL_RGB; } else if (temp->format->BytesPerPixel == 4) { mMode = GL_RGBA; } else { SDL_FreeSurface(temp); throw Exception(Exception::BAD_IMAGE_FORMAT); } mWidth = temp->w; mHeight = temp->h; mContext = 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 (mObject) { // already loaded return; } if (!mContext) loadFromFile(); glGenTextures(1, &mObject); glBindTexture(GL_TEXTURE_2D, mObject); glTexImage2D //gluBuild2DMipmaps ( GL_TEXTURE_2D, 0, mMode, //3, mContext->w, mContext->h, 0, mMode, GL_UNSIGNED_BYTE, mContext->pixels ); setProperties(); SDL_FreeSurface(mContext); mContext = 0; } /** * Sets some texture properties such as the filters and external coordinate * behavior. */ void setProperties() { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mMinFilter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mMagFilter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mWrapS); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mWrapT); } inline void setMinFilter(GLuint filter) { bind(); mMinFilter = filter; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mMinFilter); } inline void setMagFilter(GLuint filter) { bind(); mMagFilter = filter; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mMagFilter); } inline void setWrapS(GLuint wrap) { bind(); mWrapS = wrap; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mWrapS); } inline void setWrapT(GLuint wrap) { bind(); mWrapT = wrap; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mWrapT); } inline void bind() { if (mObject == 0) { uploadToGL(); } if (mObject != globalObject_) { glBindTexture(GL_TEXTURE_2D, mObject); globalObject_ = mObject; } } SDL_Surface* mContext; unsigned mWidth; ///< Horizontal dimension of the image. unsigned mHeight; ///< Vertical dimension. GLuint mMode; ///< Depth of the image, GL_RGB or GL_RGBA. GLuint mMinFilter; ///< Minifcation filter. GLuint mMagFilter; ///< Magnification filter. GLuint mWrapS; ///< Wrapping behavior horizontally. GLuint mWrapT; ///< Wrapping behavior vertically. GLuint mObject; ///< GL texture handle. static GLuint globalObject_; ///< Global GL texture handle. }; GLuint Texture::Impl::globalObject_ = 0; Texture::Texture(const std::string& name) : // pass through mImpl(Texture::Impl::getInstance(name)) {} /** * Bind the GL texture for mapping, etc. */ void Texture::bind() const { // pass through mImpl->bind(); } /** * Get the texture object, for the curious. */ GLuint Texture::getObject() const { // pass through return mImpl->mObject; } void Texture::resetBind() { glBindTexture(GL_TEXTURE_2D, 0); Impl::globalObject_ = 0; } unsigned Texture::getWidth() const { // pass through return mImpl->mWidth; } unsigned Texture::getHeight() const { // pass through return mImpl->mHeight; } void Texture::setMinFilter(GLuint filter) { // pass through mImpl->setMinFilter(filter); } void Texture::setMagFilter(GLuint filter) { // pass through mImpl->setMagFilter(filter); } void Texture::setWrapS(GLuint wrap) { // pass through mImpl->setWrapS(wrap); } void Texture::setWrapT(GLuint wrap) { // pass through mImpl->setWrapT(wrap); } std::string Texture::getPath(const std::string& name) { std::string path = Resource::getPath("textures/" + name + ".png"); return path; } } // namespace Mf /** vim: set ts=4 sw=4 tw=80: *************************************************/