Browse Source

Revamped and streamlined retina / high-DPI support (resolves issue #1122).

With the highdpi window flag enabled on a retina-capable display and OS, content should now appear to the user at the same size and in the same positions as with the flag disabled.

As a result, mouse and touch coordinates, Texture and graphics dimensions, and the graphics coordinate system now use pixel density-scaled units instead of pixels. Raw pixel units should generally only be used for things such as shader algorithms which execute per-pixel and rely on accurate pixel dimensions. love.window.fromPixels and friends typically don’t need to be used anymore.

Images, Canvases, and Fonts can have an optional explicit ‘pixel density’ set when creating them. This allows for easily loading high pixel density content which displays at the same size as regular or low pixel density content.

API changes:

- Added Texture:getPixelWidth/getPixelHeight/getPixelDimensions and Texture:getPixelDensity. Texture:getWidth/getHeight return the pixel density-scaled width and height (as it will appear on the screen when drawn) rather than the number of pixels on each texture dimension.

- Added love.graphics.getPixelWidth/getPixelHeight/getPixelDimensions.

- Added optional ‘pixeldensity’ field to the settings table parameter of love.graphics.newImage. It defaults to 1, or if the file the Image was loaded from has “@2x”, “@3x”, etc. at the end of its name, it uses that number as the pixel density scale by default.

- love.graphics.newCanvas now takes a table as its third parameter, with fields “format”, “msaa”, and “pixeldensity”. pixeldensity defaults to the main screen’s pixel density. The width and height parameters specify the visual size that the Canvas will be drawn at / can be drawn to (pixel density-scaled units).

- love.graphics.newVideo accepts a table as its second parameter, with optional fields “audio” and “pixeldensity”. pixeldensity defaults to 1.

- love.graphics.newFont variants have an optional pixeldensity parameter at the end of the argument list. For TrueType fonts this defaults to the current pixel density scale of the screen, and for BMFonts and ImageFonts this defaults to 1.

- Added Font:getPixelDensity.

- Renamed love.window.getPixelScale to love.window.getPixelDensity.

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
d26002f5cc
53 changed files with 871 additions and 532 deletions
  1. 14 0
      src/common/runtime.cpp
  2. 1 0
      src/common/runtime.h
  3. 42 25
      src/modules/event/sdl/Event.cpp
  4. 15 3
      src/modules/filesystem/FileData.cpp
  5. 4 0
      src/modules/filesystem/FileData.h
  6. 3 1
      src/modules/font/BMFontRasterizer.cpp
  7. 1 1
      src/modules/font/BMFontRasterizer.h
  8. 12 6
      src/modules/font/Font.cpp
  9. 5 3
      src/modules/font/Font.h
  10. 3 1
      src/modules/font/ImageRasterizer.cpp
  11. 1 1
      src/modules/font/ImageRasterizer.h
  12. 5 0
      src/modules/font/Rasterizer.cpp
  13. 3 0
      src/modules/font/Rasterizer.h
  14. 15 2
      src/modules/font/freetype/Font.cpp
  15. 4 3
      src/modules/font/freetype/Font.h
  16. 7 2
      src/modules/font/freetype/TrueTypeRasterizer.cpp
  17. 1 1
      src/modules/font/freetype/TrueTypeRasterizer.h
  18. 30 14
      src/modules/font/wrap_Font.cpp
  19. 5 2
      src/modules/graphics/Graphics.h
  20. 13 12
      src/modules/graphics/Quad.cpp
  21. 17 0
      src/modules/graphics/Texture.cpp
  22. 8 0
      src/modules/graphics/Texture.h
  23. 22 25
      src/modules/graphics/opengl/Canvas.cpp
  24. 11 5
      src/modules/graphics/opengl/Canvas.h
  25. 50 38
      src/modules/graphics/opengl/Font.cpp
  26. 6 2
      src/modules/graphics/opengl/Font.h
  27. 0 1
      src/modules/graphics/opengl/GLBuffer.cpp
  28. 89 31
      src/modules/graphics/opengl/Graphics.cpp
  29. 15 14
      src/modules/graphics/opengl/Graphics.h
  30. 53 47
      src/modules/graphics/opengl/Image.cpp
  31. 16 14
      src/modules/graphics/opengl/Image.h
  32. 1 11
      src/modules/graphics/opengl/OpenGL.cpp
  33. 1 6
      src/modules/graphics/opengl/OpenGL.h
  34. 21 10
      src/modules/graphics/opengl/Video.cpp
  35. 7 1
      src/modules/graphics/opengl/Video.h
  36. 2 2
      src/modules/graphics/opengl/wrap_Canvas.cpp
  37. 8 0
      src/modules/graphics/opengl/wrap_Font.cpp
  38. 88 32
      src/modules/graphics/opengl/wrap_Graphics.cpp
  39. 8 4
      src/modules/graphics/opengl/wrap_Graphics.lua
  40. 10 7
      src/modules/graphics/opengl/wrap_Image.cpp
  41. 1 0
      src/modules/graphics/opengl/wrap_Image.h
  42. 25 0
      src/modules/graphics/opengl/wrap_Video.cpp
  43. 33 0
      src/modules/graphics/wrap_Texture.cpp
  44. 2 2
      src/modules/keyboard/sdl/Keyboard.cpp
  45. 8 8
      src/modules/mouse/sdl/Mouse.cpp
  46. 9 2
      src/modules/window/Window.h
  47. 73 11
      src/modules/window/sdl/Window.cpp
  48. 9 2
      src/modules/window/sdl/Window.h
  49. 3 3
      src/modules/window/wrap_Window.cpp
  50. 2 3
      src/scripts/boot.lua
  51. 2 6
      src/scripts/boot.lua.h
  52. 30 55
      src/scripts/nogame.lua
  53. 57 113
      src/scripts/nogame.lua.h

+ 14 - 0
src/common/runtime.cpp

@@ -194,6 +194,20 @@ int luax_intflag(lua_State *L, int table_index, const char *key, int defaultValu
 	return retval;
 	return retval;
 }
 }
 
 
+double luax_numberflag(lua_State *L, int table_index, const char *key, double defaultValue)
+{
+	lua_getfield(L, table_index, key);
+
+	int retval;
+	if (!lua_isnumber(L, -1))
+		retval = defaultValue;
+	else
+		retval = lua_tonumber(L, -1);
+
+	lua_pop(L, 1);
+	return retval;
+}
+
 int luax_assert_argc(lua_State *L, int min)
 int luax_assert_argc(lua_State *L, int min)
 {
 {
 	int argc = lua_gettop(L);
 	int argc = lua_gettop(L);

+ 1 - 0
src/common/runtime.h

@@ -161,6 +161,7 @@ void luax_pushstring(lua_State *L, const std::string &str);
 
 
 bool luax_boolflag(lua_State *L, int table_index, const char *key, bool defaultValue);
 bool luax_boolflag(lua_State *L, int table_index, const char *key, bool defaultValue);
 int luax_intflag(lua_State *L, int table_index, const char *key, int defaultValue);
 int luax_intflag(lua_State *L, int table_index, const char *key, int defaultValue);
+double luax_numberflag(lua_State *L, int table_index, const char *key, double defaultValue);
 
 
 /**
 /**
  * Convert the value at the specified index to an Lua number, and then
  * Convert the value at the specified index to an Lua number, and then

+ 42 - 25
src/modules/event/sdl/Event.cpp

@@ -44,26 +44,30 @@ namespace sdl
 
 
 // SDL reports mouse coordinates in the window coordinate system in OS X, but
 // SDL reports mouse coordinates in the window coordinate system in OS X, but
 // we want them in pixel coordinates (may be different with high-DPI enabled.)
 // we want them in pixel coordinates (may be different with high-DPI enabled.)
-static void windowToPixelCoords(double *x, double *y)
+static void windowToDPICoords(double *x, double *y)
 {
 {
 	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	if (window)
 	if (window)
-		window->windowToPixelCoords(x, y);
+		window->windowToDPICoords(x, y);
 }
 }
 
 
 #ifndef LOVE_MACOSX
 #ifndef LOVE_MACOSX
-static void normalizedToPixelCoords(double *x, double *y)
+static void normalizedToDPICoords(double *x, double *y)
 {
 {
-	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
-	int w = 1, h = 1;
+	double w = 1.0, h = 1.0;
 
 
+	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	if (window)
 	if (window)
-		window->getPixelDimensions(w, h);
+	{
+		w = window->getWidth();
+		h = window->getHeight();
+		window->windowToDPICoords(&w, &h);
+	}
 
 
 	if (x)
 	if (x)
-		*x = ((*x) * (double) w);
+		*x = ((*x) * w);
 	if (y)
 	if (y)
-		*y = ((*y) * (double) h);
+		*y = ((*y) * h);
 }
 }
 #endif
 #endif
 
 
@@ -249,8 +253,8 @@ Message *Event::convert(const SDL_Event &e)
 			double y = (double) e.motion.y;
 			double y = (double) e.motion.y;
 			double xrel = (double) e.motion.xrel;
 			double xrel = (double) e.motion.xrel;
 			double yrel = (double) e.motion.yrel;
 			double yrel = (double) e.motion.yrel;
-			windowToPixelCoords(&x, &y);
-			windowToPixelCoords(&xrel, &yrel);
+			windowToDPICoords(&x, &y);
+			windowToDPICoords(&xrel, &yrel);
 			vargs.emplace_back(x);
 			vargs.emplace_back(x);
 			vargs.emplace_back(y);
 			vargs.emplace_back(y);
 			vargs.emplace_back(xrel);
 			vargs.emplace_back(xrel);
@@ -276,7 +280,7 @@ Message *Event::convert(const SDL_Event &e)
 
 
 			double px = (double) e.button.x;
 			double px = (double) e.button.x;
 			double py = (double) e.button.y;
 			double py = (double) e.button.y;
-			windowToPixelCoords(&px, &py);
+			windowToDPICoords(&px, &py);
 			vargs.emplace_back(px);
 			vargs.emplace_back(px);
 			vargs.emplace_back(py);
 			vargs.emplace_back(py);
 			vargs.emplace_back((double) button);
 			vargs.emplace_back((double) button);
@@ -313,15 +317,15 @@ Message *Event::convert(const SDL_Event &e)
 		if (touchNormalizationBug || fabs(touchinfo.x) >= 1.5 || fabs(touchinfo.y) >= 1.5 || fabs(touchinfo.dx) >= 1.5 || fabs(touchinfo.dy) >= 1.5)
 		if (touchNormalizationBug || fabs(touchinfo.x) >= 1.5 || fabs(touchinfo.y) >= 1.5 || fabs(touchinfo.dx) >= 1.5 || fabs(touchinfo.dy) >= 1.5)
 		{
 		{
 			touchNormalizationBug = true;
 			touchNormalizationBug = true;
-			windowToPixelCoords(&touchinfo.x, &touchinfo.y);
-			windowToPixelCoords(&touchinfo.dx, &touchinfo.dy);
+			windowToDPICoords(&touchinfo.x, &touchinfo.y);
+			windowToDPICoords(&touchinfo.dx, &touchinfo.dy);
 		}
 		}
 		else
 		else
 #endif
 #endif
 		{
 		{
-			// SDL's coords are normalized to [0, 1], but we want them in pixels.
-			normalizedToPixelCoords(&touchinfo.x, &touchinfo.y);
-			normalizedToPixelCoords(&touchinfo.dx, &touchinfo.dy);
+			// SDL's coords are normalized to [0, 1], but we want screen coords.
+			normalizedToDPICoords(&touchinfo.x, &touchinfo.y);
+			normalizedToDPICoords(&touchinfo.dx, &touchinfo.dy);
 		}
 		}
 
 
 		// We need to update the love.touch.sdl internal state from here.
 		// We need to update the love.touch.sdl internal state from here.
@@ -536,6 +540,7 @@ Message *Event::convertWindowEvent(const SDL_Event &e)
 	vargs.reserve(4);
 	vargs.reserve(4);
 
 
 	window::Window *win = nullptr;
 	window::Window *win = nullptr;
+	graphics::Graphics *gfx = nullptr;
 
 
 	if (e.type != SDL_WINDOWEVENT)
 	if (e.type != SDL_WINDOWEVENT)
 		return nullptr;
 		return nullptr;
@@ -559,17 +564,29 @@ Message *Event::convertWindowEvent(const SDL_Event &e)
 		break;
 		break;
 	case SDL_WINDOWEVENT_RESIZED:
 	case SDL_WINDOWEVENT_RESIZED:
 		{
 		{
-			int px_w = e.window.data1;
-			int px_h = e.window.data2;
+			double width  = e.window.data1;
+			double height = e.window.data2;
+
+			gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
+			win = Module::getInstance<window::Window>(Module::M_WINDOW);
 
 
-			SDL_Window *sdlwin = SDL_GetWindowFromID(e.window.windowID);
-			if (sdlwin)
-				SDL_GL_GetDrawableSize(sdlwin, &px_w, &px_h);
+			// WINDOWEVENT_SIZE_CHANGED will always occur before RESIZED.
+			// The size values in the Window aren't necessarily the same as the
+			// graphics size, which is what we want to output.
+			if (gfx)
+			{
+				width  = gfx->getWidth();
+				height = gfx->getHeight();
+			}
+			else if (win)
+			{
+				width  = win->getWidth();
+				height = win->getHeight();
+				windowToDPICoords(&width, &height);
+			}
 
 
-			vargs.emplace_back((double) px_w);
-			vargs.emplace_back((double) px_h);
-			vargs.emplace_back((double) e.window.data1);
-			vargs.emplace_back((double) e.window.data2);
+			vargs.emplace_back(width);
+			vargs.emplace_back(height);
 			msg = new Message("resize", vargs);
 			msg = new Message("resize", vargs);
 		}
 		}
 		break;
 		break;

+ 15 - 3
src/modules/filesystem/FileData.cpp

@@ -45,8 +45,15 @@ FileData::FileData(uint64 size, const std::string &filename)
 		throw love::Exception("Out of memory.");
 		throw love::Exception("Out of memory.");
 	}
 	}
 
 
-	if (filename.rfind('.') != std::string::npos)
-		extension = filename.substr(filename.rfind('.')+1);
+	size_t dotpos = filename.rfind('.');
+
+	if (dotpos != std::string::npos)
+	{
+		extension = filename.substr(dotpos + 1);
+		name = filename.substr(0, dotpos);
+	}
+	else
+		name = filename;
 }
 }
 
 
 FileData::~FileData()
 FileData::~FileData()
@@ -56,7 +63,7 @@ FileData::~FileData()
 
 
 void *FileData::getData() const
 void *FileData::getData() const
 {
 {
-	return (void *)data;
+	return data;
 }
 }
 
 
 size_t FileData::getSize() const
 size_t FileData::getSize() const
@@ -75,5 +82,10 @@ const std::string &FileData::getExtension() const
 	return extension;
 	return extension;
 }
 }
 
 
+const std::string &FileData::getName() const
+{
+	return name;
+}
+
 } // filesystem
 } // filesystem
 } // love
 } // love

+ 4 - 0
src/modules/filesystem/FileData.h

@@ -49,6 +49,7 @@ public:
 
 
 	const std::string &getFilename() const;
 	const std::string &getFilename() const;
 	const std::string &getExtension() const;
 	const std::string &getExtension() const;
+	const std::string &getName() const;
 
 
 private:
 private:
 
 
@@ -64,6 +65,9 @@ private:
 	// The extension (without dot). Used to identify file type.
 	// The extension (without dot). Used to identify file type.
 	std::string extension;
 	std::string extension;
 
 
+	// The file name without the extension (and without the dot).
+	std::string name;
+
 }; // FileData
 }; // FileData
 
 
 } // filesystem
 } // filesystem

+ 3 - 1
src/modules/font/BMFontRasterizer.cpp

@@ -131,11 +131,13 @@ std::string BMFontLine::getAttributeString(const char *name) const
 } // anonymous namespace
 } // anonymous namespace
 
 
 
 
-BMFontRasterizer::BMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &imagelist)
+BMFontRasterizer::BMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &imagelist, float pixeldensity)
 	: fontSize(0)
 	: fontSize(0)
 	, unicode(false)
 	, unicode(false)
 	, lineHeight(0)
 	, lineHeight(0)
 {
 {
+	this->pixelDensity = pixeldensity;
+
 	const std::string &filename = fontdef->getFilename();
 	const std::string &filename = fontdef->getFilename();
 
 
 	size_t separatorpos = filename.rfind('/');
 	size_t separatorpos = filename.rfind('/');

+ 1 - 1
src/modules/font/BMFontRasterizer.h

@@ -42,7 +42,7 @@ class BMFontRasterizer : public Rasterizer
 {
 {
 public:
 public:
 
 
-	BMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &imagelist);
+	BMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &imagelist, float pixeldensity);
 	virtual ~BMFontRasterizer();
 	virtual ~BMFontRasterizer();
 
 
 	// Implements Rasterizer.
 	// Implements Rasterizer.

+ 12 - 6
src/modules/font/Font.cpp

@@ -47,12 +47,18 @@ Rasterizer *Font::newTrueTypeRasterizer(int size, TrueTypeRasterizer::Hinting hi
 	return newTrueTypeRasterizer(data.get(), size, hinting);
 	return newTrueTypeRasterizer(data.get(), size, hinting);
 }
 }
 
 
-Rasterizer *Font::newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &images)
+Rasterizer *Font::newTrueTypeRasterizer(int size, float pixeldensity, TrueTypeRasterizer::Hinting hinting)
 {
 {
-	return new BMFontRasterizer(fontdef, images);
+	StrongRef<DefaultFontData> data(new DefaultFontData, Acquire::NORETAIN);
+	return newTrueTypeRasterizer(data.get(), size, pixeldensity, hinting);
+}
+
+Rasterizer *Font::newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &images, float pixeldensity)
+{
+	return new BMFontRasterizer(fontdef, images, pixeldensity);
 }
 }
 
 
-Rasterizer *Font::newImageRasterizer(love::image::ImageData *data, const std::string &text, int extraspacing)
+Rasterizer *Font::newImageRasterizer(love::image::ImageData *data, const std::string &text, int extraspacing, float pixeldensity)
 {
 {
 	std::vector<uint32> glyphs;
 	std::vector<uint32> glyphs;
 	glyphs.reserve(text.size());
 	glyphs.reserve(text.size());
@@ -70,12 +76,12 @@ Rasterizer *Font::newImageRasterizer(love::image::ImageData *data, const std::st
 		throw love::Exception("UTF-8 decoding error: %s", e.what());
 		throw love::Exception("UTF-8 decoding error: %s", e.what());
 	}
 	}
 
 
-	return newImageRasterizer(data, &glyphs[0], (int) glyphs.size(), extraspacing);
+	return newImageRasterizer(data, &glyphs[0], (int) glyphs.size(), extraspacing, pixeldensity);
 }
 }
 
 
-Rasterizer *Font::newImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int numglyphs, int extraspacing)
+Rasterizer *Font::newImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int numglyphs, int extraspacing, float pixeldensity)
 {
 {
-	return new ImageRasterizer(data, glyphs, numglyphs, extraspacing);
+	return new ImageRasterizer(data, glyphs, numglyphs, extraspacing, pixeldensity);
 }
 }
 
 
 GlyphData *Font::newGlyphData(Rasterizer *r, const std::string &text)
 GlyphData *Font::newGlyphData(Rasterizer *r, const std::string &text)

+ 5 - 3
src/modules/font/Font.h

@@ -48,12 +48,14 @@ public:
 	virtual Rasterizer *newRasterizer(love::filesystem::FileData *data) = 0;
 	virtual Rasterizer *newRasterizer(love::filesystem::FileData *data) = 0;
 
 
 	virtual Rasterizer *newTrueTypeRasterizer(int size, TrueTypeRasterizer::Hinting hinting);
 	virtual Rasterizer *newTrueTypeRasterizer(int size, TrueTypeRasterizer::Hinting hinting);
+	virtual Rasterizer *newTrueTypeRasterizer(int size, float pixeldensity, TrueTypeRasterizer::Hinting hinting);
 	virtual Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, TrueTypeRasterizer::Hinting hinting) = 0;
 	virtual Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, TrueTypeRasterizer::Hinting hinting) = 0;
+	virtual Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, float pixeldensity, TrueTypeRasterizer::Hinting hinting) = 0;
 
 
-	virtual Rasterizer *newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &images);
+	virtual Rasterizer *newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &images, float pixeldensity);
 
 
-	virtual Rasterizer *newImageRasterizer(love::image::ImageData *data, const std::string &glyphs, int extraspacing);
-	virtual Rasterizer *newImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int length, int extraspacing);
+	virtual Rasterizer *newImageRasterizer(love::image::ImageData *data, const std::string &glyphs, int extraspacing, float pixeldensity);
+	virtual Rasterizer *newImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int length, int extraspacing, float pixeldensity);
 
 
 	virtual GlyphData *newGlyphData(Rasterizer *r, const std::string &glyph);
 	virtual GlyphData *newGlyphData(Rasterizer *r, const std::string &glyph);
 	virtual GlyphData *newGlyphData(Rasterizer *r, uint32 glyph);
 	virtual GlyphData *newGlyphData(Rasterizer *r, uint32 glyph);

+ 3 - 1
src/modules/font/ImageRasterizer.cpp

@@ -34,12 +34,14 @@ inline bool equal(const love::image::pixel &a, const love::image::pixel &b)
 	return (a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a);
 	return (a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a);
 }
 }
 
 
-ImageRasterizer::ImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int numglyphs, int extraspacing)
+ImageRasterizer::ImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int numglyphs, int extraspacing, float pixeldensity)
 	: imageData(data)
 	: imageData(data)
 	, glyphs(glyphs)
 	, glyphs(glyphs)
 	, numglyphs(numglyphs)
 	, numglyphs(numglyphs)
 	, extraSpacing(extraspacing)
 	, extraSpacing(extraspacing)
 {
 {
+	this->pixelDensity = pixeldensity;
+
 	if (data->getFormat() != PIXELFORMAT_RGBA8)
 	if (data->getFormat() != PIXELFORMAT_RGBA8)
 		throw love::Exception("Only 32-bit RGBA images are supported in Image Fonts!");
 		throw love::Exception("Only 32-bit RGBA images are supported in Image Fonts!");
 
 

+ 1 - 1
src/modules/font/ImageRasterizer.h

@@ -39,7 +39,7 @@ namespace font
 class ImageRasterizer : public Rasterizer
 class ImageRasterizer : public Rasterizer
 {
 {
 public:
 public:
-	ImageRasterizer(love::image::ImageData *imageData, uint32 *glyphs, int numglyphs, int extraspacing);
+	ImageRasterizer(love::image::ImageData *imageData, uint32 *glyphs, int numglyphs, int extraspacing, float pixeldensity);
 	virtual ~ImageRasterizer();
 	virtual ~ImageRasterizer();
 
 
 	// Implement Rasterizer
 	// Implement Rasterizer

+ 5 - 0
src/modules/font/Rasterizer.cpp

@@ -102,5 +102,10 @@ float Rasterizer::getKerning(uint32 /*leftglyph*/, uint32 /*rightglyph*/) const
 	return 0.0f;
 	return 0.0f;
 }
 }
 
 
