--- /dev/null
+
+/*] 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 <cstdio> // FILE
+#include <cstring> // strncmp
+#include <stdexcept>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/bind.hpp>
+
+#include "dispatcher.hh"
+#include "manager.hh"
+#include "log.hh"
+#include "opengl.hh"
+#include "script.hh"
+#include "texture.hh"
+#include "video.hh"
+
+
+namespace moof {
+
+
+/**
+ * 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<impl>
+{
+
+ /**
+ * Delete the texture (if it is loaded) from GL.
+ */
+
+ void unload_from_gl()
+ {
+ 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 context_recreated()
+ {
+ mObject = gObject = 0;
+ upload_to_gl();
+ }
+
+ /**
+ * 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 power_of_two(int input)
+ {
+ int value = 1;
+
+ while (value < input)
+ {
+ value <<= 1;
+ }
+ return value;
+ }
+
+
+ static void bind_script_constants(script& script)
+ {
+ script::slot g = script.globals();
+
+ g.set_field("CLAMP", GL_CLAMP);
+ g.set_field("REPEAT", GL_REPEAT);
+ g.set_field("LINEAR", GL_LINEAR);
+ g.set_field("NEAREST", GL_NEAREST);
+ g.set_field("LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR);
+ g.set_field("LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST);
+ g.set_field("NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR);
+ g.set_field("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
+ dispatcher& dispatcher = dispatcher::global();
+ mNewContextDispatch = dispatcher.add_target("video.newcontext",
+ boost::bind(&impl::context_recreated, this));
+ }
+
+ ~impl()
+ {
+ unload_from_gl();
+ }
+
+
+ void init(const std::string& name)
+ {
+ std::string path(name);
+
+ texture::find_path(path);
+
+ mImage = image::alloc(path);
+ if (!mImage->is_valid())
+ {
+ throw std::runtime_error("texture not found: " + name);
+ }
+
+ mImage->flip();
+
+ script script;
+
+ bind_script_constants(script);
+ log::import(script);
+
+ if (script.do_string(mImage->comment()) != script::success)
+ {
+ std::string str;
+ script[-1].get(str);
+ log_warning(str);
+ }
+ else
+ {
+ log_info << "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 upload_to_gl()
+ {
+ if (mObject)
+ {
+ // already loaded
+ return;
+ }
+
+ glGenTextures(1, &mObject);
+ glBindTexture(GL_TEXTURE_2D, mObject);
+
+ glTexImage2D
+ //gluBuild2DMipmaps
+ (
+ GL_TEXTURE_2D,
+ 0,
+ mImage->mode(),
+ //3,
+ mImage->width(),
+ mImage->height(),
+ 0,
+ mImage->mode(),
+ GL_UNSIGNED_BYTE,
+ mImage->pixels()
+ );
+
+ set_properties();
+ }
+
+
+ /**
+ * Sets some texture properties such as the filters and external
+ * coordinate behavior.
+ */
+
+ void set_properties()
+ {
+ 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);
+ }
+
+ void min_filter(GLuint filter)
+ {
+ bind();
+ mMinFilter = filter;
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mMinFilter);
+ }
+
+ void mag_filter(GLuint filter)
+ {
+ bind();
+ mMagFilter = filter;
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mMagFilter);
+ }
+
+ void wrap_s(GLuint wrap)
+ {
+ bind();
+ mWrapS = wrap;
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mWrapS);
+ }
+
+ void wrap_t(GLuint wrap)
+ {
+ bind();
+ mWrapT = wrap;
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mWrapT);
+ }
+
+
+ void bind()
+ {
+ if (mObject == 0)
+ {
+ upload_to_gl();
+ }
+ if (mObject != gObject)
+ {
+ glBindTexture(GL_TEXTURE_2D, mObject);
+ gObject = mObject;
+ }
+ }
+
+
+ bool tile_coordinates(int index, scalar coords[8]) const
+ {
+ // make sure the index represents a real tile
+ if (index < 0 && 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;
+ }
+
+ image_ptr mImage;
+
+ GLuint mMinFilter; ///< Minification filter.
+ GLuint mMagFilter; ///< Magnification filter.
+ GLuint mWrapS; ///< Wrapping behavior horizontally.
+ GLuint mWrapT; ///< Wrapping behavior vertically.
+ int mTilesS;
+ int mTilesT;
+
+ GLuint mObject; ///< GL texture handle.
+ static GLuint gObject; ///< Global GL texture handle.
+
+ dispatcher::handle mNewContextDispatch;
+};
+
+GLuint texture::impl::gObject = 0;
+
+
+texture::texture(const std::string& name) : // FIXME: this is really weird
+ image(name),
+ // pass through
+ impl_(texture::impl::instance(name)) {}
+
+
+/**
+ * Bind the GL texture for mapping, etc.
+ */
+
+void texture::bind() const
+{
+ // pass through
+ impl_->bind();
+}
+
+
+/**
+ * Get the texture object, for the curious.
+ */
+
+GLuint texture::object() const
+{
+ // pass through
+ return impl_->mObject;
+}
+
+
+void texture::reset_binding()
+{
+ glBindTexture(GL_TEXTURE_2D, 0);
+ impl::gObject = 0;
+}
+
+
+void texture::min_filter(GLuint filter)
+{
+ // pass through
+ impl_->min_filter(filter);
+}
+
+void texture::mag_filter(GLuint filter)
+{
+ // pass through
+ impl_->mag_filter(filter);
+}
+
+void texture::wrap_s(GLuint wrap)
+{
+ // pass through
+ impl_->wrap_s(wrap);
+}
+
+void texture::wrap_t(GLuint wrap)
+{
+ // pass through
+ impl_->wrap_t(wrap);
+}
+
+
+bool texture::tile_coordinates(int index, scalar coords[8]) const
+{
+ // pass through
+ return impl_->tile_coordinates(index, coords);
+}
+
+bool texture::tile_coordinates(int index, scalar coords[8],
+ orientation orientation) const
+{
+ if (tile_coordinates(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::find_path(std::string& name)
+{
+ return resource::find_path(name, "textures/", "png");
+}
+
+
+} // namespace moof
+