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,