Commits

Bart van Strien committed fd3f318 Merge

Merge in default

  • Participants
  • Parent commits c5286ec, bf8e12d
  • Branches minor

Comments (0)

Files changed (26)

File src/modules/audio/wrap_Source.cpp

 	if (vmin < .0f || vmin > 1.f || vmax < .0f || vmax > 1.f)
 		return luaL_error(L, "Invalid volume limits: [%f:%f]. Must be in [0:1]", vmin, vmax);
 	t->setMinVolume(vmin);
-	t->setMaxVolume(vmin);
+	t->setMaxVolume(vmax);
 	return 0;
 }
 

File src/modules/graphics/Graphics.cpp

 	{ "subtractive", Graphics::BLEND_SUBTRACTIVE },
 	{ "multiplicative", Graphics::BLEND_MULTIPLICATIVE },
 	{ "premultiplied", Graphics::BLEND_PREMULTIPLIED },
+	{ "none", Graphics::BLEND_NONE },
 };
 
 StringMap<Graphics::BlendMode, Graphics::BLEND_MAX_ENUM> Graphics::blendModes(Graphics::blendModeEntries, sizeof(Graphics::blendModeEntries));
 	{ "pixeleffect", Graphics::SUPPORT_PIXELEFFECT },
 	{ "npot", Graphics::SUPPORT_NPOT },
 	{ "subtractive", Graphics::SUPPORT_SUBTRACTIVE },
+	{ "mipmap", Graphics::SUPPORT_MIPMAP },
 };
 
 StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM> Graphics::support(Graphics::supportEntries, sizeof(Graphics::supportEntries));

File src/modules/graphics/Graphics.h

 		BLEND_SUBTRACTIVE,
 		BLEND_MULTIPLICATIVE,
 		BLEND_PREMULTIPLIED,
+		BLEND_NONE,
 		BLEND_MAX_ENUM
 	};
 
 		SUPPORT_PIXELEFFECT,
 		SUPPORT_NPOT,
 		SUPPORT_SUBTRACTIVE,
+		SUPPORT_MIPMAP,
 		SUPPORT_MAX_ENUM
 	};
 

File src/modules/graphics/Image.cpp

 namespace graphics
 {
 
+Image::Filter Image::defaultFilter;
+
 Image::Filter::Filter()
 	: min(FILTER_LINEAR)
 	, mag(FILTER_LINEAR)
+	, mipmap(FILTER_NONE)
 {
 }
 
 {
 }
 
+void Image::setDefaultFilter(const Filter &f)
+{
+	defaultFilter = f;
+}
+
+const Image::Filter &Image::getDefaultFilter()
+{
+	return defaultFilter;
+}
+
 bool Image::getConstant(const char *in, FilterMode &out)
 {
 	return filterModes.find(in, out);

File src/modules/graphics/Image.h

 	{
 		FILTER_LINEAR = 1,
 		FILTER_NEAREST,
+		FILTER_NONE,
 		FILTER_MAX_ENUM
 	};
 
 		Filter();
 		FilterMode min;
 		FilterMode mag;
+		FilterMode mipmap;
 	};
 
 	struct Wrap
 	};
 
 	virtual ~Image();
+	
+	// The default filter.
+	static void setDefaultFilter(const Filter &f);
+	static const Filter &getDefaultFilter();
 
 	static bool getConstant(const char *in, FilterMode &out);
 	static bool getConstant(FilterMode in, const char  *&out);
 
 private:
 
+	// The default image filter
+	static Filter defaultFilter;
+
 	static StringMap<FilterMode, FILTER_MAX_ENUM>::Entry filterModeEntries[];
 	static StringMap<FilterMode, FILTER_MAX_ENUM> filterModes;
 	static StringMap<WrapMode, WRAP_MAX_ENUM>::Entry wrapModeEntries[];

File src/modules/graphics/opengl/Canvas.cpp

 
 		glGenTextures(1, &img);
 		bindTexture(img);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+		setTextureFilter(Image::getDefaultFilter());
+
 		glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height,
 			0, GL_RGBA, format, NULL);
 		bindTexture(0);
 
 		glGenTextures(1, &img);
 		bindTexture(img);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+		setTextureFilter(Image::getDefaultFilter());
 
 		glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height,
 			0, GL_RGBA, format, NULL);
 
 		glGenTextures(1, &img);
 		bindTexture(img);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+		setTextureFilter(Image::getDefaultFilter());
+
 		glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height,
 			0, GL_RGBA, format, NULL);
 		bindTexture(0);
 	vertices[3].s = 1;
 	vertices[3].t = 0;
 
+	settings.filter = Image::getDefaultFilter();
+
 	getStrategy();
 
 	loadVolatile();
 
 void Canvas::setFilter(const Image::Filter &f)
 {
-	GLint gmin = (f.min == Image::FILTER_NEAREST) ? GL_NEAREST : GL_LINEAR;
-	GLint gmag = (f.mag == Image::FILTER_NEAREST) ? GL_NEAREST : GL_LINEAR;
-
 	bindTexture(img);
-
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gmin);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gmag);
+	setTextureFilter(f);
 }
 
 Image::Filter Canvas::getFilter() const
 {
-	GLint gmin, gmag;
-
 	bindTexture(img);
-	glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &gmin);
-	glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &gmag);
-
-	Image::Filter f;
-	f.min = (gmin == GL_NEAREST) ? Image::FILTER_NEAREST : Image::FILTER_LINEAR;
-	f.mag = (gmag == GL_NEAREST) ? Image::FILTER_NEAREST : Image::FILTER_LINEAR;
-	return f;
+	return getTextureFilter();
 }
 
 void Canvas::setWrap(const Image::Wrap &w)
 {
-	GLint wrap_s = (w.s == Image::WRAP_CLAMP) ? GL_CLAMP_TO_EDGE : GL_REPEAT;
-	GLint wrap_t = (w.t == Image::WRAP_CLAMP) ? GL_CLAMP_TO_EDGE : GL_REPEAT;
-
 	bindTexture(img);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t);
+	setTextureWrap(w);
 }
 
 Image::Wrap Canvas::getWrap() const
 {
-	GLint wrap_s, wrap_t;
 	bindTexture(img);
-	glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &wrap_s);
-	glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &wrap_t);
-
-	Image::Wrap w;
-	w.s = (wrap_s == GL_CLAMP_TO_EDGE) ? Image::WRAP_CLAMP : Image::WRAP_REPEAT;
-	w.t = (wrap_t == GL_CLAMP_TO_EDGE) ? Image::WRAP_CLAMP : Image::WRAP_REPEAT;
-
-	return w;
+	return getTextureWrap();
 }
 
 bool Canvas::loadVolatile()

