diff --git a/CMakeLists.txt b/CMakeLists.txt index 2386770d7..ec1459325 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1418,6 +1418,17 @@ set(LOVE_SRC_3P_WUFF add_library(love_3p_wuff ${LOVE_SRC_3P_WUFF}) +# +# xxHash +# + +set(LOVE_SRC_3P_XXHASH + src/libraries/xxHash/xxhash.c + src/libraries/xxHash/xxhash.h +) + +add_library(love_3p_xxhash ${LOVE_SRC_3P_XXHASH}) + set(LOVE_3P love_3p_box2d love_3p_ddsparse @@ -1429,6 +1440,7 @@ set(LOVE_3P love_3p_lz4 love_3p_noise1234 love_3p_wuff + love_3p_xxhash ) love_disable_warnings(love_3p_box2d love_3p_enet love_3p_luasocket) diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index 70c7975b7..f16045158 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -880,6 +880,9 @@ FA4943551D9DDFFE00E8D38A /* HashFunction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1DC2611C5D9555008F99A0 /* HashFunction.cpp */; }; FA4B66C91ABBCF1900558F15 /* Timer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4B66C81ABBCF1900558F15 /* Timer.cpp */; }; FA4B66CA1ABBCF1900558F15 /* Timer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4B66C81ABBCF1900558F15 /* Timer.cpp */; }; + FA4F2B791DE0125B00CA37D7 /* xxhash.c in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2B771DE0125B00CA37D7 /* xxhash.c */; }; + FA4F2B7A1DE0125B00CA37D7 /* xxhash.h in Headers */ = {isa = PBXBuildFile; fileRef = FA4F2B781DE0125B00CA37D7 /* xxhash.h */; }; + FA4F2B7B1DE0181B00CA37D7 /* xxhash.c in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2B771DE0125B00CA37D7 /* xxhash.c */; }; FA56D9BC1C208A0200D8D3C7 /* libmodplug.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA56D9BA1C2089EE00D8D3C7 /* libmodplug.a */; }; FA577AB016C7507900860150 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA577A7916C71A1700860150 /* Cocoa.framework */; }; FA577AC216C7512D00860150 /* FreeType.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA577A6716C719D900860150 /* FreeType.framework */; }; @@ -1575,6 +1578,8 @@ FA41A3C61C0A1F950084430C /* ASTCHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ASTCHandler.cpp; sourceTree = ""; }; FA41A3C71C0A1F950084430C /* ASTCHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTCHandler.h; sourceTree = ""; }; FA4B66C81ABBCF1900558F15 /* Timer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Timer.cpp; sourceTree = ""; }; + FA4F2B771DE0125B00CA37D7 /* xxhash.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xxhash.c; sourceTree = ""; }; + FA4F2B781DE0125B00CA37D7 /* xxhash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xxhash.h; sourceTree = ""; }; FA56D9BA1C2089EE00D8D3C7 /* libmodplug.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libmodplug.a; sourceTree = ""; }; FA577A6716C719D900860150 /* FreeType.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FreeType.framework; path = /Library/Frameworks/FreeType.framework; sourceTree = ""; }; FA577A6D16C719EA00860150 /* Lua.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Lua.framework; path = /Library/Frameworks/Lua.framework; sourceTree = ""; }; @@ -1783,6 +1788,7 @@ FA1557BE1CE90A2C00AFF582 /* tinyexr */, FA0B7A191A958EA3000E1D17 /* utf8 */, FA0B7A1F1A958EA3000E1D17 /* Wuff */, + FA4F2B761DE0125B00CA37D7 /* xxHash */, ); name = libraries; path = ../../src/libraries; @@ -2890,6 +2896,15 @@ path = theora; sourceTree = ""; }; + FA4F2B761DE0125B00CA37D7 /* xxHash */ = { + isa = PBXGroup; + children = ( + FA4F2B771DE0125B00CA37D7 /* xxhash.c */, + FA4F2B781DE0125B00CA37D7 /* xxhash.h */, + ); + path = xxHash; + sourceTree = ""; + }; FA56D9B91C2089CE00D8D3C7 /* modplug */ = { isa = PBXGroup; children = ( @@ -3286,6 +3301,7 @@ FA0B7E5C1A95902C000E1D17 /* wrap_MotorJoint.h in Headers */, FA0B7A7C1A958EA3000E1D17 /* b2Contact.h in Headers */, FA0B7A881A958EA3000E1D17 /* b2PolygonAndCircleContact.h in Headers */, + FA4F2B7A1DE0125B00CA37D7 /* xxhash.h in Headers */, FA0B7DDE1A95902C000E1D17 /* wrap_BezierCurve.h in Headers */, FA0B7DED1A95902C000E1D17 /* Cursor.h in Headers */, FA0B7E501A95902C000E1D17 /* wrap_Fixture.h in Headers */, @@ -3567,6 +3583,7 @@ FA0B7CD41A95902C000E1D17 /* Source.cpp in Sources */, FAA3A9AF1B7D465A00CED060 /* android.cpp in Sources */, FA0B7CD11A95902C000E1D17 /* Audio.cpp in Sources */, + FA4F2B7B1DE0181B00CA37D7 /* xxhash.c in Sources */, FA0B7D131A95902C000E1D17 /* Font.cpp in Sources */, FA0B7EC91A95902C000E1D17 /* threads.cpp in Sources */, FA0B7A781A958EA3000E1D17 /* b2CircleContact.cpp in Sources */, @@ -3894,6 +3911,7 @@ FA0B7DA51A95902C000E1D17 /* PNGHandler.cpp in Sources */, FA0B7B371A958EA3000E1D17 /* wuff_internal.c in Sources */, FA0B7E971A95902C000E1D17 /* Sound.cpp in Sources */, + FA4F2B791DE0125B00CA37D7 /* xxhash.c in Sources */, FA0B7E361A95902C000E1D17 /* WheelJoint.cpp in Sources */, FA0B7A471A958EA3000E1D17 /* b2PolygonShape.cpp in Sources */, FA0B7D8D1A95902C000E1D17 /* ddsHandler.cpp in Sources */, diff --git a/src/libraries/xxHash/xxhash.c b/src/libraries/xxHash/xxhash.c new file mode 100755 index 000000000..f74880b0d --- /dev/null +++ b/src/libraries/xxHash/xxhash.c @@ -0,0 +1,889 @@ +/* +* xxHash - Fast Hash algorithm +* Copyright (C) 2012-2016, Yann Collet +* +* BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* You can contact the author at : +* - xxHash homepage: http://www.xxhash.com +* - xxHash source repository : https://github.com/Cyan4973/xxHash +*/ + + +/* ************************************* +* Tuning parameters +***************************************/ +/*!XXH_FORCE_MEMORY_ACCESS : + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method doesn't depend on compiler but violate C standard. + * It can generate buggy code on targets which do not support unaligned memory accesses. + * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) + * See http://stackoverflow.com/a/32095106/646947 for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ +# if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# define XXH_FORCE_MEMORY_ACCESS 2 +# elif defined(__INTEL_COMPILER) || \ + (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) )) +# define XXH_FORCE_MEMORY_ACCESS 1 +# endif +#endif + +/*!XXH_ACCEPT_NULL_INPUT_POINTER : + * If the input pointer is a null pointer, xxHash default behavior is to trigger a memory access error, since it is a bad pointer. + * When this option is enabled, xxHash output for null input pointers will be the same as a null-length input. + * By default, this option is disabled. To enable it, uncomment below define : + */ +/* #define XXH_ACCEPT_NULL_INPUT_POINTER 1 */ + +/*!XXH_FORCE_NATIVE_FORMAT : + * By default, xxHash library provides endian-independent Hash values, based on little-endian convention. + * Results are therefore identical for little-endian and big-endian CPU. + * This comes at a performance cost for big-endian CPU, since some swapping is required to emulate little-endian format. + * Should endian-independence be of no importance for your application, you may set the #define below to 1, + * to improve speed for Big-endian CPU. + * This option has no impact on Little_Endian CPU. + */ +#ifndef XXH_FORCE_NATIVE_FORMAT /* can be defined externally */ +# define XXH_FORCE_NATIVE_FORMAT 0 +#endif + +/*!XXH_FORCE_ALIGN_CHECK : + * This is a minor performance trick, only useful with lots of very small keys. + * It means : check for aligned/unaligned input. + * The check costs one initial branch per hash; set to 0 when the input data + * is guaranteed to be aligned. + */ +#ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */ +# if defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64) +# define XXH_FORCE_ALIGN_CHECK 0 +# else +# define XXH_FORCE_ALIGN_CHECK 1 +# endif +#endif + + +/* ************************************* +* Includes & Memory related functions +***************************************/ +/*! Modify the local functions below should you wish to use some other memory routines +* for malloc(), free() */ +#include +static void* XXH_malloc(size_t s) { return malloc(s); } +static void XXH_free (void* p) { free(p); } +/*! and for memcpy() */ +#include +static void* XXH_memcpy(void* dest, const void* src, size_t size) { return memcpy(dest,src,size); } + +#define XXH_STATIC_LINKING_ONLY +#include "xxhash.h" + + +/* ************************************* +* Compiler Specific Options +***************************************/ +#ifdef _MSC_VER /* Visual Studio */ +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# define FORCE_INLINE static __forceinline +#else +# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define FORCE_INLINE static inline +# endif +# else +# define FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +#endif + + +/* ************************************* +* Basic Types +***************************************/ +#ifndef MEM_MODULE +# if !defined (__VMS) && (defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; + typedef int32_t S32; +# else + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; + typedef signed int S32; +# endif +#endif + +#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) + +/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */ +static U32 XXH_read32(const void* memPtr) { return *(const U32*) memPtr; } + +#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U32 u32; } __attribute__((packed)) unalign; +static U32 XXH_read32(const void* ptr) { return ((const unalign*)ptr)->u32; } + +#else + +/* portable and safe solution. Generally efficient. + * see : http://stackoverflow.com/a/32095106/646947 + */ +static U32 XXH_read32(const void* memPtr) +{ + U32 val; + memcpy(&val, memPtr, sizeof(val)); + return val; +} + +#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ + + +/* **************************************** +* Compiler-specific Functions and Macros +******************************************/ +#define XXH_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) + +/* Note : although _rotl exists for minGW (GCC under windows), performance seems poor */ +#if defined(_MSC_VER) +# define XXH_rotl32(x,r) _rotl(x,r) +# define XXH_rotl64(x,r) _rotl64(x,r) +#else +# define XXH_rotl32(x,r) ((x << r) | (x >> (32 - r))) +# define XXH_rotl64(x,r) ((x << r) | (x >> (64 - r))) +#endif + +#if defined(_MSC_VER) /* Visual Studio */ +# define XXH_swap32 _byteswap_ulong +#elif XXH_GCC_VERSION >= 403 +# define XXH_swap32 __builtin_bswap32 +#else +static U32 XXH_swap32 (U32 x) +{ + return ((x << 24) & 0xff000000 ) | + ((x << 8) & 0x00ff0000 ) | + ((x >> 8) & 0x0000ff00 ) | + ((x >> 24) & 0x000000ff ); +} +#endif + + +/* ************************************* +* Architecture Macros +***************************************/ +typedef enum { XXH_bigEndian=0, XXH_littleEndian=1 } XXH_endianess; + +/* XXH_CPU_LITTLE_ENDIAN can be defined externally, for example on the compiler command line */ +#ifndef XXH_CPU_LITTLE_ENDIAN + static const int g_one = 1; +# define XXH_CPU_LITTLE_ENDIAN (*(const char*)(&g_one)) +#endif + + +/* *************************** +* Memory reads +*****************************/ +typedef enum { XXH_aligned, XXH_unaligned } XXH_alignment; + +FORCE_INLINE U32 XXH_readLE32_align(const void* ptr, XXH_endianess endian, XXH_alignment align) +{ + if (align==XXH_unaligned) + return endian==XXH_littleEndian ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr)); + else + return endian==XXH_littleEndian ? *(const U32*)ptr : XXH_swap32(*(const U32*)ptr); +} + +FORCE_INLINE U32 XXH_readLE32(const void* ptr, XXH_endianess endian) +{ + return XXH_readLE32_align(ptr, endian, XXH_unaligned); +} + +static U32 XXH_readBE32(const void* ptr) +{ + return XXH_CPU_LITTLE_ENDIAN ? XXH_swap32(XXH_read32(ptr)) : XXH_read32(ptr); +} + + +/* ************************************* +* Macros +***************************************/ +#define XXH_STATIC_ASSERT(c) { enum { XXH_static_assert = 1/(int)(!!(c)) }; } /* use only *after* variable declarations */ +XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; } + + +/* ******************************************************************* +* 32-bits hash functions +*********************************************************************/ +static const U32 PRIME32_1 = 2654435761U; +static const U32 PRIME32_2 = 2246822519U; +static const U32 PRIME32_3 = 3266489917U; +static const U32 PRIME32_4 = 668265263U; +static const U32 PRIME32_5 = 374761393U; + +static U32 XXH32_round(U32 seed, U32 input) +{ + seed += input * PRIME32_2; + seed = XXH_rotl32(seed, 13); + seed *= PRIME32_1; + return seed; +} + +FORCE_INLINE U32 XXH32_endian_align(const void* input, size_t len, U32 seed, XXH_endianess endian, XXH_alignment align) +{ + const BYTE* p = (const BYTE*)input; + const BYTE* bEnd = p + len; + U32 h32; +#define XXH_get32bits(p) XXH_readLE32_align(p, endian, align) + +#ifdef XXH_ACCEPT_NULL_INPUT_POINTER + if (p==NULL) { + len=0; + bEnd=p=(const BYTE*)(size_t)16; + } +#endif + + if (len>=16) { + const BYTE* const limit = bEnd - 16; + U32 v1 = seed + PRIME32_1 + PRIME32_2; + U32 v2 = seed + PRIME32_2; + U32 v3 = seed + 0; + U32 v4 = seed - PRIME32_1; + + do { + v1 = XXH32_round(v1, XXH_get32bits(p)); p+=4; + v2 = XXH32_round(v2, XXH_get32bits(p)); p+=4; + v3 = XXH32_round(v3, XXH_get32bits(p)); p+=4; + v4 = XXH32_round(v4, XXH_get32bits(p)); p+=4; + } while (p<=limit); + + h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7) + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18); + } else { + h32 = seed + PRIME32_5; + } + + h32 += (U32) len; + + while (p+4<=bEnd) { + h32 += XXH_get32bits(p) * PRIME32_3; + h32 = XXH_rotl32(h32, 17) * PRIME32_4 ; + p+=4; + } + + while (p> 15; + h32 *= PRIME32_2; + h32 ^= h32 >> 13; + h32 *= PRIME32_3; + h32 ^= h32 >> 16; + + return h32; +} + + +XXH_PUBLIC_API unsigned int XXH32 (const void* input, size_t len, unsigned int seed) +{ +#if 0 + /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ + XXH32_state_t state; + XXH32_reset(&state, seed); + XXH32_update(&state, input, len); + return XXH32_digest(&state); +#else + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if (XXH_FORCE_ALIGN_CHECK) { + if ((((size_t)input) & 3) == 0) { /* Input is 4-bytes aligned, leverage the speed benefit */ + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned); + else + return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned); + } } + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned); + else + return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned); +#endif +} + + + +/*====== Hash streaming ======*/ + +XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void) +{ + return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t)); +} +XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr) +{ + XXH_free(statePtr); + return XXH_OK; +} + +XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState) +{ + memcpy(dstState, srcState, sizeof(*dstState)); +} + +XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, unsigned int seed) +{ + XXH32_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ + memset(&state, 0, sizeof(state)-4); /* do not write into reserved, for future removal */ + state.v1 = seed + PRIME32_1 + PRIME32_2; + state.v2 = seed + PRIME32_2; + state.v3 = seed + 0; + state.v4 = seed - PRIME32_1; + memcpy(statePtr, &state, sizeof(state)); + return XXH_OK; +} + + +FORCE_INLINE XXH_errorcode XXH32_update_endian (XXH32_state_t* state, const void* input, size_t len, XXH_endianess endian) +{ + const BYTE* p = (const BYTE*)input; + const BYTE* const bEnd = p + len; + +#ifdef XXH_ACCEPT_NULL_INPUT_POINTER + if (input==NULL) return XXH_ERROR; +#endif + + state->total_len_32 += (unsigned)len; + state->large_len |= (len>=16) | (state->total_len_32>=16); + + if (state->memsize + len < 16) { /* fill in tmp buffer */ + XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, len); + state->memsize += (unsigned)len; + return XXH_OK; + } + + if (state->memsize) { /* some data left from previous update */ + XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, 16-state->memsize); + { const U32* p32 = state->mem32; + state->v1 = XXH32_round(state->v1, XXH_readLE32(p32, endian)); p32++; + state->v2 = XXH32_round(state->v2, XXH_readLE32(p32, endian)); p32++; + state->v3 = XXH32_round(state->v3, XXH_readLE32(p32, endian)); p32++; + state->v4 = XXH32_round(state->v4, XXH_readLE32(p32, endian)); p32++; + } + p += 16-state->memsize; + state->memsize = 0; + } + + if (p <= bEnd-16) { + const BYTE* const limit = bEnd - 16; + U32 v1 = state->v1; + U32 v2 = state->v2; + U32 v3 = state->v3; + U32 v4 = state->v4; + + do { + v1 = XXH32_round(v1, XXH_readLE32(p, endian)); p+=4; + v2 = XXH32_round(v2, XXH_readLE32(p, endian)); p+=4; + v3 = XXH32_round(v3, XXH_readLE32(p, endian)); p+=4; + v4 = XXH32_round(v4, XXH_readLE32(p, endian)); p+=4; + } while (p<=limit); + + state->v1 = v1; + state->v2 = v2; + state->v3 = v3; + state->v4 = v4; + } + + if (p < bEnd) { + XXH_memcpy(state->mem32, p, (size_t)(bEnd-p)); + state->memsize = (unsigned)(bEnd-p); + } + + return XXH_OK; +} + +XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* state_in, const void* input, size_t len) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_update_endian(state_in, input, len, XXH_littleEndian); + else + return XXH32_update_endian(state_in, input, len, XXH_bigEndian); +} + + + +FORCE_INLINE U32 XXH32_digest_endian (const XXH32_state_t* state, XXH_endianess endian) +{ + const BYTE * p = (const BYTE*)state->mem32; + const BYTE* const bEnd = (const BYTE*)(state->mem32) + state->memsize; + U32 h32; + + if (state->large_len) { + h32 = XXH_rotl32(state->v1, 1) + XXH_rotl32(state->v2, 7) + XXH_rotl32(state->v3, 12) + XXH_rotl32(state->v4, 18); + } else { + h32 = state->v3 /* == seed */ + PRIME32_5; + } + + h32 += state->total_len_32; + + while (p+4<=bEnd) { + h32 += XXH_readLE32(p, endian) * PRIME32_3; + h32 = XXH_rotl32(h32, 17) * PRIME32_4; + p+=4; + } + + while (p> 15; + h32 *= PRIME32_2; + h32 ^= h32 >> 13; + h32 *= PRIME32_3; + h32 ^= h32 >> 16; + + return h32; +} + + +XXH_PUBLIC_API unsigned int XXH32_digest (const XXH32_state_t* state_in) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_digest_endian(state_in, XXH_littleEndian); + else + return XXH32_digest_endian(state_in, XXH_bigEndian); +} + + +/*====== Canonical representation ======*/ + +/*! Default XXH result types are basic unsigned 32 and 64 bits. +* The canonical representation follows human-readable write convention, aka big-endian (large digits first). +* These functions allow transformation of hash result into and from its canonical format. +* This way, hash values can be written into a file or buffer, and remain comparable across different systems and programs. +*/ + +XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash) +{ + XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t)); + if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash); + memcpy(dst, &hash, sizeof(*dst)); +} + +XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src) +{ + return XXH_readBE32(src); +} + + +#ifndef XXH_NO_LONG_LONG + +/* ******************************************************************* +* 64-bits hash functions +*********************************************************************/ + +/*====== Memory access ======*/ + +#ifndef MEM_MODULE +# define MEM_MODULE +# if !defined (__VMS) && (defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + typedef uint64_t U64; +# else + typedef unsigned long long U64; /* if your compiler doesn't support unsigned long long, replace by another 64-bit type here. Note that xxhash.h will also need to be updated. */ +# endif +#endif + + +#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) + +/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */ +static U64 XXH_read64(const void* memPtr) { return *(const U64*) memPtr; } + +#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U32 u32; U64 u64; } __attribute__((packed)) unalign64; +static U64 XXH_read64(const void* ptr) { return ((const unalign64*)ptr)->u64; } + +#else + +/* portable and safe solution. Generally efficient. + * see : http://stackoverflow.com/a/32095106/646947 + */ + +static U64 XXH_read64(const void* memPtr) +{ + U64 val; + memcpy(&val, memPtr, sizeof(val)); + return val; +} + +#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ + +#if defined(_MSC_VER) /* Visual Studio */ +# define XXH_swap64 _byteswap_uint64 +#elif XXH_GCC_VERSION >= 403 +# define XXH_swap64 __builtin_bswap64 +#else +static U64 XXH_swap64 (U64 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); +} +#endif + +FORCE_INLINE U64 XXH_readLE64_align(const void* ptr, XXH_endianess endian, XXH_alignment align) +{ + if (align==XXH_unaligned) + return endian==XXH_littleEndian ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr)); + else + return endian==XXH_littleEndian ? *(const U64*)ptr : XXH_swap64(*(const U64*)ptr); +} + +FORCE_INLINE U64 XXH_readLE64(const void* ptr, XXH_endianess endian) +{ + return XXH_readLE64_align(ptr, endian, XXH_unaligned); +} + +static U64 XXH_readBE64(const void* ptr) +{ + return XXH_CPU_LITTLE_ENDIAN ? XXH_swap64(XXH_read64(ptr)) : XXH_read64(ptr); +} + + +/*====== xxh64 ======*/ + +static const U64 PRIME64_1 = 11400714785074694791ULL; +static const U64 PRIME64_2 = 14029467366897019727ULL; +static const U64 PRIME64_3 = 1609587929392839161ULL; +static const U64 PRIME64_4 = 9650029242287828579ULL; +static const U64 PRIME64_5 = 2870177450012600261ULL; + +static U64 XXH64_round(U64 acc, U64 input) +{ + acc += input * PRIME64_2; + acc = XXH_rotl64(acc, 31); + acc *= PRIME64_1; + return acc; +} + +static U64 XXH64_mergeRound(U64 acc, U64 val) +{ + val = XXH64_round(0, val); + acc ^= val; + acc = acc * PRIME64_1 + PRIME64_4; + return acc; +} + +FORCE_INLINE U64 XXH64_endian_align(const void* input, size_t len, U64 seed, XXH_endianess endian, XXH_alignment align) +{ + const BYTE* p = (const BYTE*)input; + const BYTE* bEnd = p + len; + U64 h64; +#define XXH_get64bits(p) XXH_readLE64_align(p, endian, align) + +#ifdef XXH_ACCEPT_NULL_INPUT_POINTER + if (p==NULL) { + len=0; + bEnd=p=(const BYTE*)(size_t)32; + } +#endif + + if (len>=32) { + const BYTE* const limit = bEnd - 32; + U64 v1 = seed + PRIME64_1 + PRIME64_2; + U64 v2 = seed + PRIME64_2; + U64 v3 = seed + 0; + U64 v4 = seed - PRIME64_1; + + do { + v1 = XXH64_round(v1, XXH_get64bits(p)); p+=8; + v2 = XXH64_round(v2, XXH_get64bits(p)); p+=8; + v3 = XXH64_round(v3, XXH_get64bits(p)); p+=8; + v4 = XXH64_round(v4, XXH_get64bits(p)); p+=8; + } while (p<=limit); + + h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18); + h64 = XXH64_mergeRound(h64, v1); + h64 = XXH64_mergeRound(h64, v2); + h64 = XXH64_mergeRound(h64, v3); + h64 = XXH64_mergeRound(h64, v4); + + } else { + h64 = seed + PRIME64_5; + } + + h64 += (U64) len; + + while (p+8<=bEnd) { + U64 const k1 = XXH64_round(0, XXH_get64bits(p)); + h64 ^= k1; + h64 = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; + p+=8; + } + + if (p+4<=bEnd) { + h64 ^= (U64)(XXH_get32bits(p)) * PRIME64_1; + h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3; + p+=4; + } + + while (p> 33; + h64 *= PRIME64_2; + h64 ^= h64 >> 29; + h64 *= PRIME64_3; + h64 ^= h64 >> 32; + + return h64; +} + + +XXH_PUBLIC_API unsigned long long XXH64 (const void* input, size_t len, unsigned long long seed) +{ +#if 0 + /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ + XXH64_state_t state; + XXH64_reset(&state, seed); + XXH64_update(&state, input, len); + return XXH64_digest(&state); +#else + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if (XXH_FORCE_ALIGN_CHECK) { + if ((((size_t)input) & 7)==0) { /* Input is aligned, let's leverage the speed advantage */ + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned); + else + return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned); + } } + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned); + else + return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned); +#endif +} + +/*====== Hash Streaming ======*/ + +XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void) +{ + return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t)); +} +XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr) +{ + XXH_free(statePtr); + return XXH_OK; +} + +XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dstState, const XXH64_state_t* srcState) +{ + memcpy(dstState, srcState, sizeof(*dstState)); +} + +XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, unsigned long long seed) +{ + XXH64_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ + memset(&state, 0, sizeof(state)-8); /* do not write into reserved, for future removal */ + state.v1 = seed + PRIME64_1 + PRIME64_2; + state.v2 = seed + PRIME64_2; + state.v3 = seed + 0; + state.v4 = seed - PRIME64_1; + memcpy(statePtr, &state, sizeof(state)); + return XXH_OK; +} + +FORCE_INLINE XXH_errorcode XXH64_update_endian (XXH64_state_t* state, const void* input, size_t len, XXH_endianess endian) +{ + const BYTE* p = (const BYTE*)input; + const BYTE* const bEnd = p + len; + +#ifdef XXH_ACCEPT_NULL_INPUT_POINTER + if (input==NULL) return XXH_ERROR; +#endif + + state->total_len += len; + + if (state->memsize + len < 32) { /* fill in tmp buffer */ + XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, len); + state->memsize += (U32)len; + return XXH_OK; + } + + if (state->memsize) { /* tmp buffer is full */ + XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, 32-state->memsize); + state->v1 = XXH64_round(state->v1, XXH_readLE64(state->mem64+0, endian)); + state->v2 = XXH64_round(state->v2, XXH_readLE64(state->mem64+1, endian)); + state->v3 = XXH64_round(state->v3, XXH_readLE64(state->mem64+2, endian)); + state->v4 = XXH64_round(state->v4, XXH_readLE64(state->mem64+3, endian)); + p += 32-state->memsize; + state->memsize = 0; + } + + if (p+32 <= bEnd) { + const BYTE* const limit = bEnd - 32; + U64 v1 = state->v1; + U64 v2 = state->v2; + U64 v3 = state->v3; + U64 v4 = state->v4; + + do { + v1 = XXH64_round(v1, XXH_readLE64(p, endian)); p+=8; + v2 = XXH64_round(v2, XXH_readLE64(p, endian)); p+=8; + v3 = XXH64_round(v3, XXH_readLE64(p, endian)); p+=8; + v4 = XXH64_round(v4, XXH_readLE64(p, endian)); p+=8; + } while (p<=limit); + + state->v1 = v1; + state->v2 = v2; + state->v3 = v3; + state->v4 = v4; + } + + if (p < bEnd) { + XXH_memcpy(state->mem64, p, (size_t)(bEnd-p)); + state->memsize = (unsigned)(bEnd-p); + } + + return XXH_OK; +} + +XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* state_in, const void* input, size_t len) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_update_endian(state_in, input, len, XXH_littleEndian); + else + return XXH64_update_endian(state_in, input, len, XXH_bigEndian); +} + +FORCE_INLINE U64 XXH64_digest_endian (const XXH64_state_t* state, XXH_endianess endian) +{ + const BYTE * p = (const BYTE*)state->mem64; + const BYTE* const bEnd = (const BYTE*)state->mem64 + state->memsize; + U64 h64; + + if (state->total_len >= 32) { + U64 const v1 = state->v1; + U64 const v2 = state->v2; + U64 const v3 = state->v3; + U64 const v4 = state->v4; + + h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18); + h64 = XXH64_mergeRound(h64, v1); + h64 = XXH64_mergeRound(h64, v2); + h64 = XXH64_mergeRound(h64, v3); + h64 = XXH64_mergeRound(h64, v4); + } else { + h64 = state->v3 + PRIME64_5; + } + + h64 += (U64) state->total_len; + + while (p+8<=bEnd) { + U64 const k1 = XXH64_round(0, XXH_readLE64(p, endian)); + h64 ^= k1; + h64 = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; + p+=8; + } + + if (p+4<=bEnd) { + h64 ^= (U64)(XXH_readLE32(p, endian)) * PRIME64_1; + h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3; + p+=4; + } + + while (p> 33; + h64 *= PRIME64_2; + h64 ^= h64 >> 29; + h64 *= PRIME64_3; + h64 ^= h64 >> 32; + + return h64; +} + +XXH_PUBLIC_API unsigned long long XXH64_digest (const XXH64_state_t* state_in) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_digest_endian(state_in, XXH_littleEndian); + else + return XXH64_digest_endian(state_in, XXH_bigEndian); +} + + +/*====== Canonical representation ======*/ + +XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash) +{ + XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t)); + if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash); + memcpy(dst, &hash, sizeof(*dst)); +} + +XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src) +{ + return XXH_readBE64(src); +} + +#endif /* XXH_NO_LONG_LONG */ diff --git a/src/libraries/xxHash/xxhash.h b/src/libraries/xxHash/xxhash.h new file mode 100755 index 000000000..9d831e03b --- /dev/null +++ b/src/libraries/xxHash/xxhash.h @@ -0,0 +1,293 @@ +/* + xxHash - Extremely Fast Hash algorithm + Header File + Copyright (C) 2012-2016, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - xxHash source repository : https://github.com/Cyan4973/xxHash +*/ + +/* Notice extracted from xxHash homepage : + +xxHash is an extremely fast Hash algorithm, running at RAM speed limits. +It also successfully passes all tests from the SMHasher suite. + +Comparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo @3GHz) + +Name Speed Q.Score Author +xxHash 5.4 GB/s 10 +CrapWow 3.2 GB/s 2 Andrew +MumurHash 3a 2.7 GB/s 10 Austin Appleby +SpookyHash 2.0 GB/s 10 Bob Jenkins +SBox 1.4 GB/s 9 Bret Mulvey +Lookup3 1.2 GB/s 9 Bob Jenkins +SuperFastHash 1.2 GB/s 1 Paul Hsieh +CityHash64 1.05 GB/s 10 Pike & Alakuijala +FNV 0.55 GB/s 5 Fowler, Noll, Vo +CRC32 0.43 GB/s 9 +MD5-32 0.33 GB/s 10 Ronald L. Rivest +SHA1-32 0.28 GB/s 10 + +Q.Score is a measure of quality of the hash function. +It depends on successfully passing SMHasher test set. +10 is a perfect score. + +A 64-bits version, named XXH64, is available since r35. +It offers much better speed, but for 64-bits applications only. +Name Speed on 64 bits Speed on 32 bits +XXH64 13.8 GB/s 1.9 GB/s +XXH32 6.8 GB/s 6.0 GB/s +*/ + +#ifndef XXHASH_H_5627135585666179 +#define XXHASH_H_5627135585666179 1 + +#if defined (__cplusplus) +extern "C" { +#endif + + +/* **************************** +* Definitions +******************************/ +#include /* size_t */ +typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode; + + +/* **************************** +* API modifier +******************************/ +/** XXH_PRIVATE_API +* This is useful to include xxhash functions in `static` mode +* in order to inline them, and remove their symbol from the public list. +* Methodology : +* #define XXH_PRIVATE_API +* #include "xxhash.h" +* `xxhash.c` is automatically included. +* It's not useful to compile and link it as a separate module. +*/ +#ifdef XXH_PRIVATE_API +# ifndef XXH_STATIC_LINKING_ONLY +# define XXH_STATIC_LINKING_ONLY +# endif +# if defined(__GNUC__) +# define XXH_PUBLIC_API static __inline __attribute__((unused)) +# elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# define XXH_PUBLIC_API static inline +# elif defined(_MSC_VER) +# define XXH_PUBLIC_API static __inline +# else +# define XXH_PUBLIC_API static /* this version may generate warnings for unused static functions; disable the relevant warning */ +# endif +#else +# define XXH_PUBLIC_API /* do nothing */ +#endif /* XXH_PRIVATE_API */ + +/*!XXH_NAMESPACE, aka Namespace Emulation : + +If you want to include _and expose_ xxHash functions from within your own library, +but also want to avoid symbol collisions with other libraries which may also include xxHash, + +you can use XXH_NAMESPACE, to automatically prefix any public symbol from xxhash library +with the value of XXH_NAMESPACE (therefore, avoid NULL and numeric values). + +Note that no change is required within the calling program as long as it includes `xxhash.h` : +regular symbol name will be automatically translated by this header. +*/ +#ifdef XXH_NAMESPACE +# define XXH_CAT(A,B) A##B +# define XXH_NAME2(A,B) XXH_CAT(A,B) +# define XXH_versionNumber XXH_NAME2(XXH_NAMESPACE, XXH_versionNumber) +# define XXH32 XXH_NAME2(XXH_NAMESPACE, XXH32) +# define XXH32_createState XXH_NAME2(XXH_NAMESPACE, XXH32_createState) +# define XXH32_freeState XXH_NAME2(XXH_NAMESPACE, XXH32_freeState) +# define XXH32_reset XXH_NAME2(XXH_NAMESPACE, XXH32_reset) +# define XXH32_update XXH_NAME2(XXH_NAMESPACE, XXH32_update) +# define XXH32_digest XXH_NAME2(XXH_NAMESPACE, XXH32_digest) +# define XXH32_copyState XXH_NAME2(XXH_NAMESPACE, XXH32_copyState) +# define XXH32_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH32_canonicalFromHash) +# define XXH32_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH32_hashFromCanonical) +# define XXH64 XXH_NAME2(XXH_NAMESPACE, XXH64) +# define XXH64_createState XXH_NAME2(XXH_NAMESPACE, XXH64_createState) +# define XXH64_freeState XXH_NAME2(XXH_NAMESPACE, XXH64_freeState) +# define XXH64_reset XXH_NAME2(XXH_NAMESPACE, XXH64_reset) +# define XXH64_update XXH_NAME2(XXH_NAMESPACE, XXH64_update) +# define XXH64_digest XXH_NAME2(XXH_NAMESPACE, XXH64_digest) +# define XXH64_copyState XXH_NAME2(XXH_NAMESPACE, XXH64_copyState) +# define XXH64_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH64_canonicalFromHash) +# define XXH64_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH64_hashFromCanonical) +#endif + + +/* ************************************* +* Version +***************************************/ +#define XXH_VERSION_MAJOR 0 +#define XXH_VERSION_MINOR 6 +#define XXH_VERSION_RELEASE 2 +#define XXH_VERSION_NUMBER (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE) +XXH_PUBLIC_API unsigned XXH_versionNumber (void); + + +/*-********************************************************************** +* 32-bits hash +************************************************************************/ +typedef unsigned int XXH32_hash_t; + +/*! XXH32() : + Calculate the 32-bits hash of sequence "length" bytes stored at memory address "input". + The memory between input & input+length must be valid (allocated and read-accessible). + "seed" can be used to alter the result predictably. + Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark) : 5.4 GB/s */ +XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t length, unsigned int seed); + +/*====== Streaming ======*/ +typedef struct XXH32_state_s XXH32_state_t; /* incomplete type */ +XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void); +XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr); +XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dst_state, const XXH32_state_t* src_state); + +XXH_PUBLIC_API XXH_errorcode XXH32_reset (XXH32_state_t* statePtr, unsigned int seed); +XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void* input, size_t length); +XXH_PUBLIC_API XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr); + +/* +These functions generate the xxHash of an input provided in multiple segments. +Note that, for small input, they are slower than single-call functions, due to state management. +For small input, prefer `XXH32()` and `XXH64()` . + +XXH state must first be allocated, using XXH*_createState() . + +Start a new hash by initializing state with a seed, using XXH*_reset(). + +Then, feed the hash state by calling XXH*_update() as many times as necessary. +Obviously, input must be allocated and read accessible. +The function returns an error code, with 0 meaning OK, and any other value meaning there is an error. + +Finally, a hash value can be produced anytime, by using XXH*_digest(). +This function returns the nn-bits hash as an int or long long. + +It's still possible to continue inserting input into the hash state after a digest, +and generate some new hashes later on, by calling again XXH*_digest(). + +When done, free XXH state space if it was allocated dynamically. +*/ + +/*====== Canonical representation ======*/ + +typedef struct { unsigned char digest[4]; } XXH32_canonical_t; +XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash); +XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src); + +/* Default result type for XXH functions are primitive unsigned 32 and 64 bits. +* The canonical representation uses human-readable write convention, aka big-endian (large digits first). +* These functions allow transformation of hash result into and from its canonical format. +* This way, hash values can be written into a file / memory, and remain comparable on different systems and programs. +*/ + + +#ifndef XXH_NO_LONG_LONG +/*-********************************************************************** +* 64-bits hash +************************************************************************/ +typedef unsigned long long XXH64_hash_t; + +/*! XXH64() : + Calculate the 64-bits hash of sequence of length "len" stored at memory address "input". + "seed" can be used to alter the result predictably. + This function runs faster on 64-bits systems, but slower on 32-bits systems (see benchmark). +*/ +XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t length, unsigned long long seed); + +/*====== Streaming ======*/ +typedef struct XXH64_state_s XXH64_state_t; /* incomplete type */ +XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void); +XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr); +XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dst_state, const XXH64_state_t* src_state); + +XXH_PUBLIC_API XXH_errorcode XXH64_reset (XXH64_state_t* statePtr, unsigned long long seed); +XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* statePtr, const void* input, size_t length); +XXH_PUBLIC_API XXH64_hash_t XXH64_digest (const XXH64_state_t* statePtr); + +/*====== Canonical representation ======*/ +typedef struct { unsigned char digest[8]; } XXH64_canonical_t; +XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash); +XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src); +#endif /* XXH_NO_LONG_LONG */ + + +#ifdef XXH_STATIC_LINKING_ONLY + +/* ================================================================================================ + This section contains definitions which are not guaranteed to remain stable. + They may change in future versions, becoming incompatible with a different version of the library. + They shall only be used with static linking. + Never use these definitions in association with dynamic linking ! +=================================================================================================== */ + +/* These definitions are only meant to make possible + static allocation of XXH state, on stack or in a struct for example. + Never use members directly. */ + +struct XXH32_state_s { + unsigned total_len_32; + unsigned large_len; + unsigned v1; + unsigned v2; + unsigned v3; + unsigned v4; + unsigned mem32[4]; /* buffer defined as U32 for alignment */ + unsigned memsize; + unsigned reserved; /* never read nor write, will be removed in a future version */ +}; /* typedef'd to XXH32_state_t */ + +#ifndef XXH_NO_LONG_LONG /* remove 64-bits support */ +struct XXH64_state_s { + unsigned long long total_len; + unsigned long long v1; + unsigned long long v2; + unsigned long long v3; + unsigned long long v4; + unsigned long long mem64[4]; /* buffer defined as U64 for alignment */ + unsigned memsize; + unsigned reserved[2]; /* never read nor write, will be removed in a future version */ +}; /* typedef'd to XXH64_state_t */ +#endif + +#ifdef XXH_PRIVATE_API +# include "xxhash.c" /* include xxhash function bodies as `static`, for inlining */ +#endif + +#endif /* XXH_STATIC_LINKING_ONLY */ + + +#if defined (__cplusplus) +} +#endif + +#endif /* XXHASH_H_5627135585666179 */ diff --git a/src/modules/event/sdl/Event.cpp b/src/modules/event/sdl/Event.cpp index 50de0a7a4..7d38818cc 100644 --- a/src/modules/event/sdl/Event.cpp +++ b/src/modules/event/sdl/Event.cpp @@ -112,6 +112,8 @@ Event::~Event() void Event::pump() { + exceptionIfInRenderPass(); + SDL_Event e; while (SDL_PollEvent(&e)) @@ -127,6 +129,8 @@ void Event::pump() Message *Event::wait() { + exceptionIfInRenderPass(); + SDL_Event e; if (SDL_WaitEvent(&e) != 1) @@ -137,6 +141,8 @@ Message *Event::wait() void Event::clear() { + exceptionIfInRenderPass(); + SDL_Event e; while (SDL_PollEvent(&e)) @@ -147,6 +153,17 @@ void Event::clear() love::event::Event::clear(); } +void Event::exceptionIfInRenderPass() +{ + // Some core OS graphics functionality (e.g. swap buffers on some platforms) + // happens inside SDL_PumpEvents - which is called by SDL_PollEvent and + // friends. It's probably a bad idea to call those functions while we're in + // a render pass. + auto gfx = Module::getInstance(Module::M_GRAPHICS); + if (gfx != nullptr && gfx->isPassActive()) + throw love::Exception("Cannot call this function while a render pass is active in love.graphics."); +} + Message *Event::convert(const SDL_Event &e) const { Message *msg = nullptr; diff --git a/src/modules/event/sdl/Event.h b/src/modules/event/sdl/Event.h index 9ab44174e..978dd4566 100644 --- a/src/modules/event/sdl/Event.h +++ b/src/modules/event/sdl/Event.h @@ -68,6 +68,8 @@ public: private: + void exceptionIfInRenderPass(); + Message *convert(const SDL_Event &e) const; Message *convertJoystickEvent(const SDL_Event &e) const; Message *convertWindowEvent(const SDL_Event &e) const; diff --git a/src/modules/event/wrap_Event.cpp b/src/modules/event/wrap_Event.cpp index 06b9686f6..908000032 100644 --- a/src/modules/event/wrap_Event.cpp +++ b/src/modules/event/wrap_Event.cpp @@ -53,15 +53,16 @@ int w_poll(lua_State *L) return 1; } -int w_pump(lua_State *) +int w_pump(lua_State *L) { - instance()->pump(); + luax_catchexcept(L, [&]() { instance()->pump(); }); return 0; } int w_wait(lua_State *L) { - Message *m = instance()->wait(); + Message *m = nullptr; + luax_catchexcept(L, [&]() { m = instance()->wait(); }); if (m) { int args = m->toLua(L); @@ -85,9 +86,9 @@ int w_push(lua_State *L) return 1; } -int w_clear(lua_State *) +int w_clear(lua_State *L) { - instance()->clear(); + luax_catchexcept(L, [&]() { instance()->clear(); }); return 0; } diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 06e55db5e..cc713e2e4 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -34,6 +34,8 @@ namespace love namespace graphics { +const int MAX_COLOR_RENDER_TARGETS = 16; + /** * Globally sets whether gamma correction is enabled. Ideally this should be set * prior to using any Graphics module function. @@ -59,6 +61,14 @@ void gammaCorrectColor(Colorf &c); **/ void unGammaCorrectColor(Colorf &c); +class RenderOutsidePassException : public love::Exception +{ +public: + RenderOutsidePassException() + : Exception("Cannot draw outside of a render pass!") + {} +}; + class Graphics : public Module { public: @@ -178,7 +188,7 @@ public: struct Stats { int drawCalls; - int canvasSwitches; + int renderPasses; int shaderSwitches; int canvases; int images; @@ -257,6 +267,8 @@ public: **/ virtual bool isActive() const = 0; + virtual bool isPassActive() const = 0; + static bool getConstant(const char *in, DrawMode &out); static bool getConstant(DrawMode in, const char *&out); diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp index 51d80bbc3..ffafa4182 100644 --- a/src/modules/graphics/opengl/Canvas.cpp +++ b/src/modules/graphics/opengl/Canvas.cpp @@ -36,11 +36,10 @@ namespace opengl static GLenum createFBO(GLuint &framebuffer, GLuint texture) { // get currently bound fbo to reset to it later - GLint current_fbo; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); + GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); glGenFramebuffers(1, &framebuffer); - gl.bindFramebuffer(GL_FRAMEBUFFER, framebuffer); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, framebuffer); if (texture != 0) { @@ -53,14 +52,19 @@ static GLenum createFBO(GLuint &framebuffer, GLuint texture) GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - // unbind framebuffer - gl.bindFramebuffer(GL_FRAMEBUFFER, (GLuint) current_fbo); - + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); return status; } -static GLenum createMSAABuffer(int width, int height, int &samples, GLenum iformat, GLuint &buffer) +static bool createMSAABuffer(int width, int height, int &samples, GLenum iformat, GLuint &buffer) { + GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); + + // Temporary FBO used to clear the renderbuffer. + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); + glGenRenderbuffers(1, &buffer); glBindRenderbuffer(GL_RENDERBUFFER, buffer); @@ -73,7 +77,7 @@ static GLenum createMSAABuffer(int width, int height, int &samples, GLenum iform GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status == GL_FRAMEBUFFER_COMPLETE) + if (status == GL_FRAMEBUFFER_COMPLETE && samples > 1) { // Initialize the buffer to transparent black. glClearColor(0.0f, 0.0f, 0.0f, 0.0f); @@ -83,22 +87,21 @@ static GLenum createMSAABuffer(int width, int height, int &samples, GLenum iform { glDeleteRenderbuffers(1, &buffer); buffer = 0; + samples = 0; } - return status; + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); + gl.deleteFramebuffer(fbo); + + return status == GL_FRAMEBUFFER_COMPLETE && samples > 1; } -Canvas *Canvas::current = nullptr; -OpenGL::Viewport Canvas::systemViewport = OpenGL::Viewport(); -bool Canvas::screenHasSRGB = false; int Canvas::canvasCount = 0; Canvas::Canvas(int width, int height, Format format, int msaa) : fbo(0) - , resolve_fbo(0) , texture(0) , msaa_buffer(0) - , depth_stencil(0) , format(format) , requested_samples(msaa) , actual_samples(0) @@ -143,65 +146,15 @@ Canvas::Canvas(int width, int height, Format format, int msaa) Canvas::~Canvas() { --canvasCount; - - // reset framebuffer if still using this one - if (current == this) - stopGrab(); - unloadVolatile(); } -bool Canvas::createMSAAFBO(GLenum internalformat) -{ - actual_samples = requested_samples; - - if (actual_samples <= 1) - { - actual_samples = 0; - return false; - } - - // Create our FBO without a texture. - status = createFBO(fbo, 0); - - GLuint previous = gl.getDefaultFBO(); - if (current != this) - { - if (current != nullptr) - previous = current->fbo; - - gl.bindFramebuffer(GL_FRAMEBUFFER, fbo); - } - - // Create and attach the MSAA buffer for our FBO. - status = createMSAABuffer(width, height, actual_samples, internalformat, msaa_buffer); - - // Create the FBO used for the MSAA resolve, and attach the texture. - if (status == GL_FRAMEBUFFER_COMPLETE) - status = createFBO(resolve_fbo, texture); - - if (status != GL_FRAMEBUFFER_COMPLETE) - { - // Clean up. - glDeleteFramebuffers(1, &fbo); - glDeleteFramebuffers(1, &resolve_fbo); - glDeleteRenderbuffers(1, &msaa_buffer); - fbo = msaa_buffer = resolve_fbo = 0; - actual_samples = 0; - } - - if (current != this) - gl.bindFramebuffer(GL_FRAMEBUFFER, previous); - - return status == GL_FRAMEBUFFER_COMPLETE; -} - bool Canvas::loadVolatile() { OpenGL::TempDebugGroup debuggroup("Canvas load"); - fbo = depth_stencil = texture = 0; - resolve_fbo = msaa_buffer = 0; + fbo = texture = 0; + msaa_buffer = 0; status = GL_FRAMEBUFFER_COMPLETE; // glTexImage2D is guaranteed to error in this case. @@ -239,8 +192,8 @@ bool Canvas::loadVolatile() while (glGetError() != GL_NO_ERROR) /* Clear the error buffer. */; - glTexImage2D(GL_TEXTURE_2D, 0, iformat, width, height, 0, - externalformat, textype, nullptr); + glTexImage2D(GL_TEXTURE_2D, 0, iformat, width, height, 0, externalformat, + textype, nullptr); if (glGetError() != GL_NO_ERROR) { @@ -250,24 +203,27 @@ bool Canvas::loadVolatile() return false; } - // Try to create a MSAA FBO if requested. On failure (or no requested MSAA), - // fall back to a regular FBO. - if (!createMSAAFBO(internalformat)) - status = createFBO(fbo, texture); + // Create a canvas-local FBO used for glReadPixels as well as MSAA blitting. + status = createFBO(fbo, texture); if (status != GL_FRAMEBUFFER_COMPLETE) - { - if (fbo != 0) - { - glDeleteFramebuffers(1, &fbo); - fbo = 0; - } + { + if (fbo != 0) + { + gl.deleteFramebuffer(fbo); + fbo = 0; + } return false; - } + } + + actual_samples = requested_samples == 1 ? 0 : requested_samples; + + if (actual_samples > 0 && !createMSAABuffer(width, height, actual_samples, internalformat, msaa_buffer)) + actual_samples = 0; size_t prevmemsize = texture_memory; - texture_memory = (getFormatBitsPerPixel(format) * width * height) / 8; + texture_memory = ((getFormatBitsPerPixel(format) * width) / 8) * height; if (msaa_buffer != 0) texture_memory += (texture_memory * actual_samples); @@ -278,33 +234,35 @@ bool Canvas::loadVolatile() void Canvas::unloadVolatile() { - glDeleteFramebuffers(1, &fbo); - glDeleteFramebuffers(1, &resolve_fbo); + if (fbo != 0) + gl.deleteFramebuffer(fbo); - glDeleteRenderbuffers(1, &depth_stencil); - glDeleteRenderbuffers(1, &msaa_buffer); + if (msaa_buffer != 0) + glDeleteRenderbuffers(1, &msaa_buffer); - gl.deleteTexture(texture); + if (texture != 0) + gl.deleteTexture(texture); fbo = 0; - resolve_fbo = 0; - depth_stencil = 0; msaa_buffer = 0; texture = 0; - attachedCanvases.clear(); - gl.updateTextureMemorySize(texture_memory, 0); texture_memory = 0; } void Canvas::drawv(const Matrix4 &t, const Vertex *v) { - // FIXME: This doesn't handle cases where the Canvas is used as a texture - // in a SpriteBatch, Mesh, or ParticleSystem, or when the Canvas is used in - // a shader as a non-default texture. - if (Canvas::current == this) - throw love::Exception("Cannot draw a Canvas to itself."); + auto gfx = Module::getInstance(Module::M_GRAPHICS); + if (gfx != nullptr) + { + const PassInfo &info = gfx->getActivePass(); + for (const auto &attachment : info.colorAttachments) + { + if (attachment.canvas == this) + throw love::Exception("Cannot render a Canvas to itself!"); + } + } OpenGL::TempDebugGroup debuggroup("Canvas draw"); @@ -377,320 +335,6 @@ const void *Canvas::getHandle() const return &texture; } -void Canvas::setupGrab() -{ - // already grabbing - if (current == this) - return; - - // cleanup after previous Canvas - if (current != nullptr) - { - systemViewport = current->systemViewport; - current->stopGrab(true); - } - else - systemViewport = gl.getViewport(); - - // indicate we are using this Canvas. - current = this; - - // bind the framebuffer object. - gl.bindFramebuffer(GL_FRAMEBUFFER, fbo); - gl.setViewport({0, 0, width, height}); - - // Set up the projection matrix - gl.matrices.projection.push_back(Matrix4::ortho(0.0, (float) width, 0.0, (float) height)); -} - -void Canvas::startGrab(const std::vector &canvases) -{ - // Whether the new canvas list is different from the old one. - // A more thorough check is done below. - bool canvaseschanged = canvases.size() != attachedCanvases.size(); - bool hasSRGBcanvas = getSizedFormat(format) == FORMAT_SRGB; - - if (canvases.size() > 0) - { - if ((int) canvases.size() + 1 > gl.getMaxRenderTargets()) - throw love::Exception("This system can't simultaneously render to %d canvases.", canvases.size()+1); - - if (actual_samples != 0) - throw love::Exception("Multi-canvas rendering is not supported with MSAA."); - } - - bool multiformatsupported = isMultiFormatMultiCanvasSupported(); - - for (size_t i = 0; i < canvases.size(); i++) - { - if (canvases[i]->getWidth() != width || canvases[i]->getHeight() != height) - throw love::Exception("All canvases must have the same dimensions."); - - Format otherformat = canvases[i]->getTextureFormat(); - - if (otherformat != format && !multiformatsupported) - throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats."); - - if (canvases[i]->getMSAA() != 0) - throw love::Exception("Multi-canvas rendering is not supported with MSAA."); - - if (!canvaseschanged && canvases[i] != attachedCanvases[i]) - canvaseschanged = true; - - if (getSizedFormat(otherformat) == FORMAT_SRGB) - hasSRGBcanvas = true; - } - - OpenGL::TempDebugGroup debuggroup("Canvas set"); - - setupGrab(); - - // Make sure the correct sRGB setting is used when drawing to the canvases. - if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control) - { - if (hasSRGBcanvas && !gl.hasFramebufferSRGB()) - gl.setFramebufferSRGB(true); - else if (!hasSRGBcanvas && gl.hasFramebufferSRGB()) - gl.setFramebufferSRGB(false); - } - - // Don't attach anything if there's nothing to change. - if (!canvaseschanged) - return; - - // Attach the canvas textures to the active FBO and set up MRTs. - std::vector drawbuffers; - drawbuffers.reserve(canvases.size() + 1); - - drawbuffers.push_back(GL_COLOR_ATTACHMENT0); - - // Attach the canvas textures to the currently bound framebuffer. - for (int i = 0; i < (int) canvases.size(); i++) - { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1 + i, - GL_TEXTURE_2D, *(GLuint *) canvases[i]->getHandle(), 0); - - drawbuffers.push_back(GL_COLOR_ATTACHMENT1 + i); - } - - // set up multiple render targets - glDrawBuffers((int) drawbuffers.size(), &drawbuffers[0]); - - // We want to avoid reference cycles, so we don't retain the attached - // Canvases here. The code in Graphics::setCanvas retains them. - - attachedCanvases = canvases; -} - -void Canvas::startGrab() -{ - OpenGL::TempDebugGroup debuggroup("Canvas set"); - - setupGrab(); - - // Make sure the correct sRGB setting is used when drawing to the canvas. - if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control) - { - bool isSRGB = getSizedFormat(format) == FORMAT_SRGB; - if (isSRGB && !gl.hasFramebufferSRGB()) - gl.setFramebufferSRGB(true); - else if (!isSRGB && gl.hasFramebufferSRGB()) - gl.setFramebufferSRGB(false); - } - - if (attachedCanvases.size() > 0) - { - // Make sure the FBO is only using a single draw buffer. - // GLES3 only has glDrawBuffers, so we avoid using glDrawBuffer. - const GLenum buffers[] = {GL_COLOR_ATTACHMENT0}; - glDrawBuffers(1, buffers); - - attachedCanvases.clear(); - } -} - -void Canvas::stopGrab(bool switchingToOtherCanvas) -{ - // i am not grabbing. leave me alone - if (current != this) - return; - - OpenGL::TempDebugGroup debuggroup("Canvas un-set"); - - // Make sure the canvas texture is up to date if we're using MSAA. - resolveMSAA(false); - - if (gl.matrices.projection.size() > 1) - gl.matrices.projection.pop_back(); - - if (!switchingToOtherCanvas) - { - // bind system framebuffer. - gl.bindFramebuffer(GL_FRAMEBUFFER, gl.getDefaultFBO()); - current = nullptr; - gl.setViewport(systemViewport); - - if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control) - { - if (screenHasSRGB && !gl.hasFramebufferSRGB()) - gl.setFramebufferSRGB(true); - else if (!screenHasSRGB && gl.hasFramebufferSRGB()) - gl.setFramebufferSRGB(false); - } - } -} - -bool Canvas::checkCreateStencil() -{ - // Do nothing if we've already created the stencil buffer. - if (depth_stencil != 0) - return true; - - OpenGL::TempDebugGroup debuggroup("Canvas create stencil"); - - if (current != this) - gl.bindFramebuffer(GL_FRAMEBUFFER, fbo); - - GLenum format = GL_STENCIL_INDEX8; - std::vector attachments = {GL_STENCIL_ATTACHMENT}; - - // Prefer a combined depth/stencil buffer. - if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object) - { - format = GL_DEPTH24_STENCIL8; - attachments = {GL_DEPTH_STENCIL_ATTACHMENT}; - } - else if (GLAD_EXT_packed_depth_stencil || GLAD_OES_packed_depth_stencil) - { - format = GL_DEPTH24_STENCIL8; - attachments = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT}; - } - - glGenRenderbuffers(1, &depth_stencil); - glBindRenderbuffer(GL_RENDERBUFFER, depth_stencil); - - if (requested_samples > 1) - glRenderbufferStorageMultisample(GL_RENDERBUFFER, requested_samples, format, width, height); - else - glRenderbufferStorage(GL_RENDERBUFFER, format, width, height); - - // Attach the buffer to the framebuffer object. - for (GLenum attachment : attachments) - glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, depth_stencil); - - glBindRenderbuffer(GL_RENDERBUFFER, 0); - - bool success = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE; - - // We don't want the stencil buffer filled with garbage. - if (success) - glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - else - { - glDeleteRenderbuffers(1, &depth_stencil); - depth_stencil = 0; - } - - if (current && current != this) - gl.bindFramebuffer(GL_FRAMEBUFFER, current->fbo); - else if (!current) - gl.bindFramebuffer(GL_FRAMEBUFFER, gl.getDefaultFBO()); - - return success; -} - -love::image::ImageData *Canvas::newImageData(love::image::Image *image, int x, int y, int w, int h) -{ - if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > width || (y + h) > height) - throw love::Exception("Invalid ImageData rectangle dimensions."); - - GLenum datatype = GL_UNSIGNED_BYTE; - image::ImageData::Format imageformat = image::ImageData::FORMAT_RGBA8; - - switch (getSizedFormat(format)) - { - case FORMAT_RGB10A2: // FIXME: Conversions aren't supported in GLES - datatype = GL_UNSIGNED_SHORT; - imageformat = image::ImageData::FORMAT_RGBA16; - break; - case FORMAT_R16F: - case FORMAT_RG16F: - case FORMAT_RGBA16F: - case FORMAT_RG11B10F: // FIXME: Conversions aren't supported in GLES - datatype = GL_HALF_FLOAT; - imageformat = image::ImageData::FORMAT_RGBA16F; - break; - case FORMAT_R32F: - case FORMAT_RG32F: - case FORMAT_RGBA32F: - datatype = GL_FLOAT; - imageformat = image::ImageData::FORMAT_RGBA32F; - break; - default: - break; - } - - size_t size = w * h * image::ImageData::getPixelSize(imageformat); - uint8 *pixels = nullptr; - try - { - pixels = new uint8[size]; - } - catch (std::bad_alloc &) - { - throw love::Exception("Out of memory."); - } - - // Make sure the canvas texture is up to date if we're using MSAA. - if (current == this) - resolveMSAA(false); - - // Our texture is attached to 'resolve_fbo' when we use MSAA. - if (resolve_fbo != 0) - gl.bindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo); - else - gl.bindFramebuffer(GL_FRAMEBUFFER, fbo); - - glReadPixels(x, y, w, h, GL_RGBA, datatype, pixels); - - GLuint prevfbo = current ? current->fbo : gl.getDefaultFBO(); - gl.bindFramebuffer(GL_FRAMEBUFFER, prevfbo); - - // The new ImageData now owns the pixel data, so we don't delete it here. - return image->newImageData(w, h, imageformat, pixels, true); -} - -bool Canvas::resolveMSAA(bool restoreprev) -{ - if (resolve_fbo == 0 || msaa_buffer == 0) - return false; - - OpenGL::TempDebugGroup debuggroup("Canvas MSAA resolve"); - - GLint w = width; - GLint h = height; - - // Do the MSAA resolve by blitting the MSAA renderbuffer to the texture. - // For many of the MSAA extensions that add suffixes to the functions, we - // assign function pointers in OpenGL.cpp so we can call the core functions. - - gl.bindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo); - - if (GLAD_APPLE_framebuffer_multisample) - glResolveMultisampleFramebufferAPPLE(); - else - glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); - - if (restoreprev) - { - GLuint fbo = current ? current->fbo : gl.getDefaultFBO(); - gl.bindFramebuffer(GL_FRAMEBUFFER, fbo); - } - - return true; -} - Canvas::Format Canvas::getSizedFormat(Canvas::Format format) { switch (format) @@ -949,7 +593,7 @@ bool Canvas::isFormatSupported(Canvas::Format format) GLuint fbo = 0; supported = (createFBO(fbo, texture) == GL_FRAMEBUFFER_COMPLETE); - glDeleteFramebuffers(1, &fbo); + gl.deleteFramebuffer(fbo); gl.deleteTexture(texture); diff --git a/src/modules/graphics/opengl/Canvas.h b/src/modules/graphics/opengl/Canvas.h index 03d829bd9..e67800c62 100644 --- a/src/modules/graphics/opengl/Canvas.h +++ b/src/modules/graphics/opengl/Canvas.h @@ -82,26 +82,6 @@ public: bool setWrap(const Texture::Wrap &w) override; const void *getHandle() const override; - /** - * @param canvases A list of other canvases to temporarily attach to this one, - * to allow drawing to multiple canvases at once. - **/ - void startGrab(const std::vector &canvases); - void startGrab(); - void stopGrab(bool switchingToOtherCanvas = false); - - /** - * Create and attach a stencil buffer to this Canvas' framebuffer, if necessary. - **/ - bool checkCreateStencil(); - - love::image::ImageData *newImageData(love::image::Image *image, int x, int y, int w, int h); - - inline const std::vector &getAttachedCanvases() const - { - return attachedCanvases; - } - inline GLenum getStatus() const { return status; @@ -117,19 +97,26 @@ public: return actual_samples; } + inline int getRequestedMSAA() const + { + return requested_samples; + } + + inline ptrdiff_t getMSAAHandle() const + { + return msaa_buffer; + } + + inline GLuint getFBO() const + { + return fbo; + } + static Format getSizedFormat(Format format); static bool isSupported(); static bool isMultiFormatMultiCanvasSupported(); static bool isFormatSupported(Format format); - static Canvas *current; - - // The viewport dimensions of the system (default) framebuffer. - static OpenGL::Viewport systemViewport; - - // Whether the main screen should have linear -> sRGB conversions enabled. - static bool screenHasSRGB; - static int canvasCount; static bool getConstant(const char *in, Format &out); @@ -137,29 +124,20 @@ public: private: - void setupGrab(); - - bool createMSAAFBO(GLenum internalformat); - bool resolveMSAA(bool restoreprev); - void drawv(const Matrix4 &t, const Vertex *v); static void convertFormat(Format format, GLenum &internalformat, GLenum &externalformat, GLenum &type); static size_t getFormatBitsPerPixel(Format format); GLuint fbo; - GLuint resolve_fbo; GLuint texture; GLuint msaa_buffer; - GLuint depth_stencil; Format format; GLenum status; - std::vector attachedCanvases; - int requested_samples; int actual_samples; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 7635853cf..fcc5f1694 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -27,6 +27,9 @@ #include "font/Font.h" #include "Polyline.h" #include "math/MathModule.h" +#include "window/Window.h" + +#include "libraries/xxHash/xxhash.h" // C++ #include @@ -50,28 +53,34 @@ namespace opengl { Graphics::Graphics() - : currentWindow(Module::getInstance(Module::M_WINDOW)) - , quadIndices(nullptr) + : quadIndices(nullptr) , width(0) , height(0) , created(false) , active(true) + , canCaptureScreenshot(true) + , currentPass() , writingToStencil(false) + , renderPassCount(0) { gl = OpenGL(); states.reserve(10); states.push_back(DisplayState()); - if (currentWindow.get()) + auto window = getInstance(M_WINDOW); + + if (window != nullptr) { - int w, h; - love::window::WindowSettings wsettings; + window->setGraphics(this); - currentWindow->getWindow(w, h, wsettings); + if (window->isOpen()) + { + int w = 0, h = 0; + window->getPixelDimensions(w, h); - if (currentWindow->isOpen()) setMode(w, h); + } } } @@ -123,7 +132,6 @@ void Graphics::restoreState(const DisplayState &s) setFont(s.font.get()); setShader(s.shader.get()); - setCanvas(s.canvases); setColorMask(s.colorMask); setWireframe(s.wireframe); @@ -166,20 +174,6 @@ void Graphics::restoreStateChecked(const DisplayState &s) setFont(s.font.get()); setShader(s.shader.get()); - bool canvaseschanged = s.canvases.size() != cur.canvases.size(); - - for (size_t i = 0; i < s.canvases.size() && i < cur.canvases.size(); i++) - { - if (s.canvases[i].get() != cur.canvases[i].get()) - { - canvaseschanged = true; - break; - } - } - - if (canvaseschanged) - setCanvas(s.canvases); - if (s.colorMask != cur.colorMask) setColorMask(s.colorMask); @@ -217,32 +211,18 @@ void Graphics::setViewportSize(int width, int height) this->width = width; this->height = height; - if (!isCreated()) - return; + if (currentPass.active && currentPass.info.colorAttachmentCount == 0) + { + // Set the viewport to top-left corner. + gl.setViewport({0, 0, width, height}, false); - // We want to affect the main screen, not any Canvas that's currently active - // (not that any *should* be active when this is called.) - std::vector> canvases = states.back().canvases; - setCanvas(); - - // Set the viewport to top-left corner. - gl.setViewport({0, 0, width, height}); - - // If a canvas was bound before this function was called, it needs to be - // made aware of the new system viewport size. - Canvas::systemViewport = gl.getViewport(); - - // Set up the projection matrix - gl.matrices.projection.back() = Matrix4::ortho(0.0, (float) width, (float) height, 0.0); - - // Restore the previously active Canvas. - setCanvas(canvases); + // Set up the projection matrix + gl.matrices.projection.back() = Matrix4::ortho(0.0, (float) width, (float) height, 0.0); + } } bool Graphics::setMode(int width, int height) { - currentWindow.set(Module::getInstance(Module::M_WINDOW)); - this->width = width; this->height = height; @@ -282,8 +262,6 @@ bool Graphics::setMode(int width, int height) else setGammaCorrect(false); - Canvas::screenHasSRGB = isGammaCorrect(); - bool enabledebug = false; if (GLAD_VERSION_3_0) @@ -350,6 +328,15 @@ void Graphics::unSetMode() // mode change. Volatile::unloadAll(); + for (const auto &pair : framebufferObjects) + gl.deleteFramebuffer(pair.second); + + for (const CachedRenderbuffer &rb : stencilBuffers) + glDeleteRenderbuffers(1, &rb.renderbuffer); + + framebufferObjects.clear(); + stencilBuffers.clear(); + gl.deInitContext(); created = false; @@ -369,7 +356,8 @@ 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.get() && currentWindow->isOpen(); + auto window = getInstance(M_WINDOW); + return active && isCreated() && window != nullptr && window->isOpen(); } static void APIENTRY debugCB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei /*len*/, const GLchar *msg, const GLvoid* /*usr*/) @@ -439,91 +427,181 @@ void Graphics::reset() origin(); } -void Graphics::clear(Colorf c) +void Graphics::beginPass(PassInfo::BeginAction beginAction, Colorf clearColor) { - gammaCorrectColor(c); + if (currentPass.active) + throw love::Exception("Cannot call beginPass while another render pass is active!"); - glClearColor(c.r, c.g, c.b, c.a); - glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + currentPass.active = true; - if (gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current) + OpenGL::TempDebugGroup debuggroup("Render Pass begin"); + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); + + gl.setViewport({0, 0, width, height}, false); + + // The projection matrix is flipped compared to rendering to a canvas, due + // to OpenGL considering (0,0) bottom-left instead of top-left. + gl.matrices.projection.back() = Matrix4::ortho(0.0, (float) width, (float) height, 0.0); + + if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control) { - // This seems to be enough to fix the bug for me. Other methods I've - // tried (e.g. dummy draws) don't work in all cases. - gl.useProgram(0); - gl.useProgram(Shader::current->getProgram()); + if (isGammaCorrect() && !gl.hasFramebufferSRGB()) + gl.setFramebufferSRGB(true); + else if (!isGammaCorrect() && gl.hasFramebufferSRGB()) + gl.setFramebufferSRGB(false); } + + // Always clear the stencil buffer, for now. + GLbitfield clearflags = GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT; + + if (beginAction == PassInfo::BEGIN_CLEAR) + { + Colorf c = clearColor; + gammaCorrectColor(c); + + glClearColor(c.r, c.g, c.b, c.a); + clearflags |= GL_COLOR_BUFFER_BIT; + } + + if (clearflags != 0) + glClear(clearflags); + + PassInfo info; + info.colorAttachmentCount = 0; + info.stencil = true; + + currentPass.info = info; + + renderPassCount++; + + canCaptureScreenshot = false; } -void Graphics::clear(const std::vector &colors) +void Graphics::beginPass(const PassInfo &info) { - if (colors.size() == 0) - return; + if (info.colorAttachmentCount == 0) + throw love::Exception("At least one Canvas must be specified for an off-screen render pass."); - size_t numcanvases = states.back().canvases.size(); + if (currentPass.active) + throw love::Exception("Cannot call beginPass while another render pass is active!"); - if (numcanvases > 0 && colors.size() != numcanvases) - throw love::Exception("Number of clear colors must match the number of active canvases (%ld)", states.back().canvases.size()); + if (info.colorAttachmentCount > gl.getMaxRenderTargets()) + throw love::Exception("This system can't simultaneously render to %d canvases.", info.colorAttachmentCount); - // We want to take the single-color codepath if there's no active Canvas, or - // if there's only one active Canvas. The multi-color codepath (in the loop - // below) assumes MRT functions are available, and also may call more - // expensive GL functions which are unnecessary if only one Canvas is active. - if (numcanvases <= 1) + Canvas *firstcanvas = info.colorAttachments[0].canvas; + + bool multiformatsupported = isSupported(Feature::FEATURE_MULTI_CANVAS_FORMATS); + Canvas::Format firstformat = firstcanvas->getTextureFormat(); + + bool hasSRGBcanvas = Canvas::getSizedFormat(firstformat) == Canvas::FORMAT_SRGB; + + for (int i = 1; i < info.colorAttachmentCount; i++) { - if (colors[0].enabled) - clear(colors[0].toColor()); + Canvas *c = info.colorAttachments[i].canvas; - return; + if (c->getWidth() != firstcanvas->getWidth() || c->getHeight() != firstcanvas->getHeight()) + throw love::Exception("All canvases in a render pass must have the same dimensions."); + + if (!multiformatsupported && c->getTextureFormat() != firstformat) + throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats."); + + if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA()) + throw love::Exception("All Canvases in a render pass must have the same requested MSAA value."); + + if (Canvas::getSizedFormat(c->getTextureFormat()) == Canvas::FORMAT_SRGB) + hasSRGBcanvas = true; } - bool drawbuffermodified = false; + OpenGL::TempDebugGroup debuggroup("Render Pass begin"); - for (int i = 0; i < (int) colors.size(); i++) + bindCachedFBOForPass(info); + + currentPass.active = true; + + int w = firstcanvas->getWidth(); + int h = firstcanvas->getHeight(); + + gl.setViewport({0, 0, w, h}, true); + gl.matrices.projection.back() = Matrix4::ortho(0.0, (float) w, 0.0, (float) h); + + // Make sure the correct sRGB setting is used when drawing to the canvases. + if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control) { - if (!colors[i].enabled) - continue; - - GLfloat c[] = {colors[i].r, colors[i].g, colors[i].b, colors[i].a}; - - // TODO: Investigate a potential bug on AMD drivers in Windows/Linux - // which apparently causes the clear color to be incorrect when mixed - // sRGB and linear render targets are active. - if (isGammaCorrect()) - { - for (int i = 0; i < 3; i++) - c[i] = math::gammaToLinear(c[i]); - } - - if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0) - glClearBufferfv(GL_COLOR, i, c); - else - { - glDrawBuffer(GL_COLOR_ATTACHMENT0 + i); - glClearColor(c[0], c[1], c[2], c[3]); - glClear(GL_COLOR_BUFFER_BIT); - - drawbuffermodified = true; - } + if (hasSRGBcanvas && !gl.hasFramebufferSRGB()) + gl.setFramebufferSRGB(true); + else if (!hasSRGBcanvas && gl.hasFramebufferSRGB()) + gl.setFramebufferSRGB(false); } - glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + GLbitfield clearflags = 0; - // Revert to the expected draw buffers once we're done, if glClearBuffer - // wasn't supported. - if (drawbuffermodified) + // Take a single-color clear codepath if there's only one specified Canvas. + // The multi-color codepath (in the loop below) assumes MRT functions are + // available, and also may call more expensive GL functions which are + // unnecessary if only one Canvas is used. + if (info.colorAttachmentCount <= 1) { - std::vector bufs; + if (info.colorAttachmentCount > 0 && info.colorAttachments[0].beginAction == PassInfo::BEGIN_CLEAR) + { + clearflags |= GL_COLOR_BUFFER_BIT; + Colorf c = info.colorAttachments[0].clearColor; + gammaCorrectColor(c); + glClearColor(c.r, c.g, c.b, c.a); + } + } + else + { + bool drawbuffermodified = false; - for (int i = 0; i < (int) states.back().canvases.size(); i++) - bufs.push_back(GL_COLOR_ATTACHMENT0 + i); + for (int i = 0; i < info.colorAttachmentCount; i++) + { + if (info.colorAttachments[i].beginAction == PassInfo::BEGIN_CLEAR) + { + Colorf c = info.colorAttachments[i].clearColor; + gammaCorrectColor(c); + + if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0) + { + const GLfloat carray[] = {c.r, c.g, c.b, c.a}; + glClearBufferfv(GL_COLOR, i, carray); + } + else + { + glDrawBuffer(GL_COLOR_ATTACHMENT0 + i); + glClearColor(c.r, c.g, c.b, c.a); + glClear(GL_COLOR_BUFFER_BIT); + + drawbuffermodified = true; + } + } + } + + // Revert to the expected draw buffers once we're done, if glClearBuffer + // wasn't supported. + if (drawbuffermodified) + { + std::vector bufs; + + for (int i = 0; i < info.colorAttachmentCount; i++) + bufs.push_back(GL_COLOR_ATTACHMENT0 + i); - if (bufs.size() > 1) glDrawBuffers((int) bufs.size(), &bufs[0]); - else - glDrawBuffer(GL_COLOR_ATTACHMENT0); + } } + if (info.stencil) + clearflags |= GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT; + + if (clearflags != 0) + glClear(clearflags); + + for (int i = 0; i < info.colorAttachmentCount; i++) + info.colorAttachments[i].canvas->retain(); + + currentPass.info = info; + renderPassCount++; + if (gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current) { // This seems to be enough to fix the bug for me. Other methods I've @@ -533,21 +611,146 @@ void Graphics::clear(const std::vector &colors) } } -void Graphics::discard(const std::vector &colorbuffers, bool stencil) +void Graphics::endPass() +{ + endPass(0, 0, 0, 0, nullptr, nullptr); +} + +void Graphics::endPass(int sX, int sY, int sW, int sH, const ScreenshotInfo *info, void *screenshotCallbackData) +{ + if (!currentPass.active) + return; // Should this error instead? + + StrongRef imagedata; + + auto &attachments = currentPass.info.colorAttachments; + int attachmentcount = currentPass.info.colorAttachmentCount; + + if (info != nullptr) + { + if (attachmentcount == 0) + throw love::Exception("Use captureScreenshot to capture the main screen's contents."); + + if (sX < 0 || sY < 0 || sW <= 0 || sH <= 0 || (sX + sW) > getPassWidth() || (sY + sH) > getPassHeight()) + throw love::Exception("Invalid rectangle dimensions."); + + auto imagemodule = Module::getInstance(M_IMAGE); + + if (imagemodule == nullptr) + throw love::Exception("The love.image module must be loaded to capture a Canvas' contents."); + + image::ImageData::Format format; + switch (Canvas::getSizedFormat(attachments[0].canvas->getTextureFormat())) + { + case Canvas::FORMAT_RGB10A2: // FIXME: Conversions aren't supported in GLES + format = image::ImageData::FORMAT_RGBA16; + break; + case Canvas::FORMAT_R16F: + case Canvas::FORMAT_RG16F: + case Canvas::FORMAT_RGBA16F: + case Canvas::FORMAT_RG11B10F: // FIXME: Conversions aren't supported in GLES + format = image::ImageData::FORMAT_RGBA16F; + break; + case Canvas::FORMAT_R32F: + case Canvas::FORMAT_RG32F: + case Canvas::FORMAT_RGBA32F: + format = image::ImageData::FORMAT_RGBA32F; + break; + default: + format = image::ImageData::FORMAT_RGBA8; + break; + } + + imagedata.set(imagemodule->newImageData(sW, sH, format), Acquire::NORETAIN); + } + + if (currentPass.info.stencil) + discard(OpenGL::FRAMEBUFFER_ALL, {}, true); + + if (attachmentcount > 0 && attachments[0].canvas->getMSAA() > 1) + { + int w = attachments[0].canvas->getWidth(); + int h = attachments[0].canvas->getHeight(); + + for (int i = 0; i < attachmentcount; i++) + { + glReadBuffer(GL_COLOR_ATTACHMENT0 + i); + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, attachments[i].canvas->getFBO()); + + if (GLAD_APPLE_framebuffer_multisample) + glResolveMultisampleFramebufferAPPLE(); + else + glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + } + + if (info != nullptr) + { + if (attachments[0].canvas->getMSAA() > 1) + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_READ, attachments[0].canvas->getFBO()); + else if (attachmentcount > 1) + glReadBuffer(GL_COLOR_ATTACHMENT0); + + GLenum datatype; + switch (imagedata->getFormat()) + { + case image::ImageData::FORMAT_RGBA16: + datatype = GL_UNSIGNED_SHORT; + break; + case image::ImageData::FORMAT_RGBA16F: + datatype = GL_HALF_FLOAT; + break; + case image::ImageData::FORMAT_RGBA32F: + datatype = GL_FLOAT; + break; + default: + datatype = GL_UNSIGNED_BYTE; + break; + } + + glReadPixels(sX, sY, sW, sH, GL_RGBA, datatype, imagedata->getData()); + + info->callback(imagedata, info->ref, screenshotCallbackData); + } + + for (int i = 0; i < currentPass.info.colorAttachmentCount; i++) + currentPass.info.colorAttachments[i].canvas->release(); + + currentPass.active = false; +} + +const PassInfo &Graphics::getActivePass() const +{ + return currentPass.info; +} + +bool Graphics::isPassActive() const +{ + return currentPass.active; +} + +void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector &colorbuffers, bool depthstencil) { if (!(GLAD_VERSION_4_3 || GLAD_ARB_invalidate_subdata || GLAD_ES_VERSION_3_0 || GLAD_EXT_discard_framebuffer)) return; + GLenum gltarget = GL_FRAMEBUFFER; + if (target == OpenGL::FRAMEBUFFER_READ) + gltarget = GL_READ_FRAMEBUFFER; + else if (target == OpenGL::FRAMEBUFFER_DRAW) + gltarget = GL_DRAW_FRAMEBUFFER; + std::vector attachments; attachments.reserve(colorbuffers.size()); // glDiscardFramebuffer uses different attachment enums for the default FBO. - if (!Canvas::current && gl.getDefaultFBO() == 0) + if (currentPass.info.colorAttachmentCount == 0 && gl.getDefaultFBO() == 0) { if (colorbuffers.size() > 0 && colorbuffers[0]) attachments.push_back(GL_COLOR); - if (stencil) + if (depthstencil) { attachments.push_back(GL_STENCIL); attachments.push_back(GL_DEPTH); @@ -555,9 +758,7 @@ void Graphics::discard(const std::vector &colorbuffers, bool stencil) } else { - int rendertargetcount = 1; - if (Canvas::current) - rendertargetcount = (int) states.back().canvases.size(); + int rendertargetcount = std::max(currentPass.info.colorAttachmentCount, 1); for (int i = 0; i < (int) colorbuffers.size(); i++) { @@ -565,7 +766,7 @@ void Graphics::discard(const std::vector &colorbuffers, bool stencil) attachments.push_back(GL_COLOR_ATTACHMENT0 + i); } - if (stencil) + if (depthstencil) { attachments.push_back(GL_STENCIL_ATTACHMENT); attachments.push_back(GL_DEPTH_ATTACHMENT); @@ -574,22 +775,264 @@ void Graphics::discard(const std::vector &colorbuffers, bool stencil) // Hint for the driver that it doesn't need to save these buffers. if (GLAD_VERSION_4_3 || GLAD_ARB_invalidate_subdata || GLAD_ES_VERSION_3_0) - glInvalidateFramebuffer(GL_FRAMEBUFFER, (GLint) attachments.size(), &attachments[0]); + glInvalidateFramebuffer(gltarget, (GLint) attachments.size(), &attachments[0]); else if (GLAD_EXT_discard_framebuffer) - glDiscardFramebufferEXT(GL_FRAMEBUFFER, (GLint) attachments.size(), &attachments[0]); + glDiscardFramebufferEXT(gltarget, (GLint) attachments.size(), &attachments[0]); } -void Graphics::present() +void Graphics::bindCachedFBOForPass(const PassInfo &pass) +{ + PassBufferInfo info; + memset(&info, 0, sizeof(PassBufferInfo)); + + info.stencil = pass.stencil; + + int ncanvases = pass.colorAttachmentCount; + + for (int i = 0; i < ncanvases; i++) + info.canvases[i] = pass.colorAttachments[i].canvas; + + uint32 hash = XXH32(&info, offsetof(PassBufferInfo, canvases) + ncanvases * sizeof(Canvas *), 0); + + GLuint fbo = framebufferObjects[hash]; + + if (fbo != 0) + { + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); + } + else + { + int w = info.canvases[0]->getWidth(); + int h = info.canvases[0]->getHeight(); + int msaa = std::max(info.canvases[0]->getMSAA(), 1); + + glGenFramebuffers(1, &fbo); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); + + std::vector drawbuffers; + drawbuffers.reserve(ncanvases); + + for (int i = 0; i < ncanvases; i++) + { + drawbuffers.push_back(GL_COLOR_ATTACHMENT0 + i); + + if (msaa > 1) + { + GLuint rbo = (GLuint) info.canvases[i]->getMSAAHandle(); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, drawbuffers[i], GL_RENDERBUFFER, rbo); + } + else + { + GLuint tex = *(GLuint *) info.canvases[i]->getHandle(); + glFramebufferTexture2D(GL_FRAMEBUFFER, drawbuffers[i], GL_TEXTURE_2D, tex, 0); + } + } + + if (drawbuffers.size() > 1) + glDrawBuffers(1, &drawbuffers[0]); + + GLuint stencil = attachCachedStencilBuffer(w, h, msaa); + + if (stencil == 0) + { + gl.deleteFramebuffer(fbo); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); + throw love::Exception("Could not create stencil buffer!"); + } + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) + { + gl.deleteFramebuffer(fbo); + const char *sstr = OpenGL::framebufferStatusString(status); + throw love::Exception("Could not create Framebuffer Object! %s", sstr); + } + + framebufferObjects[hash] = fbo; + } +} + +GLuint Graphics::attachCachedStencilBuffer(int w, int h, int samples) +{ + samples = samples == 1 ? 0 : samples; + + for (const CachedRenderbuffer &rb : stencilBuffers) + { + if (rb.w == w && rb.h == h && rb.samples == samples) + { + // Attach the buffer to the framebuffer object. + for (GLenum attachment : rb.attachments) + { + if (attachment != GL_NONE) + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, rb.renderbuffer); + } + + return rb.renderbuffer; + } + } + + OpenGL::TempDebugGroup debuggroup("Created cached stencil buffer"); + + CachedRenderbuffer rb; + rb.w = w; + rb.h = h; + rb.samples = samples; + + rb.attachments[0] = GL_STENCIL_ATTACHMENT; + rb.attachments[1] = GL_NONE; + + GLenum format = GL_STENCIL_INDEX8; + + // Prefer a combined depth/stencil buffer. + if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object) + { + format = GL_DEPTH24_STENCIL8; + rb.attachments[0] = GL_DEPTH_STENCIL_ATTACHMENT; + } + else if (GLAD_EXT_packed_depth_stencil || GLAD_OES_packed_depth_stencil) + { + format = GL_DEPTH24_STENCIL8; + rb.attachments[0] = GL_DEPTH_ATTACHMENT; + rb.attachments[1] = GL_STENCIL_ATTACHMENT; + } + + glGenRenderbuffers(1, &rb.renderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, rb.renderbuffer); + + if (rb.samples > 1) + glRenderbufferStorageMultisample(GL_RENDERBUFFER, rb.samples, format, rb.w, rb.h); + else + glRenderbufferStorage(GL_RENDERBUFFER, format, rb.w, rb.h); + + // Attach the buffer to the framebuffer object. + for (GLenum attachment : rb.attachments) + { + if (attachment != GL_NONE) + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, rb.renderbuffer); + } + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + glDeleteRenderbuffers(1, &rb.renderbuffer); + rb.renderbuffer = 0; + } + + if (rb.renderbuffer != 0) + stencilBuffers.push_back(rb); + + return rb.renderbuffer; +} + +void Graphics::captureScreenshot(const ScreenshotInfo &info) +{ + if (!canCaptureScreenshot) + throw love::Exception("captureScreenshot cannot be called once rendering to the main screen has begun."); + + pendingScreenshotCallbacks.push_back(info); +} + +void Graphics::present(void *screenshotCallbackData) { if (!isActive()) return; - // Make sure we don't have a canvas active. - std::vector> canvases = states.back().canvases; - setCanvas(); + if (currentPass.active) + throw love::Exception("present cannot be called while a render pass is active."); - // Discard the stencil buffer before swapping. - discard({}, true); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); + + if (!pendingScreenshotCallbacks.empty()) + { + int w = getWidth(); + int h = getHeight(); + + size_t row = 4 * w; + size_t size = row * h; + + GLubyte *pixels = nullptr; + GLubyte *screenshot = nullptr; + + try + { + pixels = new GLubyte[size]; + screenshot = new GLubyte[size]; + } + catch (std::exception &) + { + delete[] pixels; + delete[] screenshot; + throw love::Exception("Out of memory."); + } + +#ifdef LOVE_IOS + SDL_SysWMinfo info = {}; + SDL_VERSION(&info.version); + SDL_GetWindowWMInfo(SDL_GL_GetCurrentWindow(), &info); + + if (info.info.uikit.resolveFramebuffer != 0) + { + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, info.info.uikit.resolveFramebuffer); + + // We need to do an explicit MSAA resolve on iOS, because it uses GLES + // FBOs rather than a system framebuffer. + if (GLAD_ES_VERSION_3_0) + glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + else if (GLAD_APPLE_framebuffer_multisample) + glResolveMultisampleFramebufferAPPLE(); + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_READ, info.info.uikit.resolveFramebuffer); + } +#endif + + glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Replace alpha values with full opacity. + for (size_t i = 3; i < size; i += 4) + pixels[i] = 255; + + // OpenGL sucks and reads pixels from the lower-left. Let's fix that. + GLubyte *src = pixels - row; + GLubyte *dst = screenshot + size; + + for (int i = 0; i < h; ++i) + memcpy(dst-=row, src+=row, row); + + delete[] pixels; + + auto imagemodule = Module::getInstance(M_IMAGE); + + for (int i = 0; i < (int) pendingScreenshotCallbacks.size(); i++) + { + const auto &info = pendingScreenshotCallbacks[i]; + image::ImageData *img = nullptr; + + try + { + img = imagemodule->newImageData(w, h, image::ImageData::FORMAT_RGBA8, screenshot); + } + catch (love::Exception &) + { + delete[] screenshot; + info.callback(nullptr, info.ref, nullptr); + for (int j = i + 1; j < (int) pendingScreenshotCallbacks.size(); j++) + { + const auto &ninfo = pendingScreenshotCallbacks[j]; + ninfo.callback(nullptr, ninfo.ref, nullptr); + } + pendingScreenshotCallbacks.clear(); + throw; + } + + info.callback(img, info.ref, screenshotCallbackData); + img->release(); + } + + delete[] screenshot; + pendingScreenshotCallbacks.clear(); + } #ifdef LOVE_IOS // Hack: SDL's color renderbuffer must be bound when swapBuffers is called. @@ -599,16 +1042,16 @@ void Graphics::present() glBindRenderbuffer(GL_RENDERBUFFER, info.info.uikit.colorbuffer); #endif - if (currentWindow.get()) - currentWindow->swapBuffers(); - - // Restore the currently active canvas, if there is one. - setCanvas(canvases); + auto window = getInstance(M_WINDOW); + if (window != nullptr) + window->swapBuffers(); // Reset the per-frame stat counts. gl.stats.drawCalls = 0; - gl.stats.framebufferBinds = 0; gl.stats.shaderSwitches = 0; + renderPassCount = 0; + + canCaptureScreenshot = true; } int Graphics::getWidth() const @@ -621,6 +1064,22 @@ int Graphics::getHeight() const return height; } +int Graphics::getPassWidth() const +{ + if (currentPass.active && currentPass.info.colorAttachmentCount > 0) + return currentPass.info.colorAttachments[0].canvas->getWidth(); + else + return width; +} + +int Graphics::getPassHeight() const +{ + if (currentPass.active && currentPass.info.colorAttachmentCount > 0) + return currentPass.info.colorAttachments[0].canvas->getHeight(); + else + return height; +} + bool Graphics::isCreated() const { return created; @@ -632,7 +1091,7 @@ void Graphics::setScissor(int x, int y, int width, int height) glEnable(GL_SCISSOR_TEST); // OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor. - gl.setScissor({rect.x, rect.y, rect.w, rect.h}); + gl.setScissor({rect.x, rect.y, rect.w, rect.h}, currentPass.info.colorAttachmentCount > 0); states.back().scissor = true; states.back().scissorRect = rect; @@ -679,11 +1138,10 @@ bool Graphics::getScissor(int &x, int &y, int &width, int &height) const void Graphics::drawToStencilBuffer(StencilAction action, int value) { - writingToStencil = true; + if (!currentPass.active || !currentPass.info.stencil) + throw love::Exception("Stenciling must be enabled in the active render pass."); - // Make sure the active canvas has a stencil buffer. - if (Canvas::current) - Canvas::current->checkCreateStencil(); + writingToStencil = true; // Disable color writes but don't save the state for it. glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); @@ -737,6 +1195,9 @@ void Graphics::stopDrawToStencilBuffer() void Graphics::setStencilTest(CompareMode compare, int value) { + if (!currentPass.info.stencil && compare != COMPARE_ALWAYS) + throw love::Exception("Stenciling must be enabled in the active render pass."); + DisplayState &state = states.back(); state.stencilCompare = compare; state.stencilTestValue = value; @@ -750,10 +1211,6 @@ void Graphics::setStencilTest(CompareMode compare, int value) return; } - // Make sure the active canvas has a stencil buffer. - if (Canvas::current) - Canvas::current->checkCreateStencil(); - GLenum glcompare = GL_EQUAL; /** @@ -863,9 +1320,6 @@ Canvas *Graphics::newCanvas(int width, int height, Canvas::Format format, int ms else if (height > gl.getMaxTextureSize()) throw Exception("Cannot create canvas: height of %d pixels is too large for this system.", height); - while (GL_NO_ERROR != glGetError()) - /* clear opengl error flag */; - Canvas *canvas = new Canvas(width, height, format, msaa); GLenum err = canvas->getStatus(); @@ -873,40 +1327,8 @@ Canvas *Graphics::newCanvas(int width, int height, Canvas::Format format, int ms if (err == GL_FRAMEBUFFER_COMPLETE) return canvas; - // create error message - std::stringstream error_string; - error_string << "Cannot create canvas: "; - switch (err) - { - case GL_FRAMEBUFFER_UNSUPPORTED: - error_string << "Not supported by your OpenGL drivers."; - break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - error_string << "Texture format cannot be rendered to on this system."; - break; - // remaining error codes are highly unlikely: - case GL_FRAMEBUFFER_UNDEFINED: - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: - case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: - case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: - error_string << "Error in graphics driver."; - break; - default: - // my intel hda card wrongly returns 0 to glCheckFramebufferStatus() but sets - // no error flag. I think it meant to return GL_FRAMEBUFFER_UNSUPPORTED, but who - // knows. - if (glGetError() == GL_NO_ERROR) - error_string << "May not be supported by your OpenGL drivers."; - // the remaining error is an indication of a serious fuckup since it should - // only be returned if glCheckFramebufferStatus() was called with the wrong - // arguments. - else - error_string << "Cannot create canvas: Aliens did it (OpenGL error code: " << glGetError() << ")"; - } - canvas->release(); - throw Exception("%s", error_string.str().c_str()); + throw love::Exception("Cannot create Canvas: %s", OpenGL::framebufferStatusString(err)); return nullptr; // never reached } @@ -1016,74 +1438,6 @@ Shader *Graphics::getShader() const return states.back().shader.get(); } -void Graphics::setCanvas(Canvas *canvas) -{ - if (canvas == nullptr) - return setCanvas(); - - DisplayState &state = states.back(); - - canvas->startGrab(); - - std::vector> canvasref; - canvasref.push_back(canvas); - - std::swap(state.canvases, canvasref); -} - -void Graphics::setCanvas(const std::vector &canvases) -{ - if (canvases.size() == 0) - return setCanvas(); - else if (canvases.size() == 1) - return setCanvas(canvases[0]); - - DisplayState &state = states.back(); - - auto attachments = std::vector(canvases.begin() + 1, canvases.end()); - canvases[0]->startGrab(attachments); - - std::vector> canvasrefs; - canvasrefs.reserve(canvases.size()); - - for (Canvas *c : canvases) - canvasrefs.push_back(c); - - std::swap(state.canvases, canvasrefs); -} - -void Graphics::setCanvas(const std::vector> &canvases) -{ - std::vector canvaslist; - canvaslist.reserve(canvases.size()); - - for (const StrongRef &c : canvases) - canvaslist.push_back(c.get()); - - return setCanvas(canvaslist); -} - -void Graphics::setCanvas() -{ - DisplayState &state = states.back(); - - if (Canvas::current != nullptr) - Canvas::current->stopGrab(); - - state.canvases.clear(); -} - -std::vector Graphics::getCanvas() const -{ - std::vector canvases; - canvases.reserve(states.back().canvases.size()); - - for (const StrongRef &c : states.back().canvases) - canvases.push_back(c.get()); - - return canvases; -} - void Graphics::setColorMask(ColorMask mask) { glColorMask(mask.r, mask.g, mask.b, mask.a); @@ -1258,8 +1612,27 @@ bool Graphics::isWireframe() const return states.back().wireframe; } +void Graphics::draw(Drawable *drawable, const Matrix4 &m) +{ + if (!currentPass.active) + throw RenderOutsidePassException(); + + drawable->draw(m); +} + +void Graphics::drawq(Texture *texture, Quad *quad, const Matrix4 &m) +{ + if (!currentPass.active) + throw RenderOutsidePassException(); + + texture->drawq(quad, m); +} + void Graphics::print(const std::vector &str, const Matrix4 &m) { + if (!currentPass.active) + throw RenderOutsidePassException(); + checkSetDefaultFont(); DisplayState &state = states.back(); @@ -1270,6 +1643,9 @@ void Graphics::print(const std::vector &str, const Matrix4 void Graphics::printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m) { + if (!currentPass.active) + throw RenderOutsidePassException(); + checkSetDefaultFont(); DisplayState &state = states.back(); @@ -1284,6 +1660,9 @@ void Graphics::printf(const std::vector &str, float wrap, F void Graphics::points(const float *coords, const uint8 *colors, size_t numpoints) { + if (!currentPass.active) + throw RenderOutsidePassException(); + OpenGL::TempDebugGroup debuggroup("Graphics points draw"); gl.prepareDraw(); @@ -1305,6 +1684,9 @@ void Graphics::points(const float *coords, const uint8 *colors, size_t numpoints void Graphics::polyline(const float *coords, size_t count) { + if (!currentPass.active) + throw RenderOutsidePassException(); + const DisplayState &state = states.back(); if (state.lineJoin == LINE_JOIN_NONE) @@ -1335,6 +1717,9 @@ void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h) void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h, float rx, float ry, int points) { + if (!currentPass.active) + throw RenderOutsidePassException(); + if (rx == 0 || ry == 0) { rectangle(mode, x, y, w, h); @@ -1419,6 +1804,9 @@ void Graphics::circle(DrawMode mode, float x, float y, float radius) void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b, int points) { + if (!currentPass.active) + throw RenderOutsidePassException(); + float two_pi = static_cast(LOVE_M_PI * 2); if (points <= 0) points = 1; float angle_shift = (two_pi / points); @@ -1446,6 +1834,9 @@ void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b) void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float radius, float angle1, float angle2, int points) { + if (!currentPass.active) + throw RenderOutsidePassException(); + // Nothing to display with no points or equal angles. (Or is there with line mode?) if (points <= 0 || angle1 == angle2) return; @@ -1539,6 +1930,9 @@ void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float r /// @param count the number of coordinates/size of the array void Graphics::polygon(DrawMode mode, const float *coords, size_t count) { + if (!currentPass.active) + throw RenderOutsidePassException(); + // coords is an array of a closed loop of vertices, i.e. // coords[count-2] = coords[0], coords[count-1] = coords[1] if (mode == DRAW_LINE) @@ -1558,99 +1952,6 @@ void Graphics::polygon(DrawMode mode, const float *coords, size_t count) } } -love::image::ImageData *Graphics::newScreenshot(love::image::Image *image, bool copyAlpha) -{ - // Temporarily unbind the currently active canvas (glReadPixels reads the - // active framebuffer, not the main one.) - std::vector> canvases = states.back().canvases; - setCanvas(); - - int w = getWidth(); - int h = getHeight(); - - int row = 4 * w; - int size = row * h; - - GLubyte *pixels = nullptr; - GLubyte *screenshot = nullptr; - - try - { - pixels = new GLubyte[size]; - screenshot = new GLubyte[size]; - } - catch (std::exception &) - { - delete[] pixels; - delete[] screenshot; - setCanvas(canvases); - throw love::Exception("Out of memory."); - } - -#ifdef LOVE_IOS - SDL_SysWMinfo info = {}; - SDL_VERSION(&info.version); - SDL_GetWindowWMInfo(SDL_GL_GetCurrentWindow(), &info); - - if (info.info.uikit.resolveFramebuffer != 0) - { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, info.info.uikit.resolveFramebuffer); - - // We need to do an explicit MSAA resolve on iOS, because it uses GLES - // FBOs rather than a system framebuffer. - if (GLAD_ES_VERSION_3_0) - glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); - else if (GLAD_APPLE_framebuffer_multisample) - glResolveMultisampleFramebufferAPPLE(); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, info.info.uikit.resolveFramebuffer); - } -#endif - - glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - -#ifdef LOVE_IOS - // Restore the previous binding for the main framebuffer. - if (info.info.uikit.resolveFramebuffer != 0) - glBindFramebuffer(GL_FRAMEBUFFER, gl.getDefaultFBO()); -#endif - - if (!copyAlpha) - { - // Replace alpha values with full opacity. - for (int i = 3; i < size; i += 4) - pixels[i] = 255; - } - - // OpenGL sucks and reads pixels from the lower-left. Let's fix that. - GLubyte *src = pixels - row; - GLubyte *dst = screenshot + size; - - for (int i = 0; i < h; ++i) - memcpy(dst-=row, src+=row, row); - - delete[] pixels; - - love::image::ImageData *img = nullptr; - try - { - // Tell the new ImageData that it owns the screenshot data, so we don't - // need to delete it here. - img = image->newImageData(w, h, image::ImageData::FORMAT_RGBA8, screenshot, true); - } - catch (love::Exception &) - { - delete[] screenshot; - setCanvas(canvases); - throw; - } - - // Re-bind the active canvas, if necessary. - setCanvas(canvases); - - return img; -} - Graphics::RendererInfo Graphics::getRendererInfo() const { RendererInfo info; @@ -1686,7 +1987,7 @@ Graphics::Stats Graphics::getStats() const Stats stats; stats.drawCalls = gl.stats.drawCalls; - stats.canvasSwitches = gl.stats.framebufferBinds; + stats.renderPasses = renderPassCount; stats.shaderSwitches = gl.stats.shaderSwitches; stats.canvases = Canvas::canvasCount; stats.images = Image::imageCount; diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 138858103..785df2606 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -24,6 +24,7 @@ // STD #include #include +#include // OpenGL #include "OpenGL.h" @@ -35,8 +36,6 @@ #include "image/Image.h" #include "image/ImageData.h" -#include "window/Window.h" - #include "video/VideoStream.h" #include "Font.h" @@ -53,21 +52,63 @@ namespace love { + +class Reference; + namespace graphics { namespace opengl { +struct PassInfo +{ + enum BeginAction + { + BEGIN_LOAD, + BEGIN_CLEAR, + BEGIN_DISCARD, + }; + + enum EndAction + { + END_STORE, + END_DISCARD, + }; + + struct ColorAttachment + { + Canvas *canvas = nullptr; + Colorf clearColor = Colorf(0.0f, 0.0f, 0.0f, 0.0f); + BeginAction beginAction = BEGIN_LOAD; + }; + + ColorAttachment colorAttachments[MAX_COLOR_RENDER_TARGETS]; + int colorAttachmentCount = 0; + + bool stencil = false; + + bool addColorAttachment(const ColorAttachment &attachment) + { + if (colorAttachmentCount + 1 < MAX_COLOR_RENDER_TARGETS) + { + colorAttachments[colorAttachmentCount++] = attachment; + return true; + } + + return false; + } +}; + class Graphics : public love::graphics::Graphics { public: - struct OptionalColorf - { - float r, g, b, a; - bool enabled; + typedef void (*ScreenshotCallback)(love::image::ImageData *i, Reference *ref, void *ud); - Colorf toColor() const { return Colorf(r, g, b, a); } + struct ScreenshotInfo + { + ScreenshotCallback callback; + Reference *ref; }; Graphics(); @@ -92,25 +133,19 @@ public: **/ void reset(); - /** - * Clears the screen to a specific color. - **/ - void clear(Colorf c); + void beginPass(PassInfo::BeginAction beginAction, Colorf clearColor); + void beginPass(const PassInfo &info); - /** - * Clears each active canvas to a different color. - **/ - void clear(const std::vector &colors); + void endPass(); + void endPass(int sX, int sY, int sW, int sH, const ScreenshotInfo *info, void *screenshotCallbackData); - /** - * Discards the contents of the screen. - **/ - void discard(const std::vector &colorbuffers, bool stencil); + const PassInfo &getActivePass() const; + virtual bool isPassActive() const; /** * Flips buffers. (Rendered geometry is presented on screen). **/ - void present(); + void present(void *screenshotCallbackData); /** * Gets the width of the current graphics viewport. @@ -122,6 +157,9 @@ public: **/ int getHeight() const; + int getPassWidth() const; + int getPassHeight() const; + /** * True if a graphics viewport is set. **/ @@ -231,13 +269,6 @@ public: Shader *getShader() const; - void setCanvas(Canvas *canvas); - void setCanvas(const std::vector &canvases); - void setCanvas(const std::vector> &canvases); - void setCanvas(); - - std::vector getCanvas() const; - /** * Sets the enabled color components when rendering. **/ @@ -330,6 +361,9 @@ public: **/ bool isWireframe() const; + void draw(Drawable *drawable, const Matrix4 &m); + void drawq(Texture *texture, Quad *quad, const Matrix4 &m); + /** * Draws text at the specified coordinates **/ @@ -422,12 +456,7 @@ public: **/ void polygon(DrawMode mode, const float *coords, size_t count); - /** - * Creates a screenshot of the view and saves it to the default folder. - * @param image The love.image module. - * @param copyAlpha If the alpha channel should be copied or set to full opacity (1.0). - **/ - love::image::ImageData *newScreenshot(love::image::Image *image, bool copyAlpha = true); + void captureScreenshot(const ScreenshotInfo &info); /** * Returns system-dependent renderer information. @@ -488,8 +517,6 @@ private: StrongRef font; StrongRef shader; - std::vector> canvases; - ColorMask colorMask = ColorMask(true, true, true, true); bool wireframe = false; @@ -500,19 +527,47 @@ private: float defaultMipmapSharpness = 0.0f; }; + struct CurrentPass + { + PassInfo info; + bool active; + }; + + struct PassBufferInfo + { + bool stencil; + Canvas *canvases[MAX_COLOR_RENDER_TARGETS]; + }; + + struct CachedRenderbuffer + { + int w; + int h; + int samples; + GLenum attachments[2]; + GLuint renderbuffer; + }; + void restoreState(const DisplayState &s); void restoreStateChecked(const DisplayState &s); + void bindCachedFBOForPass(const PassInfo &pass); + void discard(OpenGL::FramebufferTarget target, const std::vector &colorbuffers, bool depthstencil); + GLuint attachCachedStencilBuffer(int w, int h, int samples); + void checkSetDefaultFont(); int calculateEllipsePoints(float rx, float ry) const; - StrongRef currentWindow; - StrongRef defaultFont; std::vector pixelSizeStack; // stores current size of a pixel (needed for line drawing) + std::vector pendingScreenshotCallbacks; + + std::unordered_map framebufferObjects; + std::vector stencilBuffers; + QuadIndices *quadIndices; int width; @@ -520,11 +575,17 @@ private: bool created; bool active; + bool canCaptureScreenshot; + + CurrentPass currentPass; + bool writingToStencil; std::vector states; std::vector stackTypes; // Keeps track of the pushed stack types. + int renderPassCount; + static const size_t MAX_USER_STACK_DEPTH = 64; }; // Graphics diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 50400c8ba..37615272d 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -23,7 +23,6 @@ #include "OpenGL.h" #include "Shader.h" -#include "Canvas.h" #include "common/Exception.h" // C++ @@ -135,6 +134,10 @@ void OpenGL::setupContext() else state.pointSize = 1.0f; + for (int i = 0; i < 2; i++) + state.boundFramebuffers[i] = std::numeric_limits::max(); + bindFramebuffer(FRAMEBUFFER_ALL, getDefaultFBO()); + if (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB || GLAD_EXT_sRGB_write_control) { @@ -283,7 +286,7 @@ void OpenGL::initMaxValues() glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxdrawbuffers); } - maxRenderTargets = std::min(maxattachments, maxdrawbuffers); + maxRenderTargets = std::max(std::min(maxattachments, maxdrawbuffers), 1); if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object || GLAD_EXT_framebuffer_multisample || GLAD_APPLE_framebuffer_multisample @@ -463,7 +466,7 @@ void OpenGL::useVertexAttribArrays(uint32 arraybits) glVertexAttrib4f(ATTRIB_COLOR, 1.0f, 1.0f, 1.0f, 1.0f); } -void OpenGL::setViewport(const OpenGL::Viewport &v) +void OpenGL::setViewport(const OpenGL::Viewport &v, bool canvasActive) { glViewport(v.x, v.y, v.w, v.h); state.viewport = v; @@ -471,7 +474,7 @@ void OpenGL::setViewport(const OpenGL::Viewport &v) // glScissor starts from the lower left, so we compensate when setting the // scissor. When the viewport is changed, we need to manually update the // scissor again. - setScissor(state.scissor); + setScissor(state.scissor, canvasActive); } OpenGL::Viewport OpenGL::getViewport() const @@ -479,9 +482,9 @@ OpenGL::Viewport OpenGL::getViewport() const return state.viewport; } -void OpenGL::setScissor(const OpenGL::Viewport &v) +void OpenGL::setScissor(const OpenGL::Viewport &v, bool canvasActive) { - if (Canvas::current) + if (canvasActive) glScissor(v.x, v.y, v.w, v.h); else { @@ -526,12 +529,53 @@ bool OpenGL::hasFramebufferSRGB() const return state.framebufferSRGBEnabled; } -void OpenGL::bindFramebuffer(GLenum target, GLuint framebuffer) +void OpenGL::bindFramebuffer(FramebufferTarget target, GLuint framebuffer) { - glBindFramebuffer(target, framebuffer); + bool bindingmodified = false; - if (target == GL_FRAMEBUFFER) - ++stats.framebufferBinds; + if ((target & FRAMEBUFFER_DRAW) && state.boundFramebuffers[0] != framebuffer) + { + bindingmodified = true; + state.boundFramebuffers[0] = framebuffer; + } + + if ((target & FRAMEBUFFER_READ) && state.boundFramebuffers[1] != framebuffer) + { + bindingmodified = true; + state.boundFramebuffers[1] = framebuffer; + } + + if (bindingmodified) + { + GLenum gltarget = GL_FRAMEBUFFER; + if (target == FRAMEBUFFER_DRAW) + gltarget = GL_DRAW_FRAMEBUFFER; + else if (target == FRAMEBUFFER_READ) + gltarget = GL_READ_FRAMEBUFFER; + + glBindFramebuffer(gltarget, framebuffer); + } +} + +GLenum OpenGL::getFramebuffer(FramebufferTarget target) const +{ + if (target & FRAMEBUFFER_DRAW) + return state.boundFramebuffers[0]; + else if (target & FRAMEBUFFER_READ) + return state.boundFramebuffers[1]; + else + return 0; +} + +void OpenGL::deleteFramebuffer(GLuint framebuffer) +{ + glDeleteFramebuffers(1, &framebuffer); + + for (int i = 0; i < 2; i++) + { + if (state.boundFramebuffers[i] == framebuffer) + state.boundFramebuffers[i] = 0; + } } void OpenGL::useProgram(GLuint program) @@ -680,7 +724,7 @@ int OpenGL::getMaxTextureSize() const int OpenGL::getMaxRenderTargets() const { - return maxRenderTargets; + return std::min(maxRenderTargets, MAX_COLOR_RENDER_TARGETS); } int OpenGL::getMaxRenderbufferSamples() const @@ -739,6 +783,36 @@ const char *OpenGL::errorString(GLenum errorcode) return text; } +const char *OpenGL::framebufferStatusString(GLenum status) +{ + switch (status) + { + case GL_FRAMEBUFFER_COMPLETE: + return "complete (success)"; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + return "Texture format cannot be rendered to on this system."; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + return "Error in graphics driver (missing render texture attachment)"; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: + return "Error in graphics driver (incomplete draw buffer)"; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: + return "Error in graphics driver (incomplete read buffer)"; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: + return "Canvas with the specified MSAA count cannot be rendered to on this system."; + case GL_FRAMEBUFFER_UNSUPPORTED: + return "Renderable textures are unsupported"; + default: + break; + } + + static char text[64] = {}; + + memset(text, 0, sizeof(text)); + sprintf(text, "0x%x", status); + + return text; +} + const char *OpenGL::debugSeverityString(GLenum severity) { switch (severity) diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index 64b4ff6c8..ce186e607 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/src/modules/graphics/opengl/OpenGL.h @@ -104,6 +104,13 @@ public: VENDOR_UNKNOWN }; + enum FramebufferTarget + { + FRAMEBUFFER_READ = (1 << 1), + FRAMEBUFFER_DRAW = (1 << 2), + FRAMEBUFFER_ALL = (FRAMEBUFFER_READ | FRAMEBUFFER_DRAW), + }; + // A rectangle representing an OpenGL viewport or a scissor box. struct Viewport { @@ -173,7 +180,6 @@ public: { size_t textureMemory; int drawCalls; - int framebufferBinds; int shaderSwitches; } stats; @@ -282,7 +288,7 @@ public: * Sets the OpenGL rendering viewport to the specified rectangle. * The y-coordinate starts at the top. **/ - void setViewport(const Viewport &v); + void setViewport(const Viewport &v, bool canvasActive); /** * Gets the current OpenGL rendering viewport rectangle. @@ -293,7 +299,7 @@ public: * Sets the scissor box to the specified rectangle. * The y-coordinate starts at the top and is flipped internally. **/ - void setScissor(const Viewport &v); + void setScissor(const Viewport &v, bool canvasActive); /** * Gets the current scissor box (regardless of whether scissoring is enabled.) @@ -323,7 +329,9 @@ public: /** * Binds a Framebuffer Object to the specified target. **/ - void bindFramebuffer(GLenum target, GLuint framebuffer); + void bindFramebuffer(FramebufferTarget target, GLuint framebuffer); + GLuint getFramebuffer(FramebufferTarget target) const; + void deleteFramebuffer(GLuint framebuffer); /** * Calls glUseProgram. @@ -413,6 +421,7 @@ public: static GLint getGLWrapMode(Texture::WrapMode wmode); static const char *errorString(GLenum errorcode); + static const char *framebufferStatusString(GLenum status); // Get human-readable strings for debug info. static const char *debugSeverityString(GLenum severity); @@ -456,6 +465,8 @@ private: float pointSize; + GLuint boundFramebuffers[2]; + bool framebufferSRGBEnabled; GLuint defaultTexture; diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index 8f5b8ddc9..6bdff33e1 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -22,7 +22,7 @@ #include "common/config.h" #include "Shader.h" -#include "Canvas.h" +#include "Graphics.h" // C++ #include @@ -76,7 +76,7 @@ Shader::Shader(const ShaderSource &source) , program(0) , builtinUniforms() , builtinAttributes() - , lastCanvas((Canvas *) -1) + , canvasWasActive(false) , lastViewport() , lastPointSize(0.0f) , videoTextureUnits() @@ -237,7 +237,7 @@ bool Shader::loadVolatile() OpenGL::TempDebugGroup debuggroup("Shader load"); // Recreating the shader program will invalidate uniforms that rely on these. - lastCanvas = (Canvas *) -1; + canvasWasActive = false; lastViewport = OpenGL::Viewport(); lastPointSize = -1.0f; @@ -339,11 +339,11 @@ bool Shader::loadVolatile() void Shader::unloadVolatile() { - if (current == this) - gl.useProgram(0); - if (program != 0) { + if (current == this) + gl.useProgram(0); + glDeleteProgram(program); program = 0; } @@ -686,7 +686,10 @@ void Shader::checkSetScreenParams() { OpenGL::Viewport view = gl.getViewport(); - if (view == lastViewport && lastCanvas == Canvas::current) + auto gfx = Module::getInstance(Module::M_GRAPHICS); + bool canvasActive = gfx->getActivePass().colorAttachmentCount > 0; + + if (view == lastViewport && canvasWasActive == canvasActive) return; // In the shader, we do pixcoord.y = gl_FragCoord.y * params.z + params.w. @@ -697,7 +700,7 @@ void Shader::checkSetScreenParams() 0.0f, 0.0f, }; - if (Canvas::current != nullptr) + if (canvasActive) { // No flipping: pixcoord.y = gl_FragCoord.y * 1.0 + 0.0. params[2] = 1.0f; @@ -719,7 +722,7 @@ void Shader::checkSetScreenParams() glUniform4fv(location, 1, params); } - lastCanvas = Canvas::current; + canvasWasActive = canvasActive; lastViewport = view; } diff --git a/src/modules/graphics/opengl/Shader.h b/src/modules/graphics/opengl/Shader.h index 61915061f..8561f3b1e 100644 --- a/src/modules/graphics/opengl/Shader.h +++ b/src/modules/graphics/opengl/Shader.h @@ -41,8 +41,6 @@ namespace graphics namespace opengl { -class Canvas; - // A GLSL shader class Shader : public Object, public Volatile { @@ -245,8 +243,7 @@ private: // Uniform name to retainable objects std::map boundRetainables; - // Pointer to the active Canvas when the screen params were last checked. - Canvas *lastCanvas; + bool canvasWasActive; OpenGL::Viewport lastViewport; float lastPointSize; diff --git a/src/modules/graphics/opengl/wrap_Canvas.cpp b/src/modules/graphics/opengl/wrap_Canvas.cpp index d1bdd5f3a..50ad2d752 100644 --- a/src/modules/graphics/opengl/wrap_Canvas.cpp +++ b/src/modules/graphics/opengl/wrap_Canvas.cpp @@ -33,55 +33,6 @@ Canvas *luax_checkcanvas(lua_State *L, int idx) return luax_checktype(L, idx, GRAPHICS_CANVAS_ID); } -int w_Canvas_renderTo(lua_State *L) -{ - Canvas *canvas = luax_checkcanvas(L, 1); - luaL_checktype(L, 2, LUA_TFUNCTION); - - auto graphics = Module::getInstance(Module::M_GRAPHICS); - - if (graphics) - { - // Save the current Canvas so we can restore it when we're done. - std::vector oldcanvases = graphics->getCanvas(); - - for (Canvas *c : oldcanvases) - c->retain(); - - luax_catchexcept(L, [&](){ graphics->setCanvas(canvas); }); - - lua_settop(L, 2); // make sure the function is on top of the stack - int status = lua_pcall(L, 0, 0, 0); - - graphics->setCanvas(oldcanvases); - - for (Canvas *c : oldcanvases) - c->release(); - - if (status != 0) - return lua_error(L); - } - - return 0; -} - -int w_Canvas_newImageData(lua_State *L) -{ - Canvas *canvas = luax_checkcanvas(L, 1); - love::image::Image *image = luax_getmodule(L, MODULE_IMAGE_ID); - int x = (int) luaL_optnumber(L, 2, 0); - int y = (int) luaL_optnumber(L, 3, 0); - int w = (int) luaL_optnumber(L, 4, canvas->getWidth()); - int h = (int) luaL_optnumber(L, 5, canvas->getHeight()); - - love::image::ImageData *img = nullptr; - luax_catchexcept(L, [&](){ img = canvas->newImageData(image, x, y, w, h); }); - - luax_pushtype(L, IMAGE_IMAGE_DATA_ID, img); - img->release(); - return 1; -} - int w_Canvas_getFormat(lua_State *L) { Canvas *canvas = luax_checkcanvas(L, 1); @@ -103,8 +54,6 @@ int w_Canvas_getMSAA(lua_State *L) static const luaL_Reg w_Canvas_functions[] = { - { "renderTo", w_Canvas_renderTo }, - { "newImageData", w_Canvas_newImageData }, { "getFormat", w_Canvas_getFormat }, { "getMSAA", w_Canvas_getMSAA }, { 0, 0 } diff --git a/src/modules/graphics/opengl/wrap_Graphics.cpp b/src/modules/graphics/opengl/wrap_Graphics.cpp index 99ee0e37b..7abed19cd 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/src/modules/graphics/opengl/wrap_Graphics.cpp @@ -27,6 +27,7 @@ #include "filesystem/wrap_Filesystem.h" #include "video/VideoStream.h" #include "image/wrap_Image.h" +#include "common/Reference.h" #include #include @@ -60,79 +61,9 @@ int w_reset(lua_State *) return 0; } -int w_clear(lua_State *L) +int w_present(lua_State *L) { - Colorf color; - - if (lua_isnoneornil(L, 1)) - color.set(0.0, 0.0, 0.0, 0.0); - else if (lua_istable(L, 1)) - { - std::vector colors((size_t) lua_gettop(L)); - - for (int i = 0; i < lua_gettop(L); i++) - { - if (lua_isnoneornil(L, i + 1) || luax_objlen(L, i + 1) == 0) - { - colors[i].enabled = false; - continue; - } - - for (int j = 1; j <= 4; j++) - lua_rawgeti(L, i + 1, j); - - colors[i].enabled = true; - colors[i].r = (float) luaL_checknumber(L, -4); - colors[i].g = (float) luaL_checknumber(L, -3); - colors[i].b = (float) luaL_checknumber(L, -2); - colors[i].a = (float) luaL_optnumber(L, -1, 1.0); - - lua_pop(L, 4); - } - - luax_catchexcept(L, [&]() { instance()->clear(colors); }); - return 0; - } - else - { - color.r = (float) luaL_checknumber(L, 1); - color.g = (float) luaL_checknumber(L, 2); - color.b = (float) luaL_checknumber(L, 3); - color.a = (float) luaL_optnumber(L, 4, 1.0); - } - - luax_catchexcept(L, [&]() { instance()->clear(color); }); - return 0; -} - -int w_discard(lua_State *L) -{ - std::vector colorbuffers; - - if (lua_istable(L, 1)) - { - for (size_t i = 1; i <= luax_objlen(L, 1); i++) - { - lua_rawgeti(L, 1, i); - colorbuffers.push_back(luax_optboolean(L, -1, true)); - lua_pop(L, 1); - } - } - else - { - bool discardcolor = luax_optboolean(L, 1, true); - size_t numbuffers = std::max((size_t) 1, instance()->getCanvas().size()); - colorbuffers = std::vector(numbuffers, discardcolor); - } - - bool stencil = luax_optboolean(L, 2, true); - instance()->discard(colorbuffers, stencil); - return 0; -} - -int w_present(lua_State *) -{ - instance()->present(); + luax_catchexcept(L, [&]() { instance()->present(L); }); return 0; } @@ -173,6 +104,274 @@ int w_getDimensions(lua_State *L) return 2; } +int w_getPassWidth(lua_State *L) +{ + lua_pushinteger(L, instance()->getPassWidth()); + return 1; +} + +int w_getPassHeight(lua_State *L) +{ + lua_pushinteger(L, instance()->getPassHeight()); + return 1; +} + +int w_getPassDimensions(lua_State *L) +{ + lua_pushinteger(L, instance()->getPassWidth()); + lua_pushinteger(L, instance()->getPassHeight()); + return 2; +} + +static int w__beginPass(lua_State *L) +{ + int nextstartidx = 1; + + if (lua_isnoneornil(L, 1)) + { + luax_catchexcept(L, [&]() { instance()->beginPass(PassInfo::BEGIN_LOAD, Colorf()); }); + nextstartidx = 1; + } + else if (lua_isnumber(L, 1)) + { + Colorf c; + c.r = (float) luaL_checknumber(L, 1); + c.g = (float) luaL_checknumber(L, 2); + c.b = (float) luaL_checknumber(L, 3); + + if (lua_isnumber(L, 4)) + { + c.a = (float) lua_tonumber(L, 4); + nextstartidx = 5; + } + else + { + c.a = 1.0f; + nextstartidx = 4; + } + + luax_catchexcept(L, [&]() { instance()->beginPass(PassInfo::BEGIN_CLEAR, c); }); + } + else if (luax_istype(L, 1, GRAPHICS_CANVAS_ID)) + { + PassInfo::ColorAttachment attachment; + attachment.canvas = luax_checkcanvas(L, 1); + attachment.beginAction = PassInfo::BEGIN_LOAD; + + if (lua_isnumber(L, 2)) + { + attachment.beginAction = PassInfo::BEGIN_CLEAR; + attachment.clearColor.r = (float) luaL_checknumber(L, 2); + attachment.clearColor.g = (float) luaL_checknumber(L, 3); + attachment.clearColor.b = (float) luaL_checknumber(L, 4); + + if (lua_isnumber(L, 5)) + { + attachment.clearColor.a = (float) lua_tonumber(L, 5); + nextstartidx = 6; + } + else + { + attachment.clearColor.a = 1.0f; + nextstartidx = 5; + } + } + else + nextstartidx = 2; + + PassInfo info; + info.addColorAttachment(attachment); + + if (lua_isboolean(L, nextstartidx)) + { + info.stencil = luax_toboolean(L, nextstartidx); + nextstartidx++; + } + else + info.stencil = false; + + luax_catchexcept(L, [&]() { instance()->beginPass(info); }); + } + else + { + luaL_checktype(L, 1, LUA_TTABLE); + + int nattachments = std::max((int) luax_objlen(L, 1), 1); + + if (nattachments > MAX_COLOR_RENDER_TARGETS) + return luaL_error(L, "Cannot render to %d Canvases at once!", nattachments); + + PassInfo info; + + for (int i = 1; i <= nattachments; i++) + { + lua_rawgeti(L, 1, i); + luaL_checktype(L, -1, LUA_TTABLE); + + PassInfo::ColorAttachment attachment; + attachment.beginAction = PassInfo::BEGIN_LOAD; + + lua_rawgeti(L, -1, 1); + attachment.canvas = luax_checkcanvas(L, -1); + + lua_rawgeti(L, -2, 2); + if (!lua_isnoneornil(L, -1)) + { + attachment.beginAction = PassInfo::BEGIN_CLEAR; + + for (int j = 3; j < 6; j++) + lua_rawgeti(L, -j, j); + + attachment.clearColor.r = (float) luaL_checknumber(L, -4); + attachment.clearColor.g = (float) luaL_checknumber(L, -3); + attachment.clearColor.b = (float) luaL_checknumber(L, -2); + attachment.clearColor.a = (float) luaL_optnumber(L, -1, 1.0); + } + + lua_pop(L, 2 + (attachment.beginAction == PassInfo::BEGIN_CLEAR ? 4 : 1)); + + info.addColorAttachment(attachment); + } + + info.stencil = luax_boolflag(L, 1, "stencil", false); + + luax_catchexcept(L, [&]() { instance()->beginPass(info); }); + + nextstartidx = 2; + } + + return nextstartidx; +} + +int w_beginPass(lua_State *L) +{ + w__beginPass(L); + return 0; +} + +static void screenshotCallback(love::image::ImageData *i, Reference *ref, void *gd) +{ + if (i != nullptr) + { + lua_State *L = (lua_State *) gd; + ref->push(L); + delete ref; + luax_pushtype(L, IMAGE_IMAGE_DATA_ID, i); + lua_call(L, 1, 0); + } + else + delete ref; +} + +static int w__endPass(lua_State *L, int startidx) +{ + if (lua_isnoneornil(L, startidx)) + { + luax_catchexcept(L, []() { instance()->endPass(); }); + } + else + { + int x, y, w, h; + + if (lua_isnumber(L, startidx)) + { + x = (int) luaL_checknumber(L, 1); + y = (int) luaL_checknumber(L, 2); + w = (int) luaL_checknumber(L, 3); + h = (int) luaL_checknumber(L, 4); + startidx += 4; + } + else + { + x = 0; + y = 0; + w = instance()->getPassWidth(); + h = instance()->getPassHeight(); + } + + luaL_checktype(L, startidx, LUA_TFUNCTION); + + Graphics::ScreenshotInfo info; + info.callback = screenshotCallback; + + lua_pushvalue(L, startidx); + info.ref = luax_refif(L, LUA_TFUNCTION); + lua_pop(L, 1); + + luax_catchexcept(L, + [&]() { instance()->endPass(x, y, w, h, &info, L); }, + [&](bool except) { if (except) delete info.ref; } + ); + } + + return 0; +} + +int w_endPass(lua_State *L) +{ + return w__endPass(L, 1); +} + +int w_renderPass(lua_State *L) +{ + int startidx = w__beginPass(L); + + if (lua_type(L, startidx) != LUA_TFUNCTION) + { + w__endPass(L, startidx + 1); + luaL_checktype(L, startidx, LUA_TFUNCTION); + return 0; + } + + int nargs = lua_gettop(L) - startidx; + int status = lua_pcall(L, nargs, 0, 0); + + w__endPass(L, startidx + 1); + + if (status != 0) + return lua_error(L); + + return 0; +} + +int w_isPassActive(lua_State *L) +{ + luax_pushboolean(L, instance()->isPassActive()); + return 1; +} + +int w_getPassCanvases(lua_State *L) +{ + if (!instance()->isPassActive()) + return 0; + + const PassInfo &info = instance()->getActivePass(); + + for (const auto &attachment : info.colorAttachments) + luax_pushtype(L, GRAPHICS_CANVAS_ID, attachment.canvas); + + return info.colorAttachmentCount; +} + +int w_captureScreenshot(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TFUNCTION); + + Graphics::ScreenshotInfo info; + info.callback = screenshotCallback; + + lua_pushvalue(L, 1); + info.ref = luax_refif(L, LUA_TFUNCTION); + lua_pop(L, 1); + + luax_catchexcept(L, + [&]() { instance()->captureScreenshot(info); }, + [&](bool except) { if (except) delete info.ref; } + ); + + return 0; +} + int w_setScissor(lua_State *L) { int nargs = lua_gettop(L); @@ -243,13 +442,13 @@ int w_stencil(lua_State *L) if (lua_toboolean(L, 4) == 0) instance()->clearStencil(); - instance()->drawToStencilBuffer(action, stencilvalue); + luax_catchexcept(L, [&](){ instance()->drawToStencilBuffer(action, stencilvalue); }); // Call stencilfunc() lua_pushvalue(L, 1); lua_call(L, 0, 0); - instance()->stopDrawToStencilBuffer(); + luax_catchexcept(L, [&](){ instance()->stopDrawToStencilBuffer(); }); return 0; } @@ -268,7 +467,7 @@ int w_setStencilTest(lua_State *L) comparevalue = (int) luaL_checknumber(L, 2); } - instance()->setStencilTest(compare, comparevalue); + luax_catchexcept(L, [&](){ instance()->setStencilTest(compare, comparevalue); }); return 0; } @@ -1246,79 +1445,6 @@ int w_isWireframe(lua_State *L) return 1; } -int w_newScreenshot(lua_State *L) -{ - love::image::Image *image = luax_getmodule(L, MODULE_IMAGE_ID); - bool copyAlpha = luax_optboolean(L, 1, false); - love::image::ImageData *i = 0; - - luax_catchexcept(L, [&](){ i = instance()->newScreenshot(image, copyAlpha); }); - - luax_pushtype(L, IMAGE_IMAGE_DATA_ID, i); - i->release(); - return 1; -} - -int w_setCanvas(lua_State *L) -{ - // Disable stencil writes. - instance()->stopDrawToStencilBuffer(); - - // called with none -> reset to default buffer - if (lua_isnoneornil(L, 1)) - { - instance()->setCanvas(); - return 0; - } - - bool is_table = lua_istable(L, 1); - std::vector canvases; - - if (is_table) - { - for (int i = 1; i <= (int) luax_objlen(L, 1); i++) - { - lua_rawgeti(L, 1, i); - canvases.push_back(luax_checkcanvas(L, -1)); - lua_pop(L, 1); - } - } - else - { - for (int i = 1; i <= lua_gettop(L); i++) - canvases.push_back(luax_checkcanvas(L, i)); - } - - luax_catchexcept(L, [&]() { - if (canvases.size() > 0) - instance()->setCanvas(canvases); - else - instance()->setCanvas(); - }); - - return 0; -} - -int w_getCanvas(lua_State *L) -{ - const std::vector canvases = instance()->getCanvas(); - int n = 0; - - for (Canvas *c : canvases) - { - luax_pushtype(L, GRAPHICS_CANVAS_ID, c); - n++; - } - - if (n == 0) - { - lua_pushnil(L); - n = 1; - } - - return n; -} - int w_setShader(lua_State *L) { if (lua_isnoneornil(L,1)) @@ -1497,8 +1623,8 @@ int w_getStats(lua_State *L) lua_pushinteger(L, stats.drawCalls); lua_setfield(L, -2, "drawcalls"); - lua_pushinteger(L, stats.canvasSwitches); - lua_setfield(L, -2, "canvasswitches"); + lua_pushinteger(L, stats.renderPasses); + lua_setfield(L, -2, "renderpasses"); lua_pushinteger(L, stats.shaderSwitches); lua_setfield(L, -2, "shaderswitches"); @@ -1555,9 +1681,9 @@ int w_draw(lua_State *L) luax_catchexcept(L, [&]() { if (texture && quad) - texture->drawq(quad, m); + instance()->drawq(texture, quad, m); else if (drawable) - drawable->draw(m); + instance()->draw(drawable, m); }); return 0; @@ -1698,11 +1824,14 @@ int w_points(lua_State *L) coords[i] = luax_tofloat(L, i + 1); } - instance()->points(coords, colors, numpoints); - - delete[] coords; - if (colors) - delete[] colors; + luax_catchexcept(L, + [&](){ instance()->points(coords, colors, numpoints); }, + [&](bool) { + delete[] coords; + if (colors) + delete[] colors; + } + ); return 0; } @@ -1738,9 +1867,11 @@ int w_line(lua_State *L) coords[i] = luax_tofloat(L, i + 1); } - instance()->polyline(coords, args); + luax_catchexcept(L, + [&](){ instance()->polyline(coords, args); }, + [&](bool) { delete[] coords; } + ); - delete[] coords; return 0; } @@ -1766,11 +1897,11 @@ int w_rectangle(lua_State *L) float ry = (float)luaL_optnumber(L, 7, rx); if (lua_isnoneornil(L, 8)) - instance()->rectangle(mode, x, y, w, h, rx, ry); + luax_catchexcept(L, [&](){ instance()->rectangle(mode, x, y, w, h, rx, ry); }); else { int points = (int) luaL_checknumber(L, 8); - instance()->rectangle(mode, x, y, w, h, rx, ry, points); + luax_catchexcept(L, [&](){ instance()->rectangle(mode, x, y, w, h, rx, ry, points); }); } return 0; @@ -1788,11 +1919,11 @@ int w_circle(lua_State *L) float radius = (float)luaL_checknumber(L, 4); if (lua_isnoneornil(L, 5)) - instance()->circle(mode, x, y, radius); + luax_catchexcept(L, [&](){ instance()->circle(mode, x, y, radius); }); else { int points = (int) luaL_checknumber(L, 5); - instance()->circle(mode, x, y, radius, points); + luax_catchexcept(L, [&](){ instance()->circle(mode, x, y, radius, points); }); } return 0; @@ -1811,11 +1942,11 @@ int w_ellipse(lua_State *L) float b = (float)luaL_optnumber(L, 5, a); if (lua_isnoneornil(L, 6)) - instance()->ellipse(mode, x, y, a, b); + luax_catchexcept(L, [&](){ instance()->ellipse(mode, x, y, a, b); }); else { int points = (int) luaL_checknumber(L, 6); - instance()->ellipse(mode, x, y, a, b, points); + luax_catchexcept(L, [&](){ instance()->ellipse(mode, x, y, a, b, points); }); } return 0; @@ -1848,11 +1979,11 @@ int w_arc(lua_State *L) float angle2 = (float) luaL_checknumber(L, startidx + 4); if (lua_isnoneornil(L, startidx + 5)) - instance()->arc(drawmode, arcmode, x, y, radius, angle1, angle2); + luax_catchexcept(L, [&](){ instance()->arc(drawmode, arcmode, x, y, radius, angle1, angle2); }); else { int points = (int) luaL_checknumber(L, startidx + 5); - instance()->arc(drawmode, arcmode, x, y, radius, angle1, angle2, points); + luax_catchexcept(L, [&](){ instance()->arc(drawmode, arcmode, x, y, radius, angle1, angle2, points); }); } return 0; @@ -1900,8 +2031,11 @@ int w_polygon(lua_State *L) // make a closed loop coords[args] = coords[0]; coords[args+1] = coords[1]; - instance()->polygon(mode, coords, args+2); - delete[] coords; + + luax_catchexcept(L, + [&](){ instance()->polygon(mode, coords, args+2); }, + [&](bool) { delete[] coords; } + ); return 0; } @@ -1987,8 +2121,6 @@ int w_inverseTransformPoint(lua_State *L) static const luaL_Reg functions[] = { { "reset", w_reset }, - { "clear", w_clear }, - { "discard", w_discard }, { "present", w_present }, { "newImage", w_newImage }, @@ -2030,9 +2162,6 @@ static const luaL_Reg functions[] = { "getPointSize", w_getPointSize }, { "setWireframe", w_setWireframe }, { "isWireframe", w_isWireframe }, - { "newScreenshot", w_newScreenshot }, - { "setCanvas", w_setCanvas }, - { "getCanvas", w_getCanvas }, { "setShader", w_setShader }, { "getShader", w_getShader }, @@ -2046,6 +2175,14 @@ static const luaL_Reg functions[] = { "getSystemLimits", w_getSystemLimits }, { "getStats", w_getStats }, + { "beginPass", w_beginPass }, + { "endPass", w_endPass }, + { "renderPass", w_renderPass }, + { "isPassActive", w_isPassActive }, + { "getPassCanvases", w_getPassCanvases }, + + { "captureScreenshot", w_captureScreenshot }, + { "draw", w_draw }, { "print", w_print }, @@ -2057,6 +2194,9 @@ static const luaL_Reg functions[] = { "getWidth", w_getWidth }, { "getHeight", w_getHeight }, { "getDimensions", w_getDimensions }, + { "getPassWidth", w_getPassWidth }, + { "getPassHeight", w_getPassHeight }, + { "getPassDimensions", w_getPassDimensions }, { "setScissor", w_setScissor }, { "intersectScissor", w_intersectScissor }, diff --git a/src/modules/window/Window.h b/src/modules/window/Window.h index c15d734c7..3549d384d 100644 --- a/src/modules/window/Window.h +++ b/src/modules/window/Window.h @@ -32,6 +32,12 @@ namespace love { + +namespace graphics +{ +class Graphics; +} + namespace window { @@ -108,6 +114,8 @@ public: // Implements Module. virtual ModuleType getModuleType() const { return M_WINDOW; } + virtual void setGraphics(graphics::Graphics *graphics) = 0; + virtual bool setWindow(int width = 800, int height = 600, WindowSettings *settings = nullptr) = 0; virtual void getWindow(int &width, int &height, WindowSettings &settings) = 0; diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index 81423f9c4..12c3ebe19 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/src/modules/window/sdl/Window.cpp @@ -79,9 +79,16 @@ Window::~Window() { close(); + graphics.set(nullptr); + SDL_QuitSubSystem(SDL_INIT_VIDEO); } +void Window::setGraphics(graphics::Graphics *graphics) +{ + this->graphics.set(graphics); +} + void Window::setGLFramebufferAttributes(int msaa, bool sRGB) { // Set GL window / framebuffer attributes. @@ -401,6 +408,12 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla bool Window::setWindow(int width, int height, WindowSettings *settings) { + if (!graphics.get()) + graphics.set(Module::getInstance(Module::M_GRAPHICS)); + + if (graphics.get() && graphics->isPassActive()) + throw love::Exception("setMode cannot be called while a render pass is active in love.graphics."); + WindowSettings f; if (settings) @@ -507,9 +520,8 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) updateSettings(f, false); - auto gfx = Module::getInstance(Module::M_GRAPHICS); - if (gfx != nullptr) - gfx->setMode(pixelWidth, pixelHeight); + if (graphics.get()) + graphics->setMode(pixelWidth, pixelHeight); #ifdef LOVE_ANDROID love::android::setImmersive(f.fullscreen); @@ -528,9 +540,8 @@ bool Window::onSizeChanged(int width, int height) SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight); - auto gfx = Module::getInstance(Module::M_GRAPHICS); - if (gfx != nullptr) - gfx->setViewportSize(pixelWidth, pixelHeight); + if (graphics.get()) + graphics->setViewportSize(pixelWidth, pixelHeight); return true; } @@ -596,13 +607,9 @@ void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphi // May be 0 if the refresh rate can't be determined. settings.refreshrate = (double) dmode.refresh_rate; - if (updateGraphicsViewport) - { - // Update the viewport size now instead of waiting for event polling. - auto gfx = Module::getInstance(Module::M_GRAPHICS); - if (gfx != nullptr) - gfx->setViewportSize(pixelWidth, pixelHeight); - } + // Update the viewport size now instead of waiting for event polling. + if (updateGraphicsViewport && graphics.get()) + graphics->setViewportSize(pixelWidth, pixelHeight); } void Window::getWindow(int &width, int &height, WindowSettings &newsettings) @@ -618,9 +625,13 @@ void Window::getWindow(int &width, int &height, WindowSettings &newsettings) void Window::close() { - auto gfx = Module::getInstance(Module::M_GRAPHICS); - if (gfx != nullptr) - gfx->unSetMode(); + if (graphics.get()) + { + if (graphics->isPassActive()) + throw love::Exception("close cannot be called while a render pass is active in love.graphics."); + + graphics->unSetMode(); + } if (context) { @@ -646,6 +657,9 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype) if (!window) return false; + if (graphics.get() && graphics->isPassActive()) + throw love::Exception("setFullscreen cannot be called while a render pass is active in love.graphics."); + WindowSettings newsettings = settings; newsettings.fullscreen = fullscreen; newsettings.fstype = fstype; diff --git a/src/modules/window/sdl/Window.h b/src/modules/window/sdl/Window.h index ade2d6f59..91e68e04f 100644 --- a/src/modules/window/sdl/Window.h +++ b/src/modules/window/sdl/Window.h @@ -41,6 +41,8 @@ public: Window(); ~Window(); + void setGraphics(graphics::Graphics *graphics); + bool setWindow(int width = 800, int height = 600, WindowSettings *settings = nullptr); void getWindow(int &width, int &height, WindowSettings &settings); @@ -149,6 +151,8 @@ private: bool hasSDL203orEarlier; ContextAttribs contextAttribs; + StrongRef graphics; + }; // Window } // sdl diff --git a/src/modules/window/wrap_Window.cpp b/src/modules/window/wrap_Window.cpp index f9a406eb4..bb439f3ef 100644 --- a/src/modules/window/wrap_Window.cpp +++ b/src/modules/window/wrap_Window.cpp @@ -119,7 +119,7 @@ int w_setMode(lua_State *L) if (lua_isnoneornil(L, 3)) { - luax_pushboolean(L, instance()->setWindow(w, h, nullptr)); + luax_catchexcept(L, [&](){ luax_pushboolean(L, instance()->setWindow(w, h, nullptr)); }); return 1; } @@ -266,10 +266,12 @@ int w_setFullscreen(lua_State *L) return luaL_error(L, "Invalid fullscreen type: %s", typestr); bool success = false; - if (fstype == Window::FULLSCREEN_MAX_ENUM) - success = instance()->setFullscreen(fullscreen); - else - success = instance()->setFullscreen(fullscreen, fstype); + luax_catchexcept(L, [&]() { + if (fstype == Window::FULLSCREEN_MAX_ENUM) + success = instance()->setFullscreen(fullscreen); + else + success = instance()->setFullscreen(fullscreen, fstype); + }); luax_pushboolean(L, success); return 1; @@ -296,9 +298,9 @@ int w_isOpen(lua_State *L) return 1; } -int w_close(lua_State * /*L*/) +int w_close(lua_State *L) { - instance()->close(); + luax_catchexcept(L, [&]() { instance()->close(); }); return 0; } diff --git a/src/scripts/boot.lua b/src/scripts/boot.lua index 98c2c9129..210643f86 100644 --- a/src/scripts/boot.lua +++ b/src/scripts/boot.lua @@ -545,9 +545,14 @@ function love.run() if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled if love.graphics and love.graphics.isActive() then - love.graphics.clear(love.graphics.getBackgroundColor()) love.graphics.origin() + + if love.drawpasses then love.drawpasses() end + + love.graphics.beginPass(love.graphics.getBackgroundColor()) if love.draw then love.draw() end + love.graphics.endPass() + love.graphics.present() end @@ -598,15 +603,18 @@ function love.errhand(msg) end end if love.audio then love.audio.stop() end + love.graphics.reset() + if love.graphics.isPassActive() then + love.graphics.endPass() + end + local font = love.graphics.setNewFont(math.floor(love.window.toPixels(14))) - love.graphics.setBackgroundColor(89/255, 157/255, 220/255) love.graphics.setColor(1, 1, 1, 1) local trace = debug.traceback() - love.graphics.clear(love.graphics.getBackgroundColor()) love.graphics.origin() local err = {} @@ -628,9 +636,7 @@ function love.errhand(msg) local function draw() local pos = love.window.toPixels(70) - love.graphics.clear(love.graphics.getBackgroundColor()) love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos) - love.graphics.present() end while true do @@ -652,7 +658,8 @@ function love.errhand(msg) end end - draw() + love.graphics.renderPass(89/255, 157/255, 220/255, draw) + love.graphics.present() if love.timer then love.timer.sleep(0.1) diff --git a/src/scripts/boot.lua.h b/src/scripts/boot.lua.h index 8f499c12a..cb0762d0a 100644 --- a/src/scripts/boot.lua.h +++ b/src/scripts/boot.lua.h @@ -1005,14 +1005,19 @@ const unsigned char boot_lua[] = 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x20, 0x61, 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, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, - 0x2e, 0x67, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, - 0x72, 0x28, 0x29, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x28, 0x29, 0x0a, + 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x70, 0x61, 0x73, + 0x73, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, + 0x70, 0x61, 0x73, 0x73, 0x65, 0x73, 0x28, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x62, + 0x65, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, + 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x29, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x65, + 0x6e, 0x64, 0x50, 0x61, 0x73, 0x73, 0x28, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x0a, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, @@ -1098,23 +1103,21 @@ const unsigned char boot_lua[] = 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x72, 0x65, 0x73, 0x65, 0x74, 0x28, 0x29, 0x0a, + 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, + 0x69, 0x73, 0x50, 0x61, 0x73, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, + 0x6e, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x65, 0x6e, + 0x64, 0x50, 0x61, 0x73, 0x73, 0x28, 0x29, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x4e, 0x65, 0x77, 0x46, 0x6f, 0x6e, 0x74, 0x28, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x74, 0x6f, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x28, 0x31, 0x34, 0x29, 0x29, 0x29, 0x0a, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, - 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x38, 0x39, - 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, 0x35, 0x37, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x32, 0x30, - 0x2f, 0x32, 0x35, 0x35, 0x29, 0x0a, - 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x29, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x74, 0x72, 0x61, 0x63, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x65, 0x62, 0x75, 0x67, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x62, 0x61, 0x63, 0x6b, 0x28, 0x29, 0x0a, - 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 0x6c, 0x65, - 0x61, 0x72, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, - 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, - 0x29, 0x29, 0x0a, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x0a, @@ -1148,16 +1151,10 @@ const unsigned char boot_lua[] = 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x74, 0x6f, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x28, 0x37, 0x30, 0x29, 0x0a, - 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 0x6c, - 0x65, 0x61, 0x72, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, - 0x67, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, - 0x28, 0x29, 0x29, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x66, 0x28, 0x70, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x2c, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x57, 0x69, 0x64, 0x74, 0x68, 0x28, 0x29, 0x20, 0x2d, 0x20, 0x70, 0x6f, 0x73, 0x29, 0x0a, - 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, - 0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x20, 0x74, 0x72, 0x75, 0x65, 0x20, 0x64, 0x6f, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x75, 0x6d, 0x70, 0x28, @@ -1194,7 +1191,12 @@ const unsigned char boot_lua[] = 0x09, 0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, - 0x09, 0x09, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x72, 0x65, + 0x6e, 0x64, 0x65, 0x72, 0x50, 0x61, 0x73, 0x73, 0x28, 0x38, 0x39, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, + 0x35, 0x37, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x32, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x64, + 0x72, 0x61, 0x77, 0x29, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, + 0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x0a, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x2e, 0x73, 0x6c, 0x65, 0x65,