+float Rasterizer::getPixelDensity() const
+{
+	return pixelDensity;
+}
+
 } // font
 } // font
 } // love
 } // love

+ 3 - 0
src/modules/font/Rasterizer.h

@@ -120,9 +120,12 @@ public:
 
 
 	virtual DataType getDataType() const = 0;
 	virtual DataType getDataType() const = 0;
 
 
+	float getPixelDensity() const;
+
 protected:
 protected:
 
 
 	FontMetrics metrics;
 	FontMetrics metrics;
+	float pixelDensity;
 
 
 }; // Rasterizer
 }; // Rasterizer
 
 

+ 15 - 2
src/modules/font/freetype/Font.cpp

@@ -20,9 +20,12 @@
 
 
 #include "Font.h"
 #include "Font.h"
 
 
+// LOVE
 #include "TrueTypeRasterizer.h"
 #include "TrueTypeRasterizer.h"
 #include "font/BMFontRasterizer.h"
 #include "font/BMFontRasterizer.h"
+#include "window/Window.h"
 
 
+// C++
 #include <string.h>
 #include <string.h>
 
 
 namespace love
 namespace love
@@ -48,14 +51,24 @@ Rasterizer *Font::newRasterizer(love::filesystem::FileData *data)
 	if (TrueTypeRasterizer::accepts(library, data))
 	if (TrueTypeRasterizer::accepts(library, data))
 		return newTrueTypeRasterizer(data, 12, TrueTypeRasterizer::HINTING_NORMAL);
 		return newTrueTypeRasterizer(data, 12, TrueTypeRasterizer::HINTING_NORMAL);
 	else if (BMFontRasterizer::accepts(data))
 	else if (BMFontRasterizer::accepts(data))
-		return newBMFontRasterizer(data, {});
+		return newBMFontRasterizer(data, {}, 1.0f);
 
 
 	throw love::Exception("Invalid font file: %s", data->getFilename().c_str());
 	throw love::Exception("Invalid font file: %s", data->getFilename().c_str());
 }
 }
 
 
 Rasterizer *Font::newTrueTypeRasterizer(love::Data *data, int size, TrueTypeRasterizer::Hinting hinting)
 Rasterizer *Font::newTrueTypeRasterizer(love::Data *data, int size, TrueTypeRasterizer::Hinting hinting)
 {
 {
-	return new TrueTypeRasterizer(library, data, size, hinting);
+	float pixeldensity = 1.0f;
+	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
+	if (window != nullptr)
+		pixeldensity = window->getPixelDensity();
+
+	return newTrueTypeRasterizer(data, size, pixeldensity, hinting);
+}
+
+Rasterizer *Font::newTrueTypeRasterizer(love::Data *data, int size, float pixeldensity, TrueTypeRasterizer::Hinting hinting)
+{
+	return new TrueTypeRasterizer(library, data, size, pixeldensity, hinting);
 }
 }
 
 
 const char *Font::getName() const
 const char *Font::getName() const

+ 4 - 3
src/modules/font/freetype/Font.h

@@ -44,11 +44,12 @@ public:
 	virtual ~Font();
 	virtual ~Font();
 
 
 	// Implements Font
 	// Implements Font
-	Rasterizer *newRasterizer(love::filesystem::FileData *data);
-	Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, TrueTypeRasterizer::Hinting hinting);
+	Rasterizer *newRasterizer(love::filesystem::FileData *data) override;
+	Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, TrueTypeRasterizer::Hinting hinting) override;
+	Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, float pixeldensity, TrueTypeRasterizer::Hinting hinting) override;
 
 
 	// Implement Module
 	// Implement Module
-	const char *getName() const;
+	const char *getName() const override;
 
 
 private:
 private:
 
 

+ 7 - 2
src/modules/font/freetype/TrueTypeRasterizer.cpp

@@ -20,9 +20,11 @@
 
 
 // LOVE
 // LOVE
 #include "TrueTypeRasterizer.h"
 #include "TrueTypeRasterizer.h"
-
 #include "common/Exception.h"
 #include "common/Exception.h"
 
 
+// C
+#include <math.h>
+
 namespace love
 namespace love
 {
 {
 namespace font
 namespace font
@@ -30,10 +32,13 @@ namespace font
 namespace freetype
 namespace freetype
 {
 {
 
 
-TrueTypeRasterizer::TrueTypeRasterizer(FT_Library library, love::Data *data, int size, Hinting hinting)
+TrueTypeRasterizer::TrueTypeRasterizer(FT_Library library, love::Data *data, int size, float pixeldensity, Hinting hinting)
 	: data(data)
 	: data(data)
 	, hinting(hinting)
 	, hinting(hinting)
 {
 {
+	this->pixelDensity = pixeldensity;
+	size = floorf(size * pixeldensity + 0.5f);
+
 	if (size <= 0)
 	if (size <= 0)
 		throw love::Exception("Invalid TrueType font size: %d", size);
 		throw love::Exception("Invalid TrueType font size: %d", size);
 
 

+ 1 - 1
src/modules/font/freetype/TrueTypeRasterizer.h

@@ -44,7 +44,7 @@ class TrueTypeRasterizer : public love::font::TrueTypeRasterizer
 {
 {
 public:
 public:
 
 
-	TrueTypeRasterizer(FT_Library library, love::Data *data, int size, Hinting hinting);
+	TrueTypeRasterizer(FT_Library library, love::Data *data, int size, float pixeldensity, Hinting hinting);
 	virtual ~TrueTypeRasterizer();
 	virtual ~TrueTypeRasterizer();
 
 
 	// Implement Rasterizer
 	// Implement Rasterizer

+ 30 - 14
src/modules/font/wrap_Font.cpp

@@ -78,7 +78,13 @@ int w_newTrueTypeRasterizer(lua_State *L)
 		if (hintstr && !TrueTypeRasterizer::getConstant(hintstr, hinting))
 		if (hintstr && !TrueTypeRasterizer::getConstant(hintstr, hinting))
 			return luaL_error(L, "Invalid TrueType font hinting mode: %s", hintstr);
 			return luaL_error(L, "Invalid TrueType font hinting mode: %s", hintstr);
 
 
-		luax_catchexcept(L, [&](){ t = instance()->newTrueTypeRasterizer(size, hinting); });
+		if (lua_isnoneornil(L, 3))
+			luax_catchexcept(L, [&](){ t = instance()->newTrueTypeRasterizer(size, hinting); });
+		else
+		{
+			float pixeldensity = (float) luaL_checknumber(L, 3);
+			luax_catchexcept(L, [&](){ t = instance()->newTrueTypeRasterizer(size, pixeldensity, hinting); });
+		}
 	}
 	}
 	else
 	else
 	{
 	{
@@ -98,10 +104,21 @@ int w_newTrueTypeRasterizer(lua_State *L)
 		if (hintstr && !TrueTypeRasterizer::getConstant(hintstr, hinting))
 		if (hintstr && !TrueTypeRasterizer::getConstant(hintstr, hinting))
 			return luaL_error(L, "Invalid TrueType font hinting mode: %s", hintstr);
 			return luaL_error(L, "Invalid TrueType font hinting mode: %s", hintstr);
 
 
-		luax_catchexcept(L,
-			[&]() { t = instance()->newTrueTypeRasterizer(d, size, hinting); },
-			[&](bool) { d->release(); }
-		);
+		if (lua_isnoneornil(L, 4))
+		{
+			luax_catchexcept(L,
+				[&]() { t = instance()->newTrueTypeRasterizer(d, size, hinting); },
+				[&](bool) { d->release(); }
+			);
+		}
+		else
+		{
+			float pixeldensity = (float) luaL_checknumber(L, 4);
+			luax_catchexcept(L,
+				[&]() { t = instance()->newTrueTypeRasterizer(d, size, pixeldensity, hinting); },
+				[&](bool) { d->release(); }
+			);
+		}
 	}
 	}
 
 
 	luax_pushtype(L, t);
 	luax_pushtype(L, t);
@@ -121,6 +138,7 @@ int w_newBMFontRasterizer(lua_State *L)
 
 
 	filesystem::FileData *d = filesystem::luax_getfiledata(L, 1);
 	filesystem::FileData *d = filesystem::luax_getfiledata(L, 1);
 	std::vector<image::ImageData *> images;
 	std::vector<image::ImageData *> images;
+	float pixeldensity = (float) luaL_optnumber(L, 3, 1.0);
 
 
 	if (lua_istable(L, 2))
 	if (lua_istable(L, 2))
 	{
 	{
@@ -138,17 +156,14 @@ int w_newBMFontRasterizer(lua_State *L)
 	}
 	}
 	else
 	else
 	{
 	{
-		for (int i = 2; i <= lua_gettop(L); i++)
-		{
-			convimagedata(L, i);
-			image::ImageData *id = luax_checktype<image::ImageData>(L, i);
-			images.push_back(id);
-			id->retain();
-		}
+		convimagedata(L, 2);
+		image::ImageData *id = luax_checktype<image::ImageData>(L, 2);
+		images.push_back(id);
+		id->retain();
 	}
 	}
 
 
 	luax_catchexcept(L,
 	luax_catchexcept(L,
-		[&]() { t = instance()->newBMFontRasterizer(d, images); },
+		[&]() { t = instance()->newBMFontRasterizer(d, images, pixeldensity); },
 		[&](bool) { d->release(); for (auto id : images) id->release(); }
 		[&](bool) { d->release(); for (auto id : images) id->release(); }
 	);
 	);
 
 
@@ -166,8 +181,9 @@ int w_newImageRasterizer(lua_State *L)
 	image::ImageData *d = luax_checktype<image::ImageData>(L, 1);
 	image::ImageData *d = luax_checktype<image::ImageData>(L, 1);
 	std::string glyphs = luax_checkstring(L, 2);
 	std::string glyphs = luax_checkstring(L, 2);
 	int extraspacing = (int) luaL_optnumber(L, 3, 0);
 	int extraspacing = (int) luaL_optnumber(L, 3, 0);
+	float pixeldensity = (float) luaL_optnumber(L, 4, 1.0);
 
 
-	luax_catchexcept(L, [&](){ t = instance()->newImageRasterizer(d, glyphs, extraspacing); });
+	luax_catchexcept(L, [&](){ t = instance()->newImageRasterizer(d, glyphs, extraspacing, pixeldensity); });
 
 
 	luax_pushtype(L, t);
 	luax_pushtype(L, t);
 	t->release();
 	t->release();

+ 5 - 2
src/modules/graphics/Graphics.h

@@ -290,14 +290,14 @@ public:
 	/**
 	/**
 	 * Sets the current graphics display viewport dimensions.
 	 * Sets the current graphics display viewport dimensions.
 	 **/
 	 **/
-	virtual void setViewportSize(int width, int height) = 0;
+	virtual void setViewportSize(int width, int height, int pixelwidth, int pixelheight) = 0;
 
 
 	/**
 	/**
 	 * Sets the current graphics display viewport and initializes the renderer.
 	 * Sets the current graphics display viewport and initializes the renderer.
 	 * @param width The viewport width.
 	 * @param width The viewport width.
 	 * @param height The viewport height.
 	 * @param height The viewport height.
 	 **/
 	 **/
-	virtual bool setMode(int width, int height) = 0;
+	virtual bool setMode(int width, int height, int pixelwidth, int pixelheight) = 0;
 
 
 	/**
 	/**
 	 * Un-sets the current graphics display mode (uninitializing objects if
 	 * Un-sets the current graphics display mode (uninitializing objects if
@@ -319,6 +319,9 @@ public:
 	 **/
 	 **/
 	virtual bool isActive() const = 0;
 	virtual bool isActive() const = 0;
 
 
+	virtual int getWidth() const = 0;
+	virtual int getHeight() const = 0;
+
 	virtual bool isCanvasActive() const = 0;
 	virtual bool isCanvasActive() const = 0;
 
 
 	virtual void flushStreamDraws() = 0;
 	virtual void flushStreamDraws() = 0;

+ 13 - 12
src/modules/graphics/Quad.cpp

@@ -46,12 +46,13 @@ Quad::~Quad()
 void Quad::refresh(const Quad::Viewport &v, double sw, double sh)
 void Quad::refresh(const Quad::Viewport &v, double sw, double sh)
 {
 {
 	viewport = v;
 	viewport = v;
+	this->sw = sw;
+	this->sh = sh;
 
 
 	// Vertices are ordered for use with triangle strips:
 	// Vertices are ordered for use with triangle strips:
-	// 0----2
-	// |  / |
-	// | /  |
-	// 1----3
+	// 0---2
+	// | / |
+	// 1---3
 	vertices[0].x = 0.0f;
 	vertices[0].x = 0.0f;
 	vertices[0].y = 0.0f;
 	vertices[0].y = 0.0f;
 	vertices[1].x = 0.0f;
 	vertices[1].x = 0.0f;
@@ -61,14 +62,14 @@ void Quad::refresh(const Quad::Viewport &v, double sw, double sh)
 	vertices[3].x = (float) v.w;
 	vertices[3].x = (float) v.w;
 	vertices[3].y = (float) v.h;
 	vertices[3].y = (float) v.h;
 
 
-	vertices[0].s = (float) (v.x/sw);
-	vertices[0].t = (float) (v.y/sh);
-	vertices[1].s = (float) (v.x/sw);
-	vertices[1].t = (float) ((v.y+v.h)/sh);
-	vertices[2].s = (float) ((v.x+v.w)/sw);
-	vertices[2].t = (float) (v.y/sh);
-	vertices[3].s = (float) ((v.x+v.w)/sw);
-	vertices[3].t = (float) ((v.y+v.h)/sh);
+	vertices[0].s = (float) (v.x / sw);
+	vertices[0].t = (float) (v.y / sh);
+	vertices[1].s = (float) (v.x / sw);
+	vertices[1].t = (float) ((v.y + v.h) / sh);
+	vertices[2].s = (float) ((v.x + v.w) / sw);
+	vertices[2].t = (float) (v.y / sh);
+	vertices[3].s = (float) ((v.x + v.w) / sw);
+	vertices[3].t = (float) ((v.y + v.h) / sh);
 }
 }
 
 
 void Quad::setViewport(const Quad::Viewport &v)
 void Quad::setViewport(const Quad::Viewport &v)

+ 17 - 0
src/modules/graphics/Texture.cpp

@@ -33,6 +33,8 @@ Texture::Texture()
 	: format(PIXELFORMAT_UNKNOWN)
 	: format(PIXELFORMAT_UNKNOWN)
 	, width(0)
 	, width(0)
 	, height(0)
 	, height(0)
+	, pixelWidth(0)
+	, pixelHeight(0)
 	, filter(getDefaultFilter())
 	, filter(getDefaultFilter())
 	, wrap()
 	, wrap()
 	, vertices()
 	, vertices()
@@ -90,6 +92,21 @@ int Texture::getHeight() const
 	return height;
 	return height;
 }
 }
 
 
+int Texture::getPixelWidth() const
+{
+	return pixelWidth;
+}
+
+int Texture::getPixelHeight() const
+{
+	return pixelHeight;
+}
+
+float Texture::getPixelDensity() const
+{
+	return (float) pixelHeight / (float) height;
+}
+
 const Texture::Filter &Texture::getFilter() const
 const Texture::Filter &Texture::getFilter() const
 {
 {
 	return filter;
 	return filter;

+ 8 - 0
src/modules/graphics/Texture.h

@@ -94,6 +94,11 @@ public:
 	virtual int getWidth() const;
 	virtual int getWidth() const;
 	virtual int getHeight() const;
 	virtual int getHeight() const;
 
 
+	virtual int getPixelWidth() const;
+	virtual int getPixelHeight() const;
+
+	float getPixelDensity() const;
+
 	virtual void setFilter(const Filter &f) = 0;
 	virtual void setFilter(const Filter &f) = 0;
 	virtual const Filter &getFilter() const;
 	virtual const Filter &getFilter() const;
 
 
@@ -125,6 +130,9 @@ protected:
 	int width;
 	int width;
 	int height;
 	int height;
 
 
+	int pixelWidth;
+	int pixelHeight;
+
 	Filter filter;
 	Filter filter;
 	Wrap wrap;
 	Wrap wrap;
 
 

+ 22 - 25
src/modules/graphics/opengl/Canvas.cpp

@@ -100,35 +100,32 @@ static bool createMSAABuffer(int width, int height, int &samples, PixelFormat pi
 love::Type Canvas::type("Canvas", &Texture::type);
 love::Type Canvas::type("Canvas", &Texture::type);
 int Canvas::canvasCount = 0;
 int Canvas::canvasCount = 0;
 
 
-Canvas::Canvas(int width, int height, PixelFormat format, int msaa)
-	: fbo(0)
+Canvas::Canvas(int width, int height, const Settings &settings)
+	: settings(settings)
+	, fbo(0)
 	, texture(0)
 	, texture(0)
     , msaa_buffer(0)
     , msaa_buffer(0)
-	, requested_format(format)
-    , requested_samples(msaa)
 	, actual_samples(0)
 	, actual_samples(0)
 	, texture_memory(0)
 	, texture_memory(0)
 {
 {
 	this->width = width;
 	this->width = width;
 	this->height = height;
 	this->height = height;
-
-	float w = static_cast<float>(width);
-	float h = static_cast<float>(height);
+	this->pixelWidth = (int) ((width * settings.pixeldensity) + 0.5);
+	this->pixelHeight = (int) ((height * settings.pixeldensity) + 0.5);
 
 
 	// Vertices are ordered for use with triangle strips:
 	// Vertices are ordered for use with triangle strips:
-	// 0----2
-	// |  / |
-	// | /  |
-	// 1----3
+	// 0---2
+	// | / |
+	// 1---3
 	// world coordinates
 	// world coordinates
 	vertices[0].x = 0;
 	vertices[0].x = 0;
 	vertices[0].y = 0;
 	vertices[0].y = 0;
 	vertices[1].x = 0;
 	vertices[1].x = 0;
-	vertices[1].y = h;
-	vertices[2].x = w;
+	vertices[1].y = (float) height;
+	vertices[2].x = (float) width;
 	vertices[2].y = 0;
 	vertices[2].y = 0;
-	vertices[3].x = w;
-	vertices[3].y = h;
+	vertices[3].x = (float) width;
+	vertices[3].y = (float) height;
 
 
 	// texture coordinates
 	// texture coordinates
 	vertices[0].s = 0;
 	vertices[0].s = 0;
@@ -140,7 +137,7 @@ Canvas::Canvas(int width, int height, PixelFormat format, int msaa)
 	vertices[3].s = 1;
 	vertices[3].s = 1;
 	vertices[3].t = 1;
 	vertices[3].t = 1;
 
 
-	this->format = getSizedFormat(requested_format);
+	this->format = getSizedFormat(settings.format);
 
 
 	loadVolatile();
 	loadVolatile();
 
 
@@ -165,7 +162,7 @@ bool Canvas::loadVolatile()
 	status = GL_FRAMEBUFFER_COMPLETE;
 	status = GL_FRAMEBUFFER_COMPLETE;
 
 
 	// glTexImage2D is guaranteed to error in this case.
 	// glTexImage2D is guaranteed to error in this case.
-	if (width > gl.getMaxTextureSize() || height > gl.getMaxTextureSize())
+	if (pixelWidth > gl.getMaxTextureSize() || pixelHeight > gl.getMaxTextureSize())
 	{
 	{
 		status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
 		status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
 		return false;
 		return false;
@@ -173,8 +170,8 @@ bool Canvas::loadVolatile()
 
 
 	// getMaxRenderbufferSamples will be 0 on systems that don't support
 	// getMaxRenderbufferSamples will be 0 on systems that don't support
 	// multisampled renderbuffers / don't export FBO multisample extensions.
 	// multisampled renderbuffers / don't export FBO multisample extensions.
-	requested_samples = std::min(requested_samples, gl.getMaxRenderbufferSamples());
-	requested_samples = std::max(requested_samples, 0);
+	settings.msaa = std::min(settings.msaa, gl.getMaxRenderbufferSamples());
+	settings.msaa = std::max(settings.msaa, 0);
 
 
 	glGenTextures(1, &texture);
 	glGenTextures(1, &texture);
 	gl.bindTextureToUnit(texture, 0, false);
 	gl.bindTextureToUnit(texture, 0, false);
@@ -191,8 +188,8 @@ bool Canvas::loadVolatile()
 	while (glGetError() != GL_NO_ERROR)
 	while (glGetError() != GL_NO_ERROR)
 		/* Clear the error buffer. */;
 		/* Clear the error buffer. */;
 
 
-	glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, width, height, 0,
-	             fmt.externalformat, fmt.type, nullptr);
+	glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, pixelWidth, pixelHeight,
+	             0, fmt.externalformat, fmt.type, nullptr);
 
 
 	if (glGetError() != GL_NO_ERROR)
 	if (glGetError() != GL_NO_ERROR)
 	{
 	{
@@ -215,14 +212,14 @@ bool Canvas::loadVolatile()
 		return false;
 		return false;
 	}
 	}
 
 
-	actual_samples = requested_samples == 1 ? 0 : requested_samples;
+	actual_samples = settings.msaa == 1 ? 0 : settings.msaa;
 
 
 	if (actual_samples > 0 && !createMSAABuffer(width, height, actual_samples, format, msaa_buffer))
 	if (actual_samples > 0 && !createMSAABuffer(width, height, actual_samples, format, msaa_buffer))
 		actual_samples = 0;
 		actual_samples = 0;
 
 
 	size_t prevmemsize = texture_memory;
 	size_t prevmemsize = texture_memory;
 
 
-	texture_memory = getPixelFormatSize(format) * width * height;
+	texture_memory = getPixelFormatSize(format) * pixelWidth * pixelHeight;
 	if (msaa_buffer != 0)
 	if (msaa_buffer != 0)
 		texture_memory += (texture_memory * actual_samples);
 		texture_memory += (texture_memory * actual_samples);
 
 
@@ -276,7 +273,7 @@ bool Canvas::setWrap(const Texture::Wrap &w)
 	wrap = w;
 	wrap = w;
 
 
 	if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
 	if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
-		&& (width != nextP2(width) || height != nextP2(height)))
+		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight)))
 	{
 	{
 		if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP)
 		if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP)
 			success = false;
 			success = false;
@@ -306,7 +303,7 @@ ptrdiff_t Canvas::getHandle() const
 
 
 love::image::ImageData *Canvas::newImageData(love::image::Image *module, int x, int y, int w, int h)
 love::image::ImageData *Canvas::newImageData(love::image::Image *module, int x, int y, int w, int h)
 {
 {
-	if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > getWidth() || (y + h) > getHeight())
+	if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > getPixelWidth() || (y + h) > getPixelHeight())
 		throw love::Exception("Invalid rectangle dimensions.");
 		throw love::Exception("Invalid rectangle dimensions.");
 
 
 	Graphics *gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 	Graphics *gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);

+ 11 - 5
src/modules/graphics/opengl/Canvas.h

@@ -45,7 +45,14 @@ public:
 
 
 	static love::Type type;
 	static love::Type type;
 
 
-	Canvas(int width, int height, PixelFormat format = PIXELFORMAT_NORMAL, int msaa = 0);
+    struct Settings
+    {
+        PixelFormat format = PIXELFORMAT_NORMAL;
+        float pixeldensity = 1.0f;
+        int msaa = 0;
+    };
+
+	Canvas(int width, int height, const Settings &settings);
 	virtual ~Canvas();
 	virtual ~Canvas();
 
 
 	// Implements Volatile.
 	// Implements Volatile.
@@ -71,7 +78,7 @@ public:
 
 
 	inline int getRequestedMSAA() const
 	inline int getRequestedMSAA() const
 	{
 	{
-		return requested_samples;
+		return settings.msaa;
 	}
 	}
 
 
 	inline ptrdiff_t getMSAAHandle() const
 	inline ptrdiff_t getMSAAHandle() const
@@ -95,16 +102,15 @@ private:
 
 
 	void drawv(Graphics *gfx, const Matrix4 &t, const Vertex *v) override;
 	void drawv(Graphics *gfx, const Matrix4 &t, const Vertex *v) override;
 
 
+    Settings settings;
+
 	GLuint fbo;
 	GLuint fbo;
 
 
 	GLuint texture;
 	GLuint texture;
 	GLuint msaa_buffer;
 	GLuint msaa_buffer;
 
 
-	PixelFormat requested_format;
-
 	GLenum status;
 	GLenum status;
 
 
-	int requested_samples;
 	int actual_samples;
 	int actual_samples;
 
 
 	size_t texture_memory;
 	size_t texture_memory;

+ 50 - 38
src/modules/graphics/opengl/Font.cpp

@@ -47,19 +47,20 @@ static inline uint16 normToUint16(double n)
 love::Type Font::type("Font", &Object::type);
 love::Type Font::type("Font", &Object::type);
 int Font::fontCount = 0;
 int Font::fontCount = 0;
 
 
-Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
+Font::Font(love::font::Rasterizer *r, const Texture::Filter &f)
 	: rasterizers({r})
 	: rasterizers({r})
 	, height(r->getHeight())
 	, height(r->getHeight())
 	, lineHeight(1)
 	, lineHeight(1)
 	, textureWidth(128)
 	, textureWidth(128)
 	, textureHeight(128)
 	, textureHeight(128)
-	, filter(filter)
+	, filter(f)
+	, pixelDensity(r->getPixelDensity())
 	, useSpacesAsTab(false)
 	, useSpacesAsTab(false)
 	, quadIndices(20) // We make this bigger at draw-time, if needed.
 	, quadIndices(20) // We make this bigger at draw-time, if needed.
 	, textureCacheID(0)
 	, textureCacheID(0)
 	, textureMemorySize(0)
 	, textureMemorySize(0)
 {
 {
-	this->filter.mipmap = Texture::FILTER_NONE;
+	filter.mipmap = Texture::FILTER_NONE;
 
 
 	// Try to find the best texture size match for the font size. default to the
 	// Try to find the best texture size match for the font size. default to the
 	// largest texture size if no rough match is found.
 	// largest texture size if no rough match is found.
@@ -100,9 +101,10 @@ Font::TextureSize Font::getNextTextureSize() const
 {
 {
 	TextureSize size = {textureWidth, textureHeight};
 	TextureSize size = {textureWidth, textureHeight};
 
 
-	int maxsize = std::min(4096, gl.getMaxTextureSize());
+	int maxwidth  = std::min(8192, gl.getMaxTextureSize());
+	int maxheight = std::min(4096, gl.getMaxTextureSize());
 
 
-	if (size.width * 2 <= maxsize || size.height * 2 <= maxsize)
+	if (size.width * 2 <= maxwidth || size.height * 2 <= maxheight)
 	{
 	{
 		// {128, 128} -> {256, 128} -> {256, 256} -> {512, 256} -> etc.
 		// {128, 128} -> {256, 128} -> {256, 256} -> {512, 256} -> etc.
 		if (size.width == size.height)
 		if (size.width == size.height)
@@ -114,7 +116,7 @@ Font::TextureSize Font::getNextTextureSize() const
 	return size;
 	return size;
 }
 }
 
 
-void Font::createTexture()
+bool Font::createTexture()
 {
 {
 	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 	gfx->flushStreamDraws();
 	gfx->flushStreamDraws();
@@ -200,6 +202,8 @@ void Font::createTexture()
 	}
 	}
 	else
 	else
 		textures.push_back(t);
 		textures.push_back(t);
+
+	return true;
 }
 }
 
 
 love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph)
 love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph)
@@ -236,32 +240,35 @@ const Font::Glyph &Font::addGlyph(uint32 glyph)
 	int w = gd->getWidth();
 	int w = gd->getWidth();
 	int h = gd->getHeight();
 	int h = gd->getHeight();
 
 
-	if (textureX + w + TEXTURE_PADDING > textureWidth)
+	if (w + TEXTURE_PADDING * 2 < textureWidth && h + TEXTURE_PADDING * 2 < textureHeight)
 	{
 	{
-		// out of space - new row!
-		textureX = TEXTURE_PADDING;
-		textureY += rowHeight;
-		rowHeight = TEXTURE_PADDING;
-	}
+		if (textureX + w + TEXTURE_PADDING > textureWidth)
+		{
+			// Out of space - new row!
+			textureX = TEXTURE_PADDING;
+			textureY += rowHeight;
+			rowHeight = TEXTURE_PADDING;
+		}
 
 
-	if (textureY + h + TEXTURE_PADDING > textureHeight)
-	{
-		// totally out of space - new texture!
-		createTexture();
+		if (textureY + h + TEXTURE_PADDING > textureHeight)
+		{
+			// Totally out of space - new texture!
+			createTexture();
 
 
-		// Makes sure the above code for checking if the glyph can fit at
-		// the current position in the texture is run again for this glyph.
-		return addGlyph(glyph);
+			// Makes sure the above code for checking if the glyph can fit at
+			// the current position in the texture is run again for this glyph.
+			return addGlyph(glyph);
+		}
 	}
 	}
 
 
 	Glyph g;
 	Glyph g;
 
 
 	g.texture = 0;
 	g.texture = 0;
-	g.spacing = gd->getAdvance();
+	g.spacing = floorf(gd->getAdvance() / pixelDensity + 0.5f);
 
 
 	memset(g.vertices, 0, sizeof(GlyphVertex) * 4);
 	memset(g.vertices, 0, sizeof(GlyphVertex) * 4);
 
 
-	// don't waste space for empty glyphs. also fixes a divide by zero bug with ATI drivers
+	// Don't waste space for empty glyphs.
 	if (w > 0 && h > 0)
 	if (w > 0 && h > 0)
 	{
 	{
 		bool isSRGB = isGammaCorrect();
 		bool isSRGB = isGammaCorrect();
@@ -278,31 +285,31 @@ const Font::Glyph &Font::addGlyph(uint32 glyph)
 
 
 		Color c(255, 255, 255, 255);
 		Color c(255, 255, 255, 255);
 
 
-		// 0----2
-		// |  / |
-		// | /  |
-		// 1----3
-		const GlyphVertex verts[4] = {
-			{float(0), float(0), normToUint16((tX+0)/tWidth), normToUint16((tY+0)/tHeight), c},
-			{float(0), float(h), normToUint16((tX+0)/tWidth), normToUint16((tY+h)/tHeight), c},
-			{float(w), float(0), normToUint16((tX+w)/tWidth), normToUint16((tY+0)/tHeight), c},
-			{float(w), float(h), normToUint16((tX+w)/tWidth), normToUint16((tY+h)/tHeight), c}
+		// 0---2
+		// | / |
+		// 1---3
+		const GlyphVertex verts[4] =
+		{
+			{0.0f,           0.0f,           normToUint16((tX+0)/tWidth), normToUint16((tY+0)/tHeight), c},
+			{0.0f,           h/pixelDensity, normToUint16((tX+0)/tWidth), normToUint16((tY+h)/tHeight), c},
+			{w/pixelDensity, 0.0f,           normToUint16((tX+w)/tWidth), normToUint16((tY+0)/tHeight), c},
+			{w/pixelDensity, h/pixelDensity, normToUint16((tX+w)/tWidth), normToUint16((tY+h)/tHeight), c}
 		};
 		};
 
 
 		// Copy vertex data to the glyph and set proper bearing.
 		// Copy vertex data to the glyph and set proper bearing.
 		for (int i = 0; i < 4; i++)
 		for (int i = 0; i < 4; i++)
 		{
 		{
 			g.vertices[i] = verts[i];
 			g.vertices[i] = verts[i];
-			g.vertices[i].x += gd->getBearingX();
-			g.vertices[i].y -= gd->getBearingY();
+			g.vertices[i].x += gd->getBearingX() / pixelDensity;
+			g.vertices[i].y -= gd->getBearingY() / pixelDensity;
 		}
 		}
 
 
 		textureX += w + TEXTURE_PADDING;
 		textureX += w + TEXTURE_PADDING;
 		rowHeight = std::max(rowHeight, h + TEXTURE_PADDING);
 		rowHeight = std::max(rowHeight, h + TEXTURE_PADDING);
 	}
 	}
 
 
-	const auto p = glyphs.insert(std::make_pair(glyph, g));
-	return p.first->second;
+	glyphs[glyph] = g;
+	return glyphs[glyph];
 }
 }
 
 
 const Font::Glyph &Font::findGlyph(uint32 glyph)
 const Font::Glyph &Font::findGlyph(uint32 glyph)
@@ -329,7 +336,7 @@ float Font::getKerning(uint32 leftglyph, uint32 rightglyph)
 	{
 	{
 		if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph))
 		if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph))
 		{
 		{
-			k = r->getKerning(leftglyph, rightglyph);
+			k = floorf(r->getKerning(leftglyph, rightglyph) / pixelDensity + 0.5f);
 			break;
 			break;
 		}
 		}
 	}
 	}
@@ -390,7 +397,7 @@ void Font::getCodepointsFromString(const std::vector<ColoredString> &strs, Color
 
 
 float Font::getHeight() const
 float Font::getHeight() const
 {
 {
-	return (float) height;
+	return (float) floorf(height / pixelDensity + 0.5f);
 }
 }
 
 
 std::vector<Font::DrawCommand> Font::generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantcolor, std::vector<GlyphVertex> &vertices, float extra_spacing, Vector offset, TextInfo *info)
 std::vector<Font::DrawCommand> Font::generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantcolor, std::vector<GlyphVertex> &vertices, float extra_spacing, Vector offset, TextInfo *info)
@@ -945,12 +952,12 @@ void Font::unloadVolatile()
 
 
 int Font::getAscent() const
 int Font::getAscent() const
 {
 {
-	return rasterizers[0]->getAscent();
+	return floorf(rasterizers[0]->getAscent() / pixelDensity + 0.5f);
 }
 }
 
 
 int Font::getDescent() const
 int Font::getDescent() const
 {
 {
-	return rasterizers[0]->getDescent();
+	return floorf(rasterizers[0]->getDescent() / pixelDensity + 0.5f);
 }
 }
 
 
 float Font::getBaseline() const
 float Font::getBaseline() const
@@ -1014,6 +1021,11 @@ void Font::setFallbacks(const std::vector<Font *> &fallbacks)
 		rasterizers.push_back(f->rasterizers[0]);
 		rasterizers.push_back(f->rasterizers[0]);
 }
 }
 
 
+float Font::getPixelDensity() const
+{
+	return pixelDensity;
+}
+
 uint32 Font::getTextureCacheID() const
 uint32 Font::getTextureCacheID() const
 {
 {
 	return textureCacheID;
 	return textureCacheID;

+ 6 - 2
src/modules/graphics/opengl/Font.h

@@ -100,7 +100,7 @@ public:
 		int vertexcount;
 		int vertexcount;
 	};
 	};
 
 
-	Font(love::font::Rasterizer *r, const Texture::Filter &filter = Texture::getDefaultFilter());
+	Font(love::font::Rasterizer *r, const Texture::Filter &filter);
 
 
 	virtual ~Font();
 	virtual ~Font();
 
 
@@ -179,6 +179,8 @@ public:
 
 
 	void setFallbacks(const std::vector<Font *> &fallbacks);
 	void setFallbacks(const std::vector<Font *> &fallbacks);
 
 
+	float getPixelDensity() const;
+
 	uint32 getTextureCacheID() const;
 	uint32 getTextureCacheID() const;
 
 
 	static bool getConstant(const char *in, AlignMode &out);
 	static bool getConstant(const char *in, AlignMode &out);
@@ -202,7 +204,7 @@ private:
 	};
 	};
 
 
 	TextureSize getNextTextureSize() const;
 	TextureSize getNextTextureSize() const;
-	void createTexture();
+	bool createTexture();
 	love::font::GlyphData *getRasterizerGlyphData(uint32 glyph);
 	love::font::GlyphData *getRasterizerGlyphData(uint32 glyph);
 	const Glyph &addGlyph(uint32 glyph);
 	const Glyph &addGlyph(uint32 glyph);
 	const Glyph &findGlyph(uint32 glyph);
 	const Glyph &findGlyph(uint32 glyph);
@@ -230,6 +232,8 @@ private:
 
 
 	Texture::Filter filter;
 	Texture::Filter filter;
 
 
+	float pixelDensity;
+
 	int textureX, textureY;
 	int textureX, textureY;
 	int rowHeight;
 	int rowHeight;
 
 

+ 0 - 1
src/modules/graphics/opengl/GLBuffer.cpp

@@ -366,7 +366,6 @@ void QuadIndices::fill()
 	using namespace love::graphics::vertex;
 	using namespace love::graphics::vertex;
 
 
 	fillIndices(TriangleIndexMode::QUADS, 0, maxSize * 4, (T *) indices);
 	fillIndices(TriangleIndexMode::QUADS, 0, maxSize * 4, (T *) indices);
-
 	indexBuffer->fill(0, indexBuffer->getSize(), indices);
 	indexBuffer->fill(0, indexBuffer->getSize(), indices);
 }
 }
 
 

+ 89 - 31
src/modules/graphics/opengl/Graphics.cpp

@@ -56,6 +56,8 @@ Graphics::Graphics()
 	: quadIndices(nullptr)
 	: quadIndices(nullptr)
 	, width(0)
 	, width(0)
 	, height(0)
 	, height(0)
+	, pixelWidth(0)
+	, pixelHeight(0)
 	, created(false)
 	, created(false)
 	, active(true)
 	, active(true)
 	, writingToStencil(false)
 	, writingToStencil(false)
@@ -74,10 +76,11 @@ Graphics::Graphics()
 
 
 		if (window->isOpen())
 		if (window->isOpen())
 		{
 		{
-			int w = 0, h = 0;
-			window->getPixelDimensions(w, h);
+			double w = window->getWidth();
+			double h = window->getHeight();
+			window->windowToDPICoords(&w, &h);
 
 
-			setMode(w, h);
+			setMode((int) w, (int) h, window->getPixelWidth(), window->getPixelHeight());
 		}
 		}
 	}
 	}
 }
 }
@@ -221,22 +224,45 @@ void Graphics::checkSetDefaultFont()
 	states.back().font.set(defaultFont.get());
 	states.back().font.set(defaultFont.get());
 }
 }
 
 
-void Graphics::setViewportSize(int width, int height)
+double Graphics::getCurrentPixelDensity() const
+{
+	if (states.back().canvases.size() > 0)
+	{
+		Canvas *c = states.back().canvases[0];
+		return (double) c->getPixelHeight() / (double) c->getHeight();
+	}
+
+	return getScreenPixelDensity();
+}
+
+double Graphics::getScreenPixelDensity() const
+{
+	return (double) getPixelHeight() / (double) getHeight();
+}
+
+void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelheight)
 {
 {
 	this->width = width;
 	this->width = width;
 	this->height = height;
 	this->height = height;
+	this->pixelWidth = pixelwidth;
+	this->pixelHeight = pixelheight;
 
 
 	if (states.back().canvases.empty())
 	if (states.back().canvases.empty())
 	{
 	{
 		// Set the viewport to top-left corner.
 		// Set the viewport to top-left corner.
-		gl.setViewport({0, 0, width, height}, false);
+		gl.setViewport({0, 0, pixelwidth, pixelheight});
+
+		// Re-apply the scissor if it was active, since the rectangle passed to
+		// glScissor is affected by the viewport dimensions.
+		if (states.back().scissor)
+			setScissor(states.back().scissorRect);
 
 
 		// Set up the projection matrix
 		// Set up the projection matrix
 		projectionMatrix = Matrix4::ortho(0.0, (float) width, (float) height, 0.0);
 		projectionMatrix = Matrix4::ortho(0.0, (float) width, (float) height, 0.0);
 	}
 	}
 }
 }
 
 
-bool Graphics::setMode(int width, int height)
+bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight)
 {
 {
 	this->width = width;
 	this->width = width;
 	this->height = height;
 	this->height = height;
@@ -247,6 +273,8 @@ bool Graphics::setMode(int width, int height)
 
 
 	created = true;
 	created = true;
 
 
+	setViewportSize(width, height, pixelwidth, pixelheight);
+
 	// Enable blending
 	// Enable blending
 	glEnable(GL_BLEND);
 	glEnable(GL_BLEND);
 
 
@@ -310,8 +338,6 @@ bool Graphics::setMode(int width, int height)
 	if (quadIndices == nullptr)
 	if (quadIndices == nullptr)
 		quadIndices = new QuadIndices(20);
 		quadIndices = new QuadIndices(20);
 
 
-	setViewportSize(width, height);
-
 	// Restore the graphics state.
 	// Restore the graphics state.
 	restoreState(states.back());
 	restoreState(states.back());
 
 
@@ -608,13 +634,15 @@ void Graphics::setCanvas(const std::vector<Canvas *> &canvases)
 	PixelFormat firstformat = firstcanvas->getPixelFormat();
 	PixelFormat firstformat = firstcanvas->getPixelFormat();
 
 
 	bool hasSRGBcanvas = firstformat == PIXELFORMAT_sRGBA8;
 	bool hasSRGBcanvas = firstformat == PIXELFORMAT_sRGBA8;
+	int pixelwidth = firstcanvas->getPixelWidth();
+	int pixelheight = firstcanvas->getPixelHeight();
 
 
 	for (int i = 1; i < ncanvases; i++)
 	for (int i = 1; i < ncanvases; i++)
 	{
 	{
 		Canvas *c = canvases[i];
 		Canvas *c = canvases[i];
 
 
-		if (c->getWidth() != firstcanvas->getWidth() || c->getHeight() != firstcanvas->getHeight())
-			throw love::Exception("All canvases in must have the same dimensions.");
+		if (c->getPixelWidth() != pixelwidth || c->getPixelHeight() != pixelheight)
+			throw love::Exception("All canvases in must have the same pixel dimensions.");
 
 
 		if (!multiformatsupported && c->getPixelFormat() != firstformat)
 		if (!multiformatsupported && c->getPixelFormat() != firstformat)
 			throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats.");
 			throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats.");
@@ -632,10 +660,15 @@ void Graphics::setCanvas(const std::vector<Canvas *> &canvases)
 
 
 	bindCachedFBO(canvases);
 	bindCachedFBO(canvases);
 
 
+	gl.setViewport({0, 0, pixelwidth, pixelheight});
+
+	// Re-apply the scissor if it was active, since the rectangle passed to
+	// glScissor is affected by the viewport dimensions.
+	if (state.scissor)
+		setScissor(state.scissorRect);
+
 	int w = firstcanvas->getWidth();
 	int w = firstcanvas->getWidth();
 	int h = firstcanvas->getHeight();
 	int h = firstcanvas->getHeight();
-
-	gl.setViewport({0, 0, w, h}, true);
 	projectionMatrix = Matrix4::ortho(0.0, (float) w, 0.0, (float) h);
 	projectionMatrix = Matrix4::ortho(0.0, (float) w, 0.0, (float) h);
 
 
 	// Make sure the correct sRGB setting is used when drawing to the canvases.
 	// Make sure the correct sRGB setting is used when drawing to the canvases.
@@ -683,7 +716,13 @@ void Graphics::setCanvas()
 	state.canvases.clear();
 	state.canvases.clear();
 
 
 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO());
 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO());
-	gl.setViewport({0, 0, width, height}, false);
+
+	gl.setViewport({0, 0, pixelWidth, pixelHeight});
+
+	// Re-apply the scissor if it was active, since the rectangle passed to
+	// glScissor is affected by the viewport dimensions.
+	if (state.scissor)
+		setScissor(state.scissorRect);
 
 
 	// The projection matrix is flipped compared to rendering to a canvas, due
 	// The projection matrix is flipped compared to rendering to a canvas, due
 	// to OpenGL considering (0,0) bottom-left instead of top-left.
 	// to OpenGL considering (0,0) bottom-left instead of top-left.
@@ -723,8 +762,8 @@ void Graphics::endPass()
 	// Resolve MSAA buffers.
 	// Resolve MSAA buffers.
 	if (canvases.size() > 0 && canvases[0]->getMSAA() > 1)
 	if (canvases.size() > 0 && canvases[0]->getMSAA() > 1)
 	{
 	{
-		int w = canvases[0]->getWidth();
-		int h = canvases[0]->getHeight();
+		int w = canvases[0]->getPixelWidth();
+		int h = canvases[0]->getPixelHeight();
 
 
 		for (int i = 0; i < (int) canvases.size(); i++)
 		for (int i = 0; i < (int) canvases.size(); i++)
 		{
 		{
@@ -909,8 +948,8 @@ void Graphics::bindCachedFBO(const std::vector<Canvas *> &canvases)
 	}
 	}
 	else
 	else
 	{
 	{
-		int w = canvases[0]->getWidth();
-		int h = canvases[0]->getHeight();
+		int w = canvases[0]->getPixelWidth();
+		int h = canvases[0]->getPixelHeight();
 		int msaa = std::max(canvases[0]->getMSAA(), 1);
 		int msaa = std::max(canvases[0]->getMSAA(), 1);
 
 
 		glGenFramebuffers(1, &fbo);
 		glGenFramebuffers(1, &fbo);
@@ -1054,8 +1093,8 @@ void Graphics::present(void *screenshotCallbackData)
 
 
 	if (!pendingScreenshotCallbacks.empty())
 	if (!pendingScreenshotCallbacks.empty())
 	{
 	{
-		int w = getWidth();
-		int h = getHeight();
+		int w = getPixelWidth();
+		int h = getPixelHeight();
 
 
 		size_t row = 4 * w;
 		size_t row = 4 * w;
 		size_t size = row * h;
 		size_t size = row * h;
@@ -1170,6 +1209,16 @@ int Graphics::getHeight() const
 	return height;
 	return height;
 }
 }
 
 
+int Graphics::getPixelWidth() const
+{
+	return pixelWidth;
+}
+
+int Graphics::getPixelHeight() const
+{
+	return pixelHeight;
+}
+
 bool Graphics::isCreated() const
 bool Graphics::isCreated() const
 {
 {
 	return created;
 	return created;
@@ -1182,8 +1231,17 @@ void Graphics::setScissor(const Rect &rect)
 	DisplayState &state = states.back();
 	DisplayState &state = states.back();
 
 
 	glEnable(GL_SCISSOR_TEST);
 	glEnable(GL_SCISSOR_TEST);
+
+	double density = getCurrentPixelDensity();
+
+	Rect glrect;
+	glrect.x = (int) (rect.x * density);
+	glrect.y = (int) (rect.y * density);
+	glrect.w = (int) (rect.w * density);
+	glrect.h = (int) (rect.h * density);
+
 	// OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor.
 	// OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor.
-	gl.setScissor(rect, !state.canvases.empty());
+	gl.setScissor(glrect, !state.canvases.empty());
 
 
 	state.scissor = true;
 	state.scissor = true;
 	state.scissorRect = rect;
 	state.scissorRect = rect;
@@ -1358,14 +1416,14 @@ void Graphics::clearStencil()
 	glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 	glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 }
 }
 
 
-Image *Graphics::newImage(const std::vector<love::image::ImageData *> &data, const Image::Flags &flags)
+Image *Graphics::newImage(const std::vector<love::image::ImageData *> &data, const Image::Settings &settings)
 {
 {
-	return new Image(data, flags);
+	return new Image(data, settings);
 }
 }
 
 
-Image *Graphics::newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Flags &flags)
+Image *Graphics::newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Settings &settings)
 {
 {
-	return new Image(cdata, flags);
+	return new Image(cdata, settings);
 }
 }
 
 
 Quad *Graphics::newQuad(Quad::Viewport v, double sw, double sh)
 Quad *Graphics::newQuad(Quad::Viewport v, double sw, double sh)
@@ -1388,15 +1446,15 @@ ParticleSystem *Graphics::newParticleSystem(Texture *texture, int size)
 	return new ParticleSystem(texture, size);
 	return new ParticleSystem(texture, size);
 }
 }
 
 
-Canvas *Graphics::newCanvas(int width, int height, PixelFormat format, int msaa)
+Canvas *Graphics::newCanvas(int width, int height, const Canvas::Settings &settings)
 {
 {
 	if (!Canvas::isSupported())
 	if (!Canvas::isSupported())
 		throw love::Exception("Canvases are not supported by your OpenGL drivers!");
 		throw love::Exception("Canvases are not supported by your OpenGL drivers!");
 
 
-	if (!Canvas::isFormatSupported(format))
+	if (!Canvas::isFormatSupported(settings.format))
 	{
 	{
 		const char *fstr = "rgba8";
 		const char *fstr = "rgba8";
-		love::getConstant(Canvas::getSizedFormat(format), fstr);
+		love::getConstant(Canvas::getSizedFormat(settings.format), fstr);
 		throw love::Exception("The %s canvas format is not supported by your OpenGL drivers.", fstr);
 		throw love::Exception("The %s canvas format is not supported by your OpenGL drivers.", fstr);
 	}
 	}
 
 
@@ -1405,7 +1463,7 @@ Canvas *Graphics::newCanvas(int width, int height, PixelFormat format, int msaa)
 	else if (height > gl.getMaxTextureSize())
 	else if (height > gl.getMaxTextureSize())
 		throw Exception("Cannot create canvas: height of %d pixels is too large for this system.", height);
 		throw Exception("Cannot create canvas: height of %d pixels is too large for this system.", height);
 
 
-	Canvas *canvas = new Canvas(width, height, format, msaa);
+	Canvas *canvas = new Canvas(width, height, settings);
 	GLenum err = canvas->getStatus();
 	GLenum err = canvas->getStatus();
 
 
 	// everything ok, return canvas (early out)
 	// everything ok, return canvas (early out)
@@ -1447,9 +1505,9 @@ Text *Graphics::newText(Font *font, const std::vector<Font::ColoredString> &text
 	return new Text(font, text);
 	return new Text(font, text);
 }
 }
 
 
-Video *Graphics::newVideo(love::video::VideoStream *stream)
+Video *Graphics::newVideo(love::video::VideoStream *stream, float pixeldensity)
 {
 {
-	return new Video(stream);
+	return new Video(stream, pixeldensity);
 }
 }
 
 
 bool Graphics::isGammaCorrect() const
 bool Graphics::isGammaCorrect() const
@@ -1690,7 +1748,7 @@ void Graphics::setPointSize(float size)
 	if (streamBufferState.primitiveMode == vertex::PrimitiveMode::POINTS)
 	if (streamBufferState.primitiveMode == vertex::PrimitiveMode::POINTS)
 		flushStreamDraws();
 		flushStreamDraws();
 
 
-	gl.setPointSize(size);
+	gl.setPointSize(size * getCurrentPixelDensity());
 	states.back().pointSize = size;
 	states.back().pointSize = size;
 }
 }
 
 

+ 15 - 14
src/modules/graphics/opengl/Graphics.h

@@ -80,8 +80,8 @@ public:
 	// Implements Module.
 	// Implements Module.
 	const char *getName() const override;
 	const char *getName() const override;
 
 
-	void setViewportSize(int width, int height) override;
-	bool setMode(int width, int height) override;
+	void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override;
+	bool setMode(int width, int height, int pixelwidth, int pixelheight) override;
 	void unSetMode() override;
 	void unSetMode() override;
 
 
 	void setActive(bool active) override;
 	void setActive(bool active) override;
@@ -109,15 +109,13 @@ public:
 	 **/
 	 **/
 	void present(void *screenshotCallbackData);
 	void present(void *screenshotCallbackData);
 
 
-	/**
-	 * Gets the width of the current graphics viewport.
-	 **/
-	int getWidth() const;
+	int getWidth() const override;
+	int getHeight() const override;
+	int getPixelWidth() const;
+	int getPixelHeight() const;
 
 
-	/**
-	 * Gets the height of the current graphics viewport.
-	 **/
-	int getHeight() const;
+	double getCurrentPixelDensity() const;
+	double getScreenPixelDensity() const;
 
 
 	/**
 	/**
 	 * True if a graphics viewport is set.
 	 * True if a graphics viewport is set.
@@ -172,8 +170,8 @@ public:
 	/**
 	/**
 	 * Creates an Image object with padding and/or optimization.
 	 * Creates an Image object with padding and/or optimization.
 	 **/
 	 **/
-	Image *newImage(const std::vector<love::image::ImageData *> &data, const Image::Flags &flags);
-	Image *newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Flags &flags);
+	Image *newImage(const std::vector<love::image::ImageData *> &data, const Image::Settings &settings);
+	Image *newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Settings &settings);
 
 
 	Quad *newQuad(Quad::Viewport v, double sw, double sh);
 	Quad *newQuad(Quad::Viewport v, double sw, double sh);
 
 
@@ -186,7 +184,7 @@ public:
 
 
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 
 
-	Canvas *newCanvas(int width, int height, PixelFormat format = PIXELFORMAT_NORMAL, int msaa = 0);
+	Canvas *newCanvas(int width, int height, const Canvas::Settings &settings);
 
 
 	Shader *newShader(const Shader::ShaderSource &source);
 	Shader *newShader(const Shader::ShaderSource &source);
 
 
@@ -198,7 +196,7 @@ public:
 
 
 	Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
 	Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
 
 
-	Video *newVideo(love::video::VideoStream *stream);
+	Video *newVideo(love::video::VideoStream *stream, float pixeldensity);
 
 
 	bool isGammaCorrect() const;
 	bool isGammaCorrect() const;
 
 
@@ -440,6 +438,9 @@ private:
 
 
 	int width;
 	int width;
 	int height;
 	int height;
+	int pixelWidth;
+	int pixelHeight;
+
 	bool created;
 	bool created;
 	bool active;
 	bool active;
 
 

+ 53 - 47
src/modules/graphics/opengl/Image.cpp

@@ -94,11 +94,11 @@ static bool verifyMipmapLevels(const std::vector<T> &miplevels)
 	return true;
 	return true;
 }
 }
 
 
-Image::Image(const std::vector<love::image::ImageData *> &imagedata, const Flags &flags)
+Image::Image(const std::vector<love::image::ImageData *> &imagedata, const Settings &settings)
 	: texture(0)
 	: texture(0)
 	, mipmapSharpness(defaultMipmapSharpness)
 	, mipmapSharpness(defaultMipmapSharpness)
 	, compressed(false)
 	, compressed(false)
-	, flags(flags)
+	, settings(settings)
 	, sRGB(false)
 	, sRGB(false)
 	, usingDefaultTexture(false)
 	, usingDefaultTexture(false)
 	, textureMemorySize(0)
 	, textureMemorySize(0)
@@ -106,11 +106,14 @@ Image::Image(const std::vector<love::image::ImageData *> &imagedata, const Flags
 	if (imagedata.empty())
 	if (imagedata.empty())
 		throw love::Exception("");
 		throw love::Exception("");
 
 
-	width = imagedata[0]->getWidth();
-	height = imagedata[0]->getHeight();
+	pixelWidth  = imagedata[0]->getWidth();
+	pixelHeight = imagedata[0]->getHeight();
+
+	width  = (int) (pixelWidth / settings.pixeldensity + 0.5);
+	height = (int) (pixelHeight / settings.pixeldensity + 0.5);
 
 
 	if (verifyMipmapLevels(imagedata))
 	if (verifyMipmapLevels(imagedata))
-		this->flags.mipmaps = true;
+		this->settings.mipmaps = true;
 
 
 	for (const auto &id : imagedata)
 	for (const auto &id : imagedata)
 		data.push_back(id);
 		data.push_back(id);
@@ -123,24 +126,27 @@ Image::Image(const std::vector<love::image::ImageData *> &imagedata, const Flags
 	++imageCount;
 	++imageCount;
 }
 }
 
 
-Image::Image(const std::vector<love::image::CompressedImageData *> &compresseddata, const Flags &flags)
+Image::Image(const std::vector<love::image::CompressedImageData *> &compresseddata, const Settings &settings)
 	: texture(0)
 	: texture(0)
 	, mipmapSharpness(defaultMipmapSharpness)
 	, mipmapSharpness(defaultMipmapSharpness)
 	, compressed(true)
 	, compressed(true)
-	, flags(flags)
+	, settings(settings)
 	, sRGB(false)
 	, sRGB(false)
 	, usingDefaultTexture(false)
 	, usingDefaultTexture(false)
 	, textureMemorySize(0)
 	, textureMemorySize(0)
 {
 {
-	width = compresseddata[0]->getWidth(0);
-	height = compresseddata[0]->getHeight(0);
+	pixelWidth  = compresseddata[0]->getWidth(0);
+	pixelHeight = compresseddata[0]->getHeight(0);
+
+	width  = (int) (pixelWidth / settings.pixeldensity + 0.5);
+	height = (int) (pixelHeight / settings.pixeldensity + 0.5);
 
 
 	if (verifyMipmapLevels(compresseddata))
 	if (verifyMipmapLevels(compresseddata))
-		this->flags.mipmaps = true;
-	else if (flags.mipmaps && getMipmapCount(width, height) != compresseddata[0]->getMipmapCount())
+		this->settings.mipmaps = true;
+	else if (settings.mipmaps && getMipmapCount(pixelWidth, pixelHeight) != compresseddata[0]->getMipmapCount())
 	{
 	{
 		if (compresseddata[0]->getMipmapCount() == 1)
 		if (compresseddata[0]->getMipmapCount() == 1)
-			this->flags.mipmaps = false;
+			this->settings.mipmaps = false;
 		else
 		else
 		{
 		{
 			throw love::Exception("Image cannot have mipmaps: compressed image data does not have all required mipmap levels (expected %d, got %d)",
 			throw love::Exception("Image cannot have mipmaps: compressed image data does not have all required mipmap levels (expected %d, got %d)",
@@ -172,10 +178,9 @@ void Image::preload()
 		vertices[i].color = Color(255, 255, 255, 255);
 		vertices[i].color = Color(255, 255, 255, 255);
 
 
 	// Vertices are ordered for use with triangle strips:
 	// Vertices are ordered for use with triangle strips:
-	// 0----2
-	// |  / |
-	// | /  |
-	// 1----3
+	// 0---2
+	// | / |
+	// 1---3
 	vertices[0].x = 0.0f;
 	vertices[0].x = 0.0f;
 	vertices[0].y = 0.0f;
 	vertices[0].y = 0.0f;
 	vertices[1].x = 0.0f;
 	vertices[1].x = 0.0f;
@@ -194,13 +199,13 @@ void Image::preload()
 	vertices[3].s = 1.0f;
 	vertices[3].s = 1.0f;
 	vertices[3].t = 1.0f;
 	vertices[3].t = 1.0f;
 
 
-	if (flags.mipmaps)
+	if (settings.mipmaps)
 		filter.mipmap = defaultMipmapFilter;
 		filter.mipmap = defaultMipmapFilter;
 
 
 	if (!isGammaCorrect())
 	if (!isGammaCorrect())
-		flags.linear = false;
+		settings.linear = false;
 
 
-	if (isGammaCorrect() && !flags.linear)
+	if (isGammaCorrect() && !settings.linear)
 		sRGB = true;
 		sRGB = true;
 	else
 	else
 		sRGB = false;
 		sRGB = false;
@@ -210,7 +215,7 @@ void Image::generateMipmaps()
 {
 {
 	// The GL_GENERATE_MIPMAP texparameter is set in loadVolatile if we don't
 	// The GL_GENERATE_MIPMAP texparameter is set in loadVolatile if we don't
 	// have support for glGenerateMipmap.
 	// have support for glGenerateMipmap.
-	if (flags.mipmaps && !isCompressed() &&
+	if (settings.mipmaps && !isCompressed() &&
 		(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object))
 		(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object))
 	{
 	{
 		if (gl.bugs.generateMipmapsRequiresTexture2DEnable)
 		if (gl.bugs.generateMipmapsRequiresTexture2DEnable)
@@ -239,13 +244,13 @@ void Image::loadFromCompressedData()
 	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB);
 	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB);
 
 
 	if (isGammaCorrect() && !sRGB)
 	if (isGammaCorrect() && !sRGB)
-		flags.linear = true;
+		settings.linear = true;
 
 
 	int count = 1;
 	int count = 1;
 
 
-	if (flags.mipmaps && cdata.size() > 1)
+	if (settings.mipmaps && cdata.size() > 1)
 		count = (int) cdata.size();
 		count = (int) cdata.size();
-	else if (flags.mipmaps)
+	else if (settings.mipmaps)
 		count = cdata[0]->getMipmapCount();
 		count = cdata[0]->getMipmapCount();
 
 
 	for (int i = 0; i < count; i++)
 	for (int i = 0; i < count; i++)
@@ -266,9 +271,9 @@ void Image::loadFromImageData()
 	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB);
 	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB);
 
 
 	if (isGammaCorrect() && !sRGB)
 	if (isGammaCorrect() && !sRGB)
-		flags.linear = true;
+		settings.linear = true;
 
 
-	int mipcount = flags.mipmaps ? (int) data.size() : 1;
+	int mipcount = settings.mipmaps ? (int) data.size() : 1;
 
 
 	for (int i = 0; i < mipcount; i++)
 	for (int i = 0; i < mipcount; i++)
 	{
 	{
@@ -310,16 +315,16 @@ bool Image::loadVolatile()
 		if (sRGB && (GLAD_ES_VERSION_2_0 && GLAD_EXT_sRGB && !GLAD_ES_VERSION_3_0)
 		if (sRGB && (GLAD_ES_VERSION_2_0 && GLAD_EXT_sRGB && !GLAD_ES_VERSION_3_0)
 			&& data.size() <= 1)
 			&& data.size() <= 1)
 		{
 		{
-			flags.mipmaps = false;
+			settings.mipmaps = false;
 			filter.mipmap = FILTER_NONE;
 			filter.mipmap = FILTER_NONE;
 		}
 		}
 	}
 	}
 
 
 	// NPOT textures don't support mipmapping without full NPOT support.
 	// NPOT textures don't support mipmapping without full NPOT support.
 	if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
 	if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
-		&& (width != nextP2(width) || height != nextP2(height)))
+		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight)))
 	{
 	{
-		flags.mipmaps = false;
+		settings.mipmaps = false;
 		filter.mipmap = FILTER_NONE;
 		filter.mipmap = FILTER_NONE;
 	}
 	}
 
 
@@ -334,16 +339,16 @@ bool Image::loadVolatile()
 	setMipmapSharpness(mipmapSharpness);
 	setMipmapSharpness(mipmapSharpness);
 
 
 	// Use a default texture if the size is too big for the system.
 	// Use a default texture if the size is too big for the system.
-	if (width > gl.getMaxTextureSize() || height > gl.getMaxTextureSize())
+	if (pixelWidth > gl.getMaxTextureSize() || pixelHeight > gl.getMaxTextureSize())
 	{
 	{
 		loadDefaultTexture();
 		loadDefaultTexture();
 		return true;
 		return true;
 	}
 	}
 
 
-	if (!flags.mipmaps && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0))
+	if (!settings.mipmaps && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0))
 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
 
 
-	if (flags.mipmaps && !isCompressed() && data.size() <= 1 &&
+	if (settings.mipmaps && !isCompressed() && data.size() <= 1 &&
 		!(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object))
 		!(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object))
 	{
 	{
 		// Auto-generate mipmaps every time the texture is modified, if
 		// Auto-generate mipmaps every time the texture is modified, if
@@ -378,7 +383,7 @@ bool Image::loadVolatile()
 	else
 	else
 		textureMemorySize = data[0]->getSize();
 		textureMemorySize = data[0]->getSize();
 
 
-	if (flags.mipmaps)
+	if (settings.mipmaps)
 		textureMemorySize *= 1.33334;
 		textureMemorySize *= 1.33334;
 
 
 	gl.updateTextureMemorySize(prevmemsize, textureMemorySize);
 	gl.updateTextureMemorySize(prevmemsize, textureMemorySize);
@@ -406,7 +411,7 @@ bool Image::refresh(int xoffset, int yoffset, int w, int h)
 		return false;
 		return false;
 
 
 	if (xoffset < 0 || yoffset < 0 || w <= 0 || h <= 0 ||
 	if (xoffset < 0 || yoffset < 0 || w <= 0 || h <= 0 ||
-		(xoffset + w) > width || (yoffset + h) > height)
+		(xoffset + w) > pixelWidth || (yoffset + h) > pixelHeight)
 	{
 	{
 		throw love::Exception("Invalid rectangle dimensions.");
 		throw love::Exception("Invalid rectangle dimensions.");
 	}
 	}
@@ -424,7 +429,7 @@ bool Image::refresh(int xoffset, int yoffset, int w, int h)
 	bool isSRGB = sRGB;
 	bool isSRGB = sRGB;
 	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, isSRGB);
 	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, isSRGB);
 
 
-	int mipcount = flags.mipmaps ? (int) data.size() : 1;
+	int mipcount = settings.mipmaps ? (int) data.size() : 1;
 
 
 	// Reupload the sub-rectangle of each mip level (if we have custom mipmaps.)
 	// Reupload the sub-rectangle of each mip level (if we have custom mipmaps.)
 	for (int i = 0; i < mipcount; i++)
 	for (int i = 0; i < mipcount; i++)
@@ -465,9 +470,9 @@ const std::vector<StrongRef<love::image::CompressedImageData>> &Image::getCompre
 
 
 void Image::setFilter(const Texture::Filter &f)
 void Image::setFilter(const Texture::Filter &f)
 {
 {
-	if (!validateFilter(f, flags.mipmaps))
+	if (!validateFilter(f, settings.mipmaps))
 	{
 	{
-		if (f.mipmap != FILTER_NONE && !flags.mipmaps)
+		if (f.mipmap != FILTER_NONE && !settings.mipmaps)
 			throw love::Exception("Non-mipmapped image cannot have mipmap filtering.");
 			throw love::Exception("Non-mipmapped image cannot have mipmap filtering.");
 		else
 		else
 			throw love::Exception("Invalid texture filter.");
 			throw love::Exception("Invalid texture filter.");
@@ -500,7 +505,7 @@ bool Image::setWrap(const Texture::Wrap &w)
 	wrap = w;
 	wrap = w;
 
 
 	if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
 	if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
-		&& (width != nextP2(width) || height != nextP2(height)))
+		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight)))
 	{
 	{
 		if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP)
 		if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP)
 			success = false;
 			success = false;
@@ -543,9 +548,9 @@ float Image::getMipmapSharpness() const
 	return mipmapSharpness;
 	return mipmapSharpness;
 }
 }
 
 
-const Image::Flags &Image::getFlags() const
+const Image::Settings &Image::getFlags() const
 {
 {
-	return flags;
+	return settings;
 }
 }
 
 
 void Image::setDefaultMipmapSharpness(float sharpness)
 void Image::setDefaultMipmapSharpness(float sharpness)
@@ -583,23 +588,24 @@ bool Image::hasSRGBSupport()
 	return GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB || GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB;
 	return GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB || GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB;
 }
 }
 
 
-bool Image::getConstant(const char *in, FlagType &out)
+bool Image::getConstant(const char *in, SettingType &out)
 {
 {
-	return flagNames.find(in, out);
+	return settingTypes.find(in, out);
 }
 }
 
 
-bool Image::getConstant(FlagType in, const char *&out)
+bool Image::getConstant(SettingType in, const char *&out)
 {
 {
-	return flagNames.find(in, out);
+	return settingTypes.find(in, out);
 }
 }
 
 
-StringMap<Image::FlagType, Image::FLAG_TYPE_MAX_ENUM>::Entry Image::flagNameEntries[] =
+StringMap<Image::SettingType, Image::SETTING_MAX_ENUM>::Entry Image::settingTypeEntries[] =
 {
 {
-	{"mipmaps", FLAG_TYPE_MIPMAPS},
-	{"linear", FLAG_TYPE_LINEAR},
+	{ "mipmaps",      SETTING_MIPMAPS      },
+	{ "linear",       SETTING_LINEAR       },
+	{ "pixeldensity", SETTING_PIXELDENSITY },
 };
 };
 
 
-StringMap<Image::FlagType, Image::FLAG_TYPE_MAX_ENUM> Image::flagNames(Image::flagNameEntries, sizeof(Image::flagNameEntries));
+StringMap<Image::SettingType, Image::SETTING_MAX_ENUM> Image::settingTypes(Image::settingTypeEntries, sizeof(Image::settingTypeEntries));
 
 
 } // opengl
 } // opengl
 } // graphics
 } // graphics

+ 16 - 14
src/modules/graphics/opengl/Image.h

@@ -54,17 +54,19 @@ public:
 
 
 	static love::Type type;
 	static love::Type type;
 
 
-	enum FlagType
+	enum SettingType
 	{
 	{
-		FLAG_TYPE_MIPMAPS,
-		FLAG_TYPE_LINEAR,
-		FLAG_TYPE_MAX_ENUM
+		SETTING_MIPMAPS,
+		SETTING_LINEAR,
+		SETTING_PIXELDENSITY,
+		SETTING_MAX_ENUM
 	};
 	};
 
 
-	struct Flags
+	struct Settings
 	{
 	{
 		bool mipmaps = false;
 		bool mipmaps = false;
 		bool linear = false;
 		bool linear = false;
+		float pixeldensity = 1.0f;
 	};
 	};
 
 
 	/**
 	/**
@@ -75,14 +77,14 @@ public:
 	 * array is a mipmap level. If more than the base level is present, all
 	 * array is a mipmap level. If more than the base level is present, all
 	 * mip levels must be present.
 	 * mip levels must be present.
 	 **/
 	 **/
-	Image(const std::vector<love::image::ImageData *> &data, const Flags &flags);
+	Image(const std::vector<love::image::ImageData *> &data, const Settings &settings);
 
 
 	/**
 	/**
 	 * Creates a new Image with compressed image data.
 	 * Creates a new Image with compressed image data.
 	 *
 	 *
 	 * @param cdata The compressed data from which to load the image.
 	 * @param cdata The compressed data from which to load the image.
 	 **/
 	 **/
-	Image(const std::vector<love::image::CompressedImageData *> &cdata, const Flags &flags);
+	Image(const std::vector<love::image::CompressedImageData *> &cdata, const Settings &settings);
 
 
 	virtual ~Image();
 	virtual ~Image();
 
 
@@ -112,7 +114,7 @@ public:
 	 **/
 	 **/
 	bool refresh(int xoffset, int yoffset, int w, int h);
 	bool refresh(int xoffset, int yoffset, int w, int h);
 
 
-	const Flags &getFlags() const;
+	const Settings &getFlags() const;
 
 
 	static void setDefaultMipmapSharpness(float sharpness);
 	static void setDefaultMipmapSharpness(float sharpness);
 	static float getDefaultMipmapSharpness();
 	static float getDefaultMipmapSharpness();
@@ -122,8 +124,8 @@ public:
 	static bool isFormatSupported(PixelFormat pixelformat);
 	static bool isFormatSupported(PixelFormat pixelformat);
 	static bool hasSRGBSupport();
 	static bool hasSRGBSupport();
 
 
-	static bool getConstant(const char *in, FlagType &out);
-	static bool getConstant(FlagType in, const char *&out);
+	static bool getConstant(const char *in, SettingType &out);
+	static bool getConstant(SettingType in, const char *&out);
 
 
 	static int imageCount;
 	static int imageCount;
 
 
@@ -154,8 +156,8 @@ private:
 	// Whether this Image is using a compressed texture.
 	// Whether this Image is using a compressed texture.
 	bool compressed;
 	bool compressed;
 
 
-	// The flags used to initialize this Image.
-	Flags flags;
+	// The settings used to initialize this Image.
+	Settings settings;
 
 
 	bool sRGB;
 	bool sRGB;
 
 
@@ -170,8 +172,8 @@ private:
 	static FilterMode defaultMipmapFilter;
 	static FilterMode defaultMipmapFilter;
 	static float defaultMipmapSharpness;
 	static float defaultMipmapSharpness;
 
 
-	static StringMap<FlagType, FLAG_TYPE_MAX_ENUM>::Entry flagNameEntries[];
-	static StringMap<FlagType, FLAG_TYPE_MAX_ENUM> flagNames;
+	static StringMap<SettingType, SETTING_MAX_ENUM>::Entry settingTypeEntries[];
+	static StringMap<SettingType, SETTING_MAX_ENUM> settingTypes;
 
 
 }; // Image
 }; // Image
 
 

+ 1 - 11
src/modules/graphics/opengl/OpenGL.cpp

@@ -416,15 +416,10 @@ void OpenGL::useVertexAttribArrays(uint32 arraybits)
 		glVertexAttrib4f(ATTRIB_COLOR, 1.0f, 1.0f, 1.0f, 1.0f);
 		glVertexAttrib4f(ATTRIB_COLOR, 1.0f, 1.0f, 1.0f, 1.0f);
 }
 }
 
 
-void OpenGL::setViewport(const Rect &v, bool canvasActive)
+void OpenGL::setViewport(const Rect &v)
 {
 {
 	glViewport(v.x, v.y, v.w, v.h);
 	glViewport(v.x, v.y, v.w, v.h);
 	state.viewport = v;
 	state.viewport = v;
-
-	// glScissor starts from the lower left, so we compensate when setting the
-	// scissor. When the viewport is changed, we need to manually update the
-	// scissor again.
-	setScissor(state.scissor, canvasActive);
 }
 }
 
 
 Rect OpenGL::getViewport() const
 Rect OpenGL::getViewport() const
@@ -446,11 +441,6 @@ void OpenGL::setScissor(const Rect &v, bool canvasActive)
 	state.scissor = v;
 	state.scissor = v;
 }
 }
 
 
-Rect OpenGL::getScissor() const
-{
-	return state.scissor;
-}
-
 void OpenGL::setPointSize(float size)
 void OpenGL::setPointSize(float size)
 {
 {
 	if (GLAD_VERSION_1_0)
 	if (GLAD_VERSION_1_0)

+ 1 - 6
src/modules/graphics/opengl/OpenGL.h

@@ -244,7 +244,7 @@ public:
 	 * Sets the OpenGL rendering viewport to the specified rectangle.
 	 * Sets the OpenGL rendering viewport to the specified rectangle.
 	 * The y-coordinate starts at the top.
 	 * The y-coordinate starts at the top.
 	 **/
 	 **/
-	void setViewport(const Rect &v, bool canvasActive);
+	void setViewport(const Rect &v);
 
 
 	/**
 	/**
 	 * Gets the current OpenGL rendering viewport rectangle.
 	 * Gets the current OpenGL rendering viewport rectangle.
@@ -257,11 +257,6 @@ public:
 	 **/
 	 **/
 	void setScissor(const Rect &v, bool canvasActive);
 	void setScissor(const Rect &v, bool canvasActive);
 
 
-	/**
-	 * Gets the current scissor box (regardless of whether scissoring is enabled.)
-	 **/
-	Rect getScissor() const;
-
 	/**
 	/**
 	 * Sets the global point size.
 	 * Sets the global point size.
 	 **/
 	 **/

+ 21 - 10
src/modules/graphics/opengl/Video.cpp

@@ -33,8 +33,10 @@ namespace opengl
 
 
 love::Type Video::type("Video", &Drawable::type);
 love::Type Video::type("Video", &Drawable::type);
 
 
-Video::Video(love::video::VideoStream *stream)
+Video::Video(love::video::VideoStream *stream, float pixeldensity)
 	: stream(stream)
 	: stream(stream)
+	, width(stream->getWidth() / pixeldensity)
+	, height(stream->getHeight() / pixeldensity)
 	, filter(Texture::getDefaultFilter())
 	, filter(Texture::getDefaultFilter())
 {
 {
 	filter.mipmap = Texture::FILTER_NONE;
 	filter.mipmap = Texture::FILTER_NONE;
@@ -45,18 +47,17 @@ Video::Video(love::video::VideoStream *stream)
 		vertices[i].color = Color(255, 255, 255, 255);
 		vertices[i].color = Color(255, 255, 255, 255);
 
 
 	// Vertices are ordered for use with triangle strips:
 	// Vertices are ordered for use with triangle strips:
-	// 0----2
-	// |  / |
-	// | /  |
-	// 1----3
+	// 0---2
+	// | / |
+	// 1---3
 	vertices[0].x = 0.0f;
 	vertices[0].x = 0.0f;
 	vertices[0].y = 0.0f;
 	vertices[0].y = 0.0f;
 	vertices[1].x = 0.0f;
 	vertices[1].x = 0.0f;
-	vertices[1].y = (float) stream->getHeight();
-	vertices[2].x = (float) stream->getWidth();
+	vertices[1].y = (float) height;
+	vertices[2].x = (float) width;
 	vertices[2].y = 0.0f;
 	vertices[2].y = 0.0f;
-	vertices[3].x = (float) stream->getWidth();
-	vertices[3].y = (float) stream->getHeight();
+	vertices[3].x = (float) width;
+	vertices[3].y = (float) height;
 
 
 	vertices[0].s = 0.0f;
 	vertices[0].s = 0.0f;
 	vertices[0].t = 0.0f;
 	vertices[0].t = 0.0f;
@@ -185,10 +186,20 @@ void Video::setSource(love::audio::Source *source)
 
 
 int Video::getWidth() const
 int Video::getWidth() const
 {
 {
-	return stream->getWidth();
+	return width;
 }
 }
 
 
 int Video::getHeight() const
 int Video::getHeight() const
+{
+	return height;
+}
+
+int Video::getPixelWidth() const
+{
+	return stream->getWidth();
+}
+
+int Video::getPixelHeight() const
 {
 {
 	return stream->getHeight();
 	return stream->getHeight();
 }
 }

+ 7 - 1
src/modules/graphics/opengl/Video.h

@@ -42,7 +42,7 @@ public:
 
 
 	static love::Type type;
 	static love::Type type;
 
 
-	Video(love::video::VideoStream *stream);
+	Video(love::video::VideoStream *stream, float pixeldensity = 1.0f);
 	~Video();
 	~Video();
 
 
 	// Volatile
 	// Volatile
@@ -60,6 +60,9 @@ public:
 	int getWidth() const;
 	int getWidth() const;
 	int getHeight() const;
 	int getHeight() const;
 
 
+	int getPixelWidth() const;
+	int getPixelHeight() const;
+
 	void setFilter(const Texture::Filter &f);
 	void setFilter(const Texture::Filter &f);
 	const Texture::Filter &getFilter() const;
 	const Texture::Filter &getFilter() const;
 
 
@@ -70,6 +73,9 @@ private:
 	StrongRef<love::video::VideoStream> stream;
 	StrongRef<love::video::VideoStream> stream;
 	StrongRef<love::audio::Source> source;
 	StrongRef<love::audio::Source> source;
 
 
+	int width;
+	int height;
+
 	GLuint textures[3];
 	GLuint textures[3];
 
 
 	Vertex vertices[4];
 	Vertex vertices[4];

+ 2 - 2
src/modules/graphics/opengl/wrap_Canvas.cpp

@@ -90,8 +90,8 @@ int w_Canvas_newImageData(lua_State *L)
 	love::image::Image *image = luax_getmodule<love::image::Image>(L, love::image::Image::type);
 	love::image::Image *image = luax_getmodule<love::image::Image>(L, love::image::Image::type);
 	int x = (int) luaL_optnumber(L, 2, 0);
 	int x = (int) luaL_optnumber(L, 2, 0);
 	int y = (int) luaL_optnumber(L, 3, 0);
 	int y = (int) luaL_optnumber(L, 3, 0);
-	int w = (int) luaL_optnumber(L, 4, canvas->getWidth());
-	int h = (int) luaL_optnumber(L, 5, canvas->getHeight());
+	int w = (int) luaL_optnumber(L, 4, canvas->getPixelWidth());
+	int h = (int) luaL_optnumber(L, 5, canvas->getPixelHeight());
 
 
 	love::image::ImageData *img = nullptr;
 	love::image::ImageData *img = nullptr;
 	luax_catchexcept(L, [&](){ img = canvas->newImageData(image, x, y, w, h); });
 	luax_catchexcept(L, [&](){ img = canvas->newImageData(image, x, y, w, h); });

+ 8 - 0
src/modules/graphics/opengl/wrap_Font.cpp

@@ -188,6 +188,13 @@ int w_Font_setFallbacks(lua_State *L)
 	return 0;
 	return 0;
 }
 }
 
 
+int w_Font_getPixelDensity(lua_State *L)
+{
+	Font *t = luax_checkfont(L, 1);
+	lua_pushnumber(L, t->getPixelDensity());
+	return 1;
+}
+
 static const luaL_Reg w_Font_functions[] =
 static const luaL_Reg w_Font_functions[] =
 {
 {
 	{ "getHeight", w_Font_getHeight },
 	{ "getHeight", w_Font_getHeight },
@@ -202,6 +209,7 @@ static const luaL_Reg w_Font_functions[] =
 	{ "getBaseline", w_Font_getBaseline },
 	{ "getBaseline", w_Font_getBaseline },
 	{ "hasGlyphs", w_Font_hasGlyphs },
 	{ "hasGlyphs", w_Font_hasGlyphs },
 	{ "setFallbacks", w_Font_setFallbacks },
 	{ "setFallbacks", w_Font_setFallbacks },
+	{ "getPixelDensity", w_Font_getPixelDensity },
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };
 
 

+ 88 - 32
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -32,6 +32,7 @@
 
 
 #include <cassert>
 #include <cassert>
 #include <cstring>
 #include <cstring>
+#include <cstdlib>
 
 
 #include <algorithm>
 #include <algorithm>
 
 
@@ -175,6 +176,25 @@ int w_getDimensions(lua_State *L)
 	return 2;
 	return 2;
 }
 }
 
 
+int w_getPixelWidth(lua_State *L)
+{
+	lua_pushinteger(L, instance()->getPixelWidth());
+	return 1;
+}
+
+int w_getPixelHeight(lua_State *L)
+{
+	lua_pushinteger(L, instance()->getPixelHeight());
+	return 1;
+}
+
+int w_getPixelDimensions(lua_State *L)
+{
+	lua_pushinteger(L, instance()->getPixelWidth());
+	lua_pushinteger(L, instance()->getPixelHeight());
+	return 2;
+}
+
 int w_setCanvas(lua_State *L)
 int w_setCanvas(lua_State *L)
 {
 {
 	// Disable stencil writes.
 	// Disable stencil writes.
@@ -385,13 +405,6 @@ int w_getStencilTest(lua_State *L)
 	return 2;
 	return 2;
 }
 }
 
 
-static const char *imageFlagName(Image::FlagType flagtype)
-{
-	const char *name = nullptr;
-	Image::getConstant(flagtype, name);
-	return name;
-}
-
 int w_newImage(lua_State *L)
 int w_newImage(lua_State *L)
 {
 {
 	luax_checkgraphicscreated(L);
 	luax_checkgraphicscreated(L);
@@ -399,13 +412,7 @@ int w_newImage(lua_State *L)
 	std::vector<love::image::ImageData *> data;
 	std::vector<love::image::ImageData *> data;
 	std::vector<love::image::CompressedImageData *> cdata;
 	std::vector<love::image::CompressedImageData *> cdata;
 
 
-	Image::Flags flags;
-	if (!lua_isnoneornil(L, 2))
-	{
-		luaL_checktype(L, 2, LUA_TTABLE);
-		flags.mipmaps = luax_boolflag(L, 2, imageFlagName(Image::FLAG_TYPE_MIPMAPS), flags.mipmaps);
-		flags.linear = luax_boolflag(L, 2, imageFlagName(Image::FLAG_TYPE_LINEAR), flags.linear);
-	}
+	Image::Settings settings;
 
 
 	bool releasedata = false;
 	bool releasedata = false;
 
 
@@ -418,6 +425,20 @@ int w_newImage(lua_State *L)
 
 
 		love::filesystem::FileData *fdata = love::filesystem::luax_getfiledata(L, 1);
 		love::filesystem::FileData *fdata = love::filesystem::luax_getfiledata(L, 1);
 
 
+		// Parse a density scale of 2.0 from "[email protected]".
+		const std::string &fname = fdata->getName();
+		size_t namelen = fname.length();
+		size_t atpos = fname.rfind('@');
+
+		if (atpos != std::string::npos && atpos + 2 < namelen
+			&& (fname[namelen - 1] == 'x' || fname[namelen - 1] == 'X'))
+		{
+			char *end = nullptr;
+			float density = std::strtof(fname.c_str() + atpos + 1, &end);
+			if (end != nullptr && density > 0.0f)
+				settings.pixeldensity = density;
+		}
+
 		if (imagemodule->isCompressed(fdata))
 		if (imagemodule->isCompressed(fdata))
 		{
 		{
 			luax_catchexcept(L,
 			luax_catchexcept(L,
@@ -441,12 +462,18 @@ int w_newImage(lua_State *L)
 	else
 	else
 		data.push_back(love::image::luax_checkimagedata(L, 1));
 		data.push_back(love::image::luax_checkimagedata(L, 1));
 
 
-	if (lua_istable(L, 2))
+	if (!lua_isnoneornil(L, 2))
 	{
 	{
-		lua_getfield(L, 2, imageFlagName(Image::FLAG_TYPE_MIPMAPS));
+		luaL_checktype(L, 2, LUA_TTABLE);
+
+		settings.mipmaps = luax_boolflag(L, 2, luax_imageSettingName(Image::SETTING_MIPMAPS), settings.mipmaps);
+		settings.linear = luax_boolflag(L, 2, luax_imageSettingName(Image::SETTING_LINEAR), settings.linear);
+		settings.pixeldensity = (float) luax_numberflag(L, 2, luax_imageSettingName(Image::SETTING_PIXELDENSITY), settings.pixeldensity);
+
+		lua_getfield(L, 2, luax_imageSettingName(Image::SETTING_MIPMAPS));
 
 
 		// Add all manually specified mipmap images to the array of imagedata.
 		// Add all manually specified mipmap images to the array of imagedata.
-		// i.e. flags = {mipmaps = {mip1, mip2, ...}}.
+		// i.e. settings = {mipmaps = {mip1, mip2, ...}}.
 		if (lua_istable(L, -1))
 		if (lua_istable(L, -1))
 		{
 		{
 			for (size_t i = 1; i <= luax_objlen(L, -1); i++)
 			for (size_t i = 1; i <= luax_objlen(L, -1); i++)
@@ -480,9 +507,9 @@ int w_newImage(lua_State *L)
 	luax_catchexcept(L,
 	luax_catchexcept(L,
 		[&]() {
 		[&]() {
 			if (!cdata.empty())
 			if (!cdata.empty())
-				image = instance()->newImage(cdata, flags);
+				image = instance()->newImage(cdata, settings);
 			else if (!data.empty())
 			else if (!data.empty())
-				image = instance()->newImage(data, flags);
+				image = instance()->newImage(data, settings);
 		},
 		},
 		[&](bool) {
 		[&](bool) {
 			if (releasedata)
 			if (releasedata)
@@ -514,8 +541,20 @@ int w_newQuad(lua_State *L)
 	v.w = luaL_checknumber(L, 3);
 	v.w = luaL_checknumber(L, 3);
 	v.h = luaL_checknumber(L, 4);
 	v.h = luaL_checknumber(L, 4);
 
 
-	double sw = luaL_checknumber(L, 5);
-	double sh = luaL_checknumber(L, 6);
+	double sw = 0.0f;
+	double sh = 0.0f;
+
+	if (luax_istype(L, 5, Texture::type))
+	{
+		Texture *texture = luax_checktexture(L, 5);
+		sw = texture->getWidth();
+		sh = texture->getHeight();
+	}
+	else
+	{
+		sw = luaL_checknumber(L, 5);
+		sh = luaL_checknumber(L, 6);
+	}
 
 
 	Quad *quad = instance()->newQuad(v, sw, sh);
 	Quad *quad = instance()->newQuad(v, sw, sh);
 	luax_pushtype(L, quad);
 	luax_pushtype(L, quad);
@@ -641,19 +680,31 @@ int w_newCanvas(lua_State *L)
 	luax_checkgraphicscreated(L);
 	luax_checkgraphicscreated(L);
 
 
 	// check if width and height are given. else default to screen dimensions.
 	// check if width and height are given. else default to screen dimensions.
-	int width       = (int) luaL_optnumber(L, 1, instance()->getWidth());
-	int height      = (int) luaL_optnumber(L, 2, instance()->getHeight());
-	const char *str = luaL_optstring(L, 3, "normal");
-	int msaa        = (int) luaL_optnumber(L, 4, 0);
+	int width  = (int) luaL_optnumber(L, 1, instance()->getWidth());
+	int height = (int) luaL_optnumber(L, 2, instance()->getHeight());
 
 
-	PixelFormat format;
-	if (!getConstant(str, format))
-		return luaL_error(L, "Invalid pixel format: %s", str);
+	Canvas::Settings settings;
+
+	// Default to the screen's current pixel density scale.
+	settings.pixeldensity = instance()->getScreenPixelDensity();
+
+	if (!lua_isnoneornil(L, 3))
+	{
+		lua_getfield(L, 3, "format");
+		if (!lua_isnoneornil(L, -1))
+		{
+			const char *str = luaL_checkstring(L, -1);
+			if (!getConstant(str, settings.format))
+				return luaL_error(L, "Invalid Canvas format: %s", str);
+		}
+		lua_pop(L, 1);
+
+		settings.pixeldensity = (float) luax_numberflag(L, 3, "pixeldensity", settings.pixeldensity);
+		settings.msaa = luax_intflag(L, 3, "msaa", settings.msaa);
+	}
 
 
 	Canvas *canvas = nullptr;
 	Canvas *canvas = nullptr;
-	luax_catchexcept(L,
-		[&](){ canvas = instance()->newCanvas(width, height, format, msaa); }
-	);
+	luax_catchexcept(L, [&](){ canvas = instance()->newCanvas(width, height, settings); });
 
 
 	if (canvas == nullptr)
 	if (canvas == nullptr)
 		return luaL_error(L, "Canvas not created, but no error thrown. I don't even...");
 		return luaL_error(L, "Canvas not created, but no error thrown. I don't even...");
@@ -1018,9 +1069,11 @@ int w_newVideo(lua_State *L)
 		luax_convobj(L, 1, "video", "newVideoStream");
 		luax_convobj(L, 1, "video", "newVideoStream");
 
 
 	auto stream = luax_checktype<love::video::VideoStream>(L, 1);
 	auto stream = luax_checktype<love::video::VideoStream>(L, 1);
+	float pixeldensity = (float) luaL_optnumber(L, 2, 1.0);
 	Video *video = nullptr;
 	Video *video = nullptr;
 
 
-	luax_catchexcept(L, [&]() { video = instance()->newVideo(stream); });
+	luax_catchexcept(L, [&]() { video = instance()->newVideo(stream, pixeldensity); });
+
 	luax_pushtype(L, video);
 	luax_pushtype(L, video);
 	video->release();
 	video->release();
 	return 1;
 	return 1;
@@ -2107,6 +2160,9 @@ static const luaL_Reg functions[] =
 	{ "getWidth", w_getWidth },
 	{ "getWidth", w_getWidth },
 	{ "getHeight", w_getHeight },
 	{ "getHeight", w_getHeight },
 	{ "getDimensions", w_getDimensions },
 	{ "getDimensions", w_getDimensions },
+	{ "getPixelWidth", w_getPixelWidth },
+	{ "getPixelHeight", w_getPixelHeight },
+	{ "getPixelDimensions", w_getPixelDimensions },
 
 
 	{ "setScissor", w_setScissor },
 	{ "setScissor", w_setScissor },
 	{ "intersectScissor", w_intersectScissor },
 	{ "intersectScissor", w_intersectScissor },

+ 8 - 4
src/modules/graphics/opengl/wrap_Graphics.lua

@@ -341,16 +341,20 @@ end
 
 
 love.graphics._setDefaultShaderCode(defaults, defaults_gammacorrect)
 love.graphics._setDefaultShaderCode(defaults, defaults_gammacorrect)
 
 
-function love.graphics.newVideo(file, loadaudio)
-	local video = love.graphics._newVideo(file)
+
+function love.graphics.newVideo(file, settings)
+	settings = settings == nil and {} or settings
+	if type(settings) ~= "table" then error("bad argument #2 to newVideo (expected table)", 2) end
+
+	local video = love.graphics._newVideo(file, settings.pixeldensity)
 	local source, success
 	local source, success
 
 
-	if loadaudio ~= false and love.audio then
+	if settings.audio ~= false and love.audio then
 		success, source = pcall(love.audio.newSource, video:getStream():getFilename(), "stream")
 		success, source = pcall(love.audio.newSource, video:getStream():getFilename(), "stream")
 	end
 	end
 	if success then
 	if success then
 		video:setSource(source)
 		video:setSource(source)
-	elseif loadaudio == true then
+	elseif settings.audio == true then
 		if love.audio then
 		if love.audio then
 			error("Video had no audio track", 2)
 			error("Video had no audio track", 2)
 		else
 		else

+ 10 - 7
src/modules/graphics/opengl/wrap_Image.cpp

@@ -114,25 +114,28 @@ int w_Image_getData(lua_State *L)
 	return n;
 	return n;
 }
 }
 
 
-static const char *imageFlagName(Image::FlagType flagtype)
+const char *luax_imageSettingName(Image::SettingType settingtype)
 {
 {
 	const char *name = nullptr;
 	const char *name = nullptr;
-	Image::getConstant(flagtype, name);
+	Image::getConstant(settingtype, name);
 	return name;
 	return name;
 }
 }
 
 
 int w_Image_getFlags(lua_State *L)
 int w_Image_getFlags(lua_State *L)
 {
 {
 	Image *i = luax_checkimage(L, 1);
 	Image *i = luax_checkimage(L, 1);
-	Image::Flags flags = i->getFlags();
+	Image::Settings settings = i->getFlags();
 
 
 	lua_createtable(L, 0, 2);
 	lua_createtable(L, 0, 2);
 
 
-	lua_pushboolean(L, flags.mipmaps);
-	lua_setfield(L, -2, imageFlagName(Image::FLAG_TYPE_MIPMAPS));
+	lua_pushboolean(L, settings.mipmaps);
+	lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_MIPMAPS));
 
 
-	lua_pushboolean(L, flags.linear);
-	lua_setfield(L, -2, imageFlagName(Image::FLAG_TYPE_LINEAR));
+	lua_pushboolean(L, settings.linear);
+	lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_LINEAR));
+
+	lua_pushnumber(L, settings.pixeldensity);
+	lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_PIXELDENSITY));
 
 
 	return 1;
 	return 1;
 }
 }

+ 1 - 0
src/modules/graphics/opengl/wrap_Image.h

@@ -33,6 +33,7 @@ namespace graphics
 namespace opengl
 namespace opengl
 {
 {
 
 
+const char *luax_imageSettingName(Image::SettingType settingtype);
 Image *luax_checkimage(lua_State *L, int idx);
 Image *luax_checkimage(lua_State *L, int idx);
 extern "C" int luaopen_image(lua_State *L);
 extern "C" int luaopen_image(lua_State *L);
 
 

+ 25 - 0
src/modules/graphics/opengl/wrap_Video.cpp

@@ -90,6 +90,28 @@ int w_Video_getDimensions(lua_State *L)
 	return 2;
 	return 2;
 }
 }
 
 
+int w_Video_getPixelWidth(lua_State *L)
+{
+	Video *video = luax_checkvideo(L, 1);
+	lua_pushnumber(L, video->getPixelWidth());
+	return 1;
+}
+
+int w_Video_getPixelHeight(lua_State *L)
+{
+	Video *video = luax_checkvideo(L, 1);
+	lua_pushnumber(L, video->getPixelHeight());
+	return 1;
+}
+
+int w_Video_getPixelDimensions(lua_State *L)
+{
+	Video *video = luax_checkvideo(L, 1);
+	lua_pushnumber(L, video->getPixelWidth());
+	lua_pushnumber(L, video->getPixelHeight());
+	return 2;
+}
+
 int w_Video_setFilter(lua_State *L)
 int w_Video_setFilter(lua_State *L)
 {
 {
 	Video *video = luax_checkvideo(L, 1);
 	Video *video = luax_checkvideo(L, 1);
@@ -136,6 +158,9 @@ static const luaL_Reg functions[] =
 	{ "getWidth", w_Video_getWidth },
 	{ "getWidth", w_Video_getWidth },
 	{ "getHeight", w_Video_getHeight },
 	{ "getHeight", w_Video_getHeight },
 	{ "getDimensions", w_Video_getDimensions },
 	{ "getDimensions", w_Video_getDimensions },
+	{ "getPixelWidth", w_Video_getPixelWidth },
+	{ "getPixelHeight", w_Video_getPixelHeight },
+	{ "getPixelDimensions", w_Video_getPixelDimensions },
 	{ "setFilter", w_Video_setFilter },
 	{ "setFilter", w_Video_setFilter },
 	{ "getFilter", w_Video_getFilter },
 	{ "getFilter", w_Video_getFilter },
 	{ 0, 0 }
 	{ 0, 0 }

+ 33 - 0
src/modules/graphics/wrap_Texture.cpp

@@ -52,6 +52,35 @@ int w_Texture_getDimensions(lua_State *L)
 	return 2;
 	return 2;
 }
 }
 
 
+int w_Texture_getPixelWidth(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+	lua_pushnumber(L, t->getPixelWidth());
+	return 1;
+}
+
+int w_Texture_getPixelHeight(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+	lua_pushnumber(L, t->getPixelHeight());
+	return 1;
+}
+
+int w_Texture_getPixelDimensions(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+	lua_pushnumber(L, t->getPixelWidth());
+	lua_pushnumber(L, t->getPixelHeight());
+	return 2;
+}
+
+int w_Texture_getPixelDensity(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+	lua_pushnumber(L, t->getPixelDensity());
+	return 1;
+}
+
 int w_Texture_setFilter(lua_State *L)
 int w_Texture_setFilter(lua_State *L)
 {
 {
 	Texture *t = luax_checktexture(L, 1);
 	Texture *t = luax_checktexture(L, 1);
@@ -130,6 +159,10 @@ const luaL_Reg w_Texture_functions[] =
 	{ "getWidth", w_Texture_getWidth },
 	{ "getWidth", w_Texture_getWidth },
 	{ "getHeight", w_Texture_getHeight },
 	{ "getHeight", w_Texture_getHeight },
 	{ "getDimensions", w_Texture_getDimensions },
 	{ "getDimensions", w_Texture_getDimensions },
+	{ "getPixelWidth", w_Texture_getPixelWidth },
+	{ "getPixelHeight", w_Texture_getPixelHeight },
+	{ "getPixelDimensions", w_Texture_getPixelDimensions },
+	{ "getPixelDensity", w_Texture_getPixelDensity },
 	{ "setFilter", w_Texture_setFilter },
 	{ "setFilter", w_Texture_setFilter },
 	{ "getFilter", w_Texture_getFilter },
 	{ "getFilter", w_Texture_getFilter },
 	{ "setWrap", w_Texture_setWrap },
 	{ "setWrap", w_Texture_setWrap },

+ 2 - 2
src/modules/keyboard/sdl/Keyboard.cpp

@@ -124,8 +124,8 @@ void Keyboard::setTextInput(bool enable, double x, double y, double w, double h)
 	auto window = Module::getInstance<window::Window>(M_WINDOW);
 	auto window = Module::getInstance<window::Window>(M_WINDOW);
 	if (window)
 	if (window)
 	{
 	{
-		window->pixelToWindowCoords(&x, &y);
-		window->pixelToWindowCoords(&w, &h);
+		window->DPIToWindowCoords(&x, &y);
+		window->DPIToWindowCoords(&w, &h);
 	}
 	}
 
 
 	SDL_Rect rect = {(int) x, (int) y, (int) w, (int) h};
 	SDL_Rect rect = {(int) x, (int) y, (int) w, (int) h};

+ 8 - 8
src/modules/mouse/sdl/Mouse.cpp

@@ -34,19 +34,19 @@ namespace sdl
 
 
 // SDL reports mouse coordinates in the window coordinate system in OS X, but
 // SDL reports mouse coordinates in the window coordinate system in OS X, but
 // we want them in pixel coordinates (may be different with high-DPI enabled.)
 // we want them in pixel coordinates (may be different with high-DPI enabled.)
-static void windowToPixelCoords(double *x, double *y)
+static void windowToDPICoords(double *x, double *y)
 {
 {
 	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	if (window)
 	if (window)
-		window->windowToPixelCoords(x, y);
+		window->windowToDPICoords(x, y);
 }
 }
 
 
 // And vice versa for setting mouse coordinates.
 // And vice versa for setting mouse coordinates.
-static void pixelToWindowCoords(double *x, double *y)
+static void DPIToWindowCoords(double *x, double *y)
 {
 {
 	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	if (window)
 	if (window)
-		window->pixelToWindowCoords(x, y);
+		window->DPIToWindowCoords(x, y);
 }
 }
 
 
 const char *Mouse::getName() const
 const char *Mouse::getName() const
@@ -118,7 +118,7 @@ double Mouse::getX() const
 	SDL_GetMouseState(&x, nullptr);
 	SDL_GetMouseState(&x, nullptr);
 
 
 	double dx = (double) x;
 	double dx = (double) x;
-	windowToPixelCoords(&dx, nullptr);
+	windowToDPICoords(&dx, nullptr);
 
 
 	return dx;
 	return dx;
 }
 }
@@ -129,7 +129,7 @@ double Mouse::getY() const
 	SDL_GetMouseState(nullptr, &y);
 	SDL_GetMouseState(nullptr, &y);
 
 
 	double dy = (double) y;
 	double dy = (double) y;
-	windowToPixelCoords(nullptr, &dy);
+	windowToDPICoords(nullptr, &dy);
 
 
 	return dy;
 	return dy;
 }
 }
@@ -141,7 +141,7 @@ void Mouse::getPosition(double &x, double &y) const
 
 
 	x = (double) mx;
 	x = (double) mx;
 	y = (double) my;
 	y = (double) my;
-	windowToPixelCoords(&x, &y);
+	windowToDPICoords(&x, &y);
 }
 }
 
 
 void Mouse::setPosition(double x, double y)
 void Mouse::setPosition(double x, double y)
@@ -152,7 +152,7 @@ void Mouse::setPosition(double x, double y)
 	if (window)
 	if (window)
 		handle = (SDL_Window *) window->getHandle();
 		handle = (SDL_Window *) window->getHandle();
 
 
-	pixelToWindowCoords(&x, &y);
+	DPIToWindowCoords(&x, &y);
 	SDL_WarpMouseInWindow(handle, (int) x, (int) y);
 	SDL_WarpMouseInWindow(handle, (int) x, (int) y);
 
 
 	// SDL_WarpMouse doesn't directly update SDL's internal mouse state in Linux
 	// SDL_WarpMouse doesn't directly update SDL's internal mouse state in Linux

+ 9 - 2
src/modules/window/Window.h

@@ -164,13 +164,20 @@ public:
 	virtual void setMouseGrab(bool grab) = 0;
 	virtual void setMouseGrab(bool grab) = 0;
 	virtual bool isMouseGrabbed() const = 0;
 	virtual bool isMouseGrabbed() const = 0;
 
 
-	virtual void getPixelDimensions(int &w, int &h) const = 0;
+	virtual int getWidth() const = 0;
+	virtual int getHeight() const = 0;
+	virtual int getPixelWidth() const = 0;
+	virtual int getPixelHeight() const = 0;
+
 	// Note: window-space coordinates are not necessarily the same as
 	// Note: window-space coordinates are not necessarily the same as
 	// density-independent units (which toPixels and fromPixels use.)
 	// density-independent units (which toPixels and fromPixels use.)
 	virtual void windowToPixelCoords(double *x, double *y) const = 0;
 	virtual void windowToPixelCoords(double *x, double *y) const = 0;
 	virtual void pixelToWindowCoords(double *x, double *y) const = 0;
 	virtual void pixelToWindowCoords(double *x, double *y) const = 0;
 
 
-	virtual double getPixelScale() const = 0;
+	virtual void windowToDPICoords(double *x, double *y) const = 0;
+	virtual void DPIToWindowCoords(double *x, double *y) const = 0;
+
+	virtual double getPixelDensity() const = 0;
 
 
 	virtual double toPixels(double x) const = 0;
 	virtual double toPixels(double x) const = 0;
 	virtual void toPixels(double wx, double wy, double &px, double &py) const = 0;
 	virtual void toPixels(double wx, double wy, double &px, double &py) const = 0;

+ 73 - 11
src/modules/window/sdl/Window.cpp

@@ -521,7 +521,11 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 	updateSettings(f, false);
 	updateSettings(f, false);
 
 
 	if (graphics.get())
 	if (graphics.get())
-		graphics->setMode(pixelWidth, pixelHeight);
+	{
+		double scaledw, scaledh;
+		fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
+		graphics->setMode((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
+	}
 
 
 #ifdef LOVE_ANDROID
 #ifdef LOVE_ANDROID
 	love::android::setImmersive(f.fullscreen);
 	love::android::setImmersive(f.fullscreen);
@@ -541,7 +545,11 @@ bool Window::onSizeChanged(int width, int height)
 	SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
 	SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
 
 
 	if (graphics.get())
 	if (graphics.get())
-		graphics->setViewportSize(pixelWidth, pixelHeight);
+	{
+		double scaledw, scaledh;
+		fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
+		graphics->setViewportSize((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
+	}
 
 
 	return true;
 	return true;
 }
 }
@@ -609,7 +617,11 @@ void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphi
 
 
 	// Update the viewport size now instead of waiting for event polling.
 	// Update the viewport size now instead of waiting for event polling.
 	if (updateGraphicsViewport && graphics.get())
 	if (updateGraphicsViewport && graphics.get())
-		graphics->setViewportSize(pixelWidth, pixelHeight);
+	{
+		double scaledw, scaledh;
+		fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
+		graphics->setViewportSize((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
+	}
 }
 }
 
 
 void Window::getWindow(int &width, int &height, WindowSettings &newsettings)
 void Window::getWindow(int &width, int &height, WindowSettings &newsettings)
@@ -940,12 +952,27 @@ bool Window::isMouseGrabbed() const
 		return mouseGrabbed;
 		return mouseGrabbed;
 }
 }
 
 
-void Window::getPixelDimensions(int &w, int &h) const
+int Window::getWidth() const
+{
+	return windowWidth;
+}
+
+int Window::getHeight() const
 {
 {
-	w = pixelWidth;
-	h = pixelHeight;
+	return windowHeight;
 }
 }
 
 
+int Window::getPixelWidth() const
+{
+	return pixelWidth;
+}
+
+int Window::getPixelHeight() const
+{
+	return pixelHeight;
+}
+
+
 void Window::windowToPixelCoords(double *x, double *y) const
 void Window::windowToPixelCoords(double *x, double *y) const
 {
 {
 	if (x != nullptr)
 	if (x != nullptr)
@@ -962,7 +989,42 @@ void Window::pixelToWindowCoords(double *x, double *y) const
 		*y = (*y) * ((double) windowHeight / (double) pixelHeight);
 		*y = (*y) * ((double) windowHeight / (double) pixelHeight);
 }
 }
 
 
-double Window::getPixelScale() const
+void Window::windowToDPICoords(double *x, double *y) const
+{
+	double px = x != nullptr ? *x : 0.0;
+	double py = y != nullptr ? *y : 0.0;
+
+	windowToPixelCoords(&px, &py);
+
+	double dpix = 0.0;
+	double dpiy = 0.0;
+
+	fromPixels(px, py, dpix, dpiy);
+
+	if (x != nullptr)
+		*x = dpix;
+	if (y != nullptr)
+		*y = dpiy;
+}
+
+void Window::DPIToWindowCoords(double *x, double *y) const
+{
+	double dpix = x != nullptr ? *x : 0.0;
+	double dpiy = y != nullptr ? *y : 0.0;
+
+	double px = 0.0;
+	double py = 0.0;
+
+	toPixels(dpix, dpiy, px, py);
+	pixelToWindowCoords(&px, &py);
+
+	if (x != nullptr)
+		*x = px;
+	if (y != nullptr)
+		*y = py;
+}
+
+double Window::getPixelDensity() const
 {
 {
 #ifdef LOVE_ANDROID
 #ifdef LOVE_ANDROID
 	return love::android::getScreenScale();
 	return love::android::getScreenScale();
@@ -973,24 +1035,24 @@ double Window::getPixelScale() const
 
 
 double Window::toPixels(double x) const
 double Window::toPixels(double x) const
 {
 {
-	return x * getPixelScale();
+	return x * getPixelDensity();
 }
 }
 
 
 void Window::toPixels(double wx, double wy, double &px, double &py) const
 void Window::toPixels(double wx, double wy, double &px, double &py) const
 {
 {
-	double scale = getPixelScale();
+	double scale = getPixelDensity();
 	px = wx * scale;
 	px = wx * scale;
 	py = wy * scale;
 	py = wy * scale;
 }
 }
 
 
 double Window::fromPixels(double x) const
 double Window::fromPixels(double x) const
 {
 {
-	return x / getPixelScale();
+	return x / getPixelDensity();
 }
 }
 
 
 void Window::fromPixels(double px, double py, double &wx, double &wy) const
 void Window::fromPixels(double px, double py, double &wx, double &wy) const
 {
 {
-	double scale = getPixelScale();
+	double scale = getPixelDensity();
 	wx = px / scale;
 	wx = px / scale;
 	wy = py / scale;
 	wy = py / scale;
 }
 }

+ 9 - 2
src/modules/window/sdl/Window.h

@@ -90,11 +90,18 @@ public:
 	void setMouseGrab(bool grab);
 	void setMouseGrab(bool grab);
 	bool isMouseGrabbed() const;
 	bool isMouseGrabbed() const;
 
 
-	void getPixelDimensions(int &w, int &h) const;
+	int getWidth() const;
+	int getHeight() const;
+	int getPixelWidth() const;
+	int getPixelHeight() const;
+
 	void windowToPixelCoords(double *x, double *y) const;
 	void windowToPixelCoords(double *x, double *y) const;
 	void pixelToWindowCoords(double *x, double *y) const;
 	void pixelToWindowCoords(double *x, double *y) const;
 
 
-	double getPixelScale() const;
+	void windowToDPICoords(double *x, double *y) const;
+	void DPIToWindowCoords(double *x, double *y) const;
+
+	double getPixelDensity() const;
 
 
 	double toPixels(double x) const;
 	double toPixels(double x) const;
 	void toPixels(double wx, double wy, double &px, double &py) const;
 	void toPixels(double wx, double wy, double &px, double &py) const;

+ 3 - 3
src/modules/window/wrap_Window.cpp

@@ -408,9 +408,9 @@ int w_isVisible(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-int w_getPixelScale(lua_State *L)
+int w_getPixelDensity(lua_State *L)
 {
 {
-	lua_pushnumber(L, instance()->getPixelScale());
+	lua_pushnumber(L, instance()->getPixelDensity());
 	return 1;
 	return 1;
 }
 }
 
 
@@ -570,7 +570,7 @@ static const luaL_Reg functions[] =
 	{ "hasFocus", w_hasFocus },
 	{ "hasFocus", w_hasFocus },
 	{ "hasMouseFocus", w_hasMouseFocus },
 	{ "hasMouseFocus", w_hasMouseFocus },
 	{ "isVisible", w_isVisible },
 	{ "isVisible", w_isVisible },
-	{ "getPixelScale", w_getPixelScale },
+	{ "getPixelDensity", w_getPixelDensity },
 	{ "toPixels", w_toPixels },
 	{ "toPixels", w_toPixels },
 	{ "fromPixels", w_fromPixels },
 	{ "fromPixels", w_fromPixels },
 	{ "minimize", w_minimize },
 	{ "minimize", w_minimize },

+ 2 - 3
src/scripts/boot.lua

@@ -603,8 +603,7 @@ function love.errhand(msg)
 	if love.audio then love.audio.stop() end
 	if love.audio then love.audio.stop() end
 
 
 	love.graphics.reset()
 	love.graphics.reset()
-
-	local font = love.graphics.setNewFont(math.floor(love.window.toPixels(14)))
+	local font = love.graphics.setNewFont(14)
 
 
 	love.graphics.setColor(1, 1, 1, 1)
 	love.graphics.setColor(1, 1, 1, 1)
 
 
@@ -630,8 +629,8 @@ function love.errhand(msg)
 	p = string.gsub(p, "%[string \"(.-)\"%]", "%1")
 	p = string.gsub(p, "%[string \"(.-)\"%]", "%1")
 
 
 	local function draw()
 	local function draw()
+		local pos = 70
 		love.graphics.clear(89/255, 157/255, 220/255)
 		love.graphics.clear(89/255, 157/255, 220/255)
-		local pos = love.window.toPixels(70)
 		love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos)
 		love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos)
 		love.graphics.present()
 		love.graphics.present()
 	end
 	end

+ 2 - 6
src/scripts/boot.lua.h

@@ -1103,9 +1103,7 @@ const unsigned char boot_lua[] =
 	0x65, 0x74, 0x28, 0x29, 0x0a,
 	0x65, 0x74, 0x28, 0x29, 0x0a,
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 
 	0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x4e, 0x65, 0x77, 0x46, 0x6f, 
 	0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x4e, 0x65, 0x77, 0x46, 0x6f, 
-	0x6e, 0x74, 0x28, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x6c, 0x6f, 0x76, 0x65, 
-	0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x74, 0x6f, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x28, 0x31, 
-	0x34, 0x29, 0x29, 0x29, 0x0a,
+	0x6e, 0x74, 0x28, 0x31, 0x34, 0x29, 0x0a,
 	0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 
 	0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 
 	0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x29, 0x0a,
 	0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x29, 0x0a,
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x74, 0x72, 0x61, 0x63, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x65, 0x62, 
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x74, 0x72, 0x61, 0x63, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x65, 0x62, 
@@ -1140,12 +1138,10 @@ const unsigned char boot_lua[] =
 	0x5c, 0x22, 0x25, 0x5d, 0x22, 0x2c, 0x20, 0x22, 0x25, 0x31, 0x22, 0x29, 0x0a,
 	0x5c, 0x22, 0x25, 0x5d, 0x22, 0x2c, 0x20, 0x22, 0x25, 0x31, 0x22, 0x29, 0x0a,
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x72, 
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x72, 
 	0x61, 0x77, 0x28, 0x29, 0x0a,
 	0x61, 0x77, 0x28, 0x29, 0x0a,
+	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x37, 0x30, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 0x6c, 
 	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 0x6c, 
 	0x65, 0x61, 0x72, 0x28, 0x38, 0x39, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, 0x35, 0x37, 0x2f, 0x32, 0x35, 
 	0x65, 0x61, 0x72, 0x28, 0x38, 0x39, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, 0x35, 0x37, 0x2f, 0x32, 0x35, 
 	0x35, 0x2c, 0x20, 0x32, 0x32, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x29, 0x0a,
 	0x35, 0x2c, 0x20, 0x32, 0x32, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x29, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 
-	0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x74, 0x6f, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x28, 0x37, 
-	0x30, 0x29, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 
 	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 
 	0x69, 0x6e, 0x74, 0x66, 0x28, 0x70, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x2c, 0x20, 
 	0x69, 0x6e, 0x74, 0x66, 0x28, 0x70, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x2c, 0x20, 
 	0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x57, 
 	0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x57, 

+ 30 - 55
src/scripts/nogame.lua

@@ -633,7 +633,7 @@ function love.nogame()
 	end
 	end
 
 
 	function Toast:center()
 	function Toast:center()
-		local ww, wh = love.window.fromPixels(love.graphics.getDimensions())
+		local ww, wh = love.graphics.getDimensions()
 		self.x = math.floor(ww / 2 / 32) * 32 + 16
 		self.x = math.floor(ww / 2 / 32) * 32 + 16
 		self.y = math.floor(wh / 2 / 32) * 32 + 16
 		self.y = math.floor(wh / 2 / 32) * 32 + 16
 	end
 	end
@@ -727,10 +727,9 @@ function love.nogame()
 	function Mosaic:init()
 	function Mosaic:init()
 		local mosaic_image = g_images.mosaic[1]
 		local mosaic_image = g_images.mosaic[1]
 
 
-		local sw, sh = mosaic_image:getDimensions()
-		local ww, wh = love.window.fromPixels(love.graphics.getDimensions())
+		local ww, wh = love.graphics.getDimensions()
 
 
-		if love.window.getPixelScale() > 1 then
+		if love.window.getPixelDensity() > 1 then
 			mosaic_image = g_images.mosaic[2]
 			mosaic_image = g_images.mosaic[2]
 		end
 		end
 
 
@@ -758,20 +757,11 @@ function love.nogame()
 		-- Insert only once. This way it appears half as often.
 		-- Insert only once. This way it appears half as often.
 		table.insert(COLORS, { 220/255, 239/255, 113/255 }) -- LIME
 		table.insert(COLORS, { 220/255, 239/255, 113/255 }) -- LIME
 
 
-		-- When using the higher-res mosaic sprite sheet, we want to draw its
-		-- sprites at the same scale as the regular-resolution one, because
-		-- we'll globally love.graphics.scale *everything* by the screen's
-		-- pixel density ratio.
-		-- We can avoid a lot of Quad scaling by taking advantage of the fact
-		-- that Quads use normalized texture coordinates internally - if we use 
-		-- the 'source image size' and quad size of the @1x image for the Quads
-		-- even when rendering them using the @2x image, it will automatically
-		-- scale as expected.
 		local QUADS = {
 		local QUADS = {
-			love.graphics.newQuad(0,  0,  32, 32, sw, sh),
-			love.graphics.newQuad(0,  32, 32, 32, sw, sh),
-			love.graphics.newQuad(32, 32, 32, 32, sw, sh),
-			love.graphics.newQuad(32, 0,  32, 32, sw, sh),
+			love.graphics.newQuad(0,  0,  32, 32, mosaic_image),
+			love.graphics.newQuad(0,  32, 32, 32, mosaic_image),
+			love.graphics.newQuad(32, 32, 32, 32, mosaic_image),
+			love.graphics.newQuad(32, 0,  32, 32, mosaic_image),
 		}
 		}
 
 
 		local exclude_left = math.floor(ww / 2 / 32)
 		local exclude_left = math.floor(ww / 2 / 32)
@@ -844,19 +834,19 @@ function love.nogame()
 		end
 		end
 
 
 		local GLYPHS = {
 		local GLYPHS = {
-			N = love.graphics.newQuad(0,  64, 32, 32, sw, sh),
-			O = love.graphics.newQuad(32, 64, 32, 32, sw, sh),
-			G = love.graphics.newQuad(0,  96, 32, 32, sw, sh),
-			A = love.graphics.newQuad(32, 96, 32, 32, sw, sh),
-			M = love.graphics.newQuad(64, 96, 32, 32, sw, sh),
-			E = love.graphics.newQuad(96, 96, 32, 32, sw, sh),
-
-			U = love.graphics.newQuad(64, 0,  32, 32, sw, sh),
-			P = love.graphics.newQuad(96, 0,  32, 32, sw, sh),
-			o = love.graphics.newQuad(64, 32, 32, 32, sw, sh),
-			S = love.graphics.newQuad(96, 32, 32, 32, sw, sh),
-			R = love.graphics.newQuad(64, 64, 32, 32, sw, sh),
-			T = love.graphics.newQuad(96, 64, 32, 32, sw, sh),
+			N = love.graphics.newQuad(0,  64, 32, 32, mosaic_image),
+			O = love.graphics.newQuad(32, 64, 32, 32, mosaic_image),
+			G = love.graphics.newQuad(0,  96, 32, 32, mosaic_image),
+			A = love.graphics.newQuad(32, 96, 32, 32, mosaic_image),
+			M = love.graphics.newQuad(64, 96, 32, 32, mosaic_image),
+			E = love.graphics.newQuad(96, 96, 32, 32, mosaic_image),
+
+			U = love.graphics.newQuad(64, 0,  32, 32, mosaic_image),
+			P = love.graphics.newQuad(96, 0,  32, 32, mosaic_image),
+			o = love.graphics.newQuad(64, 32, 32, 32, mosaic_image),
+			S = love.graphics.newQuad(96, 32, 32, 32, mosaic_image),
+			R = love.graphics.newQuad(64, 64, 32, 32, mosaic_image),
+			T = love.graphics.newQuad(96, 64, 32, 32, mosaic_image),
 		}
 		}
 
 
 		local INITIAL_TEXT_COLOR = { 15/16, 15/16, 15/16 }
 		local INITIAL_TEXT_COLOR = { 15/16, 15/16, 15/16 }
@@ -932,9 +922,9 @@ function love.nogame()
 	function love.load()
 	function love.load()
 		love.graphics.setBackgroundColor(136/255, 193/255, 206/255)
 		love.graphics.setBackgroundColor(136/255, 193/255, 206/255)
 
 
-		local function load_image(file, name)
+		local function load_image(file, name, flags)
 			local decoded = love.math.decode("base64", file)
 			local decoded = love.math.decode("base64", file)
-			return love.graphics.newImage(love.filesystem.newFileData(decoded, name:gsub("_", ".")))
+			return love.graphics.newImage(love.filesystem.newFileData(decoded, name:gsub("_", ".")), flags)
 		end
 		end
 
 
 		g_images = {}
 		g_images = {}
@@ -963,11 +953,8 @@ function love.nogame()
 
 
 	function love.draw()
 	function love.draw()
 		love.graphics.setColor(1, 1, 1)
 		love.graphics.setColor(1, 1, 1)
-		love.graphics.push()
-		love.graphics.scale(love.window.getPixelScale())
 		g_entities.mosaic:draw()
 		g_entities.mosaic:draw()
 		g_entities.toast:draw()
 		g_entities.toast:draw()
-		love.graphics.pop()
 	end
 	end
 
 
 	function love.resize(w, h)
 	function love.resize(w, h)
@@ -988,10 +975,17 @@ function love.nogame()
 		end
 		end
 	end
 	end
 
 
-	function love.mousepressed(x, y, b)
+	function love.mousepressed(x, y, b, istouch, clicks)
 		local tx = x / love.graphics.getWidth()
 		local tx = x / love.graphics.getWidth()
 		local ty = y / love.graphics.getHeight()
 		local ty = y / love.graphics.getHeight()
 		g_entities.toast:look_at(tx, ty)
 		g_entities.toast:look_at(tx, ty)
+
+		-- Double-tap the screen (when using a touch screen) to exit.
+		if istouch and clicks == 2 then
+			if love.window.showMessageBox("Exit No-Game Screen", "", {"OK", "Cancel"}) == 1 then
+				love.event.quit()
+			end
+		end
 	end
 	end
 
 
 	function love.mousemoved(x, y)
 	function love.mousemoved(x, y)
@@ -1002,25 +996,6 @@ function love.nogame()
 		end
 		end
 	end
 	end
 
 
-	local last_touch = {time=0, x=0, y=0}
-
-	function love.touchpressed(id, x, y, pressure)
-		-- Double-tap the screen (when using a touch screen) to exit.
-		if #love.touch.getTouches() == 1 then
-			local dist = math.sqrt((x-last_touch.x)^2 + (y-last_touch.y)^2)
-			local difftime = love.timer.getTime() - last_touch.time
-			if difftime < 0.3 and dist < 50 then
-				if love.window.showMessageBox("Exit No-Game Screen", "", {"OK", "Cancel"}) == 1 then
-					love.event.quit()
-				end
-			end
-
-			last_touch.time = love.timer.getTime()
-			last_touch.x = x
-			last_touch.y = y
-		end
-	end
-
 	function love.conf(t)
 	function love.conf(t)
 		t.title = "L\195\150VE " .. love._version .. " (" .. love._version_codename .. ")"
 		t.title = "L\195\150VE " .. love._version .. " (" .. love._version_codename .. ")"
 		t.gammacorrect = true
 		t.gammacorrect = true

+ 57 - 113
src/scripts/nogame.lua.h

@@ -2961,9 +2961,8 @@ const unsigned char nogame_lua[] =
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x54, 0x6f, 0x61, 0x73, 0x74, 0x3a, 0x63, 0x65, 
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x54, 0x6f, 0x61, 0x73, 0x74, 0x3a, 0x63, 0x65, 
 	0x6e, 0x74, 0x65, 0x72, 0x28, 0x29, 0x0a,
 	0x6e, 0x74, 0x65, 0x72, 0x28, 0x29, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x77, 0x77, 0x2c, 0x20, 0x77, 0x68, 0x20, 0x3d, 0x20, 0x6c, 
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x77, 0x77, 0x2c, 0x20, 0x77, 0x68, 0x20, 0x3d, 0x20, 0x6c, 
-	0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x66, 0x72, 0x6f, 0x6d, 0x50, 0x69, 0x78, 
-	0x65, 0x6c, 0x73, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 
-	0x67, 0x65, 0x74, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x28, 0x29, 0x29, 0x0a,
+	0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x44, 0x69, 
+	0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x28, 0x29, 0x0a,
 	0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x20, 0x3d, 0x20, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c, 
 	0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x20, 0x3d, 0x20, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c, 
 	0x6f, 0x6f, 0x72, 0x28, 0x77, 0x77, 0x20, 0x2f, 0x20, 0x32, 0x20, 0x2f, 0x20, 0x33, 0x32, 0x29, 0x20, 0x2a, 
 	0x6f, 0x6f, 0x72, 0x28, 0x77, 0x77, 0x20, 0x2f, 0x20, 0x32, 0x20, 0x2f, 0x20, 0x33, 0x32, 0x29, 0x20, 0x2a, 
 	0x20, 0x33, 0x32, 0x20, 0x2b, 0x20, 0x31, 0x36, 0x0a,
 	0x20, 0x33, 0x32, 0x20, 0x2b, 0x20, 0x31, 0x36, 0x0a,
@@ -3127,16 +3126,12 @@ const unsigned char nogame_lua[] =
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 
 	0x67, 0x65, 0x20, 0x3d, 0x20, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x6d, 0x6f, 0x73, 0x61, 
 	0x67, 0x65, 0x20, 0x3d, 0x20, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x6d, 0x6f, 0x73, 0x61, 
 	0x69, 0x63, 0x5b, 0x31, 0x5d, 0x0a,
 	0x69, 0x63, 0x5b, 0x31, 0x5d, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x20, 0x3d, 0x20, 0x6d, 
-	0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x3a, 0x67, 0x65, 0x74, 0x44, 0x69, 0x6d, 
-	0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x28, 0x29, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x77, 0x77, 0x2c, 0x20, 0x77, 0x68, 0x20, 0x3d, 0x20, 0x6c, 
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x77, 0x77, 0x2c, 0x20, 0x77, 0x68, 0x20, 0x3d, 0x20, 0x6c, 
-	0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x66, 0x72, 0x6f, 0x6d, 0x50, 0x69, 0x78, 
-	0x65, 0x6c, 0x73, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 
-	0x67, 0x65, 0x74, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x28, 0x29, 0x29, 0x0a,
+	0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x44, 0x69, 
+	0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x28, 0x29, 0x0a,
 	0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x67, 
 	0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x67, 
-	0x65, 0x74, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x28, 0x29, 0x20, 0x3e, 0x20, 0x31, 
-	0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+	0x65, 0x74, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x28, 0x29, 0x20, 0x3e, 
+	0x20, 0x31, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
 	0x09, 0x09, 0x09, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x20, 0x3d, 0x20, 
 	0x09, 0x09, 0x09, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x20, 0x3d, 0x20, 
 	0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5b, 0x32, 0x5d, 0x0a,
 	0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5b, 0x32, 0x5d, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
@@ -3184,53 +3179,23 @@ const unsigned char nogame_lua[] =
 	0x4f, 0x52, 0x53, 0x2c, 0x20, 0x7b, 0x20, 0x32, 0x32, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x33, 
 	0x4f, 0x52, 0x53, 0x2c, 0x20, 0x7b, 0x20, 0x32, 0x32, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x33, 
 	0x39, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, 0x31, 0x33, 0x2f, 0x32, 0x35, 0x35, 0x20, 0x7d, 0x29, 0x20, 
 	0x39, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, 0x31, 0x33, 0x2f, 0x32, 0x35, 0x35, 0x20, 0x7d, 0x29, 0x20, 
 	0x2d, 0x2d, 0x20, 0x4c, 0x49, 0x4d, 0x45, 0x0a,
 	0x2d, 0x2d, 0x20, 0x4c, 0x49, 0x4d, 0x45, 0x0a,
-	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 
-	0x65, 0x20, 0x68, 0x69, 0x67, 0x68, 0x65, 0x72, 0x2d, 0x72, 0x65, 0x73, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 
-	0x63, 0x20, 0x73, 0x70, 0x72, 0x69, 0x74, 0x65, 0x20, 0x73, 0x68, 0x65, 0x65, 0x74, 0x2c, 0x20, 0x77, 0x65, 
-	0x20, 0x77, 0x61, 0x6e, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x64, 0x72, 0x61, 0x77, 0x20, 0x69, 0x74, 0x73, 0x0a,
-	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x73, 0x70, 0x72, 0x69, 0x74, 0x65, 0x73, 0x20, 0x61, 0x74, 0x20, 0x74, 0x68, 
-	0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x20, 0x61, 0x73, 0x20, 0x74, 0x68, 
-	0x65, 0x20, 0x72, 0x65, 0x67, 0x75, 0x6c, 0x61, 0x72, 0x2d, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 
-	0x6f, 0x6e, 0x20, 0x6f, 0x6e, 0x65, 0x2c, 0x20, 0x62, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x0a,
-	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x77, 0x65, 0x27, 0x6c, 0x6c, 0x20, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x6c, 
-	0x79, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x63, 
-	0x61, 0x6c, 0x65, 0x20, 0x2a, 0x65, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2a, 0x20, 0x62, 
-	0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x27, 0x73, 0x0a,
-	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x20, 0x64, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 
-	0x20, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x2e, 0x0a,
-	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x57, 0x65, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x61, 0x76, 0x6f, 0x69, 0x64, 0x20, 
-	0x61, 0x20, 0x6c, 0x6f, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x51, 0x75, 0x61, 0x64, 0x20, 0x73, 0x63, 0x61, 0x6c, 
-	0x69, 0x6e, 0x67, 0x20, 0x62, 0x79, 0x20, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x64, 0x76, 0x61, 
-	0x6e, 0x74, 0x61, 0x67, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x61, 0x63, 0x74, 0x0a,
-	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x51, 0x75, 0x61, 0x64, 0x73, 0x20, 0x75, 0x73, 
-	0x65, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 
-	0x72, 0x65, 0x20, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x74, 
-	0x65, 0x72, 0x6e, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x2d, 0x20, 0x69, 0x66, 0x20, 0x77, 0x65, 0x20, 0x75, 0x73, 
-	0x65, 0x20, 0x0a,
-	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x27, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x69, 
-	0x6d, 0x61, 0x67, 0x65, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x27, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x71, 0x75, 0x61, 
-	0x64, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x40, 0x31, 0x78, 0x20, 
-	0x69, 0x6d, 0x61, 0x67, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x51, 0x75, 0x61, 0x64, 
-	0x73, 0x0a,
-	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x72, 0x65, 0x6e, 
-	0x64, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x6d, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 
-	0x74, 0x68, 0x65, 0x20, 0x40, 0x32, 0x78, 0x20, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 
-	0x77, 0x69, 0x6c, 0x6c, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x0a,
-	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x20, 0x61, 0x73, 0x20, 0x65, 0x78, 0x70, 0x65, 
-	0x63, 0x74, 0x65, 0x64, 0x2e, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x51, 0x55, 0x41, 0x44, 0x53, 0x20, 0x3d, 0x20, 0x7b, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x51, 0x55, 0x41, 0x44, 0x53, 0x20, 0x3d, 0x20, 0x7b, 0x0a,
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 
 	0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x30, 0x2c, 0x20, 0x20, 0x30, 0x2c, 0x20, 0x20, 0x33, 0x32, 0x2c, 
 	0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x30, 0x2c, 0x20, 0x20, 0x30, 0x2c, 0x20, 0x20, 0x33, 0x32, 0x2c, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x29, 
+	0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 
 	0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x30, 0x2c, 0x20, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 
 	0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x30, 0x2c, 0x20, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x29, 
+	0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 
 	0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 
 	0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x29, 
+	0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 
 	0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x33, 0x32, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x20, 0x33, 0x32, 0x2c, 
 	0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x33, 0x32, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x20, 0x33, 0x32, 0x2c, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x29, 
+	0x2c, 0x0a,
 	0x09, 0x09, 0x7d, 0x0a,
 	0x09, 0x09, 0x7d, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x6c, 0x65, 
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x6c, 0x65, 
 	0x66, 0x74, 0x20, 0x3d, 0x20, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x77, 0x77, 
 	0x66, 0x74, 0x20, 0x3d, 0x20, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x77, 0x77, 
@@ -3383,40 +3348,52 @@ const unsigned char nogame_lua[] =
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x47, 0x4c, 0x59, 0x50, 0x48, 0x53, 0x20, 0x3d, 0x20, 0x7b, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x47, 0x4c, 0x59, 0x50, 0x48, 0x53, 0x20, 0x3d, 0x20, 0x7b, 0x0a,
 	0x09, 0x09, 0x09, 0x4e, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x09, 0x09, 0x09, 0x4e, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x30, 0x2c, 0x20, 0x20, 0x36, 0x34, 0x2c, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x30, 0x2c, 0x20, 0x20, 0x36, 0x34, 0x2c, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 
+	0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x4f, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x09, 0x09, 0x09, 0x4f, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x33, 0x32, 0x2c, 0x20, 0x36, 0x34, 0x2c, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x33, 0x32, 0x2c, 0x20, 0x36, 0x34, 0x2c, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 
+	0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x47, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x09, 0x09, 0x09, 0x47, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x30, 0x2c, 0x20, 0x20, 0x39, 0x36, 0x2c, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x30, 0x2c, 0x20, 0x20, 0x39, 0x36, 0x2c, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 
+	0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x41, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x09, 0x09, 0x09, 0x41, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x33, 0x32, 0x2c, 0x20, 0x39, 0x36, 0x2c, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x33, 0x32, 0x2c, 0x20, 0x39, 0x36, 0x2c, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 
+	0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x4d, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x09, 0x09, 0x09, 0x4d, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x36, 0x34, 0x2c, 0x20, 0x39, 0x36, 0x2c, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x36, 0x34, 0x2c, 0x20, 0x39, 0x36, 0x2c, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 
+	0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x45, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x09, 0x09, 0x09, 0x45, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x39, 0x36, 0x2c, 0x20, 0x39, 0x36, 0x2c, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x39, 0x36, 0x2c, 0x20, 0x39, 0x36, 0x2c, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 
+	0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x55, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x09, 0x09, 0x09, 0x55, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x36, 0x34, 0x2c, 0x20, 0x30, 0x2c, 0x20, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x36, 0x34, 0x2c, 0x20, 0x30, 0x2c, 0x20, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 
+	0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x50, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x09, 0x09, 0x09, 0x50, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x39, 0x36, 0x2c, 0x20, 0x30, 0x2c, 0x20, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x39, 0x36, 0x2c, 0x20, 0x30, 0x2c, 0x20, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 
+	0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x6f, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x09, 0x09, 0x09, 0x6f, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x36, 0x34, 0x2c, 0x20, 0x33, 0x32, 0x2c, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x36, 0x34, 0x2c, 0x20, 0x33, 0x32, 0x2c, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 
+	0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x53, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x09, 0x09, 0x09, 0x53, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x39, 0x36, 0x2c, 0x20, 0x33, 0x32, 0x2c, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x39, 0x36, 0x2c, 0x20, 0x33, 0x32, 0x2c, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 
+	0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x52, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x09, 0x09, 0x09, 0x52, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x36, 0x34, 0x2c, 0x20, 0x36, 0x34, 0x2c, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x36, 0x34, 0x2c, 0x20, 0x36, 0x34, 0x2c, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 
+	0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x54, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x09, 0x09, 0x09, 0x54, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x39, 0x36, 0x2c, 0x20, 0x36, 0x34, 0x2c, 
 	0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x39, 0x36, 0x2c, 0x20, 0x36, 0x34, 0x2c, 
-	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
+	0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 
+	0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
 	0x09, 0x09, 0x7d, 0x0a,
 	0x09, 0x09, 0x7d, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x54, 0x45, 
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x54, 0x45, 
 	0x58, 0x54, 0x5f, 0x43, 0x4f, 0x4c, 0x4f, 0x52, 0x20, 0x3d, 0x20, 0x7b, 0x20, 0x31, 0x35, 0x2f, 0x31, 0x36, 
 	0x58, 0x54, 0x5f, 0x43, 0x4f, 0x4c, 0x4f, 0x52, 0x20, 0x3d, 0x20, 0x7b, 0x20, 0x31, 0x35, 0x2f, 0x31, 0x36, 
@@ -3561,7 +3538,7 @@ const unsigned char nogame_lua[] =
 	0x30, 0x36, 0x2f, 0x32, 0x35, 0x35, 0x29, 0x0a,
 	0x30, 0x36, 0x2f, 0x32, 0x35, 0x35, 0x29, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 
 	0x6f, 0x61, 0x64, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x28, 0x66, 0x69, 0x6c, 0x65, 0x2c, 0x20, 0x6e, 0x61, 
 	0x6f, 0x61, 0x64, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x28, 0x66, 0x69, 0x6c, 0x65, 0x2c, 0x20, 0x6e, 0x61, 
-	0x6d, 0x65, 0x29, 0x0a,
+	0x6d, 0x65, 0x2c, 0x20, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x29, 0x0a,
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x20, 0x3d, 
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x20, 0x3d, 
 	0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x28, 
 	0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x28, 
 	0x22, 0x62, 0x61, 0x73, 0x65, 0x36, 0x34, 0x22, 0x2c, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x29, 0x0a,
 	0x22, 0x62, 0x61, 0x73, 0x65, 0x36, 0x34, 0x22, 0x2c, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x29, 0x0a,
@@ -3570,7 +3547,7 @@ const unsigned char nogame_lua[] =
 	0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x6e, 0x65, 0x77, 0x46, 0x69, 
 	0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x6e, 0x65, 0x77, 0x46, 0x69, 
 	0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x28, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x2c, 0x20, 0x6e, 0x61, 
 	0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x28, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x2c, 0x20, 0x6e, 0x61, 
 	0x6d, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5f, 0x22, 0x2c, 0x20, 0x22, 0x2e, 0x22, 0x29, 0x29, 
 	0x6d, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5f, 0x22, 0x2c, 0x20, 0x22, 0x2e, 0x22, 0x29, 0x29, 
-	0x29, 0x0a,
+	0x2c, 0x20, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x29, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x0a,
 	0x09, 0x09, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x0a,
 	0x09, 0x09, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x6f, 0x61, 0x73, 0x74, 0x20, 0x3d, 
 	0x09, 0x09, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x6f, 0x61, 0x73, 0x74, 0x20, 0x3d, 
@@ -3628,17 +3605,10 @@ const unsigned char nogame_lua[] =
 	0x77, 0x28, 0x29, 0x0a,
 	0x77, 0x28, 0x29, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 
 	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 
 	0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x29, 0x0a,
 	0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x29, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x75, 
-	0x73, 0x68, 0x28, 0x29, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x63, 
-	0x61, 0x6c, 0x65, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x67, 0x65, 
-	0x74, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x28, 0x29, 0x29, 0x0a,
 	0x09, 0x09, 0x67, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x2e, 0x6d, 0x6f, 0x73, 0x61, 0x69, 
 	0x09, 0x09, 0x67, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x2e, 0x6d, 0x6f, 0x73, 0x61, 0x69, 
 	0x63, 0x3a, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a,
 	0x63, 0x3a, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a,
 	0x09, 0x09, 0x67, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x2e, 0x74, 0x6f, 0x61, 0x73, 0x74, 
 	0x09, 0x09, 0x67, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x2e, 0x74, 0x6f, 0x61, 0x73, 0x74, 
 	0x3a, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a,
 	0x3a, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x6f, 
-	0x70, 0x28, 0x29, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x72, 0x65, 0x73, 
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x72, 0x65, 0x73, 
 	0x69, 0x7a, 0x65, 0x28, 0x77, 0x2c, 0x20, 0x68, 0x29, 0x0a,
 	0x69, 0x7a, 0x65, 0x28, 0x77, 0x2c, 0x20, 0x68, 0x29, 0x0a,
@@ -3668,7 +3638,8 @@ const unsigned char nogame_lua[] =
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x6f, 0x75, 
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x6f, 0x75, 
-	0x73, 0x65, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x28, 0x78, 0x2c, 0x20, 0x79, 0x2c, 0x20, 0x62, 0x29, 0x0a,
+	0x73, 0x65, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x28, 0x78, 0x2c, 0x20, 0x79, 0x2c, 0x20, 0x62, 0x2c, 
+	0x20, 0x69, 0x73, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x2c, 0x20, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x73, 0x29, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x74, 0x78, 0x20, 0x3d, 0x20, 0x78, 0x20, 0x2f, 0x20, 0x6c, 
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x74, 0x78, 0x20, 0x3d, 0x20, 0x78, 0x20, 0x2f, 0x20, 0x6c, 
 	0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x57, 0x69, 
 	0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x57, 0x69, 
 	0x64, 0x74, 0x68, 0x28, 0x29, 0x0a,
 	0x64, 0x74, 0x68, 0x28, 0x29, 0x0a,
@@ -3677,6 +3648,21 @@ const unsigned char nogame_lua[] =
 	0x69, 0x67, 0x68, 0x74, 0x28, 0x29, 0x0a,
 	0x69, 0x67, 0x68, 0x74, 0x28, 0x29, 0x0a,
 	0x09, 0x09, 0x67, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x2e, 0x74, 0x6f, 0x61, 0x73, 0x74, 
 	0x09, 0x09, 0x67, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x2e, 0x74, 0x6f, 0x61, 0x73, 0x74, 
 	0x3a, 0x6c, 0x6f, 0x6f, 0x6b, 0x5f, 0x61, 0x74, 0x28, 0x74, 0x78, 0x2c, 0x20, 0x74, 0x79, 0x29, 0x0a,
 	0x3a, 0x6c, 0x6f, 0x6f, 0x6b, 0x5f, 0x61, 0x74, 0x28, 0x74, 0x78, 0x2c, 0x20, 0x74, 0x79, 0x29, 0x0a,
+	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x2d, 0x74, 0x61, 0x70, 0x20, 0x74, 0x68, 
+	0x65, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x28, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x75, 0x73, 0x69, 
+	0x6e, 0x67, 0x20, 0x61, 0x20, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x29, 
+	0x20, 0x74, 0x6f, 0x20, 0x65, 0x78, 0x69, 0x74, 0x2e, 0x0a,
+	0x09, 0x09, 0x69, 0x66, 0x20, 0x69, 0x73, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x63, 
+	0x6c, 0x69, 0x63, 0x6b, 0x73, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 
+	0x73, 0x68, 0x6f, 0x77, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6f, 0x78, 0x28, 0x22, 0x45, 0x78, 
+	0x69, 0x74, 0x20, 0x4e, 0x6f, 0x2d, 0x47, 0x61, 0x6d, 0x65, 0x20, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x22, 
+	0x2c, 0x20, 0x22, 0x22, 0x2c, 0x20, 0x7b, 0x22, 0x4f, 0x4b, 0x22, 0x2c, 0x20, 0x22, 0x43, 0x61, 0x6e, 0x63, 
+	0x65, 0x6c, 0x22, 0x7d, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x71, 0x75, 0x69, 
+	0x74, 0x28, 0x29, 0x0a,
+	0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
+	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x6f, 0x75, 
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x6f, 0x75, 
 	0x73, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x28, 0x78, 0x2c, 0x20, 0x79, 0x29, 0x0a,
 	0x73, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x28, 0x78, 0x2c, 0x20, 0x79, 0x29, 0x0a,
@@ -3692,48 +3678,6 @@ const unsigned char nogame_lua[] =
 	0x74, 0x3a, 0x6c, 0x6f, 0x6f, 0x6b, 0x5f, 0x61, 0x74, 0x28, 0x74, 0x78, 0x2c, 0x20, 0x74, 0x79, 0x29, 0x0a,
 	0x74, 0x3a, 0x6c, 0x6f, 0x6f, 0x6b, 0x5f, 0x61, 0x74, 0x28, 0x74, 0x78, 0x2c, 0x20, 0x74, 0x79, 0x29, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
-	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x20, 
-	0x3d, 0x20, 0x7b, 0x74, 0x69, 0x6d, 0x65, 0x3d, 0x30, 0x2c, 0x20, 0x78, 0x3d, 0x30, 0x2c, 0x20, 0x79, 0x3d, 
-	0x30, 0x7d, 0x0a,
-	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x6f, 0x75, 
-	0x63, 0x68, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x28, 0x69, 0x64, 0x2c, 0x20, 0x78, 0x2c, 0x20, 0x79, 
-	0x2c, 0x20, 0x70, 0x72, 0x65, 0x73, 0x73, 0x75, 0x72, 0x65, 0x29, 0x0a,
-	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x2d, 0x74, 0x61, 0x70, 0x20, 0x74, 0x68, 
-	0x65, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x28, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x75, 0x73, 0x69, 
-	0x6e, 0x67, 0x20, 0x61, 0x20, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x29, 
-	0x20, 0x74, 0x6f, 0x20, 0x65, 0x78, 0x69, 0x74, 0x2e, 0x0a,
-	0x09, 0x09, 0x69, 0x66, 0x20, 0x23, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x2e, 0x67, 
-	0x65, 0x74, 0x54, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x73, 0x28, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x20, 0x74, 
-	0x68, 0x65, 0x6e, 0x0a,
-	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x64, 0x69, 0x73, 0x74, 0x20, 0x3d, 0x20, 0x6d, 0x61, 
-	0x74, 0x68, 0x2e, 0x73, 0x71, 0x72, 0x74, 0x28, 0x28, 0x78, 0x2d, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x6f, 
-	0x75, 0x63, 0x68, 0x2e, 0x78, 0x29, 0x5e, 0x32, 0x20, 0x2b, 0x20, 0x28, 0x79, 0x2d, 0x6c, 0x61, 0x73, 0x74, 
-	0x5f, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x2e, 0x79, 0x29, 0x5e, 0x32, 0x29, 0x0a,
-	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x64, 0x69, 0x66, 0x66, 0x74, 0x69, 0x6d, 0x65, 0x20, 
-	0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x74, 0x54, 0x69, 
-	0x6d, 0x65, 0x28, 0x29, 0x20, 0x2d, 0x20, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x2e, 
-	0x74, 0x69, 0x6d, 0x65, 0x0a,
-	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x64, 0x69, 0x66, 0x66, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x3c, 0x20, 0x30, 
-	0x2e, 0x33, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, 0x73, 0x74, 0x20, 0x3c, 0x20, 0x35, 0x30, 0x20, 0x74, 
-	0x68, 0x65, 0x6e, 0x0a,
-	0x09, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 
-	0x2e, 0x73, 0x68, 0x6f, 0x77, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6f, 0x78, 0x28, 0x22, 0x45, 
-	0x78, 0x69, 0x74, 0x20, 0x4e, 0x6f, 0x2d, 0x47, 0x61, 0x6d, 0x65, 0x20, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 
-	0x22, 0x2c, 0x20, 0x22, 0x22, 0x2c, 0x20, 0x7b, 0x22, 0x4f, 0x4b, 0x22, 0x2c, 0x20, 0x22, 0x43, 0x61, 0x6e, 
-	0x63, 0x65, 0x6c, 0x22, 0x7d, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
-	0x09, 0x09, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x71, 0x75, 
-	0x69, 0x74, 0x28, 0x29, 0x0a,
-	0x09, 0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
-	0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
-	0x09, 0x09, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x2e, 0x74, 0x69, 0x6d, 0x65, 
-	0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x74, 0x54, 
-	0x69, 0x6d, 0x65, 0x28, 0x29, 0x0a,
-	0x09, 0x09, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x2e, 0x78, 0x20, 0x3d, 0x20, 
-	0x78, 0x0a,
-	0x09, 0x09, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x2e, 0x79, 0x20, 0x3d, 0x20, 
-	0x79, 0x0a,
-	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
-	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 
 	0x66, 0x28, 0x74, 0x29, 0x0a,
 	0x66, 0x28, 0x74, 0x29, 0x0a,
 	0x09, 0x09, 0x74, 0x2e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x4c, 0x5c, 0x31, 0x39, 0x35, 
 	0x09, 0x09, 0x74, 0x2e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x4c, 0x5c, 0x31, 0x39, 0x35,