/*] 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 #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 { /** * 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); resource::find(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); 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; } } // namespace moof