File src/modules/graphics/opengl/Font.cpp

 #include "Font.h"
 #include "font/GlyphData.h"
 #include "Quad.h"
+#include "Image.h"
 
 #include "libraries/utf8/utf8.h"
 
 #include "common/math.h"
 #include "common/Matrix.h"
+
 #include <math.h>
-
 #include <sstream>
-
 #include <algorithm> // for max
 
 namespace love
 namespace opengl
 {
 
-const int Font::TEXTURE_WIDTHS[] = {128, 256, 256, 512, 512, 1024, 1024};
-const int Font::TEXTURE_HEIGHTS[] = {128, 128, 256, 256, 512, 512, 1024};
+const int Font::TEXTURE_WIDTHS[]  = {128, 256, 256, 512, 512, 1024, 1024};
+const int Font::TEXTURE_HEIGHTS[] = {128, 128, 256, 256, 512, 512,  1024};
 
 Font::Font(love::font::Rasterizer *r, const Image::Filter &filter)
 	: rasterizer(r)
 	, lineHeight(1)
 	, mSpacing(1)
 	, filter(filter)
+	, mipmapsharpness(0.0f)
 {
-	r->retain();
-	
 	love::font::GlyphData *gd = r->getGlyphData(32);
 	type = (gd->getFormat() == love::font::GlyphData::FORMAT_LUMINANCE_ALPHA ? FONT_TRUETYPE : FONT_IMAGE);
 	delete gd;
-	
+
 	// try to find the best texture size match for the font size
 	// default to the largest texture size if no rough match is found
 	texture_size_index = NUM_TEXTURE_SIZES - 1;
 			break;
 		}
 	}
-	
+
 	texture_width = TEXTURE_WIDTHS[texture_size_index];
 	texture_height = TEXTURE_HEIGHTS[texture_size_index];
-	
-	try
-	{
-		createTexture();
-	}
-	catch (love::Exception &e)
-	{
-		r->release();
-		throw;
-	}
+
+	loadVolatile();
+
+	r->retain();
 }
 
 Font::~Font()
 bool Font::initializeTexture(GLint format)
 {
 	GLint internalformat = (format == GL_LUMINANCE_ALPHA) ? GL_LUMINANCE8_ALPHA8 : GL_RGBA8;
-	
+
 	// clear errors before initializing
 	while (glGetError() != GL_NO_ERROR);
-	
+
 	glTexImage2D(GL_TEXTURE_2D,
 				 0,
 				 internalformat,
 				 format,
 				 GL_UNSIGNED_BYTE,
 				 NULL);
-	
+
 	return glGetError() == GL_NO_ERROR;
 }
 
 void Font::createTexture()
 {
 	texture_x = texture_y = rowHeight = TEXTURE_PADDING;
-	
+
 	GLuint t;
 	glGenTextures(1, &t);
 	textures.push_back(t);
+
 	bindTexture(t);
 
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
-					(filter.mag == Image::FILTER_LINEAR) ? GL_LINEAR : GL_NEAREST);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
-					(filter.min == Image::FILTER_LINEAR) ? GL_LINEAR : GL_NEAREST);
+	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);
+
 	GLint format = (type == FONT_TRUETYPE ? GL_LUMINANCE_ALPHA : GL_RGBA);
-	
-	
+
 	// try to initialize the texture, attempting smaller sizes if initialization fails
 	bool initialized = false;
 	while (texture_size_index >= 0)
 	{
 		texture_width = TEXTURE_WIDTHS[texture_size_index];
 		texture_height = TEXTURE_HEIGHTS[texture_size_index];
-		
+
 		initialized = initializeTexture(format);
-		
+
 		if (initialized || texture_size_index <= 0)
 			break;
-		
+
 		--texture_size_index;
 	}
-	
+
 	if (!initialized)
 	{
 		// cleanup before throwing
 		deleteTexture(t);
 		bindTexture(0);
 		textures.pop_back();
-		
+
 		throw love::Exception("Could not create font texture!");
 	}
 	
 					format,
 					GL_UNSIGNED_BYTE,
 					&emptyData[0]);
+
+	setFilter(filter);
+	setMipmapSharpness(mipmapsharpness);
 }
 
 Font::Glyph *Font::addGlyph(const int glyph)
 	}
 
 	Glyph *g = new Glyph;
-	
+
 	g->texture = 0;
 	g->spacing = gd->getAdvance();
