Alex Szpakowski avatar Alex Szpakowski committed f7ee7ec

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

Comments (0)

Files changed (4)

src/modules/graphics/opengl/Image.cpp

 
 // STD
 #include <cstring> // For memcpy
+#include <algorithm> // for min/max
 
 #include <iostream>
-using namespace std;
 
 namespace love
 {
 	: width((float)(data->getWidth()))
 	, height((float)(data->getHeight()))
 	, texture(0)
+	, mipmapsharpness(0.0f)
 {
 	data->retain();
 	this->data = data;
 	vertices[3].s = 1;
 	vertices[3].t = 0;
 
-	settings.filter = getDefaultFilter();
+	filter = getDefaultFilter();
 }
 
 Image::~Image()
 
 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
 
 bool Image::loadVolatile()
 {
+	glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &maxmipmapsharpness);
+	
 	if (hasNpot())
 		return loadVolatileNPOT();
 	else
 {
 	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);
 
 				 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,
 					GL_RGBA,
 					GL_UNSIGNED_BYTE,
 					data->getData());
-
-	setFilter(settings.filter);
-	setWrap(settings.wrap);
+	
+	setFilter(filter);
+	setWrap(wrap);
 
 	return true;
 }
 {
 	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,
 				 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)
 	{
 	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

src/modules/graphics/opengl/Image.h

 	 **/
 	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;
 
 	void unloadVolatile();
 
 	static bool hasNpot();
+	static bool hasMipmapSupport();
 
 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();

src/modules/graphics/opengl/wrap_Image.cpp

 		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;
 }
 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);
 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 },
 	{ "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 }
 };
 

src/modules/graphics/opengl/wrap_Image.h

 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
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.