From 2739ea561ad5d3b5c0589949324891475e6f27bf Mon Sep 17 00:00:00 2001 From: Alexander Szpakowski Date: Wed, 2 Jan 2013 02:50:13 -0400 Subject: [PATCH] Initial image mipmapping implementation. Includes Image:set/getMipmapFilter and Image:set/getMipmapSharpness. setMipMapFilter requires a filter mode, or nil to disable mipmapping. Enabling mipmapping on an image that does not have power-of-two dimensions will raise an error, due to inconsistent support in older graphics chipsets --- src/modules/graphics/opengl/Image.cpp | 102 +++++++++++++++++---- src/modules/graphics/opengl/Image.h | 27 ++++-- src/modules/graphics/opengl/wrap_Image.cpp | 97 +++++++++++++++++--- src/modules/graphics/opengl/wrap_Image.h | 7 ++ 4 files changed, 196 insertions(+), 37 deletions(-) diff --git a/src/modules/graphics/opengl/Image.cpp b/src/modules/graphics/opengl/Image.cpp index e659bf8c5..723876aff 100644 --- a/src/modules/graphics/opengl/Image.cpp +++ b/src/modules/graphics/opengl/Image.cpp @@ -22,9 +22,9 @@ // STD #include // For memcpy +#include // for min/max #include -using namespace std; namespace love { @@ -37,6 +37,7 @@ Image::Image(love::image::ImageData *data) : width((float)(data->getWidth())) , height((float)(data->getHeight())) , texture(0) + , mipmapsharpness(0.0f) { data->retain(); this->data = data; @@ -61,7 +62,7 @@ Image::Image(love::image::ImageData *data) vertices[3].s = 1; vertices[3].t = 0; - settings.filter = getDefaultFilter(); + filter = getDefaultFilter(); } Image::~Image() @@ -144,26 +145,72 @@ void Image::drawq(love::graphics::Quad *quad, float x, float y, float angle, flo void Image::setFilter(const Image::Filter &f) { + filter = f; + bind(); + + if (f.mipmap == FILTER_NEAREST || f.mipmap == FILTER_LINEAR) + { + if (!hasMipmapSupport()) + throw love::Exception("Mipmaps are not supported on this system!"); + + if (width != next_p2(width) || height != next_p2(height)) + throw love::Exception("Could not generate mipmaps: image does not have power of two dimensions!"); + + GLboolean aremipmapscreated; + glGetTexParameteriv(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, (GLint *)&aremipmapscreated); + + // generate mipmaps for this image if we haven't already + if (!aremipmapscreated) + { + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); + + if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object) + glGenerateMipmap(GL_TEXTURE_2D); + else if (GLEE_EXT_framebuffer_object) + glGenerateMipmapEXT(GL_TEXTURE_2D); + else + // modify single pixel in texture to trigger mip chain generation + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (GLsizei)width, (GLsizei)height, GL_RGBA, GL_UNSIGNED_BYTE, getData()); + } + } + setTextureFilter(f); } -Image::Filter Image::getFilter() const +const Image::Filter &Image::getFilter() const { - bind(); - return getTextureFilter(); + return filter; } void Image::setWrap(Image::Wrap &w) { + wrap = w; + bind(); setTextureWrap(w); } -Image::Wrap Image::getWrap() const +const Image::Wrap &Image::getWrap() const { + return wrap; +} + +void Image::setMipmapSharpness(float sharpness) +{ + if (!(GLEE_VERSION_1_4 || GLEE_EXT_texture_lod_bias)) + return; + + // LOD bias has the range (-maxbias, maxbias) + mipmapsharpness = std::min(std::max(sharpness, -maxmipmapsharpness + 0.01f), maxmipmapsharpness - 0.01f); + bind(); - return getTextureWrap(); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -mipmapsharpness); +} + +float Image::getMipmapSharpness() const +{ + return mipmapsharpness; } void Image::bind() const @@ -186,6 +233,8 @@ void Image::unload() bool Image::loadVolatile() { + glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &maxmipmapsharpness); + if (hasNpot()) return loadVolatileNPOT(); else @@ -196,9 +245,10 @@ bool Image::loadVolatilePOT() { glGenTextures(1,(GLuint *)&texture); bindTexture(texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); @@ -221,6 +271,13 @@ bool Image::loadVolatilePOT() GL_RGBA, GL_UNSIGNED_BYTE, 0); + + if (hasMipmapSupport()) + { + // auto-generate mipmaps when texture is modified, if mipmapping is enabled + bool genmipmaps = (filter.mipmap == FILTER_LINEAR) || (filter.mipmap == FILTER_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, genmipmaps ? GL_TRUE : GL_FALSE); + } glTexSubImage2D(GL_TEXTURE_2D, 0, @@ -231,9 +288,9 @@ bool Image::loadVolatilePOT() GL_RGBA, GL_UNSIGNED_BYTE, data->getData()); - - setFilter(settings.filter); - setWrap(settings.wrap); + + setFilter(filter); + setWrap(wrap); return true; } @@ -242,11 +299,19 @@ bool Image::loadVolatileNPOT() { glGenTextures(1,(GLuint *)&texture); bindTexture(texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + if (hasMipmapSupport()) + { + // auto-generate mipmaps when texture is modified, if mipmapping is enabled + bool genmipmaps = (filter.mipmap == FILTER_LINEAR) || (filter.mipmap == FILTER_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, genmipmaps ? GL_TRUE : GL_FALSE); + } glTexImage2D(GL_TEXTURE_2D, 0, @@ -257,17 +322,15 @@ bool Image::loadVolatileNPOT() GL_RGBA, GL_UNSIGNED_BYTE, data->getData()); - - setFilter(settings.filter); - setWrap(settings.wrap); + + setFilter(filter); + setWrap(wrap); return true; } void Image::unloadVolatile() { - settings.filter = getFilter(); - settings.wrap = getWrap(); // Delete the hardware texture. if (texture != 0) { @@ -300,6 +363,11 @@ bool Image::hasNpot() return GLEE_ARB_texture_non_power_of_two != 0; } +bool Image::hasMipmapSupport() +{ + return (GLEE_VERSION_1_4 || GLEE_SGIS_generate_mipmap) != 0; +} + } // opengl } // graphics } // love diff --git a/src/modules/graphics/opengl/Image.h b/src/modules/graphics/opengl/Image.h index dd2b59f73..9778a878b 100644 --- a/src/modules/graphics/opengl/Image.h +++ b/src/modules/graphics/opengl/Image.h @@ -101,11 +101,15 @@ public: **/ void setFilter(const Image::Filter &f); - Image::Filter getFilter() const; + const Image::Filter &getFilter() const; void setWrap(Image::Wrap &w); - Image::Wrap getWrap() const; + const Image::Wrap &getWrap() const; + + void setMipmapSharpness(float sharpness); + + float getMipmapSharpness() const; void bind() const; @@ -117,6 +121,7 @@ public: void unloadVolatile(); static bool hasNpot(); + static bool hasMipmapSupport(); private: @@ -138,13 +143,17 @@ private: // The source vertices of the image. vertex vertices[4]; - - // The settings we need to save when reloading. - struct - { - Image::Filter filter; - Image::Wrap wrap; - } settings; + + // Mipmap texture LOD bias value + float mipmapsharpness; + + float maxmipmapsharpness; + + // The image's filter mode + Image::Filter filter; + + // The image's wrap mode + Image::Wrap wrap; bool loadVolatilePOT(); bool loadVolatileNPOT(); diff --git a/src/modules/graphics/opengl/wrap_Image.cpp b/src/modules/graphics/opengl/wrap_Image.cpp index f0f08e4e2..88db24cc4 100644 --- a/src/modules/graphics/opengl/wrap_Image.cpp +++ b/src/modules/graphics/opengl/wrap_Image.cpp @@ -61,12 +61,22 @@ int w_Image_setFilter(lua_State *L) return luaL_error(L, "Invalid filter mode: %s", minstr); if (!Image::getConstant(magstr, mag)) return luaL_error(L, "Invalid filter mode: %s", magstr); + + const Image::Filter curfilter = t->getFilter(); Image::Filter f; f.min = min; f.mag = mag; + f.mipmap = curfilter.mipmap; - t->setFilter(f); + try + { + t->setFilter(f); + } + catch(love::Exception &e) + { + return luaL_error(L, e.what()); + } return 0; } @@ -74,18 +84,61 @@ int w_Image_setFilter(lua_State *L) int w_Image_getFilter(lua_State *L) { Image *t = luax_checkimage(L, 1); - Image::Filter f = t->getFilter(); - Image::FilterMode min = f.min; - Image::FilterMode mag = f.mag; + const Image::Filter f = t->getFilter(); const char *minstr; const char *magstr; - Image::getConstant(min, minstr); - Image::getConstant(mag, magstr); + Image::getConstant(f.min, minstr); + Image::getConstant(f.mag, magstr); lua_pushstring(L, minstr); lua_pushstring(L, magstr); return 2; } +int w_Image_setMipmapFilter(lua_State *L) +{ + Image *i = luax_checkimage(L, 1); + + const Image::Filter curfilter = i->getFilter(); + Image::Filter f; + f.min = curfilter.min; + f.mag = curfilter.mag; + + if (lua_isnoneornil(L, 2)) // disable mipmapping if no arguments are given + f.mipmap = Image::FILTER_NONE; + else + { + const char *filterstr = luaL_checkstring(L, 2); + if (!Image::getConstant(filterstr, f.mipmap)) + return luaL_error(L, "Invalid filter mode: %s", filterstr); + } + + try + { + i->setFilter(f); + } + catch(love::Exception &e) + { + return luaL_error(L, e.what()); + } + + return 0; +} + +int w_Image_getMipmapFilter(lua_State *L) +{ + Image *i = luax_checkimage(L, 1); + + const Image::Filter f = i->getFilter(); + const char *filterstr; + + if (Image::getConstant(f.mipmap, filterstr)) + lua_pushstring(L, filterstr); + else + lua_pushnil(L); // return nil if mipmap filter is FILTER_NONE + + return 1; +} + int w_Image_setWrap(lua_State *L) { Image *i = luax_checkimage(L, 1); @@ -113,18 +166,36 @@ int w_Image_setWrap(lua_State *L) int w_Image_getWrap(lua_State *L) { Image *i = luax_checkimage(L, 1); - Image::Wrap w = i->getWrap(); - Image::WrapMode s = w.s; - Image::WrapMode t = w.t; + const Image::Wrap w = i->getWrap(); const char *sstr; const char *tstr; - Image::getConstant(s, sstr); - Image::getConstant(t, tstr); + Image::getConstant(w.s, sstr); + Image::getConstant(w.t, tstr); lua_pushstring(L, sstr); lua_pushstring(L, tstr); return 2; } +int w_Image_setMipmapSharpness(lua_State *L) +{ + Image *i = luax_checkimage(L, 1); + + float sharpness = (float) luaL_checknumber(L, 2); + i->setMipmapSharpness(sharpness); + + return 0; +} + +int w_Image_getMipmapSharpness(lua_State *L) +{ + Image *i = luax_checkimage(L, 1); + + float sharpness = i->getMipmapSharpness(); + lua_pushnumber(L, sharpness); + + return 1; +} + static const luaL_Reg functions[] = { { "getWidth", w_Image_getWidth }, @@ -133,6 +204,10 @@ static const luaL_Reg functions[] = { "getFilter", w_Image_getFilter }, { "setWrap", w_Image_setWrap }, { "getWrap", w_Image_getWrap }, + { "setMipmapFilter", w_Image_setMipmapFilter }, + { "getMipmapFilter", w_Image_getMipmapFilter }, + { "setMipmapSharpness", w_Image_setMipmapSharpness }, + { "getMipmapSharpness", w_Image_getMipmapSharpness }, { 0, 0 } }; diff --git a/src/modules/graphics/opengl/wrap_Image.h b/src/modules/graphics/opengl/wrap_Image.h index 80e0fe96d..f06b73ba8 100644 --- a/src/modules/graphics/opengl/wrap_Image.h +++ b/src/modules/graphics/opengl/wrap_Image.h @@ -36,6 +36,13 @@ Image *luax_checkimage(lua_State *L, int idx); int w_Image_getWidth(lua_State *L); int w_Image_getHeight(lua_State *L); int w_Image_setFilter(lua_State *L); +int w_image_getFilter(lua_State *L); +int w_Image_setWrap(lua_State *L); +int w_Image_getWrap(lua_State *L); +int w_Image_setMipmapFilter(lua_State *L); +int w_Image_getMipmapFilter(lua_State *L); +int w_Image_setMipmapSharpness(lua_State *L); +int w_Image_getMipmapSharpness(lua_State *L); extern "C" int luaopen_image(lua_State *L); } // opengl