-	
+
 	memset(&g->quad, 0, sizeof(GlyphQuad));
 
 	// don't waste space for empty glyphs. also fixes a division by zero bug with ati drivers
 	if (w > 0 && h > 0)
 	{
 		const GLuint t = textures.back();
-		
+
 		bindTexture(t);
 		glTexSubImage2D(GL_TEXTURE_2D,
 						0,
 		v.y = (float) texture_y;
 		v.w = (float) w;
 		v.h = (float) h;
-		
+
 		Quad q = Quad(v, (const float) texture_width, (const float) texture_height);
 		const vertex *verts = q.getVertices();
-		
+
 		// copy vertex data to the glyph and set proper bearing
 		for (int i = 0; i < 4; i++)
 		{
 		rowHeight = std::max(rowHeight, h + TEXTURE_PADDING);
 
 	delete gd;
-	
+
 	glyphs[glyph] = g;
-	
+
 	return g;
 }
 
 	Glyph *g = glyphs[glyph];
 	if (!g)
 		g = addGlyph(glyph);
-	
+
 	return g;
 }
 
 {
 	float dx = 0.0f; // spacing counter for newline handling
 	float dy = 0.0f;
-	
+
 	// keeps track of when we need to switch textures in our vertex array
 	std::vector<GlyphArrayDrawInfo> glyphinfolist;
-	
+
 	std::vector<GlyphQuad> glyphquads;
 	glyphquads.reserve(text.size()); // pre-allocate space for the maximum possible number of quads
-	
+
 	int quadindex = 0;
-	
+
 	glPushMatrix();
 
 	Matrix t;
 	{
 		utf8::iterator<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
 		utf8::iterator<std::string::const_iterator> end(text.end(), text.begin(), text.end());
-		
+
 		while (i != end)
 		{
 			int g = *i++;
-			
+
 			if (g == '\n')
 			{
 				// wrap newline, but do not print it
 				dx = 0.0f;
 				continue;
 			}
-			
+
 			Glyph *glyph = findGlyph(g);
-			
+
 			// we only care about the vertices of glyphs which have a texture
 			if (glyph->texture != 0)
 			{
 				// copy glyphquad (4 vertices) from original glyph to our current quad list
 				glyphquads.push_back(glyph->quad);
-				
-				// 1.25 is magic line height for true type fonts
-				float lineheight = (type == FONT_TRUETYPE) ? floor(getHeight() / 1.25f + 0.5f) : 0.0f;
-				
+
+				float lineheight = getBaseline();
+
 				// set proper relative position
 				for (int i = 0; i < 4; i++)
 				{
 					glyphquads[quadindex].vertices[i].x += dx;
 					glyphquads[quadindex].vertices[i].y += dy + lineheight;
 				}
-				
+
 				size_t listsize = glyphinfolist.size();
-				
+
 				// check if current glyph texture has changed since the previous iteration
 				if (listsize == 0 || glyphinfolist[listsize-1].texture != glyph->texture)
 				{
 					glyphdrawinfo.texture = glyph->texture;
 					glyphinfolist.push_back(glyphdrawinfo);
 				}
-				
+
 				++quadindex;
 				++glyphinfolist[glyphinfolist.size()-1].numquads;
 			}
-			
+
 			// advance the x position for the next glyph
 			dx += glyph->spacing + letter_spacing;
 		}
 	}
-	catch (love::Exception &e)
+	catch (love::Exception &)
 	{
 		glPopMatrix();
 		throw;
 	}
 	
 	if (quadindex > 0 && glyphinfolist.size() > 0)
-	{		
+	{
 		// sort glyph draw info list by texture first, and quad position in memory second (using the struct's < operator)
 		std::sort(glyphinfolist.begin(), glyphinfolist.end());
-		
+
 		glEnableClientState(GL_VERTEX_ARRAY);
 		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-		
+
 		glVertexPointer(2, GL_FLOAT, sizeof(vertex), (GLvoid *)&glyphquads[0].vertices[0].x);
 		glTexCoordPointer(2, GL_FLOAT, sizeof(vertex), (GLvoid *)&glyphquads[0].vertices[0].s);
-		
+
 		// we need to draw a new vertex array for every section of the string that uses a different texture than the previous section
 		std::vector<GlyphArrayDrawInfo>::const_iterator it;
 		for (it = glyphinfolist.begin(); it != glyphinfolist.end(); ++it)
 		{
 			bindTexture(it->texture);
-			
+
 			int startvertex = it->startquad * 4;
 			int numvertices = it->numquads * 4;
-			
+
 			glDrawArrays(GL_QUADS, startvertex, numvertices);
 		}
-		
+
 		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 		glDisableClientState(GL_VERTEX_ARRAY);
 	}
-		
+
 	glPopMatrix();
 }
 
 	return mSpacing;
 }
 
+void Font::checkMipmapsCreated() const
+{
+	if (filter.mipmap != Image::FILTER_NEAREST && filter.mipmap != Image::FILTER_LINEAR)
+		return;
+
+	if (!Image::hasMipmapSupport())
+		throw love::Exception("Mipmap filtering is not supported on this system!");
+
+	GLboolean mipmapscreated;
+	glGetTexParameteriv(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, (GLint *)&mipmapscreated);
+
+	// generate mipmaps for this image if we haven't already
+	if (!mipmapscreated)
+	{
+		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 texel to trigger mipmap chain generation
+			std::vector<GLubyte> emptydata(type == FONT_TRUETYPE ? 2 : 4);
+			glTexSubImage2D(GL_TEXTURE_2D,
+							0,
+							0, 0,
+							1, 1,
+							type == FONT_TRUETYPE ? GL_LUMINANCE_ALPHA : GL_RGBA,
+							GL_UNSIGNED_BYTE,
+							&emptydata[0]);
+		}
+	}
+}
+
+void Font::setFilter(const Image::Filter &f)
+{
+	filter = f;
+
+	std::vector<GLuint>::const_iterator it;
+	for (it = textures.begin(); it != textures.end(); ++it)
+	{
+		bindTexture(*it);
+		checkMipmapsCreated();
+		setTextureFilter(f);
+	}
+}
+
+const Image::Filter &Font::getFilter()
+{
+	return filter;
+}
+
+void Font::setMipmapSharpness(float sharpness)
+{
+	if (!Image::hasMipmapSharpnessSupport())
+		return;
+
+	// LOD bias has the range (-maxbias, maxbias)
+	mipmapsharpness = std::min(std::max(sharpness, -maxmipmapsharpness + 0.01f), maxmipmapsharpness - 0.01f);
+
+	std::vector<GLuint>::const_iterator it;
+	for (it = textures.begin(); it != textures.end(); ++it)
+	{
+		bindTexture(*it);
+		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -mipmapsharpness); // negative bias is sharper
+	}
+}
+
+float Font::getMipmapSharpness() const
+{
+	return mipmapsharpness;
+}
+
 bool Font::loadVolatile()
 {
+	if (Image::hasMipmapSharpnessSupport())
+		glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &maxmipmapsharpness);
+
 	createTexture();
 	return true;
 }
 	textures.clear();
 }
 
+int Font::getAscent() const
+{
+	return rasterizer->getAscent();
+}
+
+int Font::getDescent() const
+{
+	return rasterizer->getDescent();
+}
+
+float Font::getBaseline() const
+{
+	// 1.25 is magic line height for true type fonts
+	return (type == FONT_TRUETYPE) ? floor(getHeight() / 1.25f + 0.5f) : 0.0f;
+}
+
 } // opengl
 } // graphics
 } // love

File src/modules/graphics/opengl/Font.h

 	 *
 	 * @param data The font data to construct from.
 	 **/
-	Font(love::font::Rasterizer *r, const Image::Filter &filter = Image::Filter());
+	Font(love::font::Rasterizer *r, const Image::Filter &filter = Image::getDefaultFilter());
 
 	virtual ~Font();
 
 	 **/
 	float getSpacing() const;
 
+	void setFilter(const Image::Filter &f);
+	const Image::Filter &getFilter();
+
+	void setMipmapSharpness(float sharpness);
+	float getMipmapSharpness() const;
+
 	// Implements Volatile.
 	bool loadVolatile();
 	void unloadVolatile();
-	
+
+	// Extra font metrics
+	int getAscent() const;
+	int getDescent() const;
+	float getBaseline() const;
+
 private:
 
 	enum FontType
 		int spacing;
 		GlyphQuad quad;
 	};
-	
+
 	// used to determine when to change textures in the vertex array generated when printing text
 	struct GlyphArrayDrawInfo
 	{
 		GLuint texture;
 		int startquad, numquads;
-		
+
 		// used when sorting with std::sort
 		// sorts by texture first (binding textures is expensive) and relative position in memory second
 		bool operator < (const GlyphArrayDrawInfo &other) const
 	int height;
 	float lineHeight;
 	float mSpacing; // modifies the spacing by multiplying it with this value
-	
+
 	int texture_size_index;
 	int texture_width;
 	int texture_height;
-	
+
 	std::vector<GLuint> textures; // vector of packed textures
 	std::map<int, Glyph *> glyphs; // maps glyphs to quad information
 	FontType type;
 	Image::Filter filter;
-	
+
 	static const int NUM_TEXTURE_SIZES = 7;
 	static const int TEXTURE_WIDTHS[NUM_TEXTURE_SIZES];
 	static const int TEXTURE_HEIGHTS[NUM_TEXTURE_SIZES];
 	int texture_x, texture_y;
 	int rowHeight;
 
+	// Mipmap texture LOD bias value
+	float mipmapsharpness;
+
+	// Implementation-dependent maximum/minimum mipmap sharpness values
+	float maxmipmapsharpness;
+
+	void checkMipmapsCreated() const;
+
 	bool initializeTexture(GLint format);
 	void createTexture();
 	Glyph *addGlyph(const int glyph);

File src/modules/graphics/opengl/Graphics.cpp

 	{
 		success = image->load();
 	}
-	catch(love::Exception &e)
+	catch(love::Exception &)
 	{
 		image->release();
-		throw love::Exception(e.what());
+		throw;
 	}
 	if (!success)
 	{
 		glBlendFunc(GL_DST_COLOR, GL_ZERO);
 	else if (mode == BLEND_PREMULTIPLIED)
 		glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+	else if (mode == BLEND_NONE)
+		glBlendFunc(GL_ONE, GL_ZERO);
 	else // mode == BLEND_ADDITIVE || mode == BLEND_SUBTRACTIVE
 		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
 }
 		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
 }
 
-void Graphics::setDefaultImageFilter(const Image::Filter &f)
-{
-	Image::setDefaultFilter(f);
-}
-
 Graphics::BlendMode Graphics::getBlendMode()
 {
 	GLint dst, src, equation;
 		return BLEND_MULTIPLICATIVE;
 	else if (src == GL_ONE && dst == GL_ONE_MINUS_SRC_ALPHA)  // && equation == GL_FUNC_ADD
 		return BLEND_PREMULTIPLIED;
+	else if (src == GL_ONE && dst == GL_ZERO)
+		return BLEND_NONE;
 
 	return BLEND_MAX_ENUM; // Should never be reached.
 }
 		return COLOR_REPLACE;
 }
 
+void Graphics::setDefaultImageFilter(const Image::Filter &f)
+{
+	Image::setDefaultFilter(f);
+}
+
 const Image::Filter &Graphics::getDefaultImageFilter() const
 {
 	return Image::getDefaultFilter();

File src/modules/graphics/opengl/Image.cpp

 
 // STD
 #include <cstring> // For memcpy
+#include <algorithm> // for min/max
 
 #include <iostream>
-using namespace std;
 
 namespace love
 {
 namespace opengl
 {
 
-Image::Filter Image::defaultFilter;
-
 Image::Image(love::image::ImageData *data)
 	: 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 = defaultFilter;
+	filter = getDefaultFilter();
 }
 
 Image::~Image()
 	drawv(t, v);
 }
 
-void Image::setFilter(const Image::Filter &f)
+void Image::checkMipmapsCreated() const
 {
-	GLint gmin, gmag;
-	gmin = gmag = 0; // so that they're not used uninitialized
+	if (filter.mipmap != FILTER_NEAREST && filter.mipmap != FILTER_LINEAR)
+		return;
 
-	switch (f.min)
-	{
-	case FILTER_LINEAR:
-		gmin = GL_LINEAR;
-		break;
-	case FILTER_NEAREST:
-		gmin = GL_NEAREST;
-		break;
-	default:
-		break;
-	}
+	if (!hasMipmapSupport())
+		throw love::Exception("Mipmap filtering is not supported on this system!");
 
-	switch (f.mag)
-	{
-	case FILTER_LINEAR:
-		gmag = GL_LINEAR;
-		break;
-	case FILTER_NEAREST:
-		gmag = GL_NEAREST;
-		break;
-	default:
-		break;
-	}
+	// some old GPUs/systems claim support for NPOT textures, but fail when generating mipmaps
+	// we can't detect which systems will do this, so we fail gracefully for all NPOT images
+	int w = int(width), h = int(height);
+	if (w != next_p2(w) || h != next_p2(h))
+		throw love::Exception("Could not generate mipmaps: image does not have power of two dimensions!");
 
 	bind();
 
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gmin);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gmag);
+	GLboolean mipmapscreated;
+	glGetTexParameteriv(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, (GLint *)&mipmapscreated);
+
+	// generate mipmaps for this image if we haven't already
+	if (!mipmapscreated)
+	{
+		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 texel to trigger mipmap chain generation
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, data->getData());
+	}
 }
 
-Image::Filter Image::getFilter() const
+void Image::setFilter(const Image::Filter &f)
 {
+	filter = f;
+
 	bind();
-
-	GLint gmin, gmag;
-
-	glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &gmin);
-	glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &gmag);
-
-	Image::Filter f;
-
-	switch (gmin)
-	{
-	case GL_NEAREST:
-		f.min = FILTER_NEAREST;
-		break;
-	case GL_LINEAR:
-	default:
-		f.min = FILTER_LINEAR;
-		break;
-	}
-
-	switch (gmin)
-	{
-	case GL_NEAREST:
-		f.mag = FILTER_NEAREST;
-		break;
-	case GL_LINEAR:
-	default:
-		f.mag = FILTER_LINEAR;
-		break;
-	}
-
-	return f;
+	checkMipmapsCreated();
+	setTextureFilter(f);
 }
 
-void Image::setWrap(Image::Wrap w)
+const Image::Filter &Image::getFilter() const
 {
-	GLint gs, gt;
+	return filter;
+}
 
-	switch (w.s)
-	{
-	case WRAP_CLAMP:
-		gs = GL_CLAMP_TO_EDGE;
-		break;
-	case WRAP_REPEAT:
-	default:
-		gs = GL_REPEAT;
-		break;
-	}
-
-	switch (w.t)
-	{
-	case WRAP_CLAMP:
-		gt = GL_CLAMP_TO_EDGE;
-		break;
-	case WRAP_REPEAT:
-	default:
-		gt = GL_REPEAT;
-		break;
-	}
+void Image::setWrap(const Image::Wrap &w)
+{
+	wrap = w;
 
 	bind();
-
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gs);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gt);
+	setTextureWrap(w);
 }
 
-Image::Wrap Image::getWrap() const
+const Image::Wrap &Image::getWrap() const
 {
+	return wrap;
+}
+
+void Image::setMipmapSharpness(float sharpness)
+{
+	if (!hasMipmapSharpnessSupport())
+		return;
+
+	// LOD bias has the range (-maxbias, maxbias)
+	mipmapsharpness = std::min(std::max(sharpness, -maxmipmapsharpness + 0.01f), maxmipmapsharpness - 0.01f);
+
 	bind();
+	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -mipmapsharpness); // negative bias is sharper
+}
 
-	GLint gs, gt;
-
-	glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &gs);
-	glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &gt);
-
-	Wrap w;
-
-	switch (gs)
-	{
-	case GL_CLAMP_TO_EDGE:
-		w.s = WRAP_CLAMP;
-		break;
-	case GL_REPEAT:
-	default:
-		w.s = WRAP_REPEAT;
-		break;
-	}
-
-	switch (gt)
-	{
-	case GL_CLAMP_TO_EDGE:
-		w.t = WRAP_CLAMP;
-		break;
-	case GL_REPEAT:
-	default:
-		w.t = WRAP_REPEAT;
-		break;
-	}
-
-	return w;
+float Image::getMipmapSharpness() const
+{
+	return mipmapsharpness;
 }
 
 void Image::bind() const
 
 bool Image::loadVolatile()
 {
+	if (hasMipmapSharpnessSupport())
+		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);
 
 					GL_UNSIGNED_BYTE,
 					data->getData());
 
-	setFilter(settings.filter);
-	setWrap(settings.wrap);
+	setMipmapSharpness(mipmapsharpness);
+	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);
 
 				 GL_UNSIGNED_BYTE,
 				 data->getData());
 
-	setFilter(settings.filter);
-	setWrap(settings.wrap);
+	setMipmapSharpness(mipmapsharpness);
+	setFilter(filter);
+	setWrap(wrap);
 
 	return true;
 }
 
 void Image::unloadVolatile()
 {
-	settings.filter = getFilter();
-	settings.wrap = getWrap();
 	// Delete the hardware texture.
 	if (texture != 0)
 	{
 
 bool Image::hasNpot()
 {
-	return GLEE_ARB_texture_non_power_of_two != 0;
+	return GLEE_VERSION_2_0 || GLEE_ARB_texture_non_power_of_two;
 }
 
-void Image::setDefaultFilter(const Image::Filter &f)
+bool Image::hasMipmapSupport()
 {
-	defaultFilter = f;
+	return (GLEE_VERSION_1_4 || GLEE_SGIS_generate_mipmap) != 0;
 }
 
-const Image::Filter &Image::getDefaultFilter()
+bool Image::hasMipmapSharpnessSupport()
 {
-	return defaultFilter;
+	return (GLEE_VERSION_1_4 || GLEE_EXT_texture_lod_bias) != 0;
 }
 
 } // opengl

File src/modules/graphics/opengl/Image.h

 
 	/**
 	 * Sets the filter mode.
-	 *
-	 * @param mode The filter mode.
+	 * @param f The filter mode.
 	 **/
 	void setFilter(const Image::Filter &f);
 
-	Image::Filter getFilter() const;
+	const Image::Filter &getFilter() const;
 
-	void setWrap(Image::Wrap r);
+	void setWrap(const 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();
-
-	// The default filter.
-	static void setDefaultFilter(const Image::Filter &f);
-	static const Image::Filter &getDefaultFilter();
+	static bool hasMipmapSupport();
+	static bool hasMipmapSharpnessSupport();
 
 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;
+
+	// Implementation-dependent maximum/minimum mipmap sharpness values
+	float maxmipmapsharpness;
+
+	// The image's filter mode
+	Image::Filter filter;
+
+	// The image's wrap mode
+	Image::Wrap wrap;
 
 	bool loadVolatilePOT();
 	bool loadVolatileNPOT();
 
-	// The default image filter
-	static Image::Filter defaultFilter;
+	void checkMipmapsCreated() const;
 
 }; // Image
 

File src/modules/graphics/opengl/OpenGL.cpp

 	glDeleteTextures(1, &texture);
 }
 
+void setTextureFilter(const graphics::Image::Filter &f)
+{
+	GLint gmin, gmag;
+
+	if (f.mipmap == Image::FILTER_NONE)
+	{
+		if (f.min == Image::FILTER_NEAREST)
+			gmin = GL_NEAREST;
+		else // f.min == Image::FILTER_LINEAR
+			gmin = GL_LINEAR;
+	}
+	else
+	{
+		if (f.min == Image::FILTER_NEAREST && f.mipmap == Image::FILTER_NEAREST)
+			gmin = GL_NEAREST_MIPMAP_NEAREST;
+		else if (f.min == Image::FILTER_NEAREST && f.mipmap == Image::FILTER_LINEAR)
+			gmin = GL_NEAREST_MIPMAP_LINEAR;
+		else if (f.min == Image::FILTER_LINEAR && f.mipmap == Image::FILTER_NEAREST)
+			gmin = GL_LINEAR_MIPMAP_NEAREST;
+		else if (f.min == Image::FILTER_LINEAR && f.mipmap == Image::FILTER_LINEAR)
+			gmin = GL_LINEAR_MIPMAP_LINEAR;
+		else
+			gmin = GL_LINEAR;
+	}
+
+
+	switch (f.mag)
+	{
+	case Image::FILTER_NEAREST:
+		gmag = GL_NEAREST;
+		break;
+	case Image::FILTER_LINEAR:
+	default:
+		gmag = GL_LINEAR;
+		break;
+	}
+
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gmin);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gmag);
+}
+
+graphics::Image::Filter getTextureFilter()
+{
+	GLint gmin, gmag;
+	glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &gmin);
+	glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &gmag);
+
+	Image::Filter f;
+
+	switch (gmin)
+	{
+	case GL_NEAREST:
+		f.min = Image::FILTER_NEAREST;
+		f.mipmap = Image::FILTER_NONE;
+		break;
+	case GL_NEAREST_MIPMAP_NEAREST:
+		f.min = f.mipmap = Image::FILTER_NEAREST;
+		break;
+	case GL_NEAREST_MIPMAP_LINEAR:
+		f.min = Image::FILTER_NEAREST;
+		f.mipmap = Image::FILTER_LINEAR;
+		break;
+	case GL_LINEAR_MIPMAP_NEAREST:
+		f.min = Image::FILTER_LINEAR;
+		f.mipmap = Image::FILTER_NEAREST;
+		break;
+	case GL_LINEAR_MIPMAP_LINEAR:
+		f.min = f.mipmap = Image::FILTER_LINEAR;
+		break;
+	case GL_LINEAR:
+	default:
+		f.min = Image::FILTER_LINEAR;
+		f.mipmap = Image::FILTER_NONE;
+		break;
+	}
+
+	switch (gmag)
+	{
+	case GL_NEAREST:
+		f.mag = Image::FILTER_NEAREST;
+		break;
+	case GL_LINEAR:
+	default:
+		f.mag = Image::FILTER_LINEAR;
+		break;
+	}
+
+	return f;
+}
+
+void setTextureWrap(const graphics::Image::Wrap &w)
+{
+	GLint gs, gt;
+
+	switch (w.s)
+	{
+	case Image::WRAP_CLAMP:
+		gs = GL_CLAMP_TO_EDGE;
+		break;
+	case Image::WRAP_REPEAT:
+	default:
+		gs = GL_REPEAT;
+		break;
+	}
+
+	switch (w.t)
+	{
+	case Image::WRAP_CLAMP:
+		gt = GL_CLAMP_TO_EDGE;
+		break;
+	case Image::WRAP_REPEAT:
+	default:
+		gt = GL_REPEAT;
+		break;
+	}
+
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gs);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gt);
+}
+
+graphics::Image::Wrap getTextureWrap()
+{
+	GLint gs, gt;
+
+	glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &gs);
+	glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &gt);
+
+	Image::Wrap w;
+
+	switch (gs)
+	{
+	case GL_CLAMP_TO_EDGE:
+		w.s = Image::WRAP_CLAMP;
+		break;
+	case GL_REPEAT:
+	default:
+		w.s = Image::WRAP_REPEAT;
+		break;
+	}
+
+	switch (gt)
+	{
+	case GL_CLAMP_TO_EDGE:
+		w.t = Image::WRAP_CLAMP;
+		break;
+	case GL_REPEAT:
+	default:
+		w.t = Image::WRAP_REPEAT;
+		break;
+	}
+
+	return w;
+}
+
 } // opengl
 } // graphics
 } // love

File src/modules/graphics/opengl/OpenGL.h

 #define LOVE_COMMON_OPENGL_H
 
 #include "GLee.h"
+#include "graphics/Image.h"
 
 namespace love
 {
  **/
 void deleteTexture(GLuint texture);
 
+/**
+ * Sets the image filter mode for the currently bound texture
+ * @param f The image filter to set
+ */
+void setTextureFilter(const graphics::Image::Filter &f);
+
+/**
+ * Returns the image filter mode for the currently bound texture
+ */
+graphics::Image::Filter getTextureFilter();
+
+/**
+ * Sets the image wrap mode for the currently bound texture
+ * @param w The wrap mode to set
+ */
+void setTextureWrap(const graphics::Image::Wrap &w);
+
+/**
+ * Returns the image wrap mode for the currently bound texture
+ */
+graphics::Image::Wrap getTextureWrap();
+
 } // opengl
 } // graphics
 } // love

File src/modules/graphics/opengl/ParticleSystem.cpp

 
 	if (pStart != 0)
 		delete [] pStart;
-	
+
 	if (particleVerts != 0)
 		delete [] particleVerts;
 }
 	pLast = pStart = new particle[size];
 
 	pEnd = pStart + size;
-	
+
 	if (particleVerts != 0)
 		delete [] particleVerts;
-	
+
 	// each particle has 4 vertices
 	particleVerts = new vertex[size*4];
 }
 void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
 {
 	if (sprite == 0) return;  // just in case of failure
-	
+
 	int numParticles = count();
 	if (numParticles == 0) return; // don't bother if there's nothing to do
 
 	static Matrix t;
 	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
 	glMultMatrixf((const GLfloat *)t.getElements());
-	
+
 	const vertex * imageVerts = sprite->getVertices();
-	
+
 	// set the vertex data for each particle (transformation, texcoords, color)
 	for (int i = 0; i < numParticles; i++)
 	{
 		particle * p = pStart + i;
-		
+
 		// particle vertices are sprite vertices transformed by particle information 
 		t.setTransformation(p->position[0], p->position[1], p->rotation, p->size, p->size, offsetX, offsetY, 0.0f, 0.0f);
 		t.transform(&particleVerts[i*4], &imageVerts[0], 4);
-		
+
 		// set the texture coordinate and color data for particle vertices
-		for (int v = 0; v < 4; v++) {
+		for (int v = 0; v < 4; v++)
+		{
 			int vi = (i * 4) + v; // current vertex index for particle
-			
+
 			particleVerts[vi].s = imageVerts[v].s;
 			particleVerts[vi].t = imageVerts[v].t;
-			
+
 			// particle colors are stored as floats (0-1) but vertex colors are stored as unsigned bytes (0-255)
 			particleVerts[vi].r = p->color.r*255;
 			particleVerts[vi].g = p->color.g*255;
 			particleVerts[vi].a = p->color.a*255;
 		}
 	}
-	
+
 	sprite->bind();
-	
+
 	glEnableClientState(GL_COLOR_ARRAY);
 	glEnableClientState(GL_VERTEX_ARRAY);
 	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-	
+
 	glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vertex), (GLvoid *)&particleVerts[0].r);
 	glVertexPointer(2, GL_FLOAT, sizeof(vertex), (GLvoid *)&particleVerts[0].x);
 	glTexCoordPointer(2, GL_FLOAT, sizeof(vertex), (GLvoid *)&particleVerts[0].s);
-	
+
 	glDrawArrays(GL_QUADS, 0, numParticles*4);
-	
+
 	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 	glDisableClientState(GL_VERTEX_ARRAY);
 	glDisableClientState(GL_COLOR_ARRAY);

File src/modules/graphics/opengl/ParticleSystem.h

 
 	// Pointer to the end of the memory allocation.
 	particle *pEnd;
-	
+
 	// array of transformed vertex data for all particles, for drawing
 	vertex * particleVerts;
 

File src/modules/graphics/opengl/PixelEffect.cpp

 
 PixelEffect *PixelEffect::current = NULL;
 
-std::map<std::string, GLint> PixelEffect::_texture_unit_pool;
-GLint PixelEffect::_current_texture_unit = 0;
-GLint PixelEffect::_max_texture_units = 0;
+std::vector<bool> PixelEffect::_unit_available;
 
 GLint PixelEffect::getTextureUnit(const std::string &name)
 {
 	if (it != _texture_unit_pool.end())
 		return it->second;
 
-	if (++_current_texture_unit >= _max_texture_units)
+	GLint unit = -1;
+	for (int i = 1; i < _unit_available.size(); ++i)
+	{
+		if (_unit_available[i])
+		{
+			unit = i;
+			break;
+		}
+	}
+
+	if (unit == -1)
 		throw love::Exception("No more texture units available");
 
-	_texture_unit_pool[name] = _current_texture_unit;
-	return _current_texture_unit;
+	_unit_available[unit] = false;
+	_texture_unit_pool[name] = unit;
+	return unit;
 }
 
 PixelEffect::PixelEffect(const std::string &code)
 	: _program(0)
 	, _code(code)
 {
-	glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &_max_texture_units);
+	if (_unit_available.empty())
+	{
+		GLint max_units;
+		glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &max_units);
+		_unit_available.resize(max_units, true);
+		_unit_available[0] = false;
+	}
 	loadVolatile();
 }
 

File src/modules/graphics/opengl/PixelEffect.h

 #include "common/Object.h"
 #include <string>
 #include <map>
+#include <vector>
 #include "OpenGL.h"
 #include "Image.h"
 #include "Canvas.h"
 	std::map<std::string, GLint> _uniforms;
 
 	// texture unit pool for setting images
-	static std::map<std::string, GLint> _texture_unit_pool;
-	static GLint _current_texture_unit;
-	static GLint _max_texture_units;
-	static GLint getTextureUnit(const std::string &name);
+	std::map<std::string, GLint> _texture_unit_pool;
+	GLint getTextureUnit(const std::string &name);
+	static std::vector<bool> _unit_available;
 };
 
 } // opengl

File src/modules/graphics/opengl/wrap_Canvas.cpp

 int w_Canvas_setFilter(lua_State *L)
 {
 	Canvas *canvas = luax_checkcanvas(L, 1);
-	const char *minstr = luaL_checkstring(L, 2);
-	const char *magstr = luaL_checkstring(L, 3);
 
 	Image::Filter f;
+
+	const char *minstr = luaL_checkstring(L, 2);
+	const char *magstr = luaL_optstring(L, 3, minstr);
+
 	if (!Image::getConstant(minstr, f.min))
-		return luaL_error(L, "Invalid min filter mode: %s", minstr);
+		return luaL_error(L, "Invalid filter mode: %s", minstr);
 	if (!Image::getConstant(magstr, f.mag))
-		return luaL_error(L, "Invalid max filter mode: %s", magstr);
+		return luaL_error(L, "Invalid filter mode: %s", magstr);
 
 	canvas->setFilter(f);
+
 	return 0;
+
 }
 
 int w_Canvas_getFilter(lua_State *L)
 {
 	Canvas *canvas = luax_checkcanvas(L, 1);
-	Image::Filter f = canvas->getFilter();
+	const Image::Filter f = canvas->getFilter();
 
 	const char *minstr;
 	const char *magstr;
 int w_Canvas_setWrap(lua_State *L)
 {
 	Canvas *canvas = luax_checkcanvas(L, 1);
-	const char *wrap_s = luaL_checkstring(L, 2);
-	const char *wrap_t = luaL_checkstring(L, 3);
 
 	Image::Wrap w;
-	if (!Image::getConstant(wrap_s, w.s))
-		return luaL_error(L, "Invalid wrap mode: %s", wrap_s);
-	if (!Image::getConstant(wrap_t, w.t))
-		return luaL_error(L, "Invalid wrap mode: %s", wrap_t);
+
+	const char *sstr = luaL_checkstring(L, 2);
+	const char *tstr = luaL_optstring(L, 3, sstr);
+
+	if (!Image::getConstant(sstr, w.s))
+		return luaL_error(L, "Invalid wrap mode: %s", sstr);
+	if (!Image::getConstant(tstr, w.t))
+		return luaL_error(L, "Invalid wrap mode, %s", tstr);
 
 	canvas->setWrap(w);
+
 	return 0;
 }
 
 int w_Canvas_getWrap(lua_State *L)
 {
 	Canvas *canvas = luax_checkcanvas(L, 1);
-	Image::Wrap w = canvas->getWrap();
+	const Image::Wrap w = canvas->getWrap();
 
 	const char *wrap_s;
 	const char *wrap_t;

File src/modules/graphics/opengl/wrap_Font.cpp

 	return 1;
 }
 
+int w_Font_setFilter(lua_State *L)
+{
+	Font *t = luax_checkfont(L, 1);
+
+	Image::Filter f;
+
+	const char *minstr = luaL_checkstring(L, 2);
+	const char *magstr = luaL_optstring(L, 3, minstr);
+
+	if (!Image::getConstant(minstr, f.min))
+		return luaL_error(L, "Invalid filter mode: %s", minstr);
+	if (!Image::getConstant(magstr, f.mag))
+		return luaL_error(L, "Invalid filter mode: %s", magstr);
+
+	if (lua_isnoneornil(L, 4))
+		f.mipmap = Image::FILTER_NONE; // mipmapping is disabled unless third argument is given
+	else
+	{
+		const char *mipmapstr = luaL_checkstring(L, 4);
+		if (!Image::getConstant(mipmapstr, f.mipmap))
+			return luaL_error(L, "Invalid filter mode: %s", mipmapstr);
+	}
+
+	try
+	{
+		t->setFilter(f);
+	}
+	catch(love::Exception &e)
+	{
+		return luaL_error(L, "%s", e.what());
+	}
+
+	return 0;
+}
+
+int w_Font_getFilter(lua_State *L)
+{
+	Font *t = luax_checkfont(L, 1);
+	const Image::Filter f = t->getFilter();
+	const char *minstr;
+	const char *magstr;
+	Image::getConstant(f.min, minstr);
+	Image::getConstant(f.mag, magstr);
+	lua_pushstring(L, minstr);
+	lua_pushstring(L, magstr);
+
+	const char *mipmapstr;
+	if (Image::getConstant(f.mipmap, mipmapstr))
+		lua_pushstring(L, mipmapstr);
+	else
+		lua_pushnil(L); // only return a mipmap filter if mipmapping is enabled
+
+	return 3;
+}
+
+int w_Font_setMipmapSharpness(lua_State *L)
+{
+	Font *t = luax_checkfont(L, 1);
+
+	float sharpness = (float) luaL_checknumber(L, 2);
+	t->setMipmapSharpness(sharpness);
+
+	return 0;
+}
+
+int w_Font_getMipmapSharpness(lua_State *L)
+{
+	Font *t = luax_checkfont(L, 1);
+	lua_pushnumber(L, t->getMipmapSharpness());
+	return 1;
+}
+
+int w_Font_getAscent(lua_State *L)
+{
+	Font *t = luax_checkfont(L, 1);
+	lua_pushnumber(L, t->getAscent());
+	return 1;
+}
+
+int w_Font_getDescent(lua_State *L)
+{
+	Font *t = luax_checkfont(L, 1);
+	lua_pushnumber(L, t->getDescent());
+	return 1;
+}
+
+int w_Font_getBaseline(lua_State *L)
+{
+	Font *t = luax_checkfont(L, 1);
+	lua_pushnumber(L, t->getBaseline());
+	return 1;
+}
+
 static const luaL_Reg functions[] =
 {
 	{ "getHeight", w_Font_getHeight },
 	{ "getWrap", w_Font_getWrap },
 	{ "setLineHeight", w_Font_setLineHeight },
 	{ "getLineHeight", w_Font_getLineHeight },
+	{ "setFilter", w_Font_setFilter },
+	{ "getFilter", w_Font_getFilter },
+	{ "setMipmapSharpness", w_Font_setMipmapSharpness },
+	{ "getMipmapSharpness", w_Font_getMipmapSharpness },
+	{ "getAscent", w_Font_getAscent },
+	{ "getDescent", w_Font_getDescent },
+	{ "getBaseline", w_Font_getBaseline },
 	{ 0, 0 }
 };
 

File src/modules/graphics/opengl/wrap_Font.h

 int w_Font_getWrap(lua_State *L);
 int w_Font_setLineHeight(lua_State *L);
 int w_Font_getLineHeight(lua_State *L);
+int w_Font_setFilter(lua_State *L);
+int w_Font_getFilter(lua_State *L);
+int w_Font_setMipmapSharpness(lua_State *L);
+int w_Font_getMipmapSharpness(lua_State *L);
+int w_Font_getAscent(lua_State *L);
+int w_Font_getDescent(lua_State *L);
+int w_Font_getBaseline(lua_State *L);
 extern "C" int luaopen_font(lua_State *L);
 
 } // opengl

File src/modules/graphics/opengl/wrap_Graphics.cpp

 {
 	Image::FilterMode min;
 	Image::FilterMode mag;
+
 	const char *minstr = luaL_checkstring(L, 1);
 	const char *magstr = luaL_optstring(L, 2, minstr);
+
 	if (!Image::getConstant(minstr, min))
 		return luaL_error(L, "Invalid filter mode: %s", minstr);
 	if (!Image::getConstant(magstr, mag))
 	Image::Filter f;
 	f.min = min;
 	f.mag = mag;
+
 	instance->setDefaultImageFilter(f);
 
 	return 0;
 
 int w_setPointStyle(lua_State *L)
 {
-	Graphics::PointStyle style = Graphics::POINT_SMOOTH;
+	Graphics::PointStyle style;
 
-	if (lua_gettop(L) >= 2)
-	{
-		const char *str = luaL_checkstring(L, 1);
-		if (!Graphics::getConstant(str, style))
-			return luaL_error(L, "Invalid point style: %s", str);
-	}
+	const char *str = luaL_checkstring(L, 1);
+	if (!Graphics::getConstant(str, style))
+		return luaL_error(L, "Invalid point style: %s", str);
 
 	instance->setPointStyle(style);
 	return 0;
 {
 	float size = (float)luaL_checknumber(L, 1);
 
-	Graphics::PointStyle style;
-	const char *str = luaL_checkstring(L, 2);
-	if (!Graphics::getConstant(str, style))
-		return luaL_error(L, "Invalid point style: %s", str);
+	Graphics::PointStyle style = Graphics::POINT_SMOOTH;
+
+	if (lua_gettop(L) >= 2)
+	{
+		const char *str = luaL_checkstring(L, 2);
+		if (!Graphics::getConstant(str, style))
+			return luaL_error(L, "Invalid point style: %s", str);
+	}
 
 	instance->setPoint(size, style);
 	return 0;
 				supported = false;
 			break;
 		case Graphics::SUPPORT_SUBTRACTIVE:
-			supported = (GLEE_VERSION_1_4 || GLEE_ARB_imaging) || (GLEE_EXT_blend_minmax && GLEE_EXT_blend_subtract);
+			if (!((GLEE_VERSION_1_4 || GLEE_ARB_imaging) || (GLEE_EXT_blend_minmax && GLEE_EXT_blend_subtract)))
+				supported = false;
+			break;
+		case Graphics::SUPPORT_MIPMAP:
+			if (!Image::hasMipmapSupport())
+				supported = false;
 			break;
 		default:
 			supported = false;

File src/modules/graphics/opengl/wrap_Image.cpp

 int w_Image_setFilter(lua_State *L)
 {
 	Image *t = luax_checkimage(L, 1);
+
 	Image::Filter f;
-	Image::FilterMode min;
-	Image::FilterMode mag;
+
 	const char *minstr = luaL_checkstring(L, 2);
 	const char *magstr = luaL_optstring(L, 3, minstr);
-	if (!Image::getConstant(minstr, min))
+
+	if (!Image::getConstant(minstr, f.min))
 		return luaL_error(L, "Invalid filter mode: %s", minstr);
-	if (!Image::getConstant(magstr, mag))
+	if (!Image::getConstant(magstr, f.mag))
 		return luaL_error(L, "Invalid filter mode: %s", magstr);
 
-	f.min = min;
-	f.mag = mag;
-	t->setFilter(f);
+	if (lua_isnoneornil(L, 4))
+		f.mipmap = Image::FILTER_NONE; // mipmapping is disabled unless third argument is given
+	else
+	{
+		const char *mipmapstr = luaL_checkstring(L, 4);
+		if (!Image::getConstant(mipmapstr, f.mipmap))
+			return luaL_error(L, "Invalid filter mode: %s", mipmapstr);
+	}
+
+	try
+	{
+		t->setFilter(f);
+	}
+	catch(love::Exception &e)
+	{
+		return luaL_error(L, "%s", 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;
+
+	const char *mipmapstr;
+	if (Image::getConstant(f.mipmap, mipmapstr))
+		lua_pushstring(L, mipmapstr);
+	else
+		lua_pushnil(L); // only return a mipmap filter if mipmapping is enabled
+
+	return 3;
 }
 
 int w_Image_setWrap(lua_State *L)
 {
 	Image *i = luax_checkimage(L, 1);
+
 	Image::Wrap w;
-	Image::WrapMode s;
-	Image::WrapMode t;
+
 	const char *sstr = luaL_checkstring(L, 2);
 	const char *tstr = luaL_optstring(L, 3, sstr);
-	if (!Image::getConstant(sstr, s))
+
+	if (!Image::getConstant(sstr, w.s))
 		return luaL_error(L, "Invalid wrap mode: %s", sstr);
-	if (!Image::getConstant(tstr, t))
+	if (!Image::getConstant(tstr, w.t))
 		return luaL_error(L, "Invalid wrap mode, %s", tstr);
 
-	w.s = s;
-	w.t = t;
 	i->setWrap(w);
+
 	return 0;
 }
 
 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;
 }