]> Dogcows Code - chaz/yoink/blobdiff - src/texture.cc
main loop code fixed to decouple updates and draws
[chaz/yoink] / src / texture.cc
index b91d23ac7456349e85db9cca2cb55e16925f1d1f..6d3e912925b8f1376018b9cc612fde8db1d4677a 100644 (file)
@@ -26,7 +26,6 @@
 
 *******************************************************************************/
 
-#include <stdexcept>
 #include <cstdlib>
 
 #include <boost/bind.hpp>
@@ -34,6 +33,8 @@
 #include <SDL/SDL.h>
 #include <SDL/SDL_image.h>
 
+#include "mippleton.hh"
+
 #include "dispatcher.hh"
 #include "opengl.hh"
 
 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<texture_impl>
 {
+
+       /**
+        * 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<texture_impl>(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<Uint8*>(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: *************************************************/
+
This page took 0.028503 seconds and 4 git commands to generate.