*******************************************************************************/
-#include <stdexcept>
#include <cstdlib>
#include <boost/bind.hpp>
#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)
{
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,
return 0;
}
- // Save the alpha blending attributes.
Uint32 savedFlags = surface->flags&(SDL_SRCALPHA|SDL_RLEACCELOK);
Uint8 savedAlpha = surface->format->alpha;
if (savedFlags & SDL_SRCALPHA)
}
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: *************************************************/
+