/*] Copyright (c) 2009-2010, Charles McGarvey [************************** **] All rights reserved. * * vi:ts=4 sw=4 tw=75 * * Distributable under the terms and conditions of the 2-clause BSD license; * see the file COPYING for a complete text of the license. * **************************************************************************/ #include // FILE #include // strncmp #include #include #include "Dispatch.hh" #include "Error.hh" #include "Manager.hh" #include "Log.hh" #include "OpenGL.hh" #include "Script.hh" #include "Texture.hh" #include "Video.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 manager so that multiple texture * objects can share the same internal objects and avoid having duplicate * textures loaded to GL. */ class Texture::Impl : public Manager { /** * Delete the texture (if it is loaded) from GL. */ void unloadFromGL() { if (mObject) { if (mObject == gObject) { gObject = 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() { mObject = gObject = 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 bindScriptConstants(Script& script) { Script::Slot g = script.globals(); g.setField("CLAMP", GL_CLAMP); g.setField("REPEAT", GL_REPEAT); g.setField("LINEAR", GL_LINEAR); g.setField("NEAREST", GL_NEAREST); g.setField("LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR); g.setField("LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST); g.setField("NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR); g.setField("NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST); } public: /** * Construction is initialization. */ Impl() : mMinFilter(GL_NEAREST), mMagFilter(GL_NEAREST), mWrapS(GL_CLAMP), mWrapT(GL_CLAMP), mTilesS(1), mTilesT(1), mObject(0) { // make sure we have a video context Video* video = Video::current(); ASSERT(video && "should have a video context set"); // we want to know when the GL context is recreated Dispatch& dispatch = Dispatch::global(); mNewContextDispatch = dispatch.addTarget("video.newcontext", boost::bind(&Impl::contextRecreated, this)); } ~Impl() { unloadFromGL(); } /** * 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 init(const std::string& name) { std::string path(name); Texture::getPath(path); mImage = Image::alloc(path); if (!mImage->isValid()) { logWarning << "texture not found: " << path << std::endl; Error(Error::RESOURCE_NOT_FOUND, path).raise(); } mImage->flip(); Script script; importLogFunctions(script); bindScriptConstants(script); if (script.doString(mImage->getComment()) != Script::SUCCESS) { std::string str; script[-1].get(str); logWarning(str); } else { logInfo << "loading tiles from texture " << path << std::endl; Script::Slot globals = script.globals(); globals.get(mTilesS, "tiles_s"); globals.get(mTilesT, "tiles_t"); globals.get(mMinFilter, "min_filter"); globals.get(mMagFilter, "mag_filter"); globals.get(mWrapS, "wrap_s"); globals.get(mWrapT, "wrap_t"); } } /** * 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; } glGenTextures(1, &mObject); glBindTexture(GL_TEXTURE_2D, mObject); glTexImage2D //gluBuild2DMipmaps ( GL_TEXTURE_2D, 0, mImage->getMode(), //3, mImage->getWidth(), mImage->getHeight(), 0, mImage->getMode(), GL_UNSIGNED_BYTE, mImage->getPixels() ); setProperties(); } /** * 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 != gObject) { glBindTexture(GL_TEXTURE_2D, mObject); gObject = mObject; } } bool getTileCoords(Texture::TileIndex index, Scalar coords[8]) const { // make sure the index represents a real tile if (index >= mTilesS * mTilesT) return false; Scalar w = 1.0 / Scalar(mTilesS); Scalar h = 1.0 / Scalar(mTilesT); coords[0] = Scalar(index % mTilesS) * w; coords[1] = (Scalar(mTilesT - 1) - Scalar(index / mTilesS)) * h; coords[2] = coords[0] + w; coords[3] = coords[1]; coords[4] = coords[2]; coords[5] = coords[1] + h; coords[6] = coords[0]; coords[7] = coords[5]; return true; } ImageP mImage; GLuint mMinFilter; ///< Minification filter. GLuint mMagFilter; ///< Magnification filter. GLuint mWrapS; ///< Wrapping behavior horizontally. GLuint mWrapT; ///< Wrapping behavior vertically. unsigned mTilesS; unsigned mTilesT; GLuint mObject; ///< GL texture handle. static GLuint gObject; ///< Global GL texture handle. Dispatch::Handle mNewContextDispatch; }; GLuint Texture::Impl::gObject = 0; Texture::Texture(const std::string& name) : // TODO hmm.. Image(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::gObject = 0; } 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); } bool Texture::getTileCoords(TileIndex index, Scalar coords[8]) const { // pass through return mImpl->getTileCoords(index, coords); } bool Texture::getTileCoords(TileIndex index, Scalar coords[8], Orientation orientation) const { if (getTileCoords(index, coords)) { if (orientation & FLIP) { // this looks kinda weird, but it's just swapping in a way that // doesn't require an intermediate variable coords[1] = coords[5]; coords[5] = coords[3]; coords[3] = coords[7]; coords[7] = coords[5]; } if (orientation & REVERSE) { coords[0] = coords[2]; coords[2] = coords[6]; coords[4] = coords[6]; coords[6] = coords[0]; } return true; } return false; } bool Texture::getPath(std::string& name) { return Resource::getPath(name, "textures/", "png"); } } // namespace Mf