From d26002f5cc3a0701c67809aea709c5428b09cd9c Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Thu, 29 Dec 2016 23:55:51 -0400 Subject: [PATCH] Revamped and streamlined retina / high-DPI support (resolves issue #1122). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/common/runtime.cpp | 14 ++ src/common/runtime.h | 1 + src/modules/event/sdl/Event.cpp | 67 ++++--- src/modules/filesystem/FileData.cpp | 18 +- src/modules/filesystem/FileData.h | 4 + src/modules/font/BMFontRasterizer.cpp | 4 +- src/modules/font/BMFontRasterizer.h | 2 +- src/modules/font/Font.cpp | 18 +- src/modules/font/Font.h | 8 +- src/modules/font/ImageRasterizer.cpp | 4 +- src/modules/font/ImageRasterizer.h | 2 +- src/modules/font/Rasterizer.cpp | 5 + src/modules/font/Rasterizer.h | 3 + src/modules/font/freetype/Font.cpp | 17 +- src/modules/font/freetype/Font.h | 7 +- .../font/freetype/TrueTypeRasterizer.cpp | 9 +- .../font/freetype/TrueTypeRasterizer.h | 2 +- src/modules/font/wrap_Font.cpp | 44 +++-- src/modules/graphics/Graphics.h | 7 +- src/modules/graphics/Quad.cpp | 25 +-- src/modules/graphics/Texture.cpp | 17 ++ src/modules/graphics/Texture.h | 8 + src/modules/graphics/opengl/Canvas.cpp | 47 +++-- src/modules/graphics/opengl/Canvas.h | 16 +- src/modules/graphics/opengl/Font.cpp | 88 +++++---- src/modules/graphics/opengl/Font.h | 8 +- src/modules/graphics/opengl/GLBuffer.cpp | 1 - src/modules/graphics/opengl/Graphics.cpp | 120 +++++++++---- src/modules/graphics/opengl/Graphics.h | 29 +-- src/modules/graphics/opengl/Image.cpp | 100 ++++++----- src/modules/graphics/opengl/Image.h | 30 ++-- src/modules/graphics/opengl/OpenGL.cpp | 12 +- src/modules/graphics/opengl/OpenGL.h | 7 +- src/modules/graphics/opengl/Video.cpp | 31 ++-- src/modules/graphics/opengl/Video.h | 8 +- src/modules/graphics/opengl/wrap_Canvas.cpp | 4 +- src/modules/graphics/opengl/wrap_Font.cpp | 8 + src/modules/graphics/opengl/wrap_Graphics.cpp | 120 +++++++++---- src/modules/graphics/opengl/wrap_Graphics.lua | 12 +- src/modules/graphics/opengl/wrap_Image.cpp | 17 +- src/modules/graphics/opengl/wrap_Image.h | 1 + src/modules/graphics/opengl/wrap_Video.cpp | 25 +++ src/modules/graphics/wrap_Texture.cpp | 33 ++++ src/modules/keyboard/sdl/Keyboard.cpp | 4 +- src/modules/mouse/sdl/Mouse.cpp | 16 +- src/modules/window/Window.h | 11 +- src/modules/window/sdl/Window.cpp | 84 +++++++-- src/modules/window/sdl/Window.h | 11 +- src/modules/window/wrap_Window.cpp | 6 +- src/scripts/boot.lua | 5 +- src/scripts/boot.lua.h | 8 +- src/scripts/nogame.lua | 83 +++------ src/scripts/nogame.lua.h | 170 ++++++------------ 53 files changed, 870 insertions(+), 531 deletions(-) diff --git a/src/common/runtime.cpp b/src/common/runtime.cpp index 49d10c9ef..89f5170e5 100644 --- a/src/common/runtime.cpp +++ b/src/common/runtime.cpp @@ -194,6 +194,20 @@ int luax_intflag(lua_State *L, int table_index, const char *key, int defaultValu 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 argc = lua_gettop(L); diff --git a/src/common/runtime.h b/src/common/runtime.h index cbd8eafad..1c9debc61 100644 --- a/src/common/runtime.h +++ b/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); 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 diff --git a/src/modules/event/sdl/Event.cpp b/src/modules/event/sdl/Event.cpp index 2f4abab13..8703eb475 100644 --- a/src/modules/event/sdl/Event.cpp +++ b/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 // 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(Module::M_WINDOW); if (window) - window->windowToPixelCoords(x, y); + window->windowToDPICoords(x, y); } #ifndef LOVE_MACOSX -static void normalizedToPixelCoords(double *x, double *y) +static void normalizedToDPICoords(double *x, double *y) { - auto window = Module::getInstance(Module::M_WINDOW); - int w = 1, h = 1; + double w = 1.0, h = 1.0; + auto window = Module::getInstance(Module::M_WINDOW); if (window) - window->getPixelDimensions(w, h); + { + w = window->getWidth(); + h = window->getHeight(); + window->windowToDPICoords(&w, &h); + } if (x) - *x = ((*x) * (double) w); + *x = ((*x) * w); if (y) - *y = ((*y) * (double) h); + *y = ((*y) * h); } #endif @@ -249,8 +253,8 @@ Message *Event::convert(const SDL_Event &e) double y = (double) e.motion.y; double xrel = (double) e.motion.xrel; 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(y); vargs.emplace_back(xrel); @@ -276,7 +280,7 @@ Message *Event::convert(const SDL_Event &e) double px = (double) e.button.x; double py = (double) e.button.y; - windowToPixelCoords(&px, &py); + windowToDPICoords(&px, &py); vargs.emplace_back(px); vargs.emplace_back(py); 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) { touchNormalizationBug = true; - windowToPixelCoords(&touchinfo.x, &touchinfo.y); - windowToPixelCoords(&touchinfo.dx, &touchinfo.dy); + windowToDPICoords(&touchinfo.x, &touchinfo.y); + windowToDPICoords(&touchinfo.dx, &touchinfo.dy); } else #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. @@ -536,6 +540,7 @@ Message *Event::convertWindowEvent(const SDL_Event &e) vargs.reserve(4); window::Window *win = nullptr; + graphics::Graphics *gfx = nullptr; if (e.type != SDL_WINDOWEVENT) return nullptr; @@ -559,17 +564,29 @@ Message *Event::convertWindowEvent(const SDL_Event &e) break; 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; - SDL_Window *sdlwin = SDL_GetWindowFromID(e.window.windowID); - if (sdlwin) - SDL_GL_GetDrawableSize(sdlwin, &px_w, &px_h); + gfx = Module::getInstance(Module::M_GRAPHICS); + win = Module::getInstance(Module::M_WINDOW); - 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); + // 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(width); + vargs.emplace_back(height); msg = new Message("resize", vargs); } break; diff --git a/src/modules/filesystem/FileData.cpp b/src/modules/filesystem/FileData.cpp index 1fb52813a..bd3c25b4c 100644 --- a/src/modules/filesystem/FileData.cpp +++ b/src/modules/filesystem/FileData.cpp @@ -45,8 +45,15 @@ FileData::FileData(uint64 size, const std::string &filename) 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() @@ -56,7 +63,7 @@ FileData::~FileData() void *FileData::getData() const { - return (void *)data; + return data; } size_t FileData::getSize() const @@ -75,5 +82,10 @@ const std::string &FileData::getExtension() const return extension; } +const std::string &FileData::getName() const +{ + return name; +} + } // filesystem } // love diff --git a/src/modules/filesystem/FileData.h b/src/modules/filesystem/FileData.h index 4f00e99bd..63797ea9a 100644 --- a/src/modules/filesystem/FileData.h +++ b/src/modules/filesystem/FileData.h @@ -49,6 +49,7 @@ public: const std::string &getFilename() const; const std::string &getExtension() const; + const std::string &getName() const; private: @@ -64,6 +65,9 @@ private: // The extension (without dot). Used to identify file type. std::string extension; + // The file name without the extension (and without the dot). + std::string name; + }; // FileData } // filesystem diff --git a/src/modules/font/BMFontRasterizer.cpp b/src/modules/font/BMFontRasterizer.cpp index 2cb50d5f7..3fee26ea5 100644 --- a/src/modules/font/BMFontRasterizer.cpp +++ b/src/modules/font/BMFontRasterizer.cpp @@ -131,11 +131,13 @@ std::string BMFontLine::getAttributeString(const char *name) const } // anonymous namespace -BMFontRasterizer::BMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector &imagelist) +BMFontRasterizer::BMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector &imagelist, float pixeldensity) : fontSize(0) , unicode(false) , lineHeight(0) { + this->pixelDensity = pixeldensity; + const std::string &filename = fontdef->getFilename(); size_t separatorpos = filename.rfind('/'); diff --git a/src/modules/font/BMFontRasterizer.h b/src/modules/font/BMFontRasterizer.h index 845841dc3..c38e50898 100644 --- a/src/modules/font/BMFontRasterizer.h +++ b/src/modules/font/BMFontRasterizer.h @@ -42,7 +42,7 @@ class BMFontRasterizer : public Rasterizer { public: - BMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector &imagelist); + BMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector &imagelist, float pixeldensity); virtual ~BMFontRasterizer(); // Implements Rasterizer. diff --git a/src/modules/font/Font.cpp b/src/modules/font/Font.cpp index 1aeb0eb29..a16c75c5a 100644 --- a/src/modules/font/Font.cpp +++ b/src/modules/font/Font.cpp @@ -47,12 +47,18 @@ Rasterizer *Font::newTrueTypeRasterizer(int size, TrueTypeRasterizer::Hinting hi return newTrueTypeRasterizer(data.get(), size, hinting); } -Rasterizer *Font::newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector &images) +Rasterizer *Font::newTrueTypeRasterizer(int size, float pixeldensity, TrueTypeRasterizer::Hinting hinting) { - return new BMFontRasterizer(fontdef, images); + StrongRef data(new DefaultFontData, Acquire::NORETAIN); + return newTrueTypeRasterizer(data.get(), size, pixeldensity, hinting); } -Rasterizer *Font::newImageRasterizer(love::image::ImageData *data, const std::string &text, int extraspacing) +Rasterizer *Font::newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector &images, float pixeldensity) +{ + return new BMFontRasterizer(fontdef, images, pixeldensity); +} + +Rasterizer *Font::newImageRasterizer(love::image::ImageData *data, const std::string &text, int extraspacing, float pixeldensity) { std::vector glyphs; 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()); } - 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) diff --git a/src/modules/font/Font.h b/src/modules/font/Font.h index 4ead490d4..941550ef1 100644 --- a/src/modules/font/Font.h +++ b/src/modules/font/Font.h @@ -48,12 +48,14 @@ public: virtual Rasterizer *newRasterizer(love::filesystem::FileData *data) = 0; 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, float pixeldensity, TrueTypeRasterizer::Hinting hinting) = 0; - virtual Rasterizer *newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector &images); + virtual Rasterizer *newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector &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, uint32 glyph); diff --git a/src/modules/font/ImageRasterizer.cpp b/src/modules/font/ImageRasterizer.cpp index ad79131ac..6e54f6bc1 100644 --- a/src/modules/font/ImageRasterizer.cpp +++ b/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); } -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) , glyphs(glyphs) , numglyphs(numglyphs) , extraSpacing(extraspacing) { + this->pixelDensity = pixeldensity; + if (data->getFormat() != PIXELFORMAT_RGBA8) throw love::Exception("Only 32-bit RGBA images are supported in Image Fonts!"); diff --git a/src/modules/font/ImageRasterizer.h b/src/modules/font/ImageRasterizer.h index 199e8504e..76d7a64bc 100644 --- a/src/modules/font/ImageRasterizer.h +++ b/src/modules/font/ImageRasterizer.h @@ -39,7 +39,7 @@ namespace font class ImageRasterizer : public Rasterizer { 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(); // Implement Rasterizer diff --git a/src/modules/font/Rasterizer.cpp b/src/modules/font/Rasterizer.cpp index 3632a18da..765059474 100644 --- a/src/modules/font/Rasterizer.cpp +++ b/src/modules/font/Rasterizer.cpp @@ -102,5 +102,10 @@ float Rasterizer::getKerning(uint32 /*leftglyph*/, uint32 /*rightglyph*/) const return 0.0f; } +float Rasterizer::getPixelDensity() const +{ + return pixelDensity; +} + } // font } // love diff --git a/src/modules/font/Rasterizer.h b/src/modules/font/Rasterizer.h index 40b72d5e5..91a906b21 100644 --- a/src/modules/font/Rasterizer.h +++ b/src/modules/font/Rasterizer.h @@ -120,9 +120,12 @@ public: virtual DataType getDataType() const = 0; + float getPixelDensity() const; + protected: FontMetrics metrics; + float pixelDensity; }; // Rasterizer diff --git a/src/modules/font/freetype/Font.cpp b/src/modules/font/freetype/Font.cpp index 6bacef752..62c7a6b83 100644 --- a/src/modules/font/freetype/Font.cpp +++ b/src/modules/font/freetype/Font.cpp @@ -20,9 +20,12 @@ #include "Font.h" +// LOVE #include "TrueTypeRasterizer.h" #include "font/BMFontRasterizer.h" +#include "window/Window.h" +// C++ #include namespace love @@ -48,14 +51,24 @@ Rasterizer *Font::newRasterizer(love::filesystem::FileData *data) if (TrueTypeRasterizer::accepts(library, data)) return newTrueTypeRasterizer(data, 12, TrueTypeRasterizer::HINTING_NORMAL); else if (BMFontRasterizer::accepts(data)) - return newBMFontRasterizer(data, {}); + return newBMFontRasterizer(data, {}, 1.0f); throw love::Exception("Invalid font file: %s", data->getFilename().c_str()); } 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(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 diff --git a/src/modules/font/freetype/Font.h b/src/modules/font/freetype/Font.h index c12813605..1d845c3a6 100644 --- a/src/modules/font/freetype/Font.h +++ b/src/modules/font/freetype/Font.h @@ -44,11 +44,12 @@ public: virtual ~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 - const char *getName() const; + const char *getName() const override; private: diff --git a/src/modules/font/freetype/TrueTypeRasterizer.cpp b/src/modules/font/freetype/TrueTypeRasterizer.cpp index 915a12648..54fb746ed 100644 --- a/src/modules/font/freetype/TrueTypeRasterizer.cpp +++ b/src/modules/font/freetype/TrueTypeRasterizer.cpp @@ -20,9 +20,11 @@ // LOVE #include "TrueTypeRasterizer.h" - #include "common/Exception.h" +// C +#include + namespace love { namespace font @@ -30,10 +32,13 @@ namespace font 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) , hinting(hinting) { + this->pixelDensity = pixeldensity; + size = floorf(size * pixeldensity + 0.5f); + if (size <= 0) throw love::Exception("Invalid TrueType font size: %d", size); diff --git a/src/modules/font/freetype/TrueTypeRasterizer.h b/src/modules/font/freetype/TrueTypeRasterizer.h index b53824267..a50810435 100644 --- a/src/modules/font/freetype/TrueTypeRasterizer.h +++ b/src/modules/font/freetype/TrueTypeRasterizer.h @@ -44,7 +44,7 @@ class TrueTypeRasterizer : public love::font::TrueTypeRasterizer { 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(); // Implement Rasterizer diff --git a/src/modules/font/wrap_Font.cpp b/src/modules/font/wrap_Font.cpp index d26bd4b04..6591429a5 100644 --- a/src/modules/font/wrap_Font.cpp +++ b/src/modules/font/wrap_Font.cpp @@ -78,7 +78,13 @@ int w_newTrueTypeRasterizer(lua_State *L) if (hintstr && !TrueTypeRasterizer::getConstant(hintstr, hinting)) 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 { @@ -98,10 +104,21 @@ int w_newTrueTypeRasterizer(lua_State *L) if (hintstr && !TrueTypeRasterizer::getConstant(hintstr, hinting)) 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); @@ -121,6 +138,7 @@ int w_newBMFontRasterizer(lua_State *L) filesystem::FileData *d = filesystem::luax_getfiledata(L, 1); std::vector images; + float pixeldensity = (float) luaL_optnumber(L, 3, 1.0); if (lua_istable(L, 2)) { @@ -138,17 +156,14 @@ int w_newBMFontRasterizer(lua_State *L) } else { - for (int i = 2; i <= lua_gettop(L); i++) - { - convimagedata(L, i); - image::ImageData *id = luax_checktype(L, i); - images.push_back(id); - id->retain(); - } + convimagedata(L, 2); + image::ImageData *id = luax_checktype(L, 2); + images.push_back(id); + id->retain(); } luax_catchexcept(L, - [&]() { t = instance()->newBMFontRasterizer(d, images); }, + [&]() { t = instance()->newBMFontRasterizer(d, images, pixeldensity); }, [&](bool) { d->release(); for (auto id : images) id->release(); } ); @@ -166,8 +181,9 @@ int w_newImageRasterizer(lua_State *L) image::ImageData *d = luax_checktype(L, 1); std::string glyphs = luax_checkstring(L, 2); 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); t->release(); diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 390aaf938..3ad19e728 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -290,14 +290,14 @@ public: /** * 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. * @param width The viewport width. * @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 @@ -319,6 +319,9 @@ public: **/ virtual bool isActive() const = 0; + virtual int getWidth() const = 0; + virtual int getHeight() const = 0; + virtual bool isCanvasActive() const = 0; virtual void flushStreamDraws() = 0; diff --git a/src/modules/graphics/Quad.cpp b/src/modules/graphics/Quad.cpp index f4bfb6a71..987d0cb1b 100644 --- a/src/modules/graphics/Quad.cpp +++ b/src/modules/graphics/Quad.cpp @@ -46,12 +46,13 @@ Quad::~Quad() void Quad::refresh(const Quad::Viewport &v, double sw, double sh) { viewport = v; + this->sw = sw; + this->sh = sh; // Vertices are ordered for use with triangle strips: - // 0----2 - // | / | - // | / | - // 1----3 + // 0---2 + // | / | + // 1---3 vertices[0].x = 0.0f; vertices[0].y = 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].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) diff --git a/src/modules/graphics/Texture.cpp b/src/modules/graphics/Texture.cpp index 4cfc6cf0b..d3daa3d65 100644 --- a/src/modules/graphics/Texture.cpp +++ b/src/modules/graphics/Texture.cpp @@ -33,6 +33,8 @@ Texture::Texture() : format(PIXELFORMAT_UNKNOWN) , width(0) , height(0) + , pixelWidth(0) + , pixelHeight(0) , filter(getDefaultFilter()) , wrap() , vertices() @@ -90,6 +92,21 @@ int Texture::getHeight() const 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 { return filter; diff --git a/src/modules/graphics/Texture.h b/src/modules/graphics/Texture.h index ac6ff72f1..4e771110e 100644 --- a/src/modules/graphics/Texture.h +++ b/src/modules/graphics/Texture.h @@ -94,6 +94,11 @@ public: virtual int getWidth() const; virtual int getHeight() const; + virtual int getPixelWidth() const; + virtual int getPixelHeight() const; + + float getPixelDensity() const; + virtual void setFilter(const Filter &f) = 0; virtual const Filter &getFilter() const; @@ -125,6 +130,9 @@ protected: int width; int height; + int pixelWidth; + int pixelHeight; + Filter filter; Wrap wrap; diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp index af0909da8..e682b901a 100644 --- a/src/modules/graphics/opengl/Canvas.cpp +++ b/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); 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) , msaa_buffer(0) - , requested_format(format) - , requested_samples(msaa) , actual_samples(0) , texture_memory(0) { this->width = width; this->height = height; - - float w = static_cast(width); - float h = static_cast(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: - // 0----2 - // | / | - // | / | - // 1----3 + // 0---2 + // | / | + // 1---3 // world coordinates vertices[0].x = 0; vertices[0].y = 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[3].x = w; - vertices[3].y = h; + vertices[3].x = (float) width; + vertices[3].y = (float) height; // texture coordinates vertices[0].s = 0; @@ -140,7 +137,7 @@ Canvas::Canvas(int width, int height, PixelFormat format, int msaa) vertices[3].s = 1; vertices[3].t = 1; - this->format = getSizedFormat(requested_format); + this->format = getSizedFormat(settings.format); loadVolatile(); @@ -165,7 +162,7 @@ bool Canvas::loadVolatile() status = GL_FRAMEBUFFER_COMPLETE; // 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; return false; @@ -173,8 +170,8 @@ bool Canvas::loadVolatile() // getMaxRenderbufferSamples will be 0 on systems that don't support // 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); gl.bindTextureToUnit(texture, 0, false); @@ -191,8 +188,8 @@ bool Canvas::loadVolatile() while (glGetError() != GL_NO_ERROR) /* 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) { @@ -215,14 +212,14 @@ bool Canvas::loadVolatile() 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)) actual_samples = 0; size_t prevmemsize = texture_memory; - texture_memory = getPixelFormatSize(format) * width * height; + texture_memory = getPixelFormatSize(format) * pixelWidth * pixelHeight; if (msaa_buffer != 0) texture_memory += (texture_memory * actual_samples); @@ -276,7 +273,7 @@ bool Canvas::setWrap(const Texture::Wrap &w) wrap = w; 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) 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) { - 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."); Graphics *gfx = Module::getInstance(Module::M_GRAPHICS); diff --git a/src/modules/graphics/opengl/Canvas.h b/src/modules/graphics/opengl/Canvas.h index 4dcb808cd..9e1389bd0 100644 --- a/src/modules/graphics/opengl/Canvas.h +++ b/src/modules/graphics/opengl/Canvas.h @@ -45,7 +45,14 @@ public: 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(); // Implements Volatile. @@ -71,7 +78,7 @@ public: inline int getRequestedMSAA() const { - return requested_samples; + return settings.msaa; } inline ptrdiff_t getMSAAHandle() const @@ -95,16 +102,15 @@ private: void drawv(Graphics *gfx, const Matrix4 &t, const Vertex *v) override; + Settings settings; + GLuint fbo; GLuint texture; GLuint msaa_buffer; - PixelFormat requested_format; - GLenum status; - int requested_samples; int actual_samples; size_t texture_memory; diff --git a/src/modules/graphics/opengl/Font.cpp b/src/modules/graphics/opengl/Font.cpp index be864cac3..f98d5ea5b 100644 --- a/src/modules/graphics/opengl/Font.cpp +++ b/src/modules/graphics/opengl/Font.cpp @@ -47,19 +47,20 @@ static inline uint16 normToUint16(double n) love::Type Font::type("Font", &Object::type); 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}) , height(r->getHeight()) , lineHeight(1) , textureWidth(128) , textureHeight(128) - , filter(filter) + , filter(f) + , pixelDensity(r->getPixelDensity()) , useSpacesAsTab(false) , quadIndices(20) // We make this bigger at draw-time, if needed. , textureCacheID(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 // largest texture size if no rough match is found. @@ -100,9 +101,10 @@ Font::TextureSize Font::getNextTextureSize() const { 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. if (size.width == size.height) @@ -114,7 +116,7 @@ Font::TextureSize Font::getNextTextureSize() const return size; } -void Font::createTexture() +bool Font::createTexture() { auto gfx = Module::getInstance(Module::M_GRAPHICS); gfx->flushStreamDraws(); @@ -200,6 +202,8 @@ void Font::createTexture() } else textures.push_back(t); + + return true; } love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph) @@ -236,32 +240,35 @@ const Font::Glyph &Font::addGlyph(uint32 glyph) int w = gd->getWidth(); 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; g.texture = 0; - g.spacing = gd->getAdvance(); + g.spacing = floorf(gd->getAdvance() / pixelDensity + 0.5f); 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) { bool isSRGB = isGammaCorrect(); @@ -278,31 +285,31 @@ const Font::Glyph &Font::addGlyph(uint32 glyph) 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. for (int i = 0; i < 4; 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; 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) @@ -329,7 +336,7 @@ float Font::getKerning(uint32 leftglyph, uint32 rightglyph) { if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph)) { - k = r->getKerning(leftglyph, rightglyph); + k = floorf(r->getKerning(leftglyph, rightglyph) / pixelDensity + 0.5f); break; } } @@ -390,7 +397,7 @@ void Font::getCodepointsFromString(const std::vector &strs, Color float Font::getHeight() const { - return (float) height; + return (float) floorf(height / pixelDensity + 0.5f); } std::vector Font::generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantcolor, std::vector &vertices, float extra_spacing, Vector offset, TextInfo *info) @@ -945,12 +952,12 @@ void Font::unloadVolatile() int Font::getAscent() const { - return rasterizers[0]->getAscent(); + return floorf(rasterizers[0]->getAscent() / pixelDensity + 0.5f); } int Font::getDescent() const { - return rasterizers[0]->getDescent(); + return floorf(rasterizers[0]->getDescent() / pixelDensity + 0.5f); } float Font::getBaseline() const @@ -1014,6 +1021,11 @@ void Font::setFallbacks(const std::vector &fallbacks) rasterizers.push_back(f->rasterizers[0]); } +float Font::getPixelDensity() const +{ + return pixelDensity; +} + uint32 Font::getTextureCacheID() const { return textureCacheID; diff --git a/src/modules/graphics/opengl/Font.h b/src/modules/graphics/opengl/Font.h index 3b9fbd3f8..713fa57a3 100644 --- a/src/modules/graphics/opengl/Font.h +++ b/src/modules/graphics/opengl/Font.h @@ -100,7 +100,7 @@ public: int vertexcount; }; - Font(love::font::Rasterizer *r, const Texture::Filter &filter = Texture::getDefaultFilter()); + Font(love::font::Rasterizer *r, const Texture::Filter &filter); virtual ~Font(); @@ -179,6 +179,8 @@ public: void setFallbacks(const std::vector &fallbacks); + float getPixelDensity() const; + uint32 getTextureCacheID() const; static bool getConstant(const char *in, AlignMode &out); @@ -202,7 +204,7 @@ private: }; TextureSize getNextTextureSize() const; - void createTexture(); + bool createTexture(); love::font::GlyphData *getRasterizerGlyphData(uint32 glyph); const Glyph &addGlyph(uint32 glyph); const Glyph &findGlyph(uint32 glyph); @@ -230,6 +232,8 @@ private: Texture::Filter filter; + float pixelDensity; + int textureX, textureY; int rowHeight; diff --git a/src/modules/graphics/opengl/GLBuffer.cpp b/src/modules/graphics/opengl/GLBuffer.cpp index 20ea8559f..bf19e19af 100644 --- a/src/modules/graphics/opengl/GLBuffer.cpp +++ b/src/modules/graphics/opengl/GLBuffer.cpp @@ -366,7 +366,6 @@ void QuadIndices::fill() using namespace love::graphics::vertex; fillIndices(TriangleIndexMode::QUADS, 0, maxSize * 4, (T *) indices); - indexBuffer->fill(0, indexBuffer->getSize(), indices); } diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index d2bef6272..30ed49b5c 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -56,6 +56,8 @@ Graphics::Graphics() : quadIndices(nullptr) , width(0) , height(0) + , pixelWidth(0) + , pixelHeight(0) , created(false) , active(true) , writingToStencil(false) @@ -74,10 +76,11 @@ Graphics::Graphics() 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()); } -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->height = height; + this->pixelWidth = pixelwidth; + this->pixelHeight = pixelheight; if (states.back().canvases.empty()) { // 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 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->height = height; @@ -247,6 +273,8 @@ bool Graphics::setMode(int width, int height) created = true; + setViewportSize(width, height, pixelwidth, pixelheight); + // Enable blending glEnable(GL_BLEND); @@ -310,8 +338,6 @@ bool Graphics::setMode(int width, int height) if (quadIndices == nullptr) quadIndices = new QuadIndices(20); - setViewportSize(width, height); - // Restore the graphics state. restoreState(states.back()); @@ -608,13 +634,15 @@ void Graphics::setCanvas(const std::vector &canvases) PixelFormat firstformat = firstcanvas->getPixelFormat(); bool hasSRGBcanvas = firstformat == PIXELFORMAT_sRGBA8; + int pixelwidth = firstcanvas->getPixelWidth(); + int pixelheight = firstcanvas->getPixelHeight(); for (int i = 1; i < ncanvases; 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) 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 &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 h = firstcanvas->getHeight(); - - gl.setViewport({0, 0, w, h}, true); projectionMatrix = Matrix4::ortho(0.0, (float) w, 0.0, (float) h); // Make sure the correct sRGB setting is used when drawing to the canvases. @@ -683,7 +716,13 @@ void Graphics::setCanvas() state.canvases.clear(); 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 // to OpenGL considering (0,0) bottom-left instead of top-left. @@ -723,8 +762,8 @@ void Graphics::endPass() // Resolve MSAA buffers. 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++) { @@ -909,8 +948,8 @@ void Graphics::bindCachedFBO(const std::vector &canvases) } 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); glGenFramebuffers(1, &fbo); @@ -1054,8 +1093,8 @@ void Graphics::present(void *screenshotCallbackData) if (!pendingScreenshotCallbacks.empty()) { - int w = getWidth(); - int h = getHeight(); + int w = getPixelWidth(); + int h = getPixelHeight(); size_t row = 4 * w; size_t size = row * h; @@ -1170,6 +1209,16 @@ int Graphics::getHeight() const return height; } +int Graphics::getPixelWidth() const +{ + return pixelWidth; +} + +int Graphics::getPixelHeight() const +{ + return pixelHeight; +} + bool Graphics::isCreated() const { return created; @@ -1182,8 +1231,17 @@ void Graphics::setScissor(const Rect &rect) DisplayState &state = states.back(); 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. - gl.setScissor(rect, !state.canvases.empty()); + gl.setScissor(glrect, !state.canvases.empty()); state.scissor = true; state.scissorRect = rect; @@ -1358,14 +1416,14 @@ void Graphics::clearStencil() glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } -Image *Graphics::newImage(const std::vector &data, const Image::Flags &flags) +Image *Graphics::newImage(const std::vector &data, const Image::Settings &settings) { - return new Image(data, flags); + return new Image(data, settings); } -Image *Graphics::newImage(const std::vector &cdata, const Image::Flags &flags) +Image *Graphics::newImage(const std::vector &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) @@ -1388,15 +1446,15 @@ ParticleSystem *Graphics::newParticleSystem(Texture *texture, int 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()) throw love::Exception("Canvases are not supported by your OpenGL drivers!"); - if (!Canvas::isFormatSupported(format)) + if (!Canvas::isFormatSupported(settings.format)) { 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); } @@ -1405,7 +1463,7 @@ Canvas *Graphics::newCanvas(int width, int height, PixelFormat format, int msaa) else if (height > gl.getMaxTextureSize()) 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(); // everything ok, return canvas (early out) @@ -1447,9 +1505,9 @@ Text *Graphics::newText(Font *font, const std::vector &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 @@ -1690,7 +1748,7 @@ void Graphics::setPointSize(float size) if (streamBufferState.primitiveMode == vertex::PrimitiveMode::POINTS) flushStreamDraws(); - gl.setPointSize(size); + gl.setPointSize(size * getCurrentPixelDensity()); states.back().pointSize = size; } diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 88bbee524..69af03835 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -80,8 +80,8 @@ public: // Implements Module. 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 setActive(bool active) override; @@ -109,15 +109,13 @@ public: **/ 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. @@ -172,8 +170,8 @@ public: /** * Creates an Image object with padding and/or optimization. **/ - Image *newImage(const std::vector &data, const Image::Flags &flags); - Image *newImage(const std::vector &cdata, const Image::Flags &flags); + Image *newImage(const std::vector &data, const Image::Settings &settings); + Image *newImage(const std::vector &cdata, const Image::Settings &settings); Quad *newQuad(Quad::Viewport v, double sw, double sh); @@ -186,7 +184,7 @@ public: 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); @@ -198,7 +196,7 @@ public: Text *newText(Font *font, const std::vector &text = {}); - Video *newVideo(love::video::VideoStream *stream); + Video *newVideo(love::video::VideoStream *stream, float pixeldensity); bool isGammaCorrect() const; @@ -440,6 +438,9 @@ private: int width; int height; + int pixelWidth; + int pixelHeight; + bool created; bool active; diff --git a/src/modules/graphics/opengl/Image.cpp b/src/modules/graphics/opengl/Image.cpp index da5d2fcbb..689d5b868 100644 --- a/src/modules/graphics/opengl/Image.cpp +++ b/src/modules/graphics/opengl/Image.cpp @@ -94,11 +94,11 @@ static bool verifyMipmapLevels(const std::vector &miplevels) return true; } -Image::Image(const std::vector &imagedata, const Flags &flags) +Image::Image(const std::vector &imagedata, const Settings &settings) : texture(0) , mipmapSharpness(defaultMipmapSharpness) , compressed(false) - , flags(flags) + , settings(settings) , sRGB(false) , usingDefaultTexture(false) , textureMemorySize(0) @@ -106,11 +106,14 @@ Image::Image(const std::vector &imagedata, const Flags if (imagedata.empty()) 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)) - this->flags.mipmaps = true; + this->settings.mipmaps = true; for (const auto &id : imagedata) data.push_back(id); @@ -123,24 +126,27 @@ Image::Image(const std::vector &imagedata, const Flags ++imageCount; } -Image::Image(const std::vector &compresseddata, const Flags &flags) +Image::Image(const std::vector &compresseddata, const Settings &settings) : texture(0) , mipmapSharpness(defaultMipmapSharpness) , compressed(true) - , flags(flags) + , settings(settings) , sRGB(false) , usingDefaultTexture(false) , 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)) - 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) - this->flags.mipmaps = false; + this->settings.mipmaps = false; else { 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 are ordered for use with triangle strips: - // 0----2 - // | / | - // | / | - // 1----3 + // 0---2 + // | / | + // 1---3 vertices[0].x = 0.0f; vertices[0].y = 0.0f; vertices[1].x = 0.0f; @@ -194,13 +199,13 @@ void Image::preload() vertices[3].s = 1.0f; vertices[3].t = 1.0f; - if (flags.mipmaps) + if (settings.mipmaps) filter.mipmap = defaultMipmapFilter; if (!isGammaCorrect()) - flags.linear = false; + settings.linear = false; - if (isGammaCorrect() && !flags.linear) + if (isGammaCorrect() && !settings.linear) sRGB = true; else sRGB = false; @@ -210,7 +215,7 @@ void Image::generateMipmaps() { // The GL_GENERATE_MIPMAP texparameter is set in loadVolatile if we don't // 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)) { if (gl.bugs.generateMipmapsRequiresTexture2DEnable) @@ -239,13 +244,13 @@ void Image::loadFromCompressedData() OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB); if (isGammaCorrect() && !sRGB) - flags.linear = true; + settings.linear = true; int count = 1; - if (flags.mipmaps && cdata.size() > 1) + if (settings.mipmaps && cdata.size() > 1) count = (int) cdata.size(); - else if (flags.mipmaps) + else if (settings.mipmaps) count = cdata[0]->getMipmapCount(); for (int i = 0; i < count; i++) @@ -266,9 +271,9 @@ void Image::loadFromImageData() OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, 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++) { @@ -310,16 +315,16 @@ bool Image::loadVolatile() if (sRGB && (GLAD_ES_VERSION_2_0 && GLAD_EXT_sRGB && !GLAD_ES_VERSION_3_0) && data.size() <= 1) { - flags.mipmaps = false; + settings.mipmaps = false; filter.mipmap = FILTER_NONE; } } // 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)) - && (width != nextP2(width) || height != nextP2(height))) + && (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight))) { - flags.mipmaps = false; + settings.mipmaps = false; filter.mipmap = FILTER_NONE; } @@ -334,16 +339,16 @@ bool Image::loadVolatile() setMipmapSharpness(mipmapSharpness); // 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(); 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); - 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)) { // Auto-generate mipmaps every time the texture is modified, if @@ -378,7 +383,7 @@ bool Image::loadVolatile() else textureMemorySize = data[0]->getSize(); - if (flags.mipmaps) + if (settings.mipmaps) textureMemorySize *= 1.33334; gl.updateTextureMemorySize(prevmemsize, textureMemorySize); @@ -406,7 +411,7 @@ bool Image::refresh(int xoffset, int yoffset, int w, int h) return false; 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."); } @@ -424,7 +429,7 @@ bool Image::refresh(int xoffset, int yoffset, int w, int h) bool isSRGB = sRGB; 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.) for (int i = 0; i < mipcount; i++) @@ -465,9 +470,9 @@ const std::vector> &Image::getCompre 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."); else throw love::Exception("Invalid texture filter."); @@ -500,7 +505,7 @@ bool Image::setWrap(const Texture::Wrap &w) wrap = w; 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) success = false; @@ -543,9 +548,9 @@ float Image::getMipmapSharpness() const return mipmapSharpness; } -const Image::Flags &Image::getFlags() const +const Image::Settings &Image::getFlags() const { - return flags; + return settings; } 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; } -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::Entry Image::flagNameEntries[] = +StringMap::Entry Image::settingTypeEntries[] = { - {"mipmaps", FLAG_TYPE_MIPMAPS}, - {"linear", FLAG_TYPE_LINEAR}, + { "mipmaps", SETTING_MIPMAPS }, + { "linear", SETTING_LINEAR }, + { "pixeldensity", SETTING_PIXELDENSITY }, }; -StringMap Image::flagNames(Image::flagNameEntries, sizeof(Image::flagNameEntries)); +StringMap Image::settingTypes(Image::settingTypeEntries, sizeof(Image::settingTypeEntries)); } // opengl } // graphics diff --git a/src/modules/graphics/opengl/Image.h b/src/modules/graphics/opengl/Image.h index 775be446b..4a408d9a1 100644 --- a/src/modules/graphics/opengl/Image.h +++ b/src/modules/graphics/opengl/Image.h @@ -54,17 +54,19 @@ public: 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 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 * mip levels must be present. **/ - Image(const std::vector &data, const Flags &flags); + Image(const std::vector &data, const Settings &settings); /** * Creates a new Image with compressed image data. * * @param cdata The compressed data from which to load the image. **/ - Image(const std::vector &cdata, const Flags &flags); + Image(const std::vector &cdata, const Settings &settings); virtual ~Image(); @@ -112,7 +114,7 @@ public: **/ bool refresh(int xoffset, int yoffset, int w, int h); - const Flags &getFlags() const; + const Settings &getFlags() const; static void setDefaultMipmapSharpness(float sharpness); static float getDefaultMipmapSharpness(); @@ -122,8 +124,8 @@ public: static bool isFormatSupported(PixelFormat pixelformat); 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; @@ -154,8 +156,8 @@ private: // Whether this Image is using a compressed texture. bool compressed; - // The flags used to initialize this Image. - Flags flags; + // The settings used to initialize this Image. + Settings settings; bool sRGB; @@ -170,8 +172,8 @@ private: static FilterMode defaultMipmapFilter; static float defaultMipmapSharpness; - static StringMap::Entry flagNameEntries[]; - static StringMap flagNames; + static StringMap::Entry settingTypeEntries[]; + static StringMap settingTypes; }; // Image diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index df02cc663..643adab02 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/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); } -void OpenGL::setViewport(const Rect &v, bool canvasActive) +void OpenGL::setViewport(const Rect &v) { glViewport(v.x, v.y, v.w, v.h); 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 @@ -446,11 +441,6 @@ void OpenGL::setScissor(const Rect &v, bool canvasActive) state.scissor = v; } -Rect OpenGL::getScissor() const -{ - return state.scissor; -} - void OpenGL::setPointSize(float size) { if (GLAD_VERSION_1_0) diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index 8cf92e1cb..12911d759 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/src/modules/graphics/opengl/OpenGL.h @@ -244,7 +244,7 @@ public: * Sets the OpenGL rendering viewport to the specified rectangle. * 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. @@ -257,11 +257,6 @@ public: **/ 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. **/ diff --git a/src/modules/graphics/opengl/Video.cpp b/src/modules/graphics/opengl/Video.cpp index 5eac5d29f..57be2e92a 100644 --- a/src/modules/graphics/opengl/Video.cpp +++ b/src/modules/graphics/opengl/Video.cpp @@ -33,8 +33,10 @@ namespace opengl love::Type Video::type("Video", &Drawable::type); -Video::Video(love::video::VideoStream *stream) +Video::Video(love::video::VideoStream *stream, float pixeldensity) : stream(stream) + , width(stream->getWidth() / pixeldensity) + , height(stream->getHeight() / pixeldensity) , filter(Texture::getDefaultFilter()) { filter.mipmap = Texture::FILTER_NONE; @@ -45,18 +47,17 @@ Video::Video(love::video::VideoStream *stream) vertices[i].color = Color(255, 255, 255, 255); // Vertices are ordered for use with triangle strips: - // 0----2 - // | / | - // | / | - // 1----3 + // 0---2 + // | / | + // 1---3 vertices[0].x = 0.0f; vertices[0].y = 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[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].t = 0.0f; @@ -185,10 +186,20 @@ void Video::setSource(love::audio::Source *source) int Video::getWidth() const { - return stream->getWidth(); + return width; } int Video::getHeight() const +{ + return height; +} + +int Video::getPixelWidth() const +{ + return stream->getWidth(); +} + +int Video::getPixelHeight() const { return stream->getHeight(); } diff --git a/src/modules/graphics/opengl/Video.h b/src/modules/graphics/opengl/Video.h index 325afbc21..b7442d340 100644 --- a/src/modules/graphics/opengl/Video.h +++ b/src/modules/graphics/opengl/Video.h @@ -42,7 +42,7 @@ public: static love::Type type; - Video(love::video::VideoStream *stream); + Video(love::video::VideoStream *stream, float pixeldensity = 1.0f); ~Video(); // Volatile @@ -60,6 +60,9 @@ public: int getWidth() const; int getHeight() const; + int getPixelWidth() const; + int getPixelHeight() const; + void setFilter(const Texture::Filter &f); const Texture::Filter &getFilter() const; @@ -70,6 +73,9 @@ private: StrongRef stream; StrongRef source; + int width; + int height; + GLuint textures[3]; Vertex vertices[4]; diff --git a/src/modules/graphics/opengl/wrap_Canvas.cpp b/src/modules/graphics/opengl/wrap_Canvas.cpp index 08694792c..bc4dfef83 100644 --- a/src/modules/graphics/opengl/wrap_Canvas.cpp +++ b/src/modules/graphics/opengl/wrap_Canvas.cpp @@ -90,8 +90,8 @@ int w_Canvas_newImageData(lua_State *L) love::image::Image *image = luax_getmodule(L, love::image::Image::type); int x = (int) luaL_optnumber(L, 2, 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; luax_catchexcept(L, [&](){ img = canvas->newImageData(image, x, y, w, h); }); diff --git a/src/modules/graphics/opengl/wrap_Font.cpp b/src/modules/graphics/opengl/wrap_Font.cpp index ce2c65a38..231b6d6fb 100644 --- a/src/modules/graphics/opengl/wrap_Font.cpp +++ b/src/modules/graphics/opengl/wrap_Font.cpp @@ -188,6 +188,13 @@ int w_Font_setFallbacks(lua_State *L) 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[] = { { "getHeight", w_Font_getHeight }, @@ -202,6 +209,7 @@ static const luaL_Reg w_Font_functions[] = { "getBaseline", w_Font_getBaseline }, { "hasGlyphs", w_Font_hasGlyphs }, { "setFallbacks", w_Font_setFallbacks }, + { "getPixelDensity", w_Font_getPixelDensity }, { 0, 0 } }; diff --git a/src/modules/graphics/opengl/wrap_Graphics.cpp b/src/modules/graphics/opengl/wrap_Graphics.cpp index 0413d3aea..095e541da 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/src/modules/graphics/opengl/wrap_Graphics.cpp @@ -32,6 +32,7 @@ #include #include +#include #include @@ -175,6 +176,25 @@ int w_getDimensions(lua_State *L) 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) { // Disable stencil writes. @@ -385,13 +405,6 @@ int w_getStencilTest(lua_State *L) 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) { luax_checkgraphicscreated(L); @@ -399,13 +412,7 @@ int w_newImage(lua_State *L) std::vector data; std::vector 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; @@ -418,6 +425,20 @@ int w_newImage(lua_State *L) love::filesystem::FileData *fdata = love::filesystem::luax_getfiledata(L, 1); + // Parse a density scale of 2.0 from "image@2x.png". + 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)) { luax_catchexcept(L, @@ -441,12 +462,18 @@ int w_newImage(lua_State *L) else 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. - // i.e. flags = {mipmaps = {mip1, mip2, ...}}. + // i.e. settings = {mipmaps = {mip1, mip2, ...}}. if (lua_istable(L, -1)) { for (size_t i = 1; i <= luax_objlen(L, -1); i++) @@ -480,9 +507,9 @@ int w_newImage(lua_State *L) luax_catchexcept(L, [&]() { if (!cdata.empty()) - image = instance()->newImage(cdata, flags); + image = instance()->newImage(cdata, settings); else if (!data.empty()) - image = instance()->newImage(data, flags); + image = instance()->newImage(data, settings); }, [&](bool) { if (releasedata) @@ -514,8 +541,20 @@ int w_newQuad(lua_State *L) v.w = luaL_checknumber(L, 3); 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); luax_pushtype(L, quad); @@ -641,19 +680,31 @@ int w_newCanvas(lua_State *L) luax_checkgraphicscreated(L); // 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; - luax_catchexcept(L, - [&](){ canvas = instance()->newCanvas(width, height, format, msaa); } - ); + luax_catchexcept(L, [&](){ canvas = instance()->newCanvas(width, height, settings); }); if (canvas == nullptr) 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"); auto stream = luax_checktype(L, 1); + float pixeldensity = (float) luaL_optnumber(L, 2, 1.0); Video *video = nullptr; - luax_catchexcept(L, [&]() { video = instance()->newVideo(stream); }); + luax_catchexcept(L, [&]() { video = instance()->newVideo(stream, pixeldensity); }); + luax_pushtype(L, video); video->release(); return 1; @@ -2107,6 +2160,9 @@ static const luaL_Reg functions[] = { "getWidth", w_getWidth }, { "getHeight", w_getHeight }, { "getDimensions", w_getDimensions }, + { "getPixelWidth", w_getPixelWidth }, + { "getPixelHeight", w_getPixelHeight }, + { "getPixelDimensions", w_getPixelDimensions }, { "setScissor", w_setScissor }, { "intersectScissor", w_intersectScissor }, diff --git a/src/modules/graphics/opengl/wrap_Graphics.lua b/src/modules/graphics/opengl/wrap_Graphics.lua index cecb7bf49..0bdf5d67d 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.lua +++ b/src/modules/graphics/opengl/wrap_Graphics.lua @@ -341,16 +341,20 @@ end 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 - 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") end if success then video:setSource(source) - elseif loadaudio == true then + elseif settings.audio == true then if love.audio then error("Video had no audio track", 2) else diff --git a/src/modules/graphics/opengl/wrap_Image.cpp b/src/modules/graphics/opengl/wrap_Image.cpp index 5ac78ebf4..fc3711262 100644 --- a/src/modules/graphics/opengl/wrap_Image.cpp +++ b/src/modules/graphics/opengl/wrap_Image.cpp @@ -114,25 +114,28 @@ int w_Image_getData(lua_State *L) return n; } -static const char *imageFlagName(Image::FlagType flagtype) +const char *luax_imageSettingName(Image::SettingType settingtype) { const char *name = nullptr; - Image::getConstant(flagtype, name); + Image::getConstant(settingtype, name); return name; } int w_Image_getFlags(lua_State *L) { Image *i = luax_checkimage(L, 1); - Image::Flags flags = i->getFlags(); + Image::Settings settings = i->getFlags(); 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; } diff --git a/src/modules/graphics/opengl/wrap_Image.h b/src/modules/graphics/opengl/wrap_Image.h index 10666a6e8..3a3122a5c 100644 --- a/src/modules/graphics/opengl/wrap_Image.h +++ b/src/modules/graphics/opengl/wrap_Image.h @@ -33,6 +33,7 @@ namespace graphics namespace opengl { +const char *luax_imageSettingName(Image::SettingType settingtype); Image *luax_checkimage(lua_State *L, int idx); extern "C" int luaopen_image(lua_State *L); diff --git a/src/modules/graphics/opengl/wrap_Video.cpp b/src/modules/graphics/opengl/wrap_Video.cpp index 9c2e54935..a368738fd 100644 --- a/src/modules/graphics/opengl/wrap_Video.cpp +++ b/src/modules/graphics/opengl/wrap_Video.cpp @@ -90,6 +90,28 @@ int w_Video_getDimensions(lua_State *L) 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) { Video *video = luax_checkvideo(L, 1); @@ -136,6 +158,9 @@ static const luaL_Reg functions[] = { "getWidth", w_Video_getWidth }, { "getHeight", w_Video_getHeight }, { "getDimensions", w_Video_getDimensions }, + { "getPixelWidth", w_Video_getPixelWidth }, + { "getPixelHeight", w_Video_getPixelHeight }, + { "getPixelDimensions", w_Video_getPixelDimensions }, { "setFilter", w_Video_setFilter }, { "getFilter", w_Video_getFilter }, { 0, 0 } diff --git a/src/modules/graphics/wrap_Texture.cpp b/src/modules/graphics/wrap_Texture.cpp index b47ff8724..ef4ea3820 100644 --- a/src/modules/graphics/wrap_Texture.cpp +++ b/src/modules/graphics/wrap_Texture.cpp @@ -52,6 +52,35 @@ int w_Texture_getDimensions(lua_State *L) 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) { Texture *t = luax_checktexture(L, 1); @@ -130,6 +159,10 @@ const luaL_Reg w_Texture_functions[] = { "getWidth", w_Texture_getWidth }, { "getHeight", w_Texture_getHeight }, { "getDimensions", w_Texture_getDimensions }, + { "getPixelWidth", w_Texture_getPixelWidth }, + { "getPixelHeight", w_Texture_getPixelHeight }, + { "getPixelDimensions", w_Texture_getPixelDimensions }, + { "getPixelDensity", w_Texture_getPixelDensity }, { "setFilter", w_Texture_setFilter }, { "getFilter", w_Texture_getFilter }, { "setWrap", w_Texture_setWrap }, diff --git a/src/modules/keyboard/sdl/Keyboard.cpp b/src/modules/keyboard/sdl/Keyboard.cpp index 84219a0b3..bc612ff83 100644 --- a/src/modules/keyboard/sdl/Keyboard.cpp +++ b/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(M_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}; diff --git a/src/modules/mouse/sdl/Mouse.cpp b/src/modules/mouse/sdl/Mouse.cpp index 65629a364..cffb6eb23 100644 --- a/src/modules/mouse/sdl/Mouse.cpp +++ b/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 // 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(Module::M_WINDOW); if (window) - window->windowToPixelCoords(x, y); + window->windowToDPICoords(x, y); } // 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(Module::M_WINDOW); if (window) - window->pixelToWindowCoords(x, y); + window->DPIToWindowCoords(x, y); } const char *Mouse::getName() const @@ -118,7 +118,7 @@ double Mouse::getX() const SDL_GetMouseState(&x, nullptr); double dx = (double) x; - windowToPixelCoords(&dx, nullptr); + windowToDPICoords(&dx, nullptr); return dx; } @@ -129,7 +129,7 @@ double Mouse::getY() const SDL_GetMouseState(nullptr, &y); double dy = (double) y; - windowToPixelCoords(nullptr, &dy); + windowToDPICoords(nullptr, &dy); return dy; } @@ -141,7 +141,7 @@ void Mouse::getPosition(double &x, double &y) const x = (double) mx; y = (double) my; - windowToPixelCoords(&x, &y); + windowToDPICoords(&x, &y); } void Mouse::setPosition(double x, double y) @@ -152,7 +152,7 @@ void Mouse::setPosition(double x, double y) if (window) handle = (SDL_Window *) window->getHandle(); - pixelToWindowCoords(&x, &y); + DPIToWindowCoords(&x, &y); SDL_WarpMouseInWindow(handle, (int) x, (int) y); // SDL_WarpMouse doesn't directly update SDL's internal mouse state in Linux diff --git a/src/modules/window/Window.h b/src/modules/window/Window.h index 3549d384d..2eb1d9c5d 100644 --- a/src/modules/window/Window.h +++ b/src/modules/window/Window.h @@ -164,13 +164,20 @@ public: virtual void setMouseGrab(bool grab) = 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 // density-independent units (which toPixels and fromPixels use.) virtual void windowToPixelCoords(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 void toPixels(double wx, double wy, double &px, double &py) const = 0; diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index 86b312084..229636f6d 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/src/modules/window/sdl/Window.cpp @@ -521,7 +521,11 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) updateSettings(f, false); 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 love::android::setImmersive(f.fullscreen); @@ -541,7 +545,11 @@ bool Window::onSizeChanged(int width, int height) SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight); 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; } @@ -609,7 +617,11 @@ void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphi // Update the viewport size now instead of waiting for event polling. 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) @@ -940,12 +952,27 @@ bool Window::isMouseGrabbed() const return mouseGrabbed; } -void Window::getPixelDimensions(int &w, int &h) const +int Window::getWidth() const { - w = pixelWidth; - h = pixelHeight; + return windowWidth; } +int Window::getHeight() const +{ + return windowHeight; +} + +int Window::getPixelWidth() const +{ + return pixelWidth; +} + +int Window::getPixelHeight() const +{ + return pixelHeight; +} + + void Window::windowToPixelCoords(double *x, double *y) const { if (x != nullptr) @@ -962,7 +989,42 @@ void Window::pixelToWindowCoords(double *x, double *y) const *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 return love::android::getScreenScale(); @@ -973,24 +1035,24 @@ double Window::getPixelScale() 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 { - double scale = getPixelScale(); + double scale = getPixelDensity(); px = wx * scale; py = wy * scale; } double Window::fromPixels(double x) const { - return x / getPixelScale(); + return x / getPixelDensity(); } void Window::fromPixels(double px, double py, double &wx, double &wy) const { - double scale = getPixelScale(); + double scale = getPixelDensity(); wx = px / scale; wy = py / scale; } diff --git a/src/modules/window/sdl/Window.h b/src/modules/window/sdl/Window.h index 91e68e04f..cbe088e15 100644 --- a/src/modules/window/sdl/Window.h +++ b/src/modules/window/sdl/Window.h @@ -90,11 +90,18 @@ public: void setMouseGrab(bool grab); 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 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; void toPixels(double wx, double wy, double &px, double &py) const; diff --git a/src/modules/window/wrap_Window.cpp b/src/modules/window/wrap_Window.cpp index bc08dd58c..3ff5d8fd3 100644 --- a/src/modules/window/wrap_Window.cpp +++ b/src/modules/window/wrap_Window.cpp @@ -408,9 +408,9 @@ int w_isVisible(lua_State *L) 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; } @@ -570,7 +570,7 @@ static const luaL_Reg functions[] = { "hasFocus", w_hasFocus }, { "hasMouseFocus", w_hasMouseFocus }, { "isVisible", w_isVisible }, - { "getPixelScale", w_getPixelScale }, + { "getPixelDensity", w_getPixelDensity }, { "toPixels", w_toPixels }, { "fromPixels", w_fromPixels }, { "minimize", w_minimize }, diff --git a/src/scripts/boot.lua b/src/scripts/boot.lua index 1a9061093..68d783d4b 100644 --- a/src/scripts/boot.lua +++ b/src/scripts/boot.lua @@ -603,8 +603,7 @@ function love.errhand(msg) if love.audio then love.audio.stop() end 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) @@ -630,8 +629,8 @@ function love.errhand(msg) p = string.gsub(p, "%[string \"(.-)\"%]", "%1") local function draw() + local pos = 70 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.present() end diff --git a/src/scripts/boot.lua.h b/src/scripts/boot.lua.h index 00f450ed4..f7453af1e 100644 --- a/src/scripts/boot.lua.h +++ b/src/scripts/boot.lua.h @@ -1103,9 +1103,7 @@ const unsigned char boot_lua[] = 0x65, 0x74, 0x28, 0x29, 0x0a, 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, - 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, 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, @@ -1140,12 +1138,10 @@ const unsigned char boot_lua[] = 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, 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, 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, - 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, 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, diff --git a/src/scripts/nogame.lua b/src/scripts/nogame.lua index bf8ad10d3..2a69f6453 100644 --- a/src/scripts/nogame.lua +++ b/src/scripts/nogame.lua @@ -633,7 +633,7 @@ function love.nogame() end 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.y = math.floor(wh / 2 / 32) * 32 + 16 end @@ -727,10 +727,9 @@ function love.nogame() function Mosaic:init() 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] end @@ -758,20 +757,11 @@ function love.nogame() -- Insert only once. This way it appears half as often. 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 = { - 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) @@ -844,19 +834,19 @@ function love.nogame() end 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), + 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, 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), + 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 } @@ -932,9 +922,9 @@ function love.nogame() function love.load() 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) - return love.graphics.newImage(love.filesystem.newFileData(decoded, name:gsub("_", "."))) + return love.graphics.newImage(love.filesystem.newFileData(decoded, name:gsub("_", ".")), flags) end g_images = {} @@ -963,11 +953,8 @@ function love.nogame() function love.draw() love.graphics.setColor(1, 1, 1) - love.graphics.push() - love.graphics.scale(love.window.getPixelScale()) g_entities.mosaic:draw() g_entities.toast:draw() - love.graphics.pop() end function love.resize(w, h) @@ -988,10 +975,17 @@ function love.nogame() end end - function love.mousepressed(x, y, b) + function love.mousepressed(x, y, b, istouch, clicks) local tx = x / love.graphics.getWidth() local ty = y / love.graphics.getHeight() 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 function love.mousemoved(x, y) @@ -1002,25 +996,6 @@ function love.nogame() 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) t.title = "L\195\150VE " .. love._version .. " (" .. love._version_codename .. ")" t.gammacorrect = true diff --git a/src/scripts/nogame.lua.h b/src/scripts/nogame.lua.h index 621384d1c..10ddb2762 100644 --- a/src/scripts/nogame.lua.h +++ b/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, 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, - 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, 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, @@ -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, 0x67, 0x65, 0x20, 0x3d, 0x20, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x6d, 0x6f, 0x73, 0x61, 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, - 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, - 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, 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, @@ -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, 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, - 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, 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, - 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, 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, 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, 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, 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, @@ -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, 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, - 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, @@ -3561,7 +3538,7 @@ const unsigned char nogame_lua[] = 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, 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, 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, @@ -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, 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, - 0x29, 0x0a, + 0x2c, 0x20, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x29, 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, 0x2e, 0x74, 0x6f, 0x61, 0x73, 0x74, 0x20, 0x3d, @@ -3628,17 +3605,10 @@ const unsigned char nogame_lua[] = 0x77, 0x28, 0x29, 0x0a, 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, - 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, 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, 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, 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, @@ -3668,7 +3638,8 @@ const unsigned char nogame_lua[] = 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, 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, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x57, 0x69, 0x64, 0x74, 0x68, 0x28, 0x29, 0x0a, @@ -3677,6 +3648,21 @@ const unsigned char nogame_lua[] = 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, 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, 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, @@ -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, 0x09, 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, 0x66, 0x28, 0x74, 0x29, 0x0a, 0x09, 0x09, 0x74, 0x2e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x4c, 0x5c, 0x31, 0x39, 0x35,