diff --git a/jni/love/src/common/int.h b/jni/love/src/common/int.h index 9cffea60..42ff9e24 100644 --- a/jni/love/src/common/int.h +++ b/jni/love/src/common/int.h @@ -21,6 +21,15 @@ #ifndef LOVE_INT_H #define LOVE_INT_H +// fixes compile errors of the form: +// "error: expected unqualified-id before '__extension__'" +#ifdef LOVE_ANDROID +#include +#undef swap16 +#undef swap32 +#undef swap64 +#endif + // C standard sized integer types. // This header was added to Visual studio in VS 2012, which is LOVE's current // minimum supported VS version (as of this comment's commit date.) @@ -47,6 +56,27 @@ typedef uint32_t uint32; typedef int64_t int64; typedef uint64_t uint64; +static inline uint16 swap16(uint16 x) +{ + return (x >> 8) | (x << 8); +} + +static inline uint32 swap32(uint32 x) +{ + return ((x & 0x000000FF) << 24) | + ((x & 0x0000FF00) << 8) | + ((x & 0x00FF0000) >> 8) | + ((x & 0xFF000000) >> 24); +} + +static inline uint64 swap64(uint64 x) +{ + return ((x << 56) & 0xFF00000000000000ULL) | ((x << 40) & 0x00FF000000000000ULL) | + ((x << 24) & 0x0000FF0000000000ULL) | ((x << 8) & 0x000000FF00000000ULL) | + ((x >> 8) & 0x00000000FF000000ULL) | ((x >> 24) & 0x0000000000FF0000ULL) | + ((x >> 40) & 0x000000000000FF00ULL) | ((x >> 56) & 0x00000000000000FFULL); +} + } // love #endif // LOVE_INT_H diff --git a/jni/love/src/modules/audio/openal/Audio.cpp b/jni/love/src/modules/audio/openal/Audio.cpp index c6af9be1..c424524d 100644 --- a/jni/love/src/modules/audio/openal/Audio.cpp +++ b/jni/love/src/modules/audio/openal/Audio.cpp @@ -37,6 +37,7 @@ Audio::PoolThread::PoolThread(Pool *pool) , finish(false) { mutex = thread::newMutex(); + threadName = "AudioPool"; } Audio::PoolThread::~PoolThread() diff --git a/jni/love/src/modules/audio/openal/Source.cpp b/jni/love/src/modules/audio/openal/Source.cpp index e9ef1a43..520edb7b 100644 --- a/jni/love/src/modules/audio/openal/Source.cpp +++ b/jni/love/src/modules/audio/openal/Source.cpp @@ -255,10 +255,7 @@ bool Source::update() alGetSourcef(source, AL_SAMPLE_OFFSET, &curOffsetSamples); - ALint b; - alGetSourcei(source, AL_BUFFER, &b); - int freq; - alGetBufferi(b, AL_FREQUENCY, &freq); + int freq = decoder->getSampleRate(); curOffsetSecs = curOffsetSamples / freq; // Get a free buffer. @@ -334,11 +331,7 @@ void Source::seekAtomic(float offset, void *unit) if (type == TYPE_STREAM) { offsetSamples = offset; - ALint buffer; - alGetSourcei(source, AL_BUFFER, &buffer); - int freq; - alGetBufferi(buffer, AL_FREQUENCY, &freq); - offset /= freq; + offset /= decoder->getSampleRate(); offsetSeconds = offset; decoder->seek(offset); } @@ -353,11 +346,7 @@ void Source::seekAtomic(float offset, void *unit) { offsetSeconds = offset; decoder->seek(offset); - ALint buffer; - alGetSourcei(source, AL_BUFFER, &buffer); - int freq; - alGetBufferi(buffer, AL_FREQUENCY, &freq); - offsetSamples = offset*freq; + offsetSamples = offset * decoder->getSampleRate(); } else { @@ -399,11 +388,7 @@ float Source::tellAtomic(void *unit) const default: { alGetSourcef(source, AL_SAMPLE_OFFSET, &offset); - ALint buffer; - alGetSourcei(source, AL_BUFFER, &buffer); - int freq; - alGetBufferi(buffer, AL_FREQUENCY, &freq); - offset /= freq; + offset /= decoder->getSampleRate(); if (type == TYPE_STREAM) offset += offsetSeconds; } break; diff --git a/jni/love/src/modules/event/sdl/Event.cpp b/jni/love/src/modules/event/sdl/Event.cpp index 8c3a44d7..7c1a34c8 100644 --- a/jni/love/src/modules/event/sdl/Event.cpp +++ b/jni/love/src/modules/event/sdl/Event.cpp @@ -59,6 +59,34 @@ static void windowToPixelCoords(int *x, int *y) #endif } +// SDL's event watch callbacks trigger when the event is actually posted inside +// SDL, unlike with SDL_PollEvents. This is useful for some events which require +// handling inside the function which triggered them on some backends. +static int SDLCALL watchAppEvents(void * /*udata*/, SDL_Event *event) +{ + graphics::Graphics *gfx = (graphics::Graphics *) Module::findInstance("love.graphics."); + + switch (event->type) + { + // On iOS, calling any OpenGL ES function after the function which triggers + // SDL_APP_DIDENTERBACKGROUND is called will kill the app, so we handle it + // with an event watch callback, which will be called inside that function. + case SDL_APP_DIDENTERBACKGROUND: + case SDL_APP_WILLENTERFOREGROUND: + // On Android, before calling setActive we should probably also call a + // window function to un-set the graphics mode and delete the context, + // after DIDENTERBACKGROUND (and the reverse after WILLENTERFOREGROUND.) + if (gfx) + gfx->setActive(event->type == SDL_APP_WILLENTERFOREGROUND); + break; + default: + break; + } + + // Don't prevent the event from being processed further. + return 1; +} + const char *Event::getName() const { @@ -69,10 +97,13 @@ Event::Event() { if (SDL_InitSubSystem(SDL_INIT_EVENTS) < 0) throw love::Exception("%s", SDL_GetError()); + + SDL_AddEventWatch(watchAppEvents, this); } Event::~Event() { + SDL_DelEventWatch(watchAppEvents, this); SDL_QuitSubSystem(SDL_INIT_EVENTS); } @@ -260,6 +291,7 @@ Message *Event::convert(const SDL_Event &e) const SDL_free(e.drop.file); break; case SDL_QUIT: + case SDL_APP_TERMINATING: msg = new Message("quit"); break; case SDL_APP_LOWMEMORY: diff --git a/jni/love/src/modules/filesystem/physfs/Filesystem.cpp b/jni/love/src/modules/filesystem/physfs/Filesystem.cpp index 9a477ed1..4f0d98cb 100644 --- a/jni/love/src/modules/filesystem/physfs/Filesystem.cpp +++ b/jni/love/src/modules/filesystem/physfs/Filesystem.cpp @@ -279,6 +279,11 @@ bool Filesystem::setIdentity(const char *ident, bool appendToPath) // (No error on fail, it means that the path doesn't exist). PHYSFS_addToSearchPath(save_path_full.c_str(), appendToPath); + // HACK: This forces setupWriteDirectory to be called the next time a file + // is opened for writing - otherwise it won't be called at all if it was + // already called at least once before. + PHYSFS_setWriteDir(nullptr); + return true; } @@ -349,16 +354,36 @@ bool Filesystem::setupWriteDirectory() if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty()) return false; - // Set the appdata folder as writable directory. + // We need to make sure the write directory is created. To do that, we also + // need to make sure all its parent directories are also created. + std::string temp_writedir = getDriveRoot(save_path_full); + std::string temp_createdir = skipDriveRoot(save_path_full); + + // On some sandboxed platforms, physfs will break when its write directory + // is the root of the drive and it tries to create a folder (even if the + // folder's path is in a writable location.) If the user's home folder is + // in the save path, we'll try starting from there instead. + if (save_path_full.find(getUserDirectory()) == 0) + { + temp_writedir = getUserDirectory(); + temp_createdir = save_path_full.substr(getUserDirectory().length()); + + // Strip leading '/' characters from the path we want to create. + size_t startpos = temp_createdir.find_first_not_of('/'); + if (startpos != std::string::npos) + temp_createdir = temp_createdir.substr(startpos); + } + + // Set either '/' or the user's home as a writable directory. // (We must create the save folder before mounting it). - if (!PHYSFS_setWriteDir(getDriveRoot(save_path_full).c_str())) + if (!PHYSFS_setWriteDir(temp_writedir.c_str())) return false; - // Create the save folder. (We're now "at" %APPDATA%). - if (!createDirectory(skipDriveRoot(save_path_full).c_str())) + // Create the save folder. (We're now "at" either '/' or the user's home). + if (!createDirectory(temp_createdir.c_str())) { // Clear the write directory in case of error. - PHYSFS_setWriteDir(0); + PHYSFS_setWriteDir(nullptr); return false; } @@ -369,7 +394,7 @@ bool Filesystem::setupWriteDirectory() // Add the directory. (Will not be readded if already present). if (!PHYSFS_addToSearchPath(save_path_full.c_str(), 0)) { - PHYSFS_setWriteDir(0); // Clear the write directory in case of error. + PHYSFS_setWriteDir(nullptr); // Clear the write directory in case of error. return false; } @@ -505,12 +530,12 @@ const char *Filesystem::getWorkingDirectory() return cwd.c_str(); } -const char *Filesystem::getUserDirectory() +std::string Filesystem::getUserDirectory() { - return PHYSFS_getUserDir(); + return std::string(PHYSFS_getUserDir()); } -const char *Filesystem::getAppdataDirectory() +std::string Filesystem::getAppdataDirectory() { #ifdef LOVE_WINDOWS if (appdata.empty()) @@ -519,7 +544,7 @@ const char *Filesystem::getAppdataDirectory() appdata = to_utf8(w_appdata); replace_char(appdata, '\\', '/'); } - return appdata.c_str(); + return appdata; #elif defined(LOVE_MACOSX) if (appdata.empty()) { @@ -527,7 +552,7 @@ const char *Filesystem::getAppdataDirectory() udir.append("/Library/Application Support"); appdata = udir; } - return appdata.c_str(); + return appdata; #elif defined(LOVE_LINUX) if (appdata.empty()) { @@ -537,7 +562,7 @@ const char *Filesystem::getAppdataDirectory() else appdata = xdgdatahome; } - return appdata.c_str(); + return appdata; #else return getUserDirectory(); #endif diff --git a/jni/love/src/modules/filesystem/physfs/Filesystem.h b/jni/love/src/modules/filesystem/physfs/Filesystem.h index f9ccfecb..025da9ac 100644 --- a/jni/love/src/modules/filesystem/physfs/Filesystem.h +++ b/jni/love/src/modules/filesystem/physfs/Filesystem.h @@ -145,14 +145,14 @@ public: /** * Gets the user home directory. **/ - const char *getUserDirectory(); + std::string getUserDirectory(); /** * Gets the APPDATA directory. On Windows, this is the folder * in the %APPDATA% enviroment variable. On Linux, this is the * user home folder. **/ - const char *getAppdataDirectory(); + std::string getAppdataDirectory(); /** * Gets the full path of the save folder. diff --git a/jni/love/src/modules/filesystem/physfs/wrap_Filesystem.cpp b/jni/love/src/modules/filesystem/physfs/wrap_Filesystem.cpp index 0a89387e..77845ef1 100644 --- a/jni/love/src/modules/filesystem/physfs/wrap_Filesystem.cpp +++ b/jni/love/src/modules/filesystem/physfs/wrap_Filesystem.cpp @@ -211,13 +211,13 @@ int w_getWorkingDirectory(lua_State *L) int w_getUserDirectory(lua_State *L) { - lua_pushstring(L, instance->getUserDirectory()); + luax_pushstring(L, instance->getUserDirectory()); return 1; } int w_getAppdataDirectory(lua_State *L) { - lua_pushstring(L, instance->getAppdataDirectory()); + luax_pushstring(L, instance->getAppdataDirectory()); return 1; } diff --git a/jni/love/src/modules/graphics/Graphics.cpp b/jni/love/src/modules/graphics/Graphics.cpp index 528a4549..de169ccd 100644 --- a/jni/love/src/modules/graphics/Graphics.cpp +++ b/jni/love/src/modules/graphics/Graphics.cpp @@ -176,6 +176,8 @@ StringMap::Entry Graphics::suppor { "mipmap", Graphics::SUPPORT_MIPMAP }, { "dxt", Graphics::SUPPORT_DXT }, { "bc5", Graphics::SUPPORT_BC5 }, + { "etc1", Graphics::SUPPORT_ETC1 }, + { "pvrtc1", Graphics::SUPPORT_PVRTC1 }, { "instancing", Graphics::SUPPORT_INSTANCING }, { "srgb", Graphics::SUPPORT_SRGB }, }; diff --git a/jni/love/src/modules/graphics/Graphics.h b/jni/love/src/modules/graphics/Graphics.h index 4f132b04..1d436078 100644 --- a/jni/love/src/modules/graphics/Graphics.h +++ b/jni/love/src/modules/graphics/Graphics.h @@ -95,6 +95,8 @@ public: SUPPORT_MIPMAP, SUPPORT_DXT, SUPPORT_BC5, + SUPPORT_ETC1, + SUPPORT_PVRTC1, SUPPORT_INSTANCING, SUPPORT_SRGB, SUPPORT_MAX_ENUM @@ -145,6 +147,20 @@ public: **/ virtual void unSetMode() = 0; + /** + * Sets whether the module is active (internal use only.) + **/ + virtual void setActive(bool active) = 0; + + /** + * Gets whether the module is active. Graphics module methods are only + * guaranteed to work when it is active. Calling them otherwise may cause + * the program to crash (or worse.) + * Normally the module will always be active as long as a window exists, it + * may be different on some platforms (especially mobile ones.) + **/ + virtual bool isActive() const = 0; + static bool getConstant(const char *in, DrawMode &out); static bool getConstant(DrawMode in, const char *&out); diff --git a/jni/love/src/modules/graphics/Texture.cpp b/jni/love/src/modules/graphics/Texture.cpp index 0a8d8554..477eb33c 100644 --- a/jni/love/src/modules/graphics/Texture.cpp +++ b/jni/love/src/modules/graphics/Texture.cpp @@ -109,16 +109,6 @@ bool Texture::getConstant(WrapMode in, const char *&out) return wrapModes.find(in, out); } -bool Texture::getConstant(const char *in, Format &out) -{ - return formats.find(in, out); -} - -bool Texture::getConstant(Format in, const char *&out) -{ - return formats.find(in, out); -} - StringMap::Entry Texture::filterModeEntries[] = { { "linear", Texture::FILTER_LINEAR }, @@ -135,15 +125,5 @@ StringMap::Entry Texture::wrapModeEnt StringMap Texture::wrapModes(Texture::wrapModeEntries, sizeof(Texture::wrapModeEntries)); -StringMap::Entry Texture::formatEntries[] = -{ - {"normal", Texture::FORMAT_NORMAL}, - {"hdr", Texture::FORMAT_HDR}, - {"srgb", Texture::FORMAT_SRGB}, -}; - -StringMap Texture::formats(Texture::formatEntries, sizeof(Texture::formatEntries)); - - } // graphics } // love diff --git a/jni/love/src/modules/graphics/Texture.h b/jni/love/src/modules/graphics/Texture.h index d9c83171..7b5a525a 100644 --- a/jni/love/src/modules/graphics/Texture.h +++ b/jni/love/src/modules/graphics/Texture.h @@ -55,14 +55,6 @@ public: FILTER_MAX_ENUM }; - enum Format - { - FORMAT_NORMAL, - FORMAT_HDR, - FORMAT_SRGB, - FORMAT_MAX_ENUM - }; - struct Filter { Filter(); @@ -119,9 +111,6 @@ public: static bool getConstant(const char *in, WrapMode &out); static bool getConstant(WrapMode in, const char *&out); - static bool getConstant(const char *in, Format &out); - static bool getConstant(Format in, const char *&out); - protected: int width; @@ -143,9 +132,6 @@ private: static StringMap::Entry wrapModeEntries[]; static StringMap wrapModes; - static StringMap::Entry formatEntries[]; - static StringMap formats; - }; // Texture } // graphics diff --git a/jni/love/src/modules/graphics/opengl/Canvas.cpp b/jni/love/src/modules/graphics/opengl/Canvas.cpp index f1edf1a3..6d29cf40 100644 --- a/jni/love/src/modules/graphics/opengl/Canvas.cpp +++ b/jni/love/src/modules/graphics/opengl/Canvas.cpp @@ -451,7 +451,7 @@ static void getStrategy() } } -Canvas::Canvas(int width, int height, Texture::Format format, int fsaa) +Canvas::Canvas(int width, int height, Format format, int fsaa) : fbo(0) , resolve_fbo(0) , texture(0) @@ -556,36 +556,31 @@ bool Canvas::loadVolatile() glGenTextures(1, &texture); gl.bindTexture(texture); + if (GLAD_ANGLE_texture_usage) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE); + setFilter(filter); setWrap(wrap); - GLint internalformat; - GLenum textype; - switch (format) - { - case Texture::FORMAT_HDR: - internalformat = GL_RGBA16F; - textype = GL_FLOAT; - break; - case Texture::FORMAT_SRGB: - internalformat = GL_SRGB_ALPHA; - textype = GL_UNSIGNED_BYTE; - break; - case Texture::FORMAT_NORMAL: - default: - internalformat = GL_RGBA; - textype = GL_UNSIGNED_BYTE; - } + GLenum internalformat = GL_RGBA; + GLenum externalformat = GL_RGBA; + GLenum textype = GL_UNSIGNED_BYTE; + + convertFormat(format, internalformat, externalformat, textype); + + // in ES2, the internalformat and format params of TexImage have to match. + if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0) + internalformat = externalformat; while (glGetError() != GL_NO_ERROR) /* Clear the error buffer. */; glTexImage2D(GL_TEXTURE_2D, 0, - internalformat, + (GLint) internalformat, width, height, 0, - GL_RGBA, + externalformat, textype, nullptr); @@ -736,10 +731,13 @@ void Canvas::setupGrab() gl.matrices.projection.push(Matrix::ortho(0.0, width, 0.0, height)); // Make sure the correct sRGB setting is used when drawing to the canvas. - if (format == FORMAT_SRGB) - glEnable(GL_FRAMEBUFFER_SRGB); - else if (screenHasSRGB) - glDisable(GL_FRAMEBUFFER_SRGB); + if (GLAD_VERSION_1_1 || GLAD_EXT_sRGB_write_control) + { + if (format == FORMAT_SRGB) + glEnable(GL_FRAMEBUFFER_SRGB); + else if (screenHasSRGB) + glDisable(GL_FRAMEBUFFER_SRGB); + } if (fsaa_buffer != 0) fsaa_dirty = true; @@ -828,8 +826,11 @@ void Canvas::stopGrab(bool switchingToOtherCanvas) if (switchingToOtherCanvas) { - if (format == FORMAT_SRGB) - glDisable(GL_FRAMEBUFFER_SRGB); + if (GLAD_VERSION_1_1 || GLAD_EXT_sRGB_write_control) + { + if (format == FORMAT_SRGB) + glDisable(GL_FRAMEBUFFER_SRGB); + } } else { @@ -838,10 +839,13 @@ void Canvas::stopGrab(bool switchingToOtherCanvas) current = nullptr; gl.setViewport(systemViewport); - if (format == FORMAT_SRGB && !screenHasSRGB) - glDisable(GL_FRAMEBUFFER_SRGB); - else if (format != FORMAT_SRGB && screenHasSRGB) - glEnable(GL_FRAMEBUFFER_SRGB); + if (GLAD_VERSION_1_1 || GLAD_EXT_sRGB_write_control) + { + if (format == FORMAT_SRGB && !screenHasSRGB) + glDisable(GL_FRAMEBUFFER_SRGB); + else if (format != FORMAT_SRGB && screenHasSRGB) + glEnable(GL_FRAMEBUFFER_SRGB); + } } gl.matrices.projection.pop(); @@ -1007,41 +1011,231 @@ bool Canvas::resolveMSAA() return true; } +Canvas::Format Canvas::getSizedFormat(Canvas::Format format) +{ + switch (format) + { + case FORMAT_NORMAL: + // 32-bit render targets don't have guaranteed support on OpenGL ES 2. + if (GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_rgb8_rgba8 || GLAD_ARM_rgba8)) + return FORMAT_RGBA4; + else + return FORMAT_RGBA8; + case FORMAT_HDR: + return FORMAT_RGBA16F; + default: + return format; + } +} + +void Canvas::convertFormat(Canvas::Format format, GLenum &internalformat, GLenum &externalformat, GLenum &type) +{ + format = getSizedFormat(format); + externalformat = GL_RGBA; + + switch (format) + { + case FORMAT_RGBA8: + default: + internalformat = GL_RGBA8; + type = GL_UNSIGNED_BYTE; + break; + case FORMAT_RGBA4: + internalformat = GL_RGBA4; + type = GL_UNSIGNED_SHORT_4_4_4_4; + break; + case FORMAT_RGB5A1: + internalformat = GL_RGB5_A1; + type = GL_UNSIGNED_SHORT_5_5_5_1; + break; + case FORMAT_RGB565: + internalformat = GL_RGB565; + externalformat = GL_RGB; + type = GL_UNSIGNED_SHORT_5_6_5; + break; + case FORMAT_RGB10A2: + internalformat = GL_RGB10_A2; + type = GL_UNSIGNED_INT_2_10_10_10_REV; + break; + case FORMAT_RG11B10F: + internalformat = GL_R11F_G11F_B10F; + externalformat = GL_RGB; + type = GL_UNSIGNED_INT_10F_11F_11F_REV; + break; + case FORMAT_RGBA16F: + internalformat = GL_RGBA16F; + if (GLAD_OES_texture_float) + type = GL_HALF_FLOAT_OES; + else if (GLAD_VERSION_1_1) + type = GL_FLOAT; + else + type = GL_HALF_FLOAT; + break; + case FORMAT_RGBA32F: + internalformat = GL_RGBA32F; + type = GL_FLOAT; + break; + case FORMAT_SRGB: + internalformat = GL_SRGB8_ALPHA8; + type = GL_UNSIGNED_BYTE; + if (GLAD_ES_VERSION_2_0) + externalformat = GL_SRGB_ALPHA; + break; + } +} + bool Canvas::isSupported() { getStrategy(); return (strategy != &strategyNone); } -bool Canvas::isHDRSupported() -{ - return GLAD_VERSION_3_0 || (isSupported() && GLAD_ARB_texture_float); -} - -bool Canvas::isSRGBSupported() -{ - if (GLAD_VERSION_3_0) - return true; - - if (!isSupported()) - return false; - - return (GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB) - && GLAD_EXT_texture_sRGB; -} - bool Canvas::isMultiCanvasSupported() { // system must support at least 4 simultanious active canvases. return gl.getMaxRenderTargets() >= 4; } +bool Canvas::supportedFormats[] = {false}; +bool Canvas::checkedFormats[] = {false}; + +bool Canvas::isFormatSupported(Canvas::Format format) +{ + if (!isSupported()) + return false; + + bool supported = true; + format = getSizedFormat(format); + + switch (format) + { + case FORMAT_RGBA8: + if (GLAD_VERSION_1_1) + supported = true; + else + supported = GLAD_ES_VERSION_3_0 || GLAD_OES_rgb8_rgba8 || GLAD_ARM_rgba8; + break; + case FORMAT_RGBA4: + case FORMAT_RGB5A1: + supported = true; + break; + case FORMAT_RGB565: + supported = GLAD_ES_VERSION_2_0 || GLAD_VERSION_4_2 || GLAD_ARB_ES2_compatibility; + break; + case FORMAT_RGB10A2: + if (GLAD_VERSION_1_1) + supported = true; + else + supported = GLAD_ES_VERSION_3_0; + break; + case FORMAT_RG11B10F: + if (GLAD_VERSION_1_1) + supported = GLAD_VERSION_3_0 || GLAD_EXT_packed_float; + else + supported = GLAD_ES_VERSION_3_0 /*|| GLAD_NV_packed_float*/; + break; + case FORMAT_RGBA16F: + if (GLAD_VERSION_1_1) + supported = GLAD_VERSION_3_0 || GLAD_ARB_texture_float; + else + supported = GLAD_EXT_color_buffer_half_float && (GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float); + break; + case FORMAT_RGBA32F: + if (GLAD_VERSION_1_1) + supported = GLAD_VERSION_3_0 || GLAD_ARB_texture_float; + else + supported = false; // GLAD_EXT_color_buffer_float && GLAD_ES_VERSION_3_0; + break; + case FORMAT_SRGB: + if (GLAD_VERSION_1_1) + supported = GLAD_VERSION_3_0 || ((GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB) + && (GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB)); + else + supported = GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB; + break; + default: + supported = false; + break; + } + + if (!supported) + return false; + + if (checkedFormats[format]) + return supportedFormats[format]; + + // Even though we might have the necessary OpenGL version or extension, + // drivers are still allowed to throw FRAMEBUFFER_UNSUPPORTED when attaching + // a texture to a FBO whose format the driver doesn't like. So we should + // test with an actual FBO. + + GLenum internalformat = GL_RGBA; + GLenum externalformat = GL_RGBA; + GLenum textype = GL_UNSIGNED_BYTE; + convertFormat(format, internalformat, externalformat, textype); + + if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0) + internalformat = externalformat; + + GLuint texture = 0; + glGenTextures(1, &texture); + gl.bindTexture(texture); + + Texture::Filter f; + f.min = f.mag = Texture::FILTER_NEAREST; + gl.setTextureFilter(f); + + Texture::Wrap w; + gl.setTextureWrap(w); + + glTexImage2D(GL_TEXTURE_2D, 0, internalformat, 2, 2, 0, externalformat, textype, nullptr); + + GLuint fbo = 0; + supported = (strategy->createFBO(fbo, texture) == GL_FRAMEBUFFER_COMPLETE); + strategy->deleteFBO(fbo, 0, 0); + + gl.deleteTexture(texture); + + // Cache the result so we don't do this for every isFormatSupported call. + checkedFormats[format] = true; + supportedFormats[format] = supported; + + return supported; +} + void Canvas::bindDefaultCanvas() { if (current != nullptr) current->stopGrab(); } +bool Canvas::getConstant(const char *in, Format &out) +{ + return formats.find(in, out); +} + +bool Canvas::getConstant(Format in, const char *&out) +{ + return formats.find(in, out); +} + +StringMap::Entry Canvas::formatEntries[] = +{ + {"normal", Canvas::FORMAT_NORMAL}, + {"hdr", Canvas::FORMAT_HDR}, + {"rgba8", Canvas::FORMAT_RGBA8}, + {"rgba4", Canvas::FORMAT_RGBA4}, + {"rgb5a1", Canvas::FORMAT_RGB5A1}, + {"rgb565", Canvas::FORMAT_RGB565}, + {"rgb10a2", Canvas::FORMAT_RGB10A2}, + {"rg11b10f", Canvas::FORMAT_RG11B10F}, + {"rgba16f", Canvas::FORMAT_RGBA16F}, + {"rgba32f", Canvas::FORMAT_RGBA32F}, + {"srgb", Canvas::FORMAT_SRGB}, +}; + +StringMap Canvas::formats(Canvas::formatEntries, sizeof(Canvas::formatEntries)); + } // opengl } // graphics } // love diff --git a/jni/love/src/modules/graphics/opengl/Canvas.h b/jni/love/src/modules/graphics/opengl/Canvas.h index 2d87b0bb..3e2a623d 100644 --- a/jni/love/src/modules/graphics/opengl/Canvas.h +++ b/jni/love/src/modules/graphics/opengl/Canvas.h @@ -25,6 +25,7 @@ #include "image/Image.h" #include "image/ImageData.h" #include "common/Matrix.h" +#include "common/StringMap.h" #include "Texture.h" #include "OpenGL.h" @@ -39,7 +40,24 @@ class Canvas : public Texture { public: - Canvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0); + // Different Canvas render target formats. + enum Format + { + FORMAT_NORMAL, // Usually RGBA8 or a similar fallback. Always supported. + FORMAT_HDR, // Usually RGBA16F. Not always supported. + FORMAT_RGBA8, // RGBA with 8 bits per component. + FORMAT_RGBA4, // RGBA with 4 bits per component. + FORMAT_RGB5A1, // RGB with 5 bits per component, and A with 1 bit. + FORMAT_RGB565, // RGB with 5, 6, and 5 bits each, respectively. + FORMAT_RGB10A2, // RGB with 10 bits each, and A with 2 bits. + FORMAT_RG11B10F, // Floating point [0, +inf]. RG with 11 FP bits each, and B with 10 FP bits. + FORMAT_RGBA16F, // Floating point [-inf, +inf]. RGBA with 16 FP bits per component. + FORMAT_RGBA32F, // Floating point [-inf, +inf]. RGBA with 32 FP bits per component. + FORMAT_SRGB, // sRGB with 8 bits per component, plus 8 bit linear A. + FORMAT_MAX_ENUM + }; + + Canvas(int width, int height, Format format = FORMAT_NORMAL, int fsaa = 0); virtual ~Canvas(); // Implements Volatile. @@ -85,7 +103,7 @@ public: return status; } - inline Texture::Format getTextureFormat() const + inline Format getTextureFormat() const { return format; } @@ -98,9 +116,8 @@ public: bool resolveMSAA(); static bool isSupported(); - static bool isHDRSupported(); - static bool isSRGBSupported(); static bool isMultiCanvasSupported(); + static bool isFormatSupported(Format format); static Canvas *current; static void bindDefaultCanvas(); @@ -111,10 +128,16 @@ public: // Whether the main screen should have linear -> sRGB conversions enabled. static bool screenHasSRGB; + static bool getConstant(const char *in, Format &out); + static bool getConstant(Format in, const char *&out); + private: bool createFSAAFBO(GLenum internalformat); + static Format getSizedFormat(Format format); + static void convertFormat(Format format, GLenum &internalformat, GLenum &externalformat, GLenum &type); + GLuint fbo; GLuint resolve_fbo; @@ -134,6 +157,12 @@ private: void setupGrab(); void drawv(const Matrix &t, const Vertex *v); + static bool supportedFormats[FORMAT_MAX_ENUM]; + static bool checkedFormats[FORMAT_MAX_ENUM]; + + static StringMap::Entry formatEntries[]; + static StringMap formats; + }; // Canvas } // opengl diff --git a/jni/love/src/modules/graphics/opengl/Font.cpp b/jni/love/src/modules/graphics/opengl/Font.cpp index d7ce211d..7790b429 100644 --- a/jni/love/src/modules/graphics/opengl/Font.cpp +++ b/jni/love/src/modules/graphics/opengl/Font.cpp @@ -42,6 +42,7 @@ const int Font::TEXTURE_HEIGHTS[] = {128, 128, 256, 256, 512, 512, 1024}; Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter) : rasterizer(r) + , indexBuffer(nullptr) , height(r->getHeight()) , lineHeight(1) , mSpacing(1) @@ -68,6 +69,8 @@ Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter) textureWidth = TEXTURE_WIDTHS[textureSizeIndex]; textureHeight = TEXTURE_HEIGHTS[textureSizeIndex]; + indexBuffer = new VertexIndex(20); + love::font::GlyphData *gd = nullptr; try @@ -93,6 +96,7 @@ Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter) Font::~Font() { + delete indexBuffer; rasterizer->release(); unloadVolatile(); } @@ -368,28 +372,17 @@ void Font::print(const std::string &text, float x, float y, float extra_spacing, // second (using the struct's < operator). std::sort(glyphinfolist.begin(), glyphinfolist.end()); - std::vector indices; - - int indicescount = 0; + int maxvertices = 0; for (auto it = glyphinfolist.begin(); it != glyphinfolist.end(); ++it) + maxvertices = std::max(maxvertices, it->vertexcount); + + // If the index buffer is too small for the number of glyphs we're going to + // draw, we need to make a new one that has the correct size. + if ((size_t) maxvertices / 4 > indexBuffer->getSize()) { - if ((it->vertexcount / 4) * 6 > indicescount) - indicescount = (it->vertexcount / 4) * 6; - } - - indices.reserve(indicescount); - - for (int i = 0; i < indicescount / 6; i++) - { - // First triangle. - indices.push_back(i * 4 + 0); - indices.push_back(i * 4 + 1); - indices.push_back(i * 4 + 2); - - // Second triangle. - indices.push_back(i * 4 + 0); - indices.push_back(i * 4 + 2); - indices.push_back(i * 4 + 3); + VertexIndex *newIndexBuffer = new VertexIndex(maxvertices / 4); + delete indexBuffer; + indexBuffer = newIndexBuffer; } gl.matrices.transform.push(gl.matrices.transform.top()); @@ -403,16 +396,36 @@ void Font::print(const std::string &text, float x, float y, float extra_spacing, gl.prepareDraw(); + VertexBuffer::Bind index_bind(*indexBuffer->getVertexBuffer()); + GLenum elemtype = indexBuffer->getType(); + const GLvoid *elemoffset = indexBuffer->getPointer(0); + + // Optimization: we can take these calls out of the loop if we have support. + if (GLAD_VERSION_3_2 || GLAD_ARB_draw_elements_base_vertex) + { + gl.setVertexAttribArray(OpenGL::ATTRIB_POS, 2, GL_FLOAT, sizeof(GlyphVertex), &glyphverts[0].x); + gl.setVertexAttribArray(OpenGL::ATTRIB_TEXCOORD, 2, GL_FLOAT, sizeof(GlyphVertex), &glyphverts[0].s); + } + // We need to draw a new vertex array for every section of the string which // uses a different texture than the previous section. for (auto it = glyphinfolist.begin(); it != glyphinfolist.end(); ++it) { gl.bindTexture(it->texture); - gl.setVertexAttribArray(OpenGL::ATTRIB_POS, 2, GL_FLOAT, sizeof(GlyphVertex), (GLvoid *)&glyphverts[it->startvertex].x); - gl.setVertexAttribArray(OpenGL::ATTRIB_TEXCOORD, 2, GL_FLOAT, sizeof(GlyphVertex), (GLvoid *)&glyphverts[it->startvertex].s); + if (GLAD_VERSION_3_2 || GLAD_ARB_draw_elements_base_vertex) + { + // Optimization: setting a base vertex is faster than redoing the + // setVertexAttribArray calls before each draw. + glDrawElementsBaseVertex(GL_TRIANGLES, (it->vertexcount / 4) * 6, elemtype, elemoffset, it->startvertex); + } + else + { + gl.setVertexAttribArray(OpenGL::ATTRIB_POS, 2, GL_FLOAT, sizeof(GlyphVertex), &glyphverts[it->startvertex].x); + gl.setVertexAttribArray(OpenGL::ATTRIB_TEXCOORD, 2, GL_FLOAT, sizeof(GlyphVertex), &glyphverts[it->startvertex].s); - glDrawElements(GL_TRIANGLES, (it->vertexcount / 4) * 6, GL_UNSIGNED_SHORT, &indices[0]); + glDrawElements(GL_TRIANGLES, (it->vertexcount / 4) * 6, elemtype, elemoffset); + } } gl.disableVertexAttribArray(OpenGL::ATTRIB_POS); diff --git a/jni/love/src/modules/graphics/opengl/Font.h b/jni/love/src/modules/graphics/opengl/Font.h index fee27745..f1602324 100644 --- a/jni/love/src/modules/graphics/opengl/Font.h +++ b/jni/love/src/modules/graphics/opengl/Font.h @@ -31,6 +31,7 @@ #include "font/Rasterizer.h" #include "graphics/Texture.h" #include "graphics/Volatile.h" +#include "VertexBuffer.h" #include "OpenGL.h" @@ -185,6 +186,8 @@ private: love::font::Rasterizer *rasterizer; + VertexIndex *indexBuffer; + int height; float lineHeight; float mSpacing; // modifies the spacing by multiplying it with this value diff --git a/jni/love/src/modules/graphics/opengl/Graphics.cpp b/jni/love/src/modules/graphics/opengl/Graphics.cpp index 42e7883c..f2be9eaf 100644 --- a/jni/love/src/modules/graphics/opengl/Graphics.cpp +++ b/jni/love/src/modules/graphics/opengl/Graphics.cpp @@ -52,6 +52,7 @@ Graphics::Graphics() , width(0) , height(0) , created(false) + , active(true) , activeStencil(false) , savedState() { @@ -72,7 +73,10 @@ Graphics::~Graphics() currentFont->release(); if (Shader::defaultShader) + { Shader::defaultShader->release(); + Shader::defaultShader = nullptr; + } currentWindow->release(); } @@ -235,12 +239,16 @@ bool Graphics::setMode(int width, int height, bool &sRGB) } // Set whether drawing converts input from linear -> sRGB colorspace. - if (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB) + if (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB + || (GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB)) { - if (sRGB) - glEnable(GL_FRAMEBUFFER_SRGB); - else - glDisable(GL_FRAMEBUFFER_SRGB); + if (GLAD_VERSION_1_1 || GLAD_EXT_sRGB_write_control) + { + if (sRGB) + glEnable(GL_FRAMEBUFFER_SRGB); + else + glDisable(GL_FRAMEBUFFER_SRGB); + } } else sRGB = false; @@ -282,6 +290,23 @@ void Graphics::unSetMode() created = false; } +void Graphics::setActive(bool active) +{ + // Make sure all pending OpenGL commands have fully executed before + // returning, if we're going from active to inactive. + if (isCreated() && this->active && !active) + glFinish(); + + this->active = active; +} + +bool Graphics::isActive() const +{ + // The graphics module is only completely 'active' if there's a window, a + // context, and the active variable is set. + return active && isCreated() && currentWindow && currentWindow->isCreated(); +} + static void APIENTRY debugCB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei /*len*/, const GLchar *msg, const GLvoid* /*usr*/) { // Human-readable strings for the debug info. @@ -364,6 +389,9 @@ void Graphics::clear() void Graphics::present() { + if (!isActive()) + return; + // Make sure we don't have a canvas active. Canvas *c = Canvas::current; Canvas::bindDefaultCanvas(); @@ -463,7 +491,7 @@ void Graphics::discardStencil() activeStencil = false; } -Image *Graphics::newImage(love::image::ImageData *data, Texture::Format format) +Image *Graphics::newImage(love::image::ImageData *data, Image::Format format) { // Create the image. Image *image = new Image(data, format); @@ -490,7 +518,7 @@ Image *Graphics::newImage(love::image::ImageData *data, Texture::Format format) return image; } -Image *Graphics::newImage(love::image::CompressedData *cdata, Texture::Format format) +Image *Graphics::newImage(love::image::CompressedData *cdata, Image::Format format) { // Create the image. Image *image = new Image(cdata, format); @@ -537,13 +565,14 @@ ParticleSystem *Graphics::newParticleSystem(Texture *texture, int size) return new ParticleSystem(texture, size); } -Canvas *Graphics::newCanvas(int width, int height, Texture::Format format, int fsaa) +Canvas *Graphics::newCanvas(int width, int height, Canvas::Format format, int fsaa) { - if (format == Texture::FORMAT_HDR && !Canvas::isHDRSupported()) - throw Exception("HDR Canvases are not supported by your OpenGL implementation"); - - if (format == Texture::FORMAT_SRGB && !Canvas::isSRGBSupported()) - throw Exception("sRGB Canvases are not supported by your OpenGL implementation"); + if (!Canvas::isFormatSupported(format)) + { + const char *fstr = "rgba8"; + Canvas::getConstant(format, fstr); + throw love::Exception("The %s canvas format is not supported by your OpenGL implementation.", fstr); + } if (width > gl.getMaxTextureSize()) throw Exception("Cannot create canvas: width of %d pixels is too large for this system.", width); @@ -594,8 +623,8 @@ Canvas *Graphics::newCanvas(int width, int height, Texture::Format format, int f } canvas->release(); - throw Exception(error_string.str().c_str()); - return NULL; // never reached + throw Exception("%s", error_string.str().c_str()); + return nullptr; // never reached } Shader *Graphics::newShader(const Shader::ShaderSources &sources) @@ -972,7 +1001,24 @@ void Graphics::circle(DrawMode mode, float x, float y, float radius, int points) float angle_shift = (two_pi / points); float phi = .0f; - float *coords = new float[2 * (points + 1)]; + float *coords = nullptr; + int extrapoints = 1; + + // Fill mode will use a triangle fan internally, so we want the "hub" of the + // fan (its first vertex) to be the midpoint of the circle. + if (mode == DRAW_FILL) + { + extrapoints = 2; + coords = new float[2 * (points + extrapoints)]; + + coords[0] = x; + coords[1] = y; + + coords += 2; + } + else + coords = new float[2 * (points + extrapoints)]; + for (int i = 0; i < points; ++i, phi += angle_shift) { coords[2*i] = x + radius * cosf(phi); @@ -982,7 +1028,10 @@ void Graphics::circle(DrawMode mode, float x, float y, float radius, int points) coords[2*points] = coords[0]; coords[2*points+1] = coords[1]; - polygon(mode, coords, (points + 1) * 2); + if (mode == DRAW_FILL) + coords -= 2; + + polygon(mode, coords, 2 * (points + extrapoints)); delete[] coords; } @@ -1059,7 +1108,7 @@ void Graphics::polygon(DrawMode mode, const float *coords, size_t count) gl.enableVertexAttribArray(OpenGL::ATTRIB_POS); gl.setVertexAttribArray(OpenGL::ATTRIB_POS, 2, GL_FLOAT, 0, (GLvoid *) coords); - glDrawArrays(GL_TRIANGLE_FAN, 0, count/2-1); // opengl will close the polygon for us + glDrawArrays(GL_TRIANGLE_FAN, 0, count / 2); gl.disableVertexAttribArray(OpenGL::ATTRIB_POS); } diff --git a/jni/love/src/modules/graphics/opengl/Graphics.h b/jni/love/src/modules/graphics/opengl/Graphics.h index e99eba2c..f7f0afcc 100644 --- a/jni/love/src/modules/graphics/opengl/Graphics.h +++ b/jni/love/src/modules/graphics/opengl/Graphics.h @@ -118,6 +118,9 @@ public: virtual bool setMode(int width, int height, bool &sRGB); virtual void unSetMode(); + virtual void setActive(bool active); + virtual bool isActive() const; + void setDebug(bool enable); /** @@ -194,8 +197,8 @@ public: /** * Creates an Image object with padding and/or optimization. **/ - Image *newImage(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL); - Image *newImage(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL); + Image *newImage(love::image::ImageData *data, Image::Format format = Image::FORMAT_NORMAL); + Image *newImage(love::image::CompressedData *cdata, Image::Format format = Image::FORMAT_NORMAL); Quad *newQuad(Quad::Viewport v, float sw, float sh); @@ -208,7 +211,7 @@ public: ParticleSystem *newParticleSystem(Texture *texture, int size); - Canvas *newCanvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0); + Canvas *newCanvas(int width, int height, Canvas::Format format = Canvas::FORMAT_NORMAL, int fsaa = 0); Shader *newShader(const Shader::ShaderSources &sources); @@ -480,6 +483,7 @@ private: int width; int height; bool created; + bool active; bool activeStencil; diff --git a/jni/love/src/modules/graphics/opengl/Image.cpp b/jni/love/src/modules/graphics/opengl/Image.cpp index 64923cab..ec50191e 100644 --- a/jni/love/src/modules/graphics/opengl/Image.cpp +++ b/jni/love/src/modules/graphics/opengl/Image.cpp @@ -36,7 +36,7 @@ float Image::maxMipmapSharpness = 0.0f; Texture::FilterMode Image::defaultMipmapFilter = Texture::FILTER_NONE; float Image::defaultMipmapSharpness = 0.0f; -Image::Image(love::image::ImageData *data, Texture::Format format) +Image::Image(love::image::ImageData *data, Format format) : data(data) , cdata(nullptr) , paddedWidth(width) @@ -55,7 +55,7 @@ Image::Image(love::image::ImageData *data, Texture::Format format) preload(); } -Image::Image(love::image::CompressedData *cdata, Texture::Format format) +Image::Image(love::image::CompressedData *cdata, Format format) : data(nullptr) , cdata(cdata) , paddedWidth(width) @@ -504,7 +504,7 @@ bool Image::refresh() return true; } -Texture::Format Image::getFormat() const +Image::Format Image::getFormat() const { return format; } @@ -602,6 +602,20 @@ GLenum Image::getCompressedFormat(image::CompressedData::Format cformat) const return GL_COMPRESSED_RG_RGTC2; case image::CompressedData::FORMAT_BC5s: return GL_COMPRESSED_SIGNED_RG_RGTC2; + case image::CompressedData::FORMAT_ETC1: + // The ETC2 format can load ETC1 textures. + if (GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_0 || GLAD_ARB_ES3_compatibility) + return GL_COMPRESSED_RGB8_ETC2; + else + return GL_ETC1_RGB8_OES; + case image::CompressedData::FORMAT_PVR1_RGB2: + return GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG; + case image::CompressedData::FORMAT_PVR1_RGB4: + return GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; + case image::CompressedData::FORMAT_PVR1_RGBA2: + return GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; + case image::CompressedData::FORMAT_PVR1_RGBA4: + return GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; default: if (srgb) return GL_SRGB_ALPHA; @@ -635,14 +649,24 @@ bool Image::hasCompressedTextureSupport(image::CompressedData::Format format) switch (format) { case image::CompressedData::FORMAT_DXT1: + return GLAD_EXT_texture_compression_s3tc || GLAD_EXT_texture_compression_dxt1; case image::CompressedData::FORMAT_DXT3: + return GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt3; case image::CompressedData::FORMAT_DXT5: - return GLAD_EXT_texture_compression_s3tc; + return GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt5; case image::CompressedData::FORMAT_BC4: case image::CompressedData::FORMAT_BC4s: case image::CompressedData::FORMAT_BC5: case image::CompressedData::FORMAT_BC5s: return (GLAD_VERSION_3_0 || GLAD_ARB_texture_compression_rgtc || GLAD_EXT_texture_compression_rgtc); + case image::CompressedData::FORMAT_ETC1: + // ETC2 support guarantees ETC1 support as well. + return GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_0 || GLAD_ARB_ES3_compatibility || GLAD_OES_compressed_ETC1_RGB8_texture; + case image::CompressedData::FORMAT_PVR1_RGB2: + case image::CompressedData::FORMAT_PVR1_RGB4: + case image::CompressedData::FORMAT_PVR1_RGBA2: + case image::CompressedData::FORMAT_PVR1_RGBA4: + return GLAD_IMG_texture_compression_pvrtc; default: break; } @@ -655,6 +679,24 @@ bool Image::hasSRGBSupport() return GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB || GLAD_EXT_sRGB; } +bool Image::getConstant(const char *in, Format &out) +{ + return formats.find(in, out); +} + +bool Image::getConstant(Format in, const char *&out) +{ + return formats.find(in, out); +} + +StringMap::Entry Image::formatEntries[] = +{ + {"normal", Image::FORMAT_NORMAL}, + {"srgb", Image::FORMAT_SRGB}, +}; + +StringMap Image::formats(Image::formatEntries, sizeof(Image::formatEntries)); + } // opengl } // graphics } // love diff --git a/jni/love/src/modules/graphics/opengl/Image.h b/jni/love/src/modules/graphics/opengl/Image.h index 4872b818..a5a99ca3 100644 --- a/jni/love/src/modules/graphics/opengl/Image.h +++ b/jni/love/src/modules/graphics/opengl/Image.h @@ -25,6 +25,7 @@ #include "common/config.h" #include "common/Matrix.h" #include "common/Vector.h" +#include "common/StringMap.h" #include "common/math.h" #include "image/ImageData.h" #include "image/CompressedData.h" @@ -50,20 +51,27 @@ class Image : public Texture { public: + enum Format + { + FORMAT_NORMAL, + FORMAT_SRGB, + FORMAT_MAX_ENUM + }; + /** * Creates a new Image. Not that anything is ready to use * before load is called. * * @param data The data from which to load the image. **/ - Image(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL); + Image(love::image::ImageData *data, Format format = FORMAT_NORMAL); /** * Creates a new Image with compressed image data. * * @param cdata The compressed data from which to load the image. **/ - Image(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL); + Image(love::image::CompressedData *cdata, Format format = FORMAT_NORMAL); /** * Destructor. Deletes the hardware texture and other resources. @@ -120,7 +128,7 @@ public: **/ bool refresh(); - Texture::Format getFormat() const; + Format getFormat() const; static void setDefaultMipmapSharpness(float sharpness); static float getDefaultMipmapSharpness(); @@ -136,6 +144,9 @@ public: static bool hasSRGBSupport(); + static bool getConstant(const char *in, Format &out); + static bool getConstant(Format in, const char *&out); + private: void uploadDefaultTexture(); @@ -165,8 +176,8 @@ private: // Whether this Image is using a compressed texture. bool compressed; - // The format to interpret the texture's data as. - Texture::Format format; + // The format to interpret the image's data as. + Format format; // True if the image wasn't able to be properly created and it had to fall // back to a default texture. @@ -188,6 +199,9 @@ private: GLenum getCompressedFormat(image::CompressedData::Format cformat) const; + static StringMap::Entry formatEntries[]; + static StringMap formats; + }; // Image } // opengl diff --git a/jni/love/src/modules/graphics/opengl/OpenGL.cpp b/jni/love/src/modules/graphics/opengl/OpenGL.cpp index 95768b81..577d0d0e 100644 --- a/jni/love/src/modules/graphics/opengl/OpenGL.cpp +++ b/jni/love/src/modules/graphics/opengl/OpenGL.cpp @@ -160,6 +160,7 @@ void OpenGL::initVendor() } // http://feedback.wildfiregames.com/report/opengl/feature/GL_VENDOR + // http://stackoverflow.com/questions/2093594/opengl-extensions-available-on-different-android-devices if (strstr(vstr, "ATI Technologies")) vendor = VENDOR_ATI_AMD; else if (strstr(vstr, "NVIDIA")) @@ -172,6 +173,16 @@ void OpenGL::initVendor() vendor = VENDOR_APPLE; else if (strstr(vstr, "Microsoft")) vendor = VENDOR_MICROSOFT; + else if (strstr(vstr, "Imagination")) + vendor = VENDOR_IMGTEC; + else if (strstr(vstr, "ARM")) + vendor = VENDOR_ARM; + else if (strstr(vstr, "Qualcomm")) + vendor = VENDOR_QUALCOMM; + else if (strstr(vstr, "Broadcom")) + vendor = VENDOR_BROADCOM; + else if (strstr(vstr, "Vivante")) + vendor = VENDOR_VIVANTE; else vendor = VENDOR_UNKNOWN; } diff --git a/jni/love/src/modules/graphics/opengl/OpenGL.h b/jni/love/src/modules/graphics/opengl/OpenGL.h index 69675e11..461781e1 100644 --- a/jni/love/src/modules/graphics/opengl/OpenGL.h +++ b/jni/love/src/modules/graphics/opengl/OpenGL.h @@ -66,8 +66,13 @@ public: VENDOR_NVIDIA, VENDOR_INTEL, VENDOR_MESA_SOFT, // Software renderer. - VENDOR_APPLE, // Software renderer. + VENDOR_APPLE, // Software renderer (or Apple A7 chips and newer.) VENDOR_MICROSOFT, // Software renderer. + VENDOR_IMGTEC, + VENDOR_ARM, + VENDOR_QUALCOMM, + VENDOR_BROADCOM, + VENDOR_VIVANTE, VENDOR_UNKNOWN }; diff --git a/jni/love/src/modules/graphics/opengl/Shader.cpp b/jni/love/src/modules/graphics/opengl/Shader.cpp index bd629da4..2b68b2e1 100644 --- a/jni/love/src/modules/graphics/opengl/Shader.cpp +++ b/jni/love/src/modules/graphics/opengl/Shader.cpp @@ -101,10 +101,9 @@ Shader::~Shader() detach(); for (auto it = boundRetainables.begin(); it != boundRetainables.end(); ++it) - { it->second->release(); - boundRetainables.erase(it); - } + + boundRetainables.clear(); unloadVolatile(); } diff --git a/jni/love/src/modules/graphics/opengl/VertexBuffer.cpp b/jni/love/src/modules/graphics/opengl/VertexBuffer.cpp index eb0bafe6..71870d71 100644 --- a/jni/love/src/modules/graphics/opengl/VertexBuffer.cpp +++ b/jni/love/src/modules/graphics/opengl/VertexBuffer.cpp @@ -189,10 +189,18 @@ void VBO::unmap() is_bound = true; } - // "orphan" current buffer to avoid implicit synchronisation on the GPU: - // http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf - glBufferData(getTarget(), (GLsizeiptr) getSize(), NULL, getUsage()); - glBufferData(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage()); + if (getUsage() == GL_STATIC_DRAW) + { + // Upload the mapped data to the buffer. + glBufferSubData(getTarget(), 0, (GLsizeiptr) getSize(), memory_map); + } + else + { + // "orphan" current buffer to avoid implicit synchronisation on the GPU: + // http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf + glBufferData(getTarget(), (GLsizeiptr) getSize(), NULL, getUsage()); + glBufferData(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage()); + } is_mapped = false; } diff --git a/jni/love/src/modules/graphics/opengl/wrap_Canvas.cpp b/jni/love/src/modules/graphics/opengl/wrap_Canvas.cpp index a3c31b7f..4efc610e 100644 --- a/jni/love/src/modules/graphics/opengl/wrap_Canvas.cpp +++ b/jni/love/src/modules/graphics/opengl/wrap_Canvas.cpp @@ -113,10 +113,10 @@ int w_Canvas_clear(lua_State *L) int w_Canvas_getFormat(lua_State *L) { Canvas *canvas = luax_checkcanvas(L, 1); - Texture::Format format = canvas->getTextureFormat(); + Canvas::Format format = canvas->getTextureFormat(); const char *str; - if (!Texture::getConstant(format, str)) - return luaL_error(L, "Unknown texture format."); + if (!Canvas::getConstant(format, str)) + return luaL_error(L, "Unknown Canvas format."); lua_pushstring(L, str); return 1; diff --git a/jni/love/src/modules/graphics/opengl/wrap_Graphics.cpp b/jni/love/src/modules/graphics/opengl/wrap_Graphics.cpp index a813164c..a30bd038 100644 --- a/jni/love/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/jni/love/src/modules/graphics/opengl/wrap_Graphics.cpp @@ -60,6 +60,12 @@ int w_isCreated(lua_State *L) return 1; } +int w_isActive(lua_State *L) +{ + luax_pushboolean(L, instance->isActive()); + return 1; +} + int w_getWidth(lua_State *L) { lua_pushinteger(L, instance->getWidth()); @@ -155,14 +161,11 @@ int w_newImage(lua_State *L) love::image::ImageData *data = nullptr; love::image::CompressedData *cdata = nullptr; - Texture::Format format = Texture::FORMAT_NORMAL; + Image::Format format = Image::FORMAT_NORMAL; const char *fstr = lua_isnoneornil(L, 2) ? nullptr : luaL_checkstring(L, 2); - if (fstr != nullptr && !Texture::getConstant(fstr, format)) - return luaL_error(L, "Invalid texture format: %s", fstr); - - if (format == Texture::FORMAT_HDR) // For now... - return luaL_error(L, "HDR images are not supported."); + if (fstr != nullptr && !Image::getConstant(fstr, format)) + return luaL_error(L, "Invalid Image format: %s", fstr); // Convert to FileData, if necessary. if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_T)) @@ -334,9 +337,9 @@ int w_newCanvas(lua_State *L) const char *str = luaL_optstring(L, 3, "normal"); int fsaa = luaL_optint(L, 4, 0); - Texture::Format format; - if (!Texture::getConstant(str, format)) - return luaL_error(L, "Invalid texture format: %s", str); + Canvas::Format format; + if (!Canvas::getConstant(str, format)) + return luaL_error(L, "Invalid Canvas format: %s", str); Canvas *canvas = nullptr; EXCEPT_GUARD(canvas = instance->newCanvas(width, height, format, fsaa);) @@ -1015,7 +1018,7 @@ int w_isSupported(lua_State *L) supported = false; break; case Graphics::SUPPORT_HDR_CANVAS: - if (!Canvas::isHDRSupported()) + if (!Canvas::isFormatSupported(Canvas::FORMAT_HDR)) supported = false; break; case Graphics::SUPPORT_MULTI_CANVAS: @@ -1046,12 +1049,20 @@ int w_isSupported(lua_State *L) if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_BC5)) supported = false; break; + case Graphics::SUPPORT_ETC1: + if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_ETC1)) + supported = false; + break; + case Graphics::SUPPORT_PVRTC1: + if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_PVR1_RGBA4)) + supported = false; + break; case Graphics::SUPPORT_INSTANCING: if (!(GLAD_ARB_draw_instanced || (GLAD_ES_VERSION_2_0 && GLAD_EXT_draw_instanced))) supported = false; break; case Graphics::SUPPORT_SRGB: - if (!Canvas::isSRGBSupported()) + if (!Canvas::isFormatSupported(Canvas::FORMAT_SRGB)) supported = false; break; default: @@ -1064,6 +1075,18 @@ int w_isSupported(lua_State *L) return 1; } +int w_hasCanvasFormat(lua_State *L) +{ + const char *str = luaL_checkstring(L, 1); + Canvas::Format format; + + if (!Canvas::getConstant(str, format)) + return luaL_error(L, "Invalid canvas format: %s", str); + + luax_pushboolean(L, Canvas::isFormatSupported(format)); + return 1; +} + int w_getRendererInfo(lua_State *L) { std::string name, version, vendor, device; @@ -1441,6 +1464,7 @@ static const luaL_Reg functions[] = { "_setDefaultShaderCode", w_setDefaultShaderCode }, { "isSupported", w_isSupported }, + { "hasCanvasFormat", w_hasCanvasFormat }, { "getRendererInfo", w_getRendererInfo }, { "getSystemLimit", w_getSystemLimit }, @@ -1450,6 +1474,7 @@ static const luaL_Reg functions[] = { "printf", w_printf }, { "isCreated", w_isCreated }, + { "isActive", w_isActive }, { "getWidth", w_getWidth }, { "getHeight", w_getHeight }, { "getDimensions", w_getDimensions }, diff --git a/jni/love/src/modules/graphics/opengl/wrap_Graphics.h b/jni/love/src/modules/graphics/opengl/wrap_Graphics.h index 88a5ad4d..0a508921 100644 --- a/jni/love/src/modules/graphics/opengl/wrap_Graphics.h +++ b/jni/love/src/modules/graphics/opengl/wrap_Graphics.h @@ -43,6 +43,7 @@ int w_reset(lua_State *L); int w_clear(lua_State *L); int w_present(lua_State *L); int w_isCreated(lua_State *L); +int w_isActive(lua_State *L); int w_getWidth(lua_State *L); int w_getHeight(lua_State *L); int w_getDimensions(lua_State *L); @@ -94,6 +95,7 @@ int w_setShader(lua_State *L); int w_getShader(lua_State *L); int w_setDefaultShaderCode(lua_State *L); int w_isSupported(lua_State *L); +int w_hasCanvasFormat(lua_State *L); int w_getRendererInfo(lua_State *L); int w_getSystemLimit(lua_State *L); int w_draw(lua_State *L); diff --git a/jni/love/src/modules/image/CompressedData.cpp b/jni/love/src/modules/image/CompressedData.cpp index e4d47331..50a1ee4d 100644 --- a/jni/love/src/modules/image/CompressedData.cpp +++ b/jni/love/src/modules/image/CompressedData.cpp @@ -110,6 +110,11 @@ StringMap::Entry Compre {"bc4s", CompressedData::FORMAT_BC4s}, {"bc5", CompressedData::FORMAT_BC5}, {"bc5s", CompressedData::FORMAT_BC5s}, + {"etc1", CompressedData::FORMAT_ETC1}, + {"pvr1rgb2", CompressedData::FORMAT_PVR1_RGB2}, + {"pvr1rgb4", CompressedData::FORMAT_PVR1_RGB4}, + {"pvr1rgba2", CompressedData::FORMAT_PVR1_RGBA2}, + {"pvr1rgba4", CompressedData::FORMAT_PVR1_RGBA4}, }; StringMap CompressedData::formats(CompressedData::formatEntries, sizeof(CompressedData::formatEntries)); diff --git a/jni/love/src/modules/image/CompressedData.h b/jni/love/src/modules/image/CompressedData.h index 42ee5e58..fb7ca41f 100644 --- a/jni/love/src/modules/image/CompressedData.h +++ b/jni/love/src/modules/image/CompressedData.h @@ -54,6 +54,11 @@ public: FORMAT_BC4s, FORMAT_BC5, FORMAT_BC5s, + FORMAT_ETC1, + FORMAT_PVR1_RGB2, + FORMAT_PVR1_RGB4, + FORMAT_PVR1_RGBA2, + FORMAT_PVR1_RGBA4, FORMAT_MAX_ENUM }; diff --git a/jni/love/src/modules/image/magpie/CompressedData.cpp b/jni/love/src/modules/image/magpie/CompressedData.cpp index 2a4e46c1..4879d84d 100644 --- a/jni/love/src/modules/image/magpie/CompressedData.cpp +++ b/jni/love/src/modules/image/magpie/CompressedData.cpp @@ -29,29 +29,47 @@ namespace image namespace magpie { -CompressedData::CompressedData(love::filesystem::FileData *filedata) +CompressedData::CompressedData(std::list formats, love::filesystem::FileData *filedata) + : formatHandlers(formats) { + for (CompressedFormatHandler *handler : formatHandlers) + handler->retain(); + load(filedata); } CompressedData::~CompressedData() { delete[] data; + + for (CompressedFormatHandler *handler : formatHandlers) + handler->release(); } void CompressedData::load(love::filesystem::FileData *filedata) { + CompressedFormatHandler *parser = nullptr; + + for (CompressedFormatHandler *handler : formatHandlers) + { + if (handler->canParse(filedata)) + { + parser = handler; + break; + } + } + + if (parser == nullptr) + throw love::Exception("Could not parse compressed data: Unknown format."); + // SubImage vector will be populated by a parser. std::vector parsedimages; Format texformat = FORMAT_UNKNOWN; - uint8 *newdata = 0; size_t newdata_size = 0; + uint8 *newdata = parser->parse(filedata, parsedimages, newdata_size, texformat); - if (ddsHandler::canParse(filedata)) - newdata = ddsHandler::parse(filedata, parsedimages, newdata_size, texformat); - - if (newdata == 0) + if (newdata == nullptr) throw love::Exception("Could not parse compressed data."); if (texformat == FORMAT_UNKNOWN) @@ -76,14 +94,6 @@ void CompressedData::load(love::filesystem::FileData *filedata) format = texformat; } -bool CompressedData::isCompressed(love::filesystem::FileData *filedata) -{ - if (ddsHandler::canParse(filedata)) - return true; - - return false; -} - } // magpie } // image } // love diff --git a/jni/love/src/modules/image/magpie/CompressedData.h b/jni/love/src/modules/image/magpie/CompressedData.h index bd1bc98c..79727b91 100644 --- a/jni/love/src/modules/image/magpie/CompressedData.h +++ b/jni/love/src/modules/image/magpie/CompressedData.h @@ -22,9 +22,13 @@ #define LOVE_IMAGE_MAGPIE_COMPRESSED_DATA_H // LOVE +#include "CompressedFormatHandler.h" #include "filesystem/FileData.h" #include "image/CompressedData.h" +// C++ +#include + namespace love { namespace image @@ -36,15 +40,16 @@ class CompressedData : public love::image::CompressedData { public: - CompressedData(love::filesystem::FileData *filedata); + CompressedData(std::list formats, love::filesystem::FileData *filedata); virtual ~CompressedData(); - static bool isCompressed(love::filesystem::FileData *filedata); - private: void load(love::filesystem::FileData *filedata); + // Compressed image format handlers we use for parsing. + std::list formatHandlers; + }; // CompressedData } // magpie diff --git a/jni/love/src/modules/image/magpie/CompressedFormatHandler.h b/jni/love/src/modules/image/magpie/CompressedFormatHandler.h new file mode 100644 index 00000000..490da2c0 --- /dev/null +++ b/jni/love/src/modules/image/magpie/CompressedFormatHandler.h @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#ifndef LOVE_IMAGE_MAGPIE_COMPRESSED_HANDLER_H +#define LOVE_IMAGE_MAGPIE_COMPRESSED_HANDLER_H + +// LOVE +#include "filesystem/FileData.h" +#include "image/CompressedData.h" +#include "common/Object.h" + +namespace love +{ +namespace image +{ +namespace magpie +{ + +/** + * Base class for all CompressedData parser library interfaces. + * We inherit from love::Object to take advantage of reference counting... + **/ +class CompressedFormatHandler : public love::Object +{ +public: + + CompressedFormatHandler() {} + virtual ~CompressedFormatHandler() {} + + /** + * Determines whether a particular FileData can be parsed as CompressedData + * by this handler. + * @param data The data to parse. + **/ + virtual bool canParse(const filesystem::FileData *data) = 0; + + /** + * Parses compressed image filedata into a list of sub-images and returns + * a single block of memory containing all the images. + * + * @param[in] filedata The data to parse. + * @param[out] image The list of sub-images generated. Byte data is a pointer + * to the returned data. + * @param[out] dataSize The total size in bytes of the returned data. + * @param[out] format The format of the Compressed Data. + * + * @return The single block of memory containing the parsed images. + **/ + virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format) = 0; + +}; // CompressedFormatHandler + +} // magpie +} // image +} // love + +#endif // LOVE_IMAGE_MAGPIE_COMPRESSED_HANDLER_H diff --git a/jni/love/src/modules/image/magpie/Image.cpp b/jni/love/src/modules/image/magpie/Image.cpp index 3953aefd..6608cdda 100644 --- a/jni/love/src/modules/image/magpie/Image.cpp +++ b/jni/love/src/modules/image/magpie/Image.cpp @@ -25,6 +25,11 @@ #include "DevilHandler.h" +#include "ddsHandler.h" +#include "PVRHandler.h" +#include "KTXHandler.h" +#include "PKMHandler.h" + namespace love { namespace image @@ -35,14 +40,22 @@ namespace magpie Image::Image() { formatHandlers.push_back(new DevilHandler); + + compressedFormatHandlers.push_back(new DDSHandler); + compressedFormatHandlers.push_back(new PVRHandler); + compressedFormatHandlers.push_back(new KTXHandler); + compressedFormatHandlers.push_back(new PKMHandler); } Image::~Image() { // ImageData objects reference the FormatHandlers in our list, so we should // release them instead of deleting them completely here. - for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it) - (*it)->release(); + for (FormatHandler *handler : formatHandlers) + handler->release(); + + for (CompressedFormatHandler *handler : compressedFormatHandlers) + handler->release(); } const char *Image::getName() const @@ -67,12 +80,18 @@ love::image::ImageData *Image::newImageData(int width, int height, void *data, b love::image::CompressedData *Image::newCompressedData(love::filesystem::FileData *data) { - return new CompressedData(data); + return new CompressedData(compressedFormatHandlers, data); } bool Image::isCompressed(love::filesystem::FileData *data) { - return CompressedData::isCompressed(data); + for (CompressedFormatHandler *handler : compressedFormatHandlers) + { + if (handler->canParse(data)) + return true; + } + + return false; } } // magpie diff --git a/jni/love/src/modules/image/magpie/Image.h b/jni/love/src/modules/image/magpie/Image.h index 75adfc47..df4b8a61 100644 --- a/jni/love/src/modules/image/magpie/Image.h +++ b/jni/love/src/modules/image/magpie/Image.h @@ -24,6 +24,7 @@ // LOVE #include "image/Image.h" #include "FormatHandler.h" +#include "CompressedFormatHandler.h" // C++ #include @@ -63,6 +64,9 @@ private: // Image format handlers we can use for decoding and encoding ImageData. std::list formatHandlers; + // Compressed image format handers we can use for parsing CompressedData. + std::list compressedFormatHandlers; + }; // Image } // magpie diff --git a/jni/love/src/modules/image/magpie/KTXHandler.cpp b/jni/love/src/modules/image/magpie/KTXHandler.cpp new file mode 100644 index 00000000..fcaf61e6 --- /dev/null +++ b/jni/love/src/modules/image/magpie/KTXHandler.cpp @@ -0,0 +1,241 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +// LOVE +#include "KTXHandler.h" +#include "common/int.h" + +// C +#include + +// C++ +#include + +namespace love +{ +namespace image +{ +namespace magpie +{ + +namespace +{ + +#define KTX_IDENTIFIER_REF {0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A} +#define KTX_ENDIAN_REF (0x04030201) +#define KTX_ENDIAN_REF_REV (0x01020304) +#define KTX_HEADER_SIZE (64) + +struct KTXHeader +{ + uint8 identifier[12]; + uint32 endianness; + uint32 glType; + uint32 glTypeSize; + uint32 glFormat; + uint32 glInternalFormat; + uint32 glBaseInternalFormat; + uint32 pixelWidth; + uint32 pixelHeight; + uint32 pixelDepth; + uint32 numberOfArrayElements; + uint32 numberOfFaces; + uint32 numberOfMipmapLevels; + uint32 bytesOfKeyValueData; +}; + +static_assert(sizeof(KTXHeader) == KTX_HEADER_SIZE, "Real size of KTX header doesn't match struct size!"); + +enum KTXGLInternalFormat +{ + KTX_GL_ETC1_RGB8_OES = 0x8D64, + + // LOVE doesn't support EAC or ETC2 yet, but it won't be hard to add. + KTX_GL_COMPRESSED_R11_EAC = 0x9270, + KTX_GL_COMPRESSED_SIGNED_R11_EAC = 0x9271, + KTX_GL_COMPRESSED_RG11_EAC = 0x9272, + KTX_GL_COMPRESSED_SIGNED_RG11_EAC = 0x9273, + KTX_GL_COMPRESSED_RGB8_ETC2 = 0x9274, + KTX_GL_COMPRESSED_SRGB8_ETC2 = 0x9275, + KTX_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9276, + KTX_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9277, + KTX_GL_COMPRESSED_RGBA8_ETC2_EAC = 0x9278, + KTX_GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = 0x9279, + + // I don't know if any KTX file contains PVR data, but why not support it. + KTX_GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00, + KTX_GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG = 0x8C01, + KTX_GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02, + KTX_GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03, + + // Same with DXT1/3/5. + KTX_GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0, + KTX_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2, + KTX_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3 +}; + +CompressedData::Format convertFormat(uint32 glformat) +{ + switch (glformat) + { + case KTX_GL_ETC1_RGB8_OES: + return CompressedData::FORMAT_ETC1; + case KTX_GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG: + return CompressedData::FORMAT_PVR1_RGB4; + case KTX_GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG: + return CompressedData::FORMAT_PVR1_RGB2; + case KTX_GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG: + return CompressedData::FORMAT_PVR1_RGBA4; + case KTX_GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG: + return CompressedData::FORMAT_PVR1_RGBA2; + case KTX_GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + return CompressedData::FORMAT_DXT1; + case KTX_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + return CompressedData::FORMAT_DXT3; + case KTX_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + return CompressedData::FORMAT_DXT5; + default: + return CompressedData::FORMAT_UNKNOWN; + } +} + +} // Anonymous namespace. + +bool KTXHandler::canParse(const filesystem::FileData *data) +{ + if (data->getSize() < sizeof(KTXHeader)) + return false; + + KTXHeader *header = (KTXHeader *) data->getData(); + uint8 ktxidentifier[12] = KTX_IDENTIFIER_REF; + + if (memcmp(header->identifier, ktxidentifier, 12) != 0) + return false; + + if (header->endianness != KTX_ENDIAN_REF && header->endianness != KTX_ENDIAN_REF_REV) + return false; + + return true; +} + +uint8 *KTXHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format) +{ + if (!canParse(filedata)) + throw love::Exception("Could not decode compressed data (not a KTX file?)"); + + KTXHeader header = *(KTXHeader *) filedata->getData(); + + if (header.endianness == KTX_ENDIAN_REF_REV) + { + uint32 *headerArray = (uint32 *) &header.glType; + for (int i = 0; i < 12; i++) + headerArray[i] = swap32(headerArray[i]); + } + + header.numberOfMipmapLevels = std::max(header.numberOfMipmapLevels, 1u); + + CompressedData::Format cformat = convertFormat(header.glInternalFormat); + + if (cformat == CompressedData::FORMAT_UNKNOWN) + throw love::Exception("Unsupported image format in KTX file."); + + if (header.numberOfArrayElements > 0) + throw love::Exception("Texture arrays in KTX files are not supported."); + + if (header.pixelDepth > 1) + throw love::Exception("3D textures in KTX files are not supported."); + + if (header.numberOfFaces > 1) + throw love::Exception("Cubemap textures in KTX files are not supported."); + + size_t fileoffset = sizeof(KTXHeader) + header.bytesOfKeyValueData; + const uint8 *filebytes = (uint8 *) filedata->getData(); + size_t totalsize = 0; + + // Calculate the total size needed to hold the data in memory. + for (int i = 0; i < (int) header.numberOfMipmapLevels; i++) + { + if (fileoffset + sizeof(uint32) > filedata->getSize()) + throw love::Exception("Could not parse KTX file: unexpected EOF."); + + uint32 mipsize = *(uint32 *) (filebytes + fileoffset); + + if (header.endianness == KTX_ENDIAN_REF_REV) + mipsize = swap32(mipsize); + + fileoffset += sizeof(uint32); + + // All mipsize fields are at a file offset that's a multiple of 4, so + // there might be some padding after the actual data in this mip level. + uint32 mipsizepadded = (mipsize + 3) & ~uint32(3); + + totalsize += mipsizepadded; + fileoffset += mipsizepadded; + } + + uint8 *data = nullptr; + try + { + data = new uint8[totalsize]; + } + catch (std::bad_alloc &) + { + throw love::Exception("Out of memory."); + } + + // Reset the file offset to the start of the file's image data. + fileoffset = sizeof(KTXHeader) + header.bytesOfKeyValueData; + size_t dataoffset = 0; + + // Copy each mipmap level of the image from the file to our block of memory. + for (int i = 0; i < (int) header.numberOfMipmapLevels; i++) + { + uint32 mipsize = *(uint32 *) (filebytes + fileoffset); + + if (header.endianness == KTX_ENDIAN_REF_REV) + mipsize = swap32(mipsize); + + fileoffset += sizeof(uint32); + + uint32 mipsizepadded = (mipsize + 3) & ~uint32(3); + + CompressedData::SubImage mip; + mip.width = (int) std::max(header.pixelWidth >> i, 1u); + mip.height = (int) std::max(header.pixelHeight >> i, 1u); + mip.size = mipsize; + + memcpy(data + dataoffset, filebytes + fileoffset, mipsize); + mip.data = data + dataoffset; + + fileoffset += mipsizepadded; + dataoffset += mipsizepadded; + + images.push_back(mip); + } + + dataSize = totalsize; + format = cformat; + + return data; +} + +} // magpie +} // image +} // love diff --git a/jni/love/src/modules/image/magpie/KTXHandler.h b/jni/love/src/modules/image/magpie/KTXHandler.h new file mode 100644 index 00000000..4ac823e7 --- /dev/null +++ b/jni/love/src/modules/image/magpie/KTXHandler.h @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#ifndef LOVE_IMAGE_MAGPIE_KTX_HANDLER_H +#define LOVE_IMAGE_MAGPIE_KTX_HANDLER_H + +#include "common/config.h" +#include "CompressedFormatHandler.h" + +namespace love +{ +namespace image +{ +namespace magpie +{ + +/** + * Handles KTX files with compressed image data inside. + **/ +class KTXHandler : public CompressedFormatHandler +{ +public: + + virtual ~KTXHandler() {} + + // Implements CompressedFormatHandler. + virtual bool canParse(const filesystem::FileData *data); + virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format); + +}; // KTXHandler + +} // magpie +} // image +} // love + +#endif // LOVE_IMAGE_MAGPIE_KTX_HANDLER_H diff --git a/jni/love/src/modules/image/magpie/PKMHandler.cpp b/jni/love/src/modules/image/magpie/PKMHandler.cpp new file mode 100644 index 00000000..26ae1972 --- /dev/null +++ b/jni/love/src/modules/image/magpie/PKMHandler.cpp @@ -0,0 +1,156 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +// LOVE +#include "PKMHandler.h" +#include "common/int.h" + +namespace love +{ +namespace image +{ +namespace magpie +{ + +namespace +{ + +// Big endian to host (and vice versa.) +inline uint16 swap16big(uint16 x) +{ +#ifdef LOVE_BIG_ENDIAN + return x; +#else + return swap16(x); +#endif +} + +static const uint8 pkmIdentifier[] = {'P','K','M',' '}; + +struct PKMHeader +{ + uint8 identifier[4]; + uint8 version[2]; + uint16 textureFormatBig; + uint16 extendedWidthBig; + uint16 extendedHeightBig; + uint16 widthBig; + uint16 heightBig; +}; + +enum PKMTextureFormat +{ + ETC1_RGB_NO_MIPMAPS = 0, + ETC2PACKAGE_RGB_NO_MIPMAPS, + ETC2PACKAGE_RGBA_NO_MIPMAPS_OLD, + ETC2PACKAGE_RGBA_NO_MIPMAPS, + ETC2PACKAGE_RGBA1_NO_MIPMAPS, + ETC2PACKAGE_R_NO_MIPMAPS, + ETC2PACKAGE_RG_NO_MIPMAPS, + ETC2PACKAGE_R_SIGNED_NO_MIPMAPS, + ETC2PACKAGE_RG_SIGNED_NO_MIPMAPS +}; + +CompressedData::Format convertFormat(uint16 texformat) +{ + switch (texformat) + { + case ETC1_RGB_NO_MIPMAPS: + return CompressedData::FORMAT_ETC1; + default: + return CompressedData::FORMAT_UNKNOWN; + } +} + +} // Anonymous namespace. + +bool PKMHandler::canParse(const filesystem::FileData *data) +{ + if (data->getSize() <= sizeof(PKMHeader)) + return false; + + const PKMHeader *header = (const PKMHeader *) data->getData(); + + if (memcmp(header->identifier, pkmIdentifier, 4) != 0) + return false; + + // At the time of this writing, only v1.0 and v2.0 exist. + if ((header->version[0] != '2' && header->version[0] != '1') || header->version[1] != '0') + return false; + + return true; +} + +uint8 *PKMHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format) +{ + if (!canParse(filedata)) + throw love::Exception("Could not decode compressed data (not a PKM file?)"); + + PKMHeader header = *(const PKMHeader *) filedata->getData(); + + header.textureFormatBig = swap16big(header.textureFormatBig); + header.extendedWidthBig = swap16big(header.extendedWidthBig); + header.extendedHeightBig = swap16big(header.extendedHeightBig); + header.widthBig = swap16big(header.widthBig); + header.heightBig = swap16big(header.heightBig); + + CompressedData::Format cformat = convertFormat(header.textureFormatBig); + + if (cformat == CompressedData::FORMAT_UNKNOWN) + throw love::Exception("Could not parse PKM file: unsupported texture format."); + + // The rest of the file after the header is all texture data. + size_t totalsize = filedata->getSize() - sizeof(PKMHeader); + uint8 *data = nullptr; + + try + { + data = new uint8[totalsize]; + } + catch (std::bad_alloc &) + { + throw love::Exception("Out of memory."); + } + + // PKM files only store a single mipmap level. + memcpy(data, (uint8 *) filedata->getData() + sizeof(PKMHeader), totalsize); + + CompressedData::SubImage mip; + + // TODO: verify whether glCompressedTexImage works properly with the unpadded + // width and height values (extended == padded.) + mip.width = header.widthBig; + mip.height = header.heightBig; + + mip.size = totalsize; + mip.data = data; + + images.push_back(mip); + + dataSize = totalsize; + format = cformat; + + return data; +} + +} // magpie +} // image +} // love + diff --git a/jni/love/src/modules/image/magpie/PKMHandler.h b/jni/love/src/modules/image/magpie/PKMHandler.h new file mode 100644 index 00000000..1df3ffda --- /dev/null +++ b/jni/love/src/modules/image/magpie/PKMHandler.h @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#ifndef LOVE_IMAGE_MAGPIE_PKM_HANDLER_H +#define LOVE_IMAGE_MAGPIE_PKM_HANDLER_H + +#include "common/config.h" +#include "CompressedFormatHandler.h" + +namespace love +{ +namespace image +{ +namespace magpie +{ + +/** + * Handles PKM files with compressed ETC data inside. + **/ +class PKMHandler : public CompressedFormatHandler +{ +public: + + virtual ~PKMHandler() {} + + // Implements CompressedFormatHandler. + virtual bool canParse(const filesystem::FileData *data); + virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format); + +}; // PKMHandler + +} // magpie +} // image +} // love + +#endif // LOVE_IMAGE_MAGPIE_PKM_HANDLER_H diff --git a/jni/love/src/modules/image/magpie/PVRHandler.cpp b/jni/love/src/modules/image/magpie/PVRHandler.cpp new file mode 100644 index 00000000..2e12c350 --- /dev/null +++ b/jni/love/src/modules/image/magpie/PVRHandler.cpp @@ -0,0 +1,385 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +// LOVE +#include "PVRHandler.h" +#include "common/int.h" + +// C++ +#include + +namespace love +{ +namespace image +{ +namespace magpie +{ + +namespace +{ + +// 'P' 'V' 'R' 3 +static const uint32 PVRTEX3_IDENT = 0x03525650; +static const uint32 PVRTEX3_IDENT_REV = 0x50565203; + +#pragma pack(push, 4) +struct PVRTexHeaderV3 +{ + uint32 version; /// Version of the file header, used to identify it. + uint32 flags; /// Various format flags. + uint64 pixelFormat; /// The pixel format, 8cc value storing the 4 channel identifiers and their respective sizes. + uint32 colorSpace; /// The Color Space of the texture, currently either linear RGB or sRGB. + uint32 channelType; /// Variable type that the channel is stored in. Supports signed/unsigned int/short/byte or float for now. + uint32 height; /// Height of the texture. + uint32 width; /// Width of the texture. + uint32 depth; /// Depth of the texture. (Z-slices) + uint32 numSurfaces; /// Number of members in a Texture Array. + uint32 numFaces; /// Number of faces in a Cube Map. Maybe be a value other than 6. + uint32 numMipmaps; /// Number of MIP Maps in the texture - NB: Includes top level. + uint32 metaDataSize; /// Size of the accompanying meta data. +}; +#pragma pack(pop) + +enum PVRV3PixelFormat +{ + ePVRTPF_PVRTCI_2bpp_RGB = 0x00, + ePVRTPF_PVRTCI_2bpp_RGBA, + ePVRTPF_PVRTCI_4bpp_RGB, + ePVRTPF_PVRTCI_4bpp_RGBA, + ePVRTPF_PVRTCII_2bpp, + ePVRTPF_PVRTCII_4bpp, + ePVRTPF_ETC1 = 0x06, + ePVRTPF_DXT1, + ePVRTPF_DXT2, + ePVRTPF_DXT3, + ePVRTPF_DXT4, + ePVRTPF_DXT5, + ePVRTF_UNKNOWN_FORMAT = 0x7F +}; + +// 'P' 'V' 'R' '!' +static const uint32 PVRTEX2_IDENT = 0x21525650; +static const uint32 PVRTEX2_IDENT_REV = 0x50565221; + +struct PVRTexHeaderV2 +{ + uint32 headerSize; + uint32 height; + uint32 width; + uint32 numMipmaps; + uint32 flags; + uint32 dataSize; + uint32 bpp; + uint32 bitmaskRed; + uint32 bitmaskGreen; + uint32 bitmaskBlue; + uint32 bitmaskAlpha; + uint32 pvrTag; + uint32 numSurfaces; +}; + +// The legacy V2 pixel types we support. +enum PVRPixelTypeV2 +{ + PixelTypePVRTC2 = 0x18, + PixelTypePVRTC4, + PixelTypePVRTCII2 = 0x1C, + PixelTypePVRTCII4, + PixelTypeDXT1 = 0x20, + PixelTypeDXT3 = 0x22, + PixelTypeDXT5 = 0x24, + PixelTypeETC1 = 0x36 +}; + +// Convert a V2 header to V3. +void ConvertPVRHeader(PVRTexHeaderV2 header2, PVRTexHeaderV3 *header3) +{ + // If the header's endianness doesn't match our own, we swap everything. + if (header2.pvrTag == PVRTEX2_IDENT_REV) + { + // All of the struct's members are uint32 values, so we can do this. + uint32 *headerArray = (uint32 *) &header2; + for (size_t i = 0; i < sizeof(PVRTexHeaderV2) / sizeof(uint32); i++) + headerArray[i] = swap32(headerArray[i]); + } + + memset(header3, 0, sizeof(PVRTexHeaderV3)); + + header3->version = PVRTEX3_IDENT; + header3->height = header2.height; + header3->width = header2.width; + header3->depth = 1; + header3->numSurfaces = header2.numSurfaces; + header3->numFaces = 1; + header3->numMipmaps = header2.numMipmaps; + header3->metaDataSize = 0; + + switch ((PVRPixelTypeV2) (header2.flags & 0xFF)) + { + case PixelTypePVRTC2: + header3->pixelFormat = ePVRTPF_PVRTCI_2bpp_RGBA; + break; + case PixelTypePVRTC4: + header3->pixelFormat = ePVRTPF_PVRTCI_4bpp_RGBA; + break; + case PixelTypePVRTCII2: + header3->pixelFormat = ePVRTPF_PVRTCII_2bpp; + break; + case PixelTypePVRTCII4: + header3->pixelFormat = ePVRTPF_PVRTCII_4bpp; + break; + case PixelTypeDXT1: + header3->pixelFormat = ePVRTPF_DXT1; + break; + case PixelTypeDXT3: + header3->pixelFormat = ePVRTPF_DXT3; + break; + case PixelTypeDXT5: + header3->pixelFormat = ePVRTPF_DXT5; + break; + case PixelTypeETC1: + header3->pixelFormat = ePVRTPF_ETC1; + break; + default: + header3->pixelFormat = ePVRTF_UNKNOWN_FORMAT; + break; + } +} + +CompressedData::Format convertFormat(PVRV3PixelFormat format) +{ + switch (format) + { + case ePVRTPF_PVRTCI_2bpp_RGB: + return CompressedData::FORMAT_PVR1_RGB2; + case ePVRTPF_PVRTCI_2bpp_RGBA: + return CompressedData::FORMAT_PVR1_RGBA2; + case ePVRTPF_PVRTCI_4bpp_RGB: + return CompressedData::FORMAT_PVR1_RGB4; + case ePVRTPF_PVRTCI_4bpp_RGBA: + return CompressedData::FORMAT_PVR1_RGBA4; + case ePVRTPF_ETC1: + return CompressedData::FORMAT_ETC1; + case ePVRTPF_DXT1: + return CompressedData::FORMAT_DXT1; + case ePVRTPF_DXT3: + return CompressedData::FORMAT_DXT3; + case ePVRTPF_DXT5: + return CompressedData::FORMAT_DXT5; + default: + return CompressedData::FORMAT_UNKNOWN; + } +} + +int getBitsPerPixel(uint64 pixelformat) +{ + // Uncompressed formats have their bits per pixel stored in the high bits. + if ((pixelformat & 0xFFFFFFFF) != pixelformat) + { + const uint8 *charformat = (const uint8 *) &pixelformat; + return charformat[4] + charformat[5] + charformat[6] + charformat[7]; + } + + switch (pixelformat) + { + case ePVRTPF_PVRTCI_2bpp_RGB: + case ePVRTPF_PVRTCI_2bpp_RGBA: + case ePVRTPF_PVRTCII_2bpp: + return 2; + case ePVRTPF_PVRTCI_4bpp_RGB: + case ePVRTPF_PVRTCI_4bpp_RGBA: + case ePVRTPF_PVRTCII_4bpp: + case ePVRTPF_ETC1: + case ePVRTPF_DXT1: + return 4; + case ePVRTPF_DXT2: + case ePVRTPF_DXT3: + case ePVRTPF_DXT4: + case ePVRTPF_DXT5: + return 8; + default: + return 0; + } +} + +void getFormatMinDimensions(uint64 pixelformat, int &minX, int &minY) +{ + switch (pixelformat) + { + case ePVRTPF_PVRTCI_2bpp_RGB: + case ePVRTPF_PVRTCI_2bpp_RGBA: + minX = 16; + minY = 8; + break; + case ePVRTPF_PVRTCI_4bpp_RGB: + case ePVRTPF_PVRTCI_4bpp_RGBA: + minX = minY = 8; + break; + case ePVRTPF_PVRTCII_2bpp: + minX = 8; + minY = 4; + break; + case ePVRTPF_PVRTCII_4bpp: + minX = minY = 4; + break; + case ePVRTPF_DXT1: + case ePVRTPF_DXT2: + case ePVRTPF_DXT3: + case ePVRTPF_DXT4: + case ePVRTPF_DXT5: + case ePVRTPF_ETC1: + minX = minY = 4; + break; + default: // We don't handle all possible formats, but that's fine. + minX = minY = 1; + break; + } +} + +size_t getMipLevelSize(const PVRTexHeaderV3 &header, int miplevel) +{ + int smallestwidth = 1; + int smallestheight = 1; + getFormatMinDimensions(header.pixelFormat, smallestwidth, smallestheight); + + int width = std::max((int) header.width >> miplevel, 1); + int height = std::max((int) header.height >> miplevel, 1); + int depth = std::max((int) header.depth >> miplevel, 1); + + // Pad the dimensions. + width += (-width) % smallestwidth; + height += (-height) % smallestheight; + + return getBitsPerPixel(header.pixelFormat) * width * height * depth / 8; +} + +} // Anonymous namespace. + + +bool PVRHandler::canParse(const filesystem::FileData *data) +{ + if (data->getSize() < sizeof(PVRTexHeaderV2) || data->getSize() < sizeof(PVRTexHeaderV3)) + return false; + + PVRTexHeaderV3 *header3 = (PVRTexHeaderV3 *) data->getData(); + + // Magic number (FourCC identifier.) + if (header3->version == PVRTEX3_IDENT || header3->version == PVRTEX3_IDENT_REV) + return true; + + // Maybe it has a V2 header. + PVRTexHeaderV2 *header2 = (PVRTexHeaderV2 *) data->getData(); + + // FourCC identifier. + if (header2->pvrTag == PVRTEX2_IDENT || header2->pvrTag == PVRTEX2_IDENT_REV) + return true; + + return false; +} + +uint8 *PVRHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format) +{ + if (!canParse(filedata)) + throw love::Exception("Could not decode compressed data (not a PVR file?)"); + + PVRTexHeaderV3 header3 = *(PVRTexHeaderV3 *) filedata->getData(); + + // If the header isn't the V3 format, assume it's V2 and convert. + if (header3.version != PVRTEX3_IDENT && header3.version != PVRTEX3_IDENT_REV) + ConvertPVRHeader(*(PVRTexHeaderV2 *) filedata->getData(), &header3); + + // If the header's endianness doesn't match our own, then we swap everything. + if (header3.version == PVRTEX3_IDENT_REV) + { + header3.version = PVRTEX3_IDENT; + header3.flags = swap32(header3.flags); + header3.pixelFormat = swap64(header3.pixelFormat); + header3.colorSpace = swap32(header3.colorSpace); + header3.channelType = swap32(header3.channelType); + header3.height = swap32(header3.height); + header3.width = swap32(header3.width); + header3.depth = swap32(header3.depth); + header3.numFaces = swap32(header3.numFaces); + header3.numMipmaps = swap32(header3.numMipmaps); + header3.metaDataSize = swap32(header3.metaDataSize); + } + + if (header3.depth > 1) + throw love::Exception("Image depths greater than 1 in PVR files are unsupported."); + + CompressedData::Format cformat = convertFormat((PVRV3PixelFormat) header3.pixelFormat); + + if (cformat == CompressedData::FORMAT_UNKNOWN) + throw love::Exception("Could not parse PVR file: unsupported image format."); + + size_t totalsize = 0; + uint8 *data = nullptr; + + // Ignore faces and surfaces except the first ones (for now.) + for (int i = 0; i < (int) header3.numMipmaps; i++) + totalsize += getMipLevelSize(header3, i); + + size_t fileoffset = sizeof(PVRTexHeaderV3) + header3.metaDataSize; + + // Make sure the file actually holds this much data... + if (filedata->getSize() < fileoffset + totalsize) + throw love::Exception("Could not parse PVR file: invalid size calculation."); + + try + { + data = new uint8[totalsize]; + } + catch (std::bad_alloc &) + { + throw love::Exception("Out of memory."); + } + + size_t curoffset = 0; + const uint8 *filebytes = (uint8 *) filedata->getData() + fileoffset; + + for (int i = 0; i < (int) header3.numMipmaps; i++) + { + size_t mipsize = getMipLevelSize(header3, i); + + if (curoffset + mipsize > totalsize) + break; // Just in case. + + CompressedData::SubImage mip; + mip.width = std::max((int) header3.width >> i, 1); + mip.height = std::max((int) header3.height >> i, 1); + mip.size = mipsize; + + memcpy(data + curoffset, filebytes + curoffset, mipsize); + mip.data = data + curoffset; + + curoffset += mipsize; + + images.push_back(mip); + } + + dataSize = totalsize; + format = cformat; + + return data; +} + +} // magpie +} // image +} // love diff --git a/jni/love/src/modules/image/magpie/PVRHandler.h b/jni/love/src/modules/image/magpie/PVRHandler.h new file mode 100644 index 00000000..f35cc9ae --- /dev/null +++ b/jni/love/src/modules/image/magpie/PVRHandler.h @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#ifndef LOVE_IMAGE_MAGPIE_PVR_HANDLER_H +#define LOVE_IMAGE_MAGPIE_PVR_HANDLER_H + +// LOVE +#include "common/config.h" +#include "CompressedFormatHandler.h" + +namespace love +{ +namespace image +{ +namespace magpie +{ + +class PVRHandler : public CompressedFormatHandler +{ +public: + + virtual ~PVRHandler() {} + + // Implements CompressedFormatHandler. + virtual bool canParse(const filesystem::FileData *data); + virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format); + +}; // PVRHandler + +} // magpie +} // image +} // love + +#endif // LOVE_IMAGE_MAGPIE_PVR_HANDLER_H diff --git a/jni/love/src/modules/image/magpie/ddsHandler.cpp b/jni/love/src/modules/image/magpie/ddsHandler.cpp index 865f3523..b744de4b 100644 --- a/jni/love/src/modules/image/magpie/ddsHandler.cpp +++ b/jni/love/src/modules/image/magpie/ddsHandler.cpp @@ -18,8 +18,10 @@ * 3. This notice may not be removed or altered from any source distribution. **/ +// LOVE #include "ddsHandler.h" +// C++ #include namespace love @@ -29,7 +31,7 @@ namespace image namespace magpie { -bool ddsHandler::canParse(const filesystem::FileData *data) +bool DDSHandler::canParse(const filesystem::FileData *data) { std::string ext = data->getExtension(); std::transform(ext.begin(), ext.end(), ext.begin(), tolower); @@ -40,14 +42,14 @@ bool ddsHandler::canParse(const filesystem::FileData *data) return dds::isCompressedDDS(data->getData(), data->getSize()); } -uint8 *ddsHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format) +uint8 *DDSHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format) { if (!dds::isDDS(filedata->getData(), filedata->getSize())) throw love::Exception("Could not decode compressed data (not a DDS file?)"); CompressedData::Format texformat = CompressedData::FORMAT_UNKNOWN; - uint8 *data = 0; + uint8 *data = nullptr; dataSize = 0; images.clear(); @@ -107,7 +109,7 @@ uint8 *ddsHandler::parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format); + // Implements CompressedFormatHandler. + virtual bool canParse(const filesystem::FileData *data); + virtual uint8 *parse(filesystem::FileData *filedata, std::vector &images, size_t &dataSize, CompressedData::Format &format); private: static CompressedData::Format convertFormat(dds::Format ddsformat); -}; // ddsHandler +}; // DDSHandler } // magpie } // image diff --git a/jni/love/src/modules/joystick/Joystick.h b/jni/love/src/modules/joystick/Joystick.h index dde6bebd..7f76c3a9 100644 --- a/jni/love/src/modules/joystick/Joystick.h +++ b/jni/love/src/modules/joystick/Joystick.h @@ -157,9 +157,9 @@ public: virtual int getID() const = 0; virtual bool isVibrationSupported() = 0; - virtual bool setVibration(float left, float right) = 0; + virtual bool setVibration(float left, float right, float duration = -1.0f) = 0; virtual bool setVibration() = 0; - virtual void getVibration(float &left, float &right) const = 0; + virtual void getVibration(float &left, float &right) = 0; static bool getConstant(const char *in, Hat &out); static bool getConstant(Hat in, const char *&out); diff --git a/jni/love/src/modules/joystick/sdl/Joystick.cpp b/jni/love/src/modules/joystick/sdl/Joystick.cpp index ed370a27..a7eccae0 100644 --- a/jni/love/src/modules/joystick/sdl/Joystick.cpp +++ b/jni/love/src/modules/joystick/sdl/Joystick.cpp @@ -25,6 +25,11 @@ // C++ #include +#include + +#ifndef SDL_TICKS_PASSED +#define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0) +#endif namespace love { @@ -34,9 +39,9 @@ namespace sdl { Joystick::Joystick(int id) - : joyhandle(0) - , controller(0) - , haptic(0) + : joyhandle(nullptr) + , controller(nullptr) + , haptic(nullptr) , instanceid(-1) , id(id) , vibration() @@ -44,9 +49,9 @@ Joystick::Joystick(int id) } Joystick::Joystick(int id, int joyindex) - : joyhandle(0) - , controller(0) - , haptic(0) + : joyhandle(nullptr) + , controller(nullptr) + , haptic(nullptr) , instanceid(-1) , id(id) , vibration() @@ -95,10 +100,7 @@ bool Joystick::open(int deviceindex) void Joystick::close() { if (haptic) - { - SDL_HapticRumbleStop(haptic); SDL_HapticClose(haptic); - } if (controller) SDL_GameControllerClose(controller); @@ -106,16 +108,16 @@ void Joystick::close() if (joyhandle) SDL_JoystickClose(joyhandle); - joyhandle = 0; - controller = 0; - haptic = 0; + joyhandle = nullptr; + controller = nullptr; + haptic = nullptr; instanceid = -1; vibration = Vibration(); } bool Joystick::isConnected() const { - return joyhandle != 0 && SDL_JoystickGetAttached(joyhandle); + return joyhandle != nullptr && SDL_JoystickGetAttached(joyhandle); } const char *Joystick::getName() const @@ -209,7 +211,7 @@ bool Joystick::openGamepad(int deviceindex) if (isGamepad()) { SDL_GameControllerClose(controller); - controller = 0; + controller = nullptr; } controller = SDL_GameControllerOpen(deviceindex); @@ -218,7 +220,7 @@ bool Joystick::openGamepad(int deviceindex) bool Joystick::isGamepad() const { - return controller != 0; + return controller != nullptr; } float Joystick::getGamepadAxis(love::joystick::Joystick::GamepadAxis axis) const @@ -289,13 +291,13 @@ bool Joystick::checkCreateHaptic() if (haptic) { SDL_HapticClose(haptic); - haptic = 0; + haptic = nullptr; } haptic = SDL_HapticOpenFromJoystick(joyhandle); vibration = Vibration(); - return haptic != 0; + return haptic != nullptr; } bool Joystick::isVibrationSupported() @@ -312,8 +314,8 @@ bool Joystick::isVibrationSupported() if (isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0) return true; - // Check SDL's simple rumble as a last resort. - if (SDL_HapticRumbleSupported(haptic) == 1) + // Test for simple sine wave support as a last resort. + if ((features & SDL_HAPTIC_SINE) != 0) return true; return false; @@ -325,7 +327,7 @@ bool Joystick::runVibrationEffect() { if (SDL_HapticUpdateEffect(haptic, vibration.id, &vibration.effect) == 0) { - if (SDL_HapticRunEffect(haptic, vibration.id, 1) != -1) + if (SDL_HapticRunEffect(haptic, vibration.id, 1) == 0) return true; } @@ -336,17 +338,14 @@ bool Joystick::runVibrationEffect() vibration.id = SDL_HapticNewEffect(haptic, &vibration.effect); - if (vibration.id != -1 && SDL_HapticRunEffect(haptic, vibration.id, 1) != -1) + if (vibration.id != -1 && SDL_HapticRunEffect(haptic, vibration.id, 1) == 0) return true; return false; } -bool Joystick::setVibration(float left, float right) +bool Joystick::setVibration(float left, float right, float duration) { - // TODO: support non-infinite durations? The working Tattiebogle Xbox - // controller driver in OS X seems to ignore durations under 1 second. - left = std::min(std::max(left, 0.0f), 1.0f); right = std::min(std::max(right, 0.0f), 1.0f); @@ -356,24 +355,32 @@ bool Joystick::setVibration(float left, float right) if (!checkCreateHaptic()) return false; + Uint32 length = SDL_HAPTIC_INFINITY; + if (duration >= 0.0f) + { + float maxduration = std::numeric_limits::max() / 1000.0f; + length = Uint32(std::min(duration, maxduration) * 1000); + } + bool success = false; unsigned int features = SDL_HapticQuery(haptic); + int axes = SDL_HapticNumAxes(haptic); if ((features & SDL_HAPTIC_LEFTRIGHT) != 0) { memset(&vibration.effect, 0, sizeof(SDL_HapticEffect)); vibration.effect.type = SDL_HAPTIC_LEFTRIGHT; - vibration.effect.leftright.length = SDL_HAPTIC_INFINITY; + vibration.effect.leftright.length = length; vibration.effect.leftright.large_magnitude = Uint16(left * LOVE_UINT16_MAX); vibration.effect.leftright.small_magnitude = Uint16(right * LOVE_UINT16_MAX); success = runVibrationEffect(); } - // Some gamepad drivers only give support for controlling the motors via - // a custom FF effect. - if (!success && isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0) + // Some gamepad drivers only give support for controlling individual motors + // through a custom FF effect. + if (!success && isGamepad() && (features & SDL_HAPTIC_CUSTOM) && axes == 2) { // NOTE: this may cause issues with drivers which support custom effects // but aren't similar to https://github.com/d235j/360Controller . @@ -385,7 +392,7 @@ bool Joystick::setVibration(float left, float right) memset(&vibration.effect, 0, sizeof(SDL_HapticEffect)); vibration.effect.type = SDL_HAPTIC_CUSTOM; - vibration.effect.custom.length = SDL_HAPTIC_INFINITY; + vibration.effect.custom.length = length; vibration.effect.custom.channels = 2; vibration.effect.custom.period = 10; vibration.effect.custom.samples = 2; @@ -394,19 +401,36 @@ bool Joystick::setVibration(float left, float right) success = runVibrationEffect(); } - // Fall back to a simple rumble if all else fails. SDL's simple rumble API - // only supports a single strength value. - if (!success && SDL_HapticRumbleInit(haptic) == 0) + // Fall back to a simple sine wave if all else fails. This only supports a + // single strength value. + if (!success && (features & SDL_HAPTIC_SINE) != 0) { + memset(&vibration.effect, 0, sizeof(SDL_HapticEffect)); + vibration.effect.type = SDL_HAPTIC_SINE; + + vibration.effect.periodic.length = length; + vibration.effect.periodic.period = 10; + float strength = std::max(left, right); - int played = SDL_HapticRumblePlay(haptic, strength, SDL_HAPTIC_INFINITY); - success = (played == 0); + vibration.effect.periodic.magnitude = Sint16(strength * 0x7FFF); + + success = runVibrationEffect(); } if (success) { vibration.left = left; vibration.right = right; + + if (length == SDL_HAPTIC_INFINITY) + vibration.endtime = SDL_HAPTIC_INFINITY; + else + vibration.endtime = SDL_GetTicks() + length; + } + else + { + vibration.left = vibration.right = 0.0f; + vibration.endtime = SDL_HAPTIC_INFINITY; } return success; @@ -417,12 +441,7 @@ bool Joystick::setVibration() bool success = true; if (SDL_WasInit(SDL_INIT_HAPTIC) && haptic && SDL_HapticIndex(haptic) != -1) - { - // Stop all playing effects on the haptic device. - // FIXME: We should only stop the vibration effect, in case we use the - // Haptic API for other things in the future. - success = (SDL_HapticStopAll(haptic) == 0); - } + success = (SDL_HapticStopEffect(haptic, vibration.id) == 0); if (success) vibration.left = vibration.right = 0.0f; @@ -430,8 +449,25 @@ bool Joystick::setVibration() return success; } -void Joystick::getVibration(float &left, float &right) const +void Joystick::getVibration(float &left, float &right) { + if (vibration.endtime != SDL_HAPTIC_INFINITY) + { + // With some drivers, the effect physically stops at the right time, but + // SDL_HapticGetEffectStatus still thinks it's playing. So we explicitly + // stop it once it's done, just to be sure. + if (SDL_TICKS_PASSED(SDL_GetTicks(), vibration.endtime)) + { + setVibration(); + vibration.endtime = SDL_HAPTIC_INFINITY; + } + } + + // Check if the haptic effect has stopped playing. + int id = vibration.id; + if (!haptic || id == -1 || SDL_HapticGetEffectStatus(haptic, id) != 1) + vibration.left = vibration.right = 0.0f; + left = vibration.left; right = vibration.right; } diff --git a/jni/love/src/modules/joystick/sdl/Joystick.h b/jni/love/src/modules/joystick/sdl/Joystick.h index 929cb545..6d9cdaba 100644 --- a/jni/love/src/modules/joystick/sdl/Joystick.h +++ b/jni/love/src/modules/joystick/sdl/Joystick.h @@ -74,9 +74,9 @@ public: int getID() const; bool isVibrationSupported(); - bool setVibration(float left, float right); + bool setVibration(float left, float right, float duration = -1.0f); bool setVibration(); - void getVibration(float &left, float &right) const; + void getVibration(float &left, float &right); static bool getConstant(Hat in, Uint8 &out); static bool getConstant(Uint8 in, Hat &out); @@ -111,8 +111,12 @@ private: Uint16 data[4]; int id; + Uint32 endtime; + Vibration() - : left(0.0f), right(0.0f), effect(), data(), id(-1) + : left(0.0f), right(0.0f) + , effect(), data(), id(-1) + , endtime(SDL_HAPTIC_INFINITY) {} } vibration; diff --git a/jni/love/src/modules/joystick/sdl/JoystickModule.cpp b/jni/love/src/modules/joystick/sdl/JoystickModule.cpp index f037927b..ab0a28b1 100644 --- a/jni/love/src/modules/joystick/sdl/JoystickModule.cpp +++ b/jni/love/src/modules/joystick/sdl/JoystickModule.cpp @@ -77,7 +77,7 @@ const char *JoystickModule::getName() const love::joystick::Joystick *JoystickModule::getJoystick(int joyindex) { if (joyindex < 0 || (size_t) joyindex >= activeSticks.size()) - return 0; + return nullptr; return activeSticks[joyindex]; } @@ -107,13 +107,13 @@ love::joystick::Joystick *JoystickModule::getJoystickFromID(int instanceid) return activeSticks[i]; } - return 0; + return nullptr; } love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex) { if (deviceindex < 0 || deviceindex >= SDL_NumJoysticks()) - return 0; + return nullptr; std::string guidstr = getDeviceGUID(deviceindex); joystick::Joystick *joystick = 0; @@ -140,7 +140,7 @@ love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex) removeJoystick(joystick); if (!joystick->open(deviceindex)) - return 0; + return nullptr; // Make sure multiple instances of the same physical joystick aren't added // to the active list. @@ -262,7 +262,7 @@ bool JoystickModule::setGamepadMapping(const std::string &guid, Joystick::Gamepa if (status == 1) checkGamepads(guid); - return status >= 0; + return status >= 0; } Joystick::JoystickInput JoystickModule::getGamepadMapping(const std::string &guid, Joystick::GamepadInput gpinput) @@ -318,7 +318,7 @@ std::string JoystickModule::stringFromGamepadInput(Joystick::GamepadInput gpinpu SDL_GameControllerAxis sdlaxis; SDL_GameControllerButton sdlbutton; - const char *gpinputname = 0; + const char *gpinputname = nullptr; switch (gpinput.type) { @@ -435,7 +435,7 @@ void JoystickModule::checkGamepads(const std::string &guid) const // Big hack time: open the index as a game controller and compare // the underlying joystick handle to the active stick's. SDL_GameController *ctrl = SDL_GameControllerOpen(d_index); - if (ctrl == NULL) + if (ctrl == nullptr) continue; SDL_Joystick *stick = SDL_GameControllerGetJoystick(ctrl); diff --git a/jni/love/src/modules/joystick/sdl/wrap_Joystick.cpp b/jni/love/src/modules/joystick/sdl/wrap_Joystick.cpp index a0eb4569..90a3f4d3 100644 --- a/jni/love/src/modules/joystick/sdl/wrap_Joystick.cpp +++ b/jni/love/src/modules/joystick/sdl/wrap_Joystick.cpp @@ -207,7 +207,8 @@ int w_Joystick_setVibration(lua_State *L) { float left = (float) luaL_checknumber(L, 2); float right = (float) luaL_optnumber(L, 3, left); - success = j->setVibration(left, right); + float duration = (float) luaL_optnumber(L, 4, -1.0); // -1 is infinite. + success = j->setVibration(left, right, duration); } luax_pushboolean(L, success); diff --git a/jni/love/src/modules/joystick/sdl/wrap_JoystickModule.cpp b/jni/love/src/modules/joystick/sdl/wrap_JoystickModule.cpp index a069e015..7a438ff1 100644 --- a/jni/love/src/modules/joystick/sdl/wrap_JoystickModule.cpp +++ b/jni/love/src/modules/joystick/sdl/wrap_JoystickModule.cpp @@ -28,7 +28,7 @@ namespace joystick namespace sdl { -static JoystickModule *instance = 0; +static JoystickModule *instance = nullptr; int w_getJoysticks(lua_State *L) { @@ -194,7 +194,7 @@ static const lua_CFunction types[] = extern "C" int luaopen_love_joystick(lua_State *L) { - if (instance == 0) + if (instance == nullptr) { EXCEPT_GUARD(instance = new JoystickModule();) } diff --git a/jni/love/src/modules/sound/lullaby/Mpg123Decoder.cpp b/jni/love/src/modules/sound/lullaby/Mpg123Decoder.cpp index a89b3ef6..01dc6c83 100644 --- a/jni/love/src/modules/sound/lullaby/Mpg123Decoder.cpp +++ b/jni/love/src/modules/sound/lullaby/Mpg123Decoder.cpp @@ -117,7 +117,7 @@ int Mpg123Decoder::decode() size += numbytes; long rate = 0; int encoding = 0; - int ret = mpg123_getformat(handle, &rate, &channels, &encoding); + mpg123_getformat(handle, &rate, &channels, &encoding); if (rate == 0) rate = sampleRate; else @@ -126,7 +126,7 @@ int Mpg123Decoder::decode() channels = MPG123_STEREO; if (encoding == 0) encoding = MPG123_ENC_SIGNED_16; - ret = mpg123_format(handle, rate, channels, encoding); + int ret = mpg123_format(handle, rate, channels, encoding); if (ret != MPG123_OK) throw love::Exception("Could not set output format."); } diff --git a/jni/love/src/modules/system/System.cpp b/jni/love/src/modules/system/System.cpp index fdfafae5..8c59478e 100755 --- a/jni/love/src/modules/system/System.cpp +++ b/jni/love/src/modules/system/System.cpp @@ -27,8 +27,9 @@ #elif defined(LOVE_ANDROID) #include "common/android.h" #elif defined(LOVE_LINUX) -#include -#include +#include +//#include +//#include #include #elif defined(LOVE_WINDOWS) #include "common/utf8.h" @@ -57,12 +58,17 @@ std::string System::getOS() const #endif } +extern "C" +{ + extern char **environ; // The environment, always available +} + bool System::openURL(const std::string &url) const { - bool success = false; #if defined(LOVE_MACOSX) + bool success = false; // We could be lazy and use system("open " + url), but this is safer. CFURLRef cfurl = CFURLCreateWithBytes(nullptr, (const UInt8 *) url.c_str(), @@ -72,38 +78,28 @@ bool System::openURL(const std::string &url) const success = LSOpenCFURLRef(cfurl, nullptr) == noErr; CFRelease(cfurl); + return success; #elif defined(LOVE_ANDROID) - success = love::android::openURL (url); + return love::android::openURL (url); #elif defined(LOVE_LINUX) - // Spawn a child process, which we'll replace with xdg-open. - pid_t pid = vfork(); + pid_t pid; + const char *argv[] = {"xdg-open", url.c_str(), nullptr}; - if (pid == 0) // Child process. - { - // Replace the child process with xdg-open and pass in the URL. - execlp("xdg-open", "xdg-open", url.c_str(), nullptr); + // Note: at the moment this process inherits our file descriptors. + // Note: the below const_cast is really ugly as well. + if (posix_spawnp(&pid, "xdg-open", nullptr, nullptr, const_cast(argv), environ) != 0) + return false; - // exec will only return if it errored, so we should exit with non-zero. - _exit(1); - } - else if (pid > 0) // Parent process. - { - // Wait for xdg-open to complete (or fail.) - int status = 0; - if (waitpid(pid, &status, 0) == pid) - success = (status == 0); - else - success = false; - } + // Wait for xdg-open to complete (or fail.) + int status = 0; + if (waitpid(pid, &status, 0) == pid) + return (status == 0); else - { - // vfork() failed. - success = false; - } + return false; #elif defined(LOVE_WINDOWS) @@ -117,11 +113,9 @@ bool System::openURL(const std::string &url) const nullptr, SW_SHOW); - success = (int) result > 32; + return (int) result > 32; #endif - - return success; } bool System::getConstant(const char *in, System::PowerState &out) diff --git a/jni/love/src/modules/thread/LuaThread.cpp b/jni/love/src/modules/thread/LuaThread.cpp index 1509822e..36688e83 100644 --- a/jni/love/src/modules/thread/LuaThread.cpp +++ b/jni/love/src/modules/thread/LuaThread.cpp @@ -38,6 +38,7 @@ LuaThread::LuaThread(const std::string &name, love::Data *code) , nargs(0) { code->retain(); + threadName = name; } LuaThread::~LuaThread() diff --git a/jni/love/src/modules/thread/sdl/Thread.cpp b/jni/love/src/modules/thread/sdl/Thread.cpp index 41684633..c6a6dc61 100644 --- a/jni/love/src/modules/thread/sdl/Thread.cpp +++ b/jni/love/src/modules/thread/sdl/Thread.cpp @@ -51,7 +51,7 @@ bool Thread::start() return false; if (thread) // Clean old handle up SDL_WaitThread(thread, 0); - thread = SDL_CreateThread(thread_runner, NULL, this); + thread = SDL_CreateThread(thread_runner, t->getThreadName(), this); running = (thread != 0); return running; } diff --git a/jni/love/src/modules/thread/threads.cpp b/jni/love/src/modules/thread/threads.cpp index 631b75b5..9abeb589 100644 --- a/jni/love/src/modules/thread/threads.cpp +++ b/jni/love/src/modules/thread/threads.cpp @@ -99,5 +99,10 @@ bool Threadable::isRunning() const return owner->isRunning(); } +const char *Threadable::getThreadName() const +{ + return threadName.empty() ? nullptr : threadName.c_str(); +} + } // thread } // love diff --git a/jni/love/src/modules/thread/threads.h b/jni/love/src/modules/thread/threads.h index f4c887e9..516abae9 100644 --- a/jni/love/src/modules/thread/threads.h +++ b/jni/love/src/modules/thread/threads.h @@ -21,9 +21,13 @@ #ifndef LOVE_THREAD_THREADS_H #define LOVE_THREAD_THREADS_H +// LOVE #include "common/config.h" #include "Thread.h" +// C++ +#include + namespace love { namespace thread @@ -83,9 +87,13 @@ public: bool start(); void wait(); bool isRunning() const; + const char *getThreadName() const; protected: + Thread *owner; + std::string threadName; + }; Mutex *newMutex(); diff --git a/jni/love/src/modules/window/sdl/Window.cpp b/jni/love/src/modules/window/sdl/Window.cpp index b1342abc..b0af3556 100644 --- a/jni/love/src/modules/window/sdl/Window.cpp +++ b/jni/love/src/modules/window/sdl/Window.cpp @@ -269,11 +269,14 @@ bool Window::setContext(int fsaa, bool vsync, bool sRGB) if (!context) { int flags = 0; + int profilemask = 0; SDL_GL_GetAttribute(SDL_GL_CONTEXT_FLAGS, &flags); + SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profilemask); if (flags & SDL_GL_CONTEXT_DEBUG_FLAG) { + profilemask &= ~SDL_GL_CONTEXT_PROFILE_COMPATIBILITY; SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profilemask); context = SDL_GL_CreateContext(window); } } @@ -315,6 +318,7 @@ void Window::setWindowGLAttributes(int fsaa, bool /* sRGB */) const SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1); + SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0); // FSAA. SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (fsaa > 0) ? 1 : 0); @@ -331,19 +335,26 @@ void Window::setWindowGLAttributes(int fsaa, bool /* sRGB */) const int contextprofile = 0; - bool tryES2 = false; + bool tryGLES = false; const char *driver = SDL_GetCurrentVideoDriver(); + const char *glesdrivers[] = {"RPI", "Android", "uikit", "winrt"}; // We always want to use OpenGL ES on certain video backends. - if (driver && (strstr(driver, "RPI") || strstr(driver, "Android") || strstr(driver, "uikit"))) - tryES2 = true; + for (size_t i = 0; i < sizeof(glesdrivers) / sizeof(glesdrivers[0]); i++) + { + if (driver && strstr(driver, glesdrivers[i])) + { + tryGLES = true; + break; + } + } const char *hint = SDL_GetHint("LOVE_GRAPHICS_USE_OPENGLES"); if (hint) - tryES2 = (*hint) != '0'; + tryGLES = (*hint) != '0'; - if (tryES2) + if (tryGLES) { // Use OpenGL ES 2+. contextprofile = SDL_GL_CONTEXT_PROFILE_ES; @@ -363,7 +374,7 @@ void Window::setWindowGLAttributes(int fsaa, bool /* sRGB */) const if (debugenv && *debugenv == '1') { SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); - if (!tryES2) + if (!tryGLES) contextprofile |= SDL_GL_CONTEXT_PROFILE_COMPATIBILITY; } else diff --git a/jni/love/src/scripts/boot.lua b/jni/love/src/scripts/boot.lua index 7ea5473c..d259a8c0 100644 --- a/jni/love/src/scripts/boot.lua +++ b/jni/love/src/scripts/boot.lua @@ -526,7 +526,7 @@ function love.run() -- Call update and draw if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled - if love.window and love.graphics and love.window.isCreated() then + if love.window and love.graphics and love.graphics.isActive() then love.graphics.clear() love.graphics.origin() if love.draw then love.draw() end diff --git a/jni/love/src/scripts/boot.lua.h b/jni/love/src/scripts/boot.lua.h index 6cdf8799..391aa8b4 100644 --- a/jni/love/src/scripts/boot.lua.h +++ b/jni/love/src/scripts/boot.lua.h @@ -943,8 +943,8 @@ const unsigned char boot_lua[] = 0x73, 0x20, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x0a, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x20, 0x61, - 0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x69, 0x73, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x69, + 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x28, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6f, diff --git a/jni/love/src/scripts/graphics.lua b/jni/love/src/scripts/graphics.lua index 7b09cd38..13a8c9d5 100644 --- a/jni/love/src/scripts/graphics.lua +++ b/jni/love/src/scripts/graphics.lua @@ -1317,9 +1317,9 @@ uniform mat4 TransformMatrix; uniform mat4 ProjectionMatrix; uniform mat4 TransformProjectionMatrix; // uniform mat4 NormalMatrix; -uniform float love_PointSize; uniform sampler2D _tex0_; -uniform vec4 love_ScreenSize;]] +uniform mediump vec4 love_ScreenSize; +uniform mediump float love_PointSize;]] GLSL.VERTEX = { HEADER = [[ @@ -1357,7 +1357,7 @@ attribute vec4 VertexPosition; attribute vec4 VertexTexCoord; attribute vec4 VertexColor; -varying vec4 VaryingTexCoord; +varying mediump vec4 VaryingTexCoord; varying lowp vec4 VaryingColor; //#if defined(GL_EXT_draw_instanced) @@ -1414,18 +1414,36 @@ void main() { precision mediump float; -varying vec4 VaryingTexCoord; +varying mediump vec4 VaryingTexCoord; varying lowp vec4 VaryingColor;]], FOOTER = GLSL.PIXEL.FOOTER, FOOTER_MULTI_CANVAS = GLSL.PIXEL.FOOTER_MULTI_CANVAS, } + -- Unfortunately, GLSL 1.20 can't deal with precision qualifier statements. + local function stripPrecision(code) + -- Remove "precision mediump float;", etc. + code = code:gsub("precision(%s+)lowp(%s+)(%w+)(%s*);", "") + code = code:gsub("precision(%s+)mediump(%s+)(%w+)(%s*);", "") + code = code:gsub("precision(%s+)highp(%s+)(%w+)(%s*);", "") + + -- Remove all remaining "lowp", "mediump", "highp". + code = code:gsub("(%W+)lowp(%s+)", "%1"):gsub("^lowp(%s+)", "") + code = code:gsub("(%W+)mediump(%s+)", "%1"):gsub("^mediump(%s+)", "") + code = code:gsub("(%W+)highp(%s+)", "%1"):gsub("^highp(%s+)", "") + + return code + end + local function createVertexCode(vertexcode, lang) + if lang == GLSL then + vertexcode = stripPrecision(vertexcode) + end local vertexcodes = { lang.VERSION, lang.SYNTAX, lang.VERTEX.HEADER, lang.UNIFORMS, - "#line 1", + lang == GLSLES and "#line 1" or "#line 0", -- specification differences for #line... vertexcode, lang.VERTEX.FOOTER, } @@ -1433,10 +1451,13 @@ varying lowp vec4 VaryingColor;]], end local function createPixelCode(pixelcode, lang, is_multicanvas) + if lang == GLSL then + pixelcode = stripPrecision(pixelcode) + end local pixelcodes = { lang.VERSION, lang.SYNTAX, lang.PIXEL.HEADER, lang.UNIFORMS, - "#line 1", + lang == GLSLES and "#line 1" or "#line 0", pixelcode, is_multicanvas and lang.PIXEL.FOOTER_MULTI_CANVAS or lang.PIXEL.FOOTER, } @@ -1539,7 +1560,7 @@ vec4 position(mat4 transform_proj, vec4 vertpos) { return transform_proj * vertpos; }]], pixel = [[ -vec4 effect(vec4 vcolor, Image texture, vec2 texcoord, vec2 pixcoord) { +lowp vec4 effect(lowp vec4 vcolor, Image texture, vec2 texcoord, vec2 pixcoord) { return Texel(texture, texcoord) * vcolor; }]] } diff --git a/jni/love/src/scripts/graphics.lua.h b/jni/love/src/scripts/graphics.lua.h index ea75ea32..ea2d7109 100644 --- a/jni/love/src/scripts/graphics.lua.h +++ b/jni/love/src/scripts/graphics.lua.h @@ -6312,12 +6312,14 @@ const unsigned char graphics_lua[] = 0x69, 0x78, 0x3b, 0x0a, 0x2f, 0x2f, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x4e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x3b, 0x0a, - 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, - 0x5f, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x5f, 0x74, 0x65, 0x78, 0x30, 0x5f, 0x3b, 0x0a, - 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, - 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x53, 0x69, 0x7a, 0x65, 0x3b, 0x5d, 0x5d, 0x0a, + 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x76, 0x65, + 0x63, 0x34, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x53, 0x69, 0x7a, 0x65, + 0x3b, 0x0a, + 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x69, 0x7a, 0x65, + 0x3b, 0x5d, 0x5d, 0x0a, 0x09, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x20, 0x3d, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x20, 0x3d, 0x20, 0x5b, 0x5b, 0x0a, 0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x0a, @@ -6373,8 +6375,9 @@ const unsigned char graphics_lua[] = 0x74, 0x65, 0x78, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, - 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, - 0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, + 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x76, 0x65, + 0x63, 0x34, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, + 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x6c, 0x6f, 0x77, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x2f, 0x2f, 0x23, 0x69, 0x66, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x28, 0x47, 0x4c, 0x5f, 0x45, @@ -6470,8 +6473,9 @@ const unsigned char graphics_lua[] = 0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x0a, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3b, 0x0a, - 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, - 0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, + 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x76, 0x65, + 0x63, 0x34, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, + 0x3b, 0x0a, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x6c, 0x6f, 0x77, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x5d, 0x5d, 0x2c, 0x0a, 0x09, 0x09, 0x46, 0x4f, 0x4f, 0x54, 0x45, 0x52, 0x20, 0x3d, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x50, 0x49, @@ -6481,16 +6485,65 @@ const unsigned char graphics_lua[] = 0x4f, 0x4f, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, 0x43, 0x41, 0x4e, 0x56, 0x41, 0x53, 0x2c, 0x0a, 0x09, 0x7d, 0x0a, + 0x09, 0x2d, 0x2d, 0x20, 0x55, 0x6e, 0x66, 0x6f, 0x72, 0x74, 0x75, 0x6e, 0x61, 0x74, 0x65, 0x6c, 0x79, 0x2c, + 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x20, 0x31, 0x2e, 0x32, 0x30, 0x20, 0x63, 0x61, 0x6e, 0x27, 0x74, 0x20, 0x64, + 0x65, 0x61, 0x6c, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x20, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x2e, 0x0a, + 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x74, + 0x72, 0x69, 0x70, 0x50, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x63, 0x6f, 0x64, 0x65, 0x29, 0x0a, + 0x09, 0x09, 0x2d, 0x2d, 0x20, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x20, 0x22, 0x70, 0x72, 0x65, 0x63, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x3b, 0x22, 0x2c, 0x20, 0x65, 0x74, 0x63, 0x2e, 0x0a, + 0x09, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62, + 0x28, 0x22, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x6c, 0x6f, + 0x77, 0x70, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x28, 0x25, 0x77, 0x2b, 0x29, 0x28, 0x25, 0x73, 0x2a, 0x29, 0x3b, + 0x22, 0x2c, 0x20, 0x22, 0x22, 0x29, 0x0a, + 0x09, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62, + 0x28, 0x22, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x6d, 0x65, + 0x64, 0x69, 0x75, 0x6d, 0x70, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x28, 0x25, 0x77, 0x2b, 0x29, 0x28, 0x25, 0x73, + 0x2a, 0x29, 0x3b, 0x22, 0x2c, 0x20, 0x22, 0x22, 0x29, 0x0a, + 0x09, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62, + 0x28, 0x22, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x68, 0x69, + 0x67, 0x68, 0x70, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x28, 0x25, 0x77, 0x2b, 0x29, 0x28, 0x25, 0x73, 0x2a, 0x29, + 0x3b, 0x22, 0x2c, 0x20, 0x22, 0x22, 0x29, 0x0a, + 0x09, 0x09, 0x2d, 0x2d, 0x20, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x72, 0x65, + 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x22, 0x6c, 0x6f, 0x77, 0x70, 0x22, 0x2c, 0x20, 0x22, 0x6d, + 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x22, 0x2c, 0x20, 0x22, 0x68, 0x69, 0x67, 0x68, 0x70, 0x22, 0x2e, 0x0a, + 0x09, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62, + 0x28, 0x22, 0x28, 0x25, 0x57, 0x2b, 0x29, 0x6c, 0x6f, 0x77, 0x70, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x22, 0x2c, + 0x20, 0x22, 0x25, 0x31, 0x22, 0x29, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5e, 0x6c, 0x6f, 0x77, 0x70, + 0x28, 0x25, 0x73, 0x2b, 0x29, 0x22, 0x2c, 0x20, 0x22, 0x22, 0x29, 0x0a, + 0x09, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62, + 0x28, 0x22, 0x28, 0x25, 0x57, 0x2b, 0x29, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x28, 0x25, 0x73, 0x2b, + 0x29, 0x22, 0x2c, 0x20, 0x22, 0x25, 0x31, 0x22, 0x29, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5e, 0x6d, + 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x22, 0x2c, 0x20, 0x22, 0x22, 0x29, 0x0a, + 0x09, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62, + 0x28, 0x22, 0x28, 0x25, 0x57, 0x2b, 0x29, 0x68, 0x69, 0x67, 0x68, 0x70, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x22, + 0x2c, 0x20, 0x22, 0x25, 0x31, 0x22, 0x29, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5e, 0x68, 0x69, 0x67, + 0x68, 0x70, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x22, 0x2c, 0x20, 0x22, 0x22, 0x29, 0x0a, + 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x29, 0x0a, + 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x20, 0x3d, 0x3d, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x20, + 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x09, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x73, 0x74, + 0x72, 0x69, 0x70, 0x50, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x76, 0x65, 0x72, 0x74, 0x65, + 0x78, 0x63, 0x6f, 0x64, 0x65, 0x29, 0x0a, + 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x53, 0x59, 0x4e, 0x54, 0x41, 0x58, 0x2c, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x2e, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x2c, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x55, 0x4e, 0x49, 0x46, 0x4f, 0x52, 0x4d, 0x53, 0x2c, 0x0a, - 0x09, 0x09, 0x09, 0x22, 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x31, 0x22, 0x2c, 0x0a, + 0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x20, 0x3d, 0x3d, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x45, 0x53, 0x20, + 0x61, 0x6e, 0x64, 0x20, 0x22, 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x31, 0x22, 0x20, 0x6f, 0x72, 0x20, 0x22, + 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x30, 0x22, 0x2c, 0x20, 0x2d, 0x2d, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x2e, 0x2e, 0x0a, 0x09, 0x09, 0x09, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x2e, 0x46, 0x4f, 0x4f, 0x54, 0x45, 0x52, 0x2c, 0x0a, @@ -6503,13 +6556,21 @@ const unsigned char graphics_lua[] = 0x65, 0x61, 0x74, 0x65, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x2c, 0x20, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x29, 0x0a, + 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x20, 0x3d, 0x3d, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x20, + 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x73, 0x74, 0x72, + 0x69, 0x70, 0x50, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, + 0x6f, 0x64, 0x65, 0x29, 0x0a, + 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x53, 0x59, 0x4e, 0x54, 0x41, 0x58, 0x2c, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x2e, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x2c, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x55, 0x4e, 0x49, 0x46, 0x4f, 0x52, 0x4d, 0x53, 0x2c, 0x0a, - 0x09, 0x09, 0x09, 0x22, 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x31, 0x22, 0x2c, 0x0a, + 0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x20, 0x3d, 0x3d, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x45, 0x53, 0x20, + 0x61, 0x6e, 0x64, 0x20, 0x22, 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x31, 0x22, 0x20, 0x6f, 0x72, 0x20, 0x22, + 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x2e, 0x46, 0x4f, 0x4f, @@ -6725,10 +6786,11 @@ const unsigned char graphics_lua[] = 0x70, 0x72, 0x6f, 0x6a, 0x20, 0x2a, 0x20, 0x76, 0x65, 0x72, 0x74, 0x70, 0x6f, 0x73, 0x3b, 0x09, 0x0a, 0x7d, 0x5d, 0x5d, 0x2c, 0x0a, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x20, 0x3d, 0x20, 0x5b, 0x5b, 0x0a, - 0x76, 0x65, 0x63, 0x34, 0x20, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x28, 0x76, 0x65, 0x63, 0x34, 0x20, 0x76, - 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2c, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, - 0x72, 0x65, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, - 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x70, 0x69, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x29, 0x20, 0x7b, 0x0a, + 0x6c, 0x6f, 0x77, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x28, 0x6c, + 0x6f, 0x77, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x76, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2c, 0x20, 0x49, + 0x6d, 0x61, 0x67, 0x65, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, + 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x70, 0x69, + 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x54, 0x65, 0x78, 0x65, 0x6c, 0x28, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x29, 0x20, 0x2a, 0x20, 0x76, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x09, 0x0a,