#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(texture* outside, bool keepInMemory)
- : interface(outside), keepData(keepInMemory), object(0), imageData(0)
- {
- 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;
}
+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)
{
- // 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 OpenGL-ready
- // right out of the box.
-
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_SetAlpha(surface, savedFlags, savedAlpha);
}
- // 2. OpenGL textures make more sense when they are "upside down."
+ // 2. OpenGL textures make more sense within the coordinate system when
+ // they are "upside down," so let's flip it.
Uint8 line[image->pitch];
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(interface->getPathToFile().c_str());
+ surface = IMG_Load(texture::getPathToResource(getName()).c_str());
if (!surface)
{
throw texture::exception("loading failed");
}
- imageData = prepareImageForGL(surface);
+ SDL_Surface* temp = prepareImageForGL(surface);
SDL_FreeSurface(surface);
- if (!imageData)
+ if (!temp)
{
- throw texture::exception("");
+ 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);
+ 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
);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ setProperties();
- if (!keepData)
- {
- SDL_FreeSurface(imageData);
- imageData = 0;
- }
+ SDL_FreeSurface(imageData);
}
- unsigned width;
- unsigned height;
- int mode;
- GLuint object;
+ /**
+ * 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_);
+ }
+
- bool keepData;
- SDL_Surface* imageData;
+ unsigned width_; ///< Horizontal dimension of the image.
+ unsigned height_; ///< Vertical dimension.
- texture* interface;
+ 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& name, bool keepInMemory)
- : resource(name), impl(new texture_impl(this, 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.
+ */
void texture::bind()
{
glBindTexture(GL_TEXTURE_2D, getObject());
}
+
+/**
+ * Get the texture object, for the curious.
+ */
+
GLuint texture::getObject()
{
- if (!impl->object)
- {
- impl->uploadToGL();
- }
-
- return impl->object;
+ // pass through
+ return impl->object_;
}
unsigned texture::getWidth()
{
- if (!impl->object)
- {
- impl->uploadToGL();
- }
-
- return impl->width;
+ // pass through
+ return impl->width_;
}
unsigned texture::getHeight()
{
- if (!impl->object)
- {
- impl->uploadToGL();
- }
+ // pass through
+ return impl->height_;
+}
+
- return impl->height;
+void texture::setMinFilter(GLuint filter)
+{
+ impl->minFilter_ = filter;
+}
+
+void texture::setMaxFilter(GLuint filter)
+{
+ impl->maxFilter_ = filter;
+}
+
+void texture::setWrapU(GLuint wrap)
+{
+ impl->wrapU_ = wrap;
+}
+
+void texture::setWrapV(GLuint wrap)
+{
+ impl->wrapV_ = wrap;
+}
+
+
+void texture::applyChanges()
+{
+ 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: *************************************************/
+