diff --git a/Android.mk b/Android.mk deleted file mode 100644 index ec867bad5..000000000 --- a/Android.mk +++ /dev/null @@ -1,106 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE := liblove -LOCAL_CFLAGS := -g -DGL_GLEXT_PROTOTYPES -DAL_ALEXT_PROTOTYPES - -LOCAL_CPPFLAGS := ${LOCAL_CFLAGS} - -# I don't think there's armeabi-v7a device without NEON instructions in 2018 -LOCAL_ARM_NEON := true - -ifeq ($(IS_ANDROID_21),yes) - # API21 defines socklen_t - LOCAL_CFLAGS += -DHAS_SOCKLEN_T=1 -endif - -LOCAL_C_INCLUDES := \ - ${LOCAL_PATH}/src \ - ${LOCAL_PATH}/src/modules \ - ${LOCAL_PATH}/src/libraries/ \ - ${LOCAL_PATH}/src/libraries/enet/libenet/include \ - ${LOCAL_PATH}/src/libraries/physfs \ - ${LOCAL_PATH}/src/libraries/glslang/glslang/Include - -LOCAL_SRC_FILES := \ - $(filter-out \ - src/libraries/luasocket/libluasocket/wsocket.c \ - ,$(subst $(LOCAL_PATH)/,,\ - $(wildcard ${LOCAL_PATH}/src/love.cpp) \ - $(wildcard ${LOCAL_PATH}/src/common/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/audio/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/audio/null/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/audio/openal/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/data/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/event/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/event/sdl/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/filesystem/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/filesystem/physfs/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/font/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/font/freetype/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/graphics/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/graphics/opengl/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/image/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/image/magpie/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/joystick/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/joystick/sdl/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/keyboard/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/keyboard/sdl/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/love/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/math/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/mouse/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/mouse/sdl/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/physics/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/physics/box2d/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/sound/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/sound/lullaby/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/system/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/system/sdl/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/thread/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/thread/sdl/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/touch/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/touch/sdl/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/timer/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/timer/sdl/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/video/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/video/theora/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/window/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/modules/window/sdl/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/ddsparse/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/box2d/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/box2d/Collision/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/box2d/Common/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/box2d/Dynamics/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/box2d/Rope/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/glad/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/glslang/glslang/GenericCodeGen/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/glslang/glslang/MachineIndependent/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/glslang/glslang/MachineIndependent/preprocessor/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/glslang/glslang/OSDependent/Unix/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/glslang/OGLCompilersDLL/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/glslang/glslang//*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/enet/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/enet/libenet/*.c) \ - $(wildcard ${LOCAL_PATH}/src/libraries/lua53/*.c) \ - $(wildcard ${LOCAL_PATH}/src/libraries/luasocket/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/luautf8/*.c) \ - $(wildcard ${LOCAL_PATH}/src/libraries/luasocket/libluasocket/*.c) \ - $(wildcard ${LOCAL_PATH}/src/libraries/lodepng/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/lz4/*.c) \ - $(wildcard ${LOCAL_PATH}/src/libraries/noise1234/*.cpp) \ - $(wildcard ${LOCAL_PATH}/src/libraries/physfs/*.c) \ - $(wildcard ${LOCAL_PATH}/src/libraries/Wuff/*.c) \ - $(wildcard ${LOCAL_PATH}/src/libraries/xxHash/*.c) \ - )) - -LOCAL_CXXFLAGS := -std=c++11 -LOCAL_SHARED_LIBRARIES := libopenal -LOCAL_STATIC_LIBRARIES := libvorbis libogg libtheora libmodplug libfreetype libluajit SDL2_static - -# $(info liblove: include dirs $(LOCAL_C_INCLUDES)) -# $(info liblove: src files $(LOCAL_SRC_FILES)) - -LOCAL_LDLIBS := -lz -lGLESv1_CM -lGLESv2 -ldl -landroid -LOCAL_LDFLAGS := -Wl,--allow-multiple-definition - -include $(BUILD_SHARED_LIBRARY) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5593b3177..02bb8c7f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,11 @@ set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) set (CMAKE_CXX_STANDARD 17) +if(APPLE) + message(WARNING "CMake is not an officially supported build system for love on Apple platforms.") + message(WARNING "Use the prebuilt .app or the xcode project in platform/xcode/ instead.") +endif() + if(MSVC) set(LOVE_CONSOLE_EXE_NAME lovec) endif() @@ -48,11 +53,15 @@ else() set(LOVE_TARGET_PLATFORM x86) endif() -option(LOVE_JIT "Use LuaJIT" TRUE) +if(APPLE) + option(LOVE_JIT "Use LuaJIT" FALSE) +else() + option(LOVE_JIT "Use LuaJIT" TRUE) +endif() if(LOVE_JIT) if(APPLE) - message(FATAL_ERROR "JIT not supported yet on Mac. Please use -DLOVE_JIT=0.") + message(WARNING "JIT not supported yet on Mac.") endif() message(STATUS "LuaJIT: Enabled") else() @@ -238,6 +247,8 @@ endfunction() # set(LOVE_SRC_COMMON + src/common/android.cpp + src/common/android.h src/common/b64.cpp src/common/b64.h src/common/Color.h @@ -290,6 +301,9 @@ if (APPLE) set(LOVE_SRC_COMMON ${LOVE_SRC_COMMON} src/common/macosx.mm ) + set(LOVE_LINK_LIBRARIES ${LOVE_LINK_LIBRARIES} objc) + set(LOVE_LINK_LIBRARIES ${LOVE_LINK_LIBRARIES} "-framework CoreFoundation") + set(LOVE_LINK_LIBRARIES ${LOVE_LINK_LIBRARIES} "-framework AppKit") endif() source_group("common" FILES ${LOVE_SRC_COMMON}) @@ -364,6 +378,8 @@ set(LOVE_SRC_MODULE_DATA src/modules/data/Compressor.h src/modules/data/DataModule.cpp src/modules/data/DataModule.h + src/modules/data/DataStream.cpp + src/modules/data/DataStream.h src/modules/data/DataView.cpp src/modules/data/DataView.h src/modules/data/HashFunction.cpp @@ -901,8 +917,6 @@ set(LOVE_SRC_MODULE_SOUND_ROOT set(LOVE_SRC_MODULE_SOUND_LULLABY src/modules/sound/lullaby/FLACDecoder.cpp src/modules/sound/lullaby/FLACDecoder.h - src/modules/sound/lullaby/GmeDecoder.cpp - src/modules/sound/lullaby/GmeDecoder.h src/modules/sound/lullaby/ModPlugDecoder.cpp src/modules/sound/lullaby/ModPlugDecoder.h src/modules/sound/lullaby/MP3Decoder.h @@ -1644,6 +1658,7 @@ if(APPLE) set(LOVE_SRC_3P_PHYSFS ${LOVE_SRC_3P_PHYSFS} src/libraries/physfs/physfs_platform_apple.m ) + set(LOVE_LINK_LIBRARIES ${LOVE_LINK_LIBRARIES} "-framework IOKit") endif() add_library(love_3p_physfs ${LOVE_SRC_3P_PHYSFS}) @@ -1764,6 +1779,7 @@ set(LOVE_LIB_SRC ) include_directories( + BEFORE src src/libraries src/libraries/box2D @@ -1792,6 +1808,7 @@ if(ANDROID) # as shared library, so change the library name and add love.cpp set(LOVE_LIB_NAME ${LOVE_EXE_NAME}) set(LOVE_LIB_SRC ${LOVE_LIB_SRC} src/love.cpp) + set(LOVE_LINK_LIBRARIES ${LOVE_LINK_LIBRARIES} android) endif() add_library(${LOVE_LIB_NAME} SHARED ${LOVE_LIB_SRC} ${LOVE_RC}) diff --git a/changes.txt b/changes.txt index 733d3a298..2b98cbc6e 100644 --- a/changes.txt +++ b/changes.txt @@ -42,6 +42,9 @@ Released: N/A * Added a variant of love.graphics.setColorMask which accepts a single boolean. * Added new 'clampone' wrap mode. * Added a variant of Font:getWidth which takes a codepoint number argument. +* Added love.keyboard.isModifierActive. +* Added love.system.getPreferredLocales. +* Added love.localechanged callback. * Changed the default font from Vera size 12 to Noto Sans size 13. * Changed the Texture class and implementation to no longer have separate Canvas and Image subclasses. diff --git a/platform/unix/configure.ac b/platform/unix/configure.ac index e431f5744..42c85fe20 100644 --- a/platform/unix/configure.ac +++ b/platform/unix/configure.ac @@ -54,9 +54,6 @@ with_clean_luaversion=`printf ${with_luaversion} | sed 's/\.//g'` # Generated sources for enabling/disabling modules m4_include([configure-modules-pre.ac]) -# Other features that can be enabled/disabled -AC_ARG_ENABLE([gme], AC_HELP_STRING([--enable-gme], [Enable GME support, for more chiptuney goodness]), [], [enable_gme=no]) - # Dependencies we always use ACLOVE_DEP_LUA ACLOVE_DEP_SDL2 @@ -72,12 +69,12 @@ AS_VAR_IF([enable_module_sound], [yes], [ ACLOVE_DEP_VORBISFILE ], []) AS_VAR_IF([enable_module_video], [yes], [ACLOVE_DEP_THEORA], []) -AS_VAR_IF([enable_gme], [yes], [ACLOVE_DEP_GME], []) # Add flags for optional libraries AC_ARG_ENABLE([library-enet], [ --disable-library-enet Turn off library enet], [], [enable_library_enet=yes]) AC_ARG_ENABLE([library-luasocket], [ --disable-library-luasocket Turn off library luasocket], [], [enable_library_luasocket=yes]) AC_ARG_ENABLE([library-lua53], [ --disable-library-lua53 Turn off library lua53 (lua 5.3 backports, required by love.data)], [], [enable_library_lua53=yes]) +AC_ARG_ENABLE([library-luahttps], [ --disable-library-luahttps Turn off library luahttps], [], [enable_library_luahttps=yes]) # Select the libraries we need to build, based on the selected modules AS_VAR_IF([enable_module_filesystem], [yes], [enable_library_physfs=yes], [enable_library_physfs=no]) diff --git a/platform/unix/deps.m4 b/platform/unix/deps.m4 index fbfb1c9a7..d84f01ac1 100644 --- a/platform/unix/deps.m4 +++ b/platform/unix/deps.m4 @@ -35,11 +35,6 @@ AC_DEFUN([ACLOVE_DEP_MPG123], [ AC_SUBST([FILE_OFFSET],[-D_FILE_OFFSET_BITS=64]), AC_SUBST([FILE_OFFSET],[]))]) -AC_DEFUN([ACLOVE_DEP_GME], [ - AC_SEARCH_LIBS([gme_open_data], [gme], [], [LOVE_MSG_ERROR([gme])]) - AC_DEFINE([LOVE_SUPPORT_GME], [], [Enable gme]) - AC_CHECK_HEADER([gme/gme.h], [includes="$includes -I/usr/include/gme"], [])]) - # For enet AC_DEFUN([ACLOVE_SOCKLEN_T], [ AC_CHECK_TYPE([socklen_t], [AC_DEFINE([HAS_SOCKLEN_T], [1], [Define if socklen_t exists.] )], , diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index bbafec76c..a4b8df827 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -526,9 +526,6 @@ FA0B7E8B1A95902C000E1D17 /* FLACDecoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C821A95902C000E1D17 /* FLACDecoder.cpp */; }; FA0B7E8C1A95902C000E1D17 /* FLACDecoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C821A95902C000E1D17 /* FLACDecoder.cpp */; }; FA0B7E8D1A95902C000E1D17 /* FLACDecoder.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7C831A95902C000E1D17 /* FLACDecoder.h */; }; - FA0B7E8E1A95902C000E1D17 /* GmeDecoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C841A95902C000E1D17 /* GmeDecoder.cpp */; }; - FA0B7E8F1A95902C000E1D17 /* GmeDecoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C841A95902C000E1D17 /* GmeDecoder.cpp */; }; - FA0B7E901A95902C000E1D17 /* GmeDecoder.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7C851A95902C000E1D17 /* GmeDecoder.h */; }; FA0B7E911A95902C000E1D17 /* ModPlugDecoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C861A95902C000E1D17 /* ModPlugDecoder.cpp */; }; FA0B7E921A95902C000E1D17 /* ModPlugDecoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C861A95902C000E1D17 /* ModPlugDecoder.cpp */; }; FA0B7E931A95902C000E1D17 /* ModPlugDecoder.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7C871A95902C000E1D17 /* ModPlugDecoder.h */; }; @@ -826,6 +823,9 @@ FA6A2B7A1F60B8250074C308 /* wrap_ByteData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6A2B781F60B8250074C308 /* wrap_ByteData.cpp */; }; FA6A2B7B1F60B8250074C308 /* wrap_ByteData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6A2B781F60B8250074C308 /* wrap_ByteData.cpp */; }; FA6BDE5C1F31725300786805 /* Color.h in Headers */ = {isa = PBXBuildFile; fileRef = FA6BDE5B1F31725300786805 /* Color.h */; }; + FA6BDF8E281219E900240F2A /* DataStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF8C281219E900240F2A /* DataStream.cpp */; }; + FA6BDF8F281219E900240F2A /* DataStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF8C281219E900240F2A /* DataStream.cpp */; }; + FA6BDF90281219E900240F2A /* DataStream.h in Headers */ = {isa = PBXBuildFile; fileRef = FA6BDF8D281219E900240F2A /* DataStream.h */; }; FA76344A1E28722A0066EF9E /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7634481E28722A0066EF9E /* StreamBuffer.cpp */; }; FA76344B1E28722A0066EF9E /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7634481E28722A0066EF9E /* StreamBuffer.cpp */; }; FA76344C1E28722A0066EF9E /* StreamBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7634491E28722A0066EF9E /* StreamBuffer.h */; }; @@ -1699,8 +1699,6 @@ FA0B7C801A95902C000E1D17 /* Decoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Decoder.cpp; sourceTree = ""; }; FA0B7C821A95902C000E1D17 /* FLACDecoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FLACDecoder.cpp; sourceTree = ""; }; FA0B7C831A95902C000E1D17 /* FLACDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLACDecoder.h; sourceTree = ""; }; - FA0B7C841A95902C000E1D17 /* GmeDecoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GmeDecoder.cpp; sourceTree = ""; }; - FA0B7C851A95902C000E1D17 /* GmeDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GmeDecoder.h; sourceTree = ""; }; FA0B7C861A95902C000E1D17 /* ModPlugDecoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ModPlugDecoder.cpp; sourceTree = ""; }; FA0B7C871A95902C000E1D17 /* ModPlugDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModPlugDecoder.h; sourceTree = ""; }; FA0B7C8A1A95902C000E1D17 /* Sound.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Sound.cpp; sourceTree = ""; }; @@ -1909,6 +1907,8 @@ FA6A2B771F60B8250074C308 /* wrap_ByteData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_ByteData.h; sourceTree = ""; }; FA6A2B781F60B8250074C308 /* wrap_ByteData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_ByteData.cpp; sourceTree = ""; }; FA6BDE5B1F31725300786805 /* Color.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Color.h; sourceTree = ""; }; + FA6BDF8C281219E900240F2A /* DataStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DataStream.cpp; sourceTree = ""; }; + FA6BDF8D281219E900240F2A /* DataStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataStream.h; sourceTree = ""; }; FA7634481E28722A0066EF9E /* StreamBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StreamBuffer.cpp; sourceTree = ""; }; FA7634491E28722A0066EF9E /* StreamBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamBuffer.h; sourceTree = ""; }; FA7DA04C1C16874A0056B200 /* wrap_Math.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wrap_Math.lua; sourceTree = ""; }; @@ -3199,8 +3199,6 @@ FA0B7C7F1A95902C000E1D17 /* CoreAudioDecoder.h */, FA0B7C821A95902C000E1D17 /* FLACDecoder.cpp */, FA0B7C831A95902C000E1D17 /* FLACDecoder.h */, - FA0B7C841A95902C000E1D17 /* GmeDecoder.cpp */, - FA0B7C851A95902C000E1D17 /* GmeDecoder.h */, FA0B7C861A95902C000E1D17 /* ModPlugDecoder.cpp */, FA0B7C871A95902C000E1D17 /* ModPlugDecoder.h */, FA522D4B23F9FE370059EE3C /* MP3Decoder.cpp */, @@ -3762,6 +3760,8 @@ FACA02E31F5E396B0084B28F /* Compressor.h */, FACA02E41F5E396B0084B28F /* DataModule.cpp */, FACA02E51F5E396B0084B28F /* DataModule.h */, + FA6BDF8C281219E900240F2A /* DataStream.cpp */, + FA6BDF8D281219E900240F2A /* DataStream.h */, FA6A2B681F5F7F560074C308 /* DataView.cpp */, FA6A2B691F5F7F560074C308 /* DataView.h */, FACA02E61F5E396B0084B28F /* HashFunction.cpp */, @@ -4023,7 +4023,6 @@ FAF140871E20934C00F898D2 /* parseVersions.h in Headers */, FA0B7AC61A958EA3000E1D17 /* types.h in Headers */, FA0B7DBD1A95902C000E1D17 /* Joystick.h in Headers */, - FA0B7E901A95902C000E1D17 /* GmeDecoder.h in Headers */, FA0B7D0E1A95902C000E1D17 /* wrap_Filesystem.h in Headers */, FA0B7EE41A95902D000E1D17 /* Window.h in Headers */, FA0B7CFC1A95902C000E1D17 /* Filesystem.h in Headers */, @@ -4370,6 +4369,7 @@ 217DFC121D9F6D490055D849 /* usocket.h in Headers */, 217DFC081D9F6D490055D849 /* udp.h in Headers */, FA0B7DCF1A95902C000E1D17 /* wrap_Keyboard.h in Headers */, + FA6BDF90281219E900240F2A /* DataStream.h in Headers */, FAF6C9DB23C2DE2900D7B5BC /* SpvBuilder.h in Headers */, FA0B7EA21A95902C000E1D17 /* Sound.h in Headers */, FA0B7B331A958EA3000E1D17 /* wuff_config.h in Headers */, @@ -4517,7 +4517,6 @@ FADF54081E3D78F700012CC0 /* Video.cpp in Sources */, FA9D8DD81DEF8411002CD881 /* Data.cpp in Sources */, FABDA9992552448300B5C523 /* b2_contact_manager.cpp in Sources */, - FA0B7E8F1A95902C000E1D17 /* GmeDecoder.cpp in Sources */, FADF542B1E3DAADA00012CC0 /* wrap_Mesh.cpp in Sources */, FA0B7CD71A95902C000E1D17 /* Audio.cpp in Sources */, FA0B7AC01A958EA3000E1D17 /* host.c in Sources */, @@ -4702,6 +4701,7 @@ FAE64A922071364B00BC7981 /* physfs_platform_winrt.cpp in Sources */, FABDA97A2552448200B5C523 /* b2_joint.cpp in Sources */, FA4F2C0F1DE936FE00CA37D7 /* timeout.c in Sources */, + FA6BDF8F281219E900240F2A /* DataStream.cpp in Sources */, FA59A2D31C06481400328DBA /* ParticleSystem.cpp in Sources */, FA0B7E131A95902C000E1D17 /* GearJoint.cpp in Sources */, FABDA99B2552448300B5C523 /* b2_polygon_contact.cpp in Sources */, @@ -4940,7 +4940,6 @@ FABDA9982552448300B5C523 /* b2_contact_manager.cpp in Sources */, FADF542A1E3DAADA00012CC0 /* wrap_Mesh.cpp in Sources */, FA0B7D2B1A95902C000E1D17 /* wrap_Rasterizer.cpp in Sources */, - FA0B7E8E1A95902C000E1D17 /* GmeDecoder.cpp in Sources */, FA0B7CD61A95902C000E1D17 /* Audio.cpp in Sources */, FA3C5E421F8C368C0003C579 /* ShaderStage.cpp in Sources */, FA0B7EAF1A95902C000E1D17 /* System.cpp in Sources */, @@ -5122,6 +5121,7 @@ FABDA9932552448300B5C523 /* b2_polygon_circle_contact.cpp in Sources */, FAC7CD851FE35E95006A60C7 /* physfs_unicode.c in Sources */, FA6A2B7A1F60B8250074C308 /* wrap_ByteData.cpp in Sources */, + FA6BDF8E281219E900240F2A /* DataStream.cpp in Sources */, FAF140551E20934C00F898D2 /* Link.cpp in Sources */, FABDA9792552448200B5C523 /* b2_joint.cpp in Sources */, FAF140841E20934C00F898D2 /* ParseHelper.cpp in Sources */, @@ -5399,7 +5399,6 @@ "\"$(SRCROOT)/../../src/libraries/enet/libenet/include\"", ); IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "@rpath"; LIBRARY_SEARCH_PATHS = ""; MACOSX_DEPLOYMENT_TARGET = 10.9; ONLY_ACTIVE_ARCH = NO; @@ -5465,7 +5464,6 @@ "\"$(SRCROOT)/../../src/libraries/enet/libenet/include\"", ); IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "@rpath"; LIBRARY_SEARCH_PATHS = ""; MACOSX_DEPLOYMENT_TARGET = 10.9; ONLY_ACTIVE_ARCH = YES; @@ -5603,7 +5601,6 @@ "\"$(SRCROOT)/../../src/libraries/enet/libenet/include\"", ); IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "@rpath"; LIBRARY_SEARCH_PATHS = ""; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 10.9; @@ -5637,6 +5634,7 @@ ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; + LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/ios/libraries/freetype", @@ -5675,6 +5673,7 @@ ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; + LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/ios/libraries/freetype", @@ -5714,6 +5713,7 @@ ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; + LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/ios/libraries/freetype", diff --git a/src/common/Stream.cpp b/src/common/Stream.cpp index d9139e355..01d4c593d 100644 --- a/src/common/Stream.cpp +++ b/src/common/Stream.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2006-2015 LOVE Development Team + * Copyright (c) 2006-2022 LOVE Development Team * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages @@ -20,10 +20,59 @@ // LOVE #include "Stream.h" +#include "Data.h" +#include "data/ByteData.h" +#include "Exception.h" namespace love { love::Type Stream::type("Stream", &Object::type); +Data *Stream::read(int64 size) +{ + int64 max = LOVE_INT64_MAX; + int64 cur = 0; + + if (isSeekable()) + { + max = getSize(); + cur = tell(); + } + + if (cur < 0) + cur = 0; + else if (cur > max) + cur = max; + + if (cur + size > max) + size = max - cur; + + StrongRef dst(new data::ByteData(size, false), Acquire::NORETAIN); + + int64 bytesRead = read(dst->getData(), size); + + if (bytesRead < 0 || (bytesRead == 0 && bytesRead != size)) + throw love::Exception("Could not read from stream."); + + if (bytesRead < size) + dst.set(new data::ByteData(dst->getData(), (size_t) bytesRead), Acquire::NORETAIN); + + dst->retain(); + return dst; +} + +bool Stream::write(Data *src) +{ + return write(src, 0, src->getSize()); +} + +bool Stream::write(Data *src, int64 offset, int64 size) +{ + if (offset < 0 || size < 0 || offset + size > src->getSize()) + throw love::Exception("Offset and size parameters do not fit within the given Data's size."); + + return write((const uint8 *) src->getData() + offset, size); +} + } // love diff --git a/src/common/Stream.h b/src/common/Stream.h index 2b69144e3..6d02dcd1a 100644 --- a/src/common/Stream.h +++ b/src/common/Stream.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2006-2015 LOVE Development Team + * Copyright (c) 2006-2022 LOVE Development Team * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages @@ -24,41 +24,92 @@ // LOVE #include #include "Object.h" +#include "int.h" namespace love { +class Data; + class Stream : public Object { public: + + enum SeekOrigin + { + SEEKORIGIN_BEGIN, + SEEKORIGIN_CURRENT, + SEEKORIGIN_END, + SEEKORIGIN_MAX_ENUM + }; + static love::Type type; virtual ~Stream() {} - // getData and getSize are assumed to talk about - // the buffer + /** + * Creates a new copy of the Stream, with the same settings as the original. + * The seek position will be reset in the copy. + **/ + virtual Stream *clone() = 0; /** - * A callback, gets called when some Stream consumer exhausts the data + * Gets whether read() is supported for this Stream. **/ - virtual void fillBackBuffer() {} + virtual bool isReadable() const = 0; /** - * Get the front buffer, Streams are supposed to be (at least) double-buffered + * Gets whether write() is supported for this Stream. **/ - virtual const void *getFrontBuffer() const = 0; + virtual bool isWritable() const = 0; /** - * Get the size of any (and in particular the front) buffer + * Gets whether seek(), tell(), and getSize() are supported for this Stream. **/ - virtual size_t getSize() const = 0; + virtual bool isSeekable() const = 0; /** - * Swap buffers. Returns true if there is new data in the front buffer, - * false otherwise. - * NOTE: If there is no back buffer ready, this call must be ignored + * Reads data into the destination buffer, and returns the number of bytes + * actually read. **/ - virtual bool swapBuffers() = 0; + virtual int64 read(void *dst, int64 size) = 0; + + /** + * Reads data into a new Data object. + **/ + virtual Data *read(int64 size); + + /** + * Writes data from the source buffer into the Stream. + **/ + virtual bool write(const void *src, int64 size) = 0; + + /** + * Writes data from the source Data object into the Stream. + **/ + virtual bool write(Data *src, int64 offset, int64 size); + bool write(Data *src); + + /** + * Flushes all data written to the Stream. + **/ + virtual bool flush() = 0; + + /** + * Gets the total size of the Stream, if supported. + **/ + virtual int64 getSize() = 0; + + /** + * Sets the current position in the Stream, if supported. + **/ + virtual bool seek(int64 pos, SeekOrigin origin = SEEKORIGIN_BEGIN) = 0; + + /** + * Gets the current position in the Stream, if supported. + **/ + virtual int64 tell() = 0; + }; // Stream } // love diff --git a/src/common/android.cpp b/src/common/android.cpp index 9de92d243..a5118f25c 100644 --- a/src/common/android.cpp +++ b/src/common/android.cpp @@ -35,7 +35,7 @@ #include #include -#include "physfs.h" +#include "libraries/physfs/physfs.h" namespace love { diff --git a/src/common/config.h b/src/common/config.h index f7787d399..1df87b98a 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -38,6 +38,8 @@ #endif #if defined(__ANDROID__) # define LOVE_ANDROID 1 +// Needed for ENet +# define HAS_SOCKLEN_T 1 #endif #if defined(__APPLE__) # include diff --git a/src/common/deprecation.cpp b/src/common/deprecation.cpp index d17109e5c..b5928da01 100644 --- a/src/common/deprecation.cpp +++ b/src/common/deprecation.cpp @@ -24,6 +24,7 @@ #include #include +#include namespace love { @@ -97,32 +98,32 @@ bool isDeprecationOutputEnabled() std::string getDeprecationNotice(const DeprecationInfo &info, bool usewhere) { - std::string notice; + std::stringstream notice; if (usewhere) - notice += info.where; + notice << info.where; - notice += "Using deprecated "; + notice << "Using deprecated "; if (info.apiType == API_FUNCTION) - notice += "function "; + notice << "function "; else if (info.apiType == API_METHOD) - notice += "method "; + notice << "method "; else if (info.apiType == API_CALLBACK) - notice += "callback "; + notice << "callback "; else if (info.apiType == API_FIELD) - notice += "field "; + notice << "field "; else if (info.apiType == API_CONSTANT) - notice += "constant "; + notice << "constant "; - notice += info.name; + notice << info.name; if (info.type == DEPRECATED_REPLACED && !info.replacement.empty()) - notice += " (replaced by " + info.replacement + ")"; + notice << " (replaced by " << info.replacement << ")"; else if (info.type == DEPRECATED_RENAMED && !info.replacement.empty()) - notice += " (renamed to " + info.replacement + ")"; + notice << " (renamed to " << info.replacement << ")"; - return notice; + return notice.str(); } GetDeprecated::GetDeprecated() diff --git a/src/common/pixelformat.cpp b/src/common/pixelformat.cpp index e9b8f7df6..a26f7f1ad 100644 --- a/src/common/pixelformat.cpp +++ b/src/common/pixelformat.cpp @@ -240,11 +240,23 @@ const PixelFormatInfo &getPixelFormatInfo(PixelFormat format) return formatInfo[format]; } +const char *getPixelFormatName(PixelFormat format) +{ + const char *name = "unknown"; + getConstant(format, name); + return name; +} + bool isPixelFormatCompressed(PixelFormat format) { return formatInfo[format].compressed; } +bool isPixelFormatColor(PixelFormat format) +{ + return formatInfo[format].color; +} + bool isPixelFormatDepthStencil(PixelFormat format) { const PixelFormatInfo &info = formatInfo[format]; diff --git a/src/common/pixelformat.h b/src/common/pixelformat.h index 355acc383..bb3687f49 100644 --- a/src/common/pixelformat.h +++ b/src/common/pixelformat.h @@ -157,11 +157,21 @@ bool getConstant(const char *in, PixelFormat &out); const PixelFormatInfo &getPixelFormatInfo(PixelFormat format); +/** + * Gets the name of the specified pixel format. + **/ +const char *getPixelFormatName(PixelFormat format); + /** * Gets whether the specified pixel format is a compressed type. **/ bool isPixelFormatCompressed(PixelFormat format); +/** + * Gets whether the specified pixel format is a color type. + **/ +bool isPixelFormatColor(PixelFormat format); + /** * Gets whether the specified pixel format is a depth or stencil type. **/ diff --git a/src/libraries/luahttps/src/lua/main.cpp b/src/libraries/luahttps/src/lua/main.cpp index 9ebff25d5..d4304b86a 100644 --- a/src/libraries/luahttps/src/lua/main.cpp +++ b/src/libraries/luahttps/src/lua/main.cpp @@ -1,4 +1,8 @@ -#include +extern "C" +{ +#include +#include +} #include "../common/HTTPS.h" #include "../common/config.h" diff --git a/src/modules/audio/openal/Audio.cpp b/src/modules/audio/openal/Audio.cpp index 3f8193f2c..68645e351 100644 --- a/src/modules/audio/openal/Audio.cpp +++ b/src/modules/audio/openal/Audio.cpp @@ -100,11 +100,6 @@ Audio::Audio() , poolThread(nullptr) , distanceModel(DISTANCE_INVERSE_CLAMPED) { -#if defined(LOVE_LINUX) - // Temporarly block signals, as the thread inherits this mask - love::thread::disableSignals(); -#endif - // Before opening new device, check if recording // is requested. if (getRequestRecordingPermission()) @@ -114,29 +109,32 @@ Audio::Audio() requestRecordingPermission(); } - // Passing null for default device. - device = alcOpenDevice(nullptr); + { +#if defined(LOVE_LINUX) + // Temporarly block signals, as the thread inherits this mask + love::thread::ScopedDisableSignals disableSignals; +#endif - if (device == nullptr) - throw love::Exception("Could not open device."); + // Passing null for default device. + device = alcOpenDevice(nullptr); + + if (device == nullptr) + throw love::Exception("Could not open device."); #ifdef ALC_EXT_EFX - ALint attribs[4] = { ALC_MAX_AUXILIARY_SENDS, MAX_SOURCE_EFFECTS, 0, 0 }; + ALint attribs[4] = { ALC_MAX_AUXILIARY_SENDS, MAX_SOURCE_EFFECTS, 0, 0 }; #else - ALint *attribs = nullptr; + ALint *attribs = nullptr; #endif - context = alcCreateContext(device, attribs); + context = alcCreateContext(device, attribs); - if (context == nullptr) - throw love::Exception("Could not create context."); + if (context == nullptr) + throw love::Exception("Could not create context."); - if (!alcMakeContextCurrent(context) || alcGetError(device) != ALC_NO_ERROR) - throw love::Exception("Could not make context current."); - -#if defined(LOVE_LINUX) - love::thread::reenableSignals(); -#endif + if (!alcMakeContextCurrent(context) || alcGetError(device) != ALC_NO_ERROR) + throw love::Exception("Could not make context current."); + } #ifdef ALC_EXT_EFX initializeEFX(); diff --git a/src/modules/audio/wrap_Audio.cpp b/src/modules/audio/wrap_Audio.cpp index 6cfa2fac2..21c8429e3 100644 --- a/src/modules/audio/wrap_Audio.cpp +++ b/src/modules/audio/wrap_Audio.cpp @@ -20,6 +20,7 @@ // LOVE #include "wrap_Audio.h" +#include "filesystem/wrap_Filesystem.h" #include "openal/Audio.h" #include "null/Audio.h" @@ -57,8 +58,23 @@ int w_newSource(lua_State *L) return luaL_error(L, "Cannot create queueable sources using newSource. Use newQueueableSource instead."); } - if (lua_isstring(L, 1) || luax_istype(L, 1, love::filesystem::File::type) || luax_istype(L, 1, love::filesystem::FileData::type)) - luax_convobj(L, 1, "sound", "newDecoder"); + if (love::filesystem::luax_cangetdata(L, 1)) + { + // stream type + if (stype == Source::TYPE_STATIC) + lua_pushstring(L, "memory"); + else if (!lua_isnone(L, 3)) + lua_pushvalue(L, 3); + else + lua_pushnil(L); + + // buffer size + lua_pushnil(L); + + // (file, buffer size, stream type) + int idxs[] = { 1, lua_gettop(L), lua_gettop(L) - 1 }; + luax_convobj(L, idxs, 3, "sound", "newDecoder"); + } if (stype == Source::TYPE_STATIC && luax_istype(L, 1, love::sound::Decoder::type)) luax_convobj(L, 1, "sound", "newSoundData"); @@ -84,20 +100,17 @@ int w_newSource(lua_State *L) int w_newQueueableSource(lua_State *L) { + int samplerate = (int) luaL_checkinteger(L, 1); + int bitdepth = (int) luaL_checkinteger(L, 2); + int channels = (int) luaL_checkinteger(L, 3); + int buffers = (int) luaL_optinteger(L, 4, 0); + Source *t = nullptr; + luax_catchexcept(L, [&]() { t = instance()->newSource(samplerate, bitdepth, channels, buffers); }); - luax_catchexcept(L, [&]() { - t = instance()->newSource((int)luaL_checkinteger(L, 1), (int)luaL_checkinteger(L, 2), (int)luaL_checkinteger(L, 3), (int)luaL_optinteger(L, 4, 0)); - }); - - if (t != nullptr) - { - luax_pushtype(L, t); - t->release(); - return 1; - } - else - return 0; //all argument type errors are checked in above constructor + luax_pushtype(L, t); + t->release(); + return 1; } static std::vector readSourceList(lua_State *L, int n) diff --git a/src/modules/data/ByteData.cpp b/src/modules/data/ByteData.cpp index 734af92c8..045f9f0e1 100644 --- a/src/modules/data/ByteData.cpp +++ b/src/modules/data/ByteData.cpp @@ -31,11 +31,12 @@ namespace data love::Type ByteData::type("ByteData", &Data::type); -ByteData::ByteData(size_t size) +ByteData::ByteData(size_t size, bool clear) : size(size) { create(); - memset(data, 0, size); + if (clear) + memset(data, 0, size); } ByteData::ByteData(const void *d, size_t size) diff --git a/src/modules/data/ByteData.h b/src/modules/data/ByteData.h index befbe079e..2e101df34 100644 --- a/src/modules/data/ByteData.h +++ b/src/modules/data/ByteData.h @@ -35,7 +35,7 @@ public: static love::Type type; - ByteData(size_t size); + ByteData(size_t size, bool clear = true); ByteData(const void *d, size_t size); ByteData(void *d, size_t size, bool own); ByteData(const ByteData &d); diff --git a/src/modules/data/DataStream.cpp b/src/modules/data/DataStream.cpp new file mode 100644 index 000000000..54962845b --- /dev/null +++ b/src/modules/data/DataStream.cpp @@ -0,0 +1,139 @@ +/** + * Copyright (c) 2006-2022 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#include "DataStream.h" +#include "common/Exception.h" +#include "common/int.h" +#include "common/Data.h" + +#include + +namespace love +{ +namespace data +{ + +love::Type DataStream::type("DataStream", &Stream::type); + +DataStream::DataStream(Data *data) + : data(data) + , offset(0) + , size(data->getSize()) + , memory((const uint8 *) data->getData()) + , writableMemory((uint8 *) data->getData()) // TODO: disallow writing sometimes? +{ +} + +DataStream::DataStream(const DataStream &other) + : data(other.data) + , offset(0) + , size(other.size) + , memory(other.memory) + , writableMemory(other.writableMemory) +{ +} + +DataStream::~DataStream() +{ +} + +DataStream *DataStream::clone() +{ + return new DataStream(*this); +} + +bool DataStream::isReadable() const +{ + return true; +} + +bool DataStream::isWritable() const +{ + return writableMemory != nullptr; +} + +bool DataStream::isSeekable() const +{ + return true; +} + +int64 DataStream::read(void* data, int64 size) +{ + if (size <= 0) + return 0; + + if (offset >= getSize()) + return 0; + + int64 readsize = std::min(size, getSize() - offset); + + memcpy(data, memory + offset, readsize); + + offset += readsize; + return readsize; +} + +bool DataStream::write(const void* data, int64 size) +{ + if (size <= 0 || writableMemory == nullptr) + return false; + + if (offset >= getSize()) + return false; + + int64 writesize = std::min(size, getSize() - offset); + + memcpy(writableMemory + offset, data, writesize); + + offset += writesize; + return true; +} + +bool DataStream::flush() +{ + return true; +} + +int64 DataStream::getSize() +{ + return size; +} + +bool DataStream::seek(int64 pos, SeekOrigin origin) +{ + if (origin == SEEKORIGIN_CURRENT) + pos += offset; + else if (origin == SEEKORIGIN_END) + pos += size; + + if (pos < 0 || pos > size) + return false; + + offset = pos; + return true; +} + +int64 DataStream::tell() +{ + return offset; +} + +} // data +} // love diff --git a/src/modules/sound/lullaby/GmeDecoder.h b/src/modules/data/DataStream.h similarity index 55% rename from src/modules/sound/lullaby/GmeDecoder.h rename to src/modules/data/DataStream.h index 61d072f43..f9247c7e3 100644 --- a/src/modules/sound/lullaby/GmeDecoder.h +++ b/src/modules/data/DataStream.h @@ -18,56 +18,52 @@ * 3. This notice may not be removed or altered from any source distribution. **/ -#ifndef LOVE_SOUND_LULLABY_GME_DECODER_H -#define LOVE_SOUND_LULLABY_GME_DECODER_H +#pragma once -#ifdef LOVE_SUPPORT_GME - -// LOVE -#include "common/Data.h" -#include "sound/Decoder.h" - -#ifdef LOVE_APPLE_USE_FRAMEWORKS -#include -#else -#include -#endif +#include "common/Stream.h" namespace love { -namespace sound -{ -namespace lullaby +namespace data { -class GmeDecoder : public Decoder +class DataStream : public love::Stream { public: - GmeDecoder(Data *data, int bufferSize); - virtual ~GmeDecoder(); + static love::Type type; - static bool accepts(const std::string &ext); + DataStream(Data *data); + virtual ~DataStream(); - love::sound::Decoder *clone(); - int decode(); - bool seek(double s); - bool rewind(); - bool isSeekable(); - int getChannelCount() const; - int getBitDepth() const; - double getDuration(); + // Implements Stream. + DataStream *clone() override; + + bool isReadable() const override; + bool isWritable() const override; + bool isSeekable() const override; + + int64 read(void* data, int64 size) override; + bool write(const void* data, int64 size) override; + + bool flush() override; + + int64 getSize() override; + + bool seek(int64 pos, SeekOrigin origin = SEEKORIGIN_BEGIN) override; + int64 tell() override; private: - Music_Emu *emu; - int num_tracks; - int cur_track; -}; // Decoder -} // lullaby -} // sound + DataStream(const DataStream &other); + + StrongRef data; + const uint8 *memory; + uint8 *writableMemory; + size_t offset; + size_t size; + +}; // DataStream + +} // data } // love - -#endif // LOVE_SUPPORT_GME - -#endif // LOVE_SOUND_LULLABY_GME_DECODER_H diff --git a/src/modules/event/sdl/Event.cpp b/src/modules/event/sdl/Event.cpp index d8d8deddf..bde9d0bfb 100644 --- a/src/modules/event/sdl/Event.cpp +++ b/src/modules/event/sdl/Event.cpp @@ -35,6 +35,8 @@ #include +#include + namespace love { namespace event @@ -418,7 +420,7 @@ Message *Event::convert(const SDL_Event &e) } else { - auto *file = new love::filesystem::NativeFile(e.drop.file); + auto *file = new love::filesystem::NativeFile(e.drop.file, love::filesystem::File::MODE_CLOSED); vargs.emplace_back(&love::filesystem::NativeFile::type, file); msg = new Message("filedropped", vargs); file->release(); @@ -426,6 +428,12 @@ Message *Event::convert(const SDL_Event &e) } SDL_free(e.drop.file); break; + case SDL_DROPBEGIN: + msg = new Message("dropbegan"); + break; + case SDL_DROPCOMPLETE: + msg = new Message("dropcompleted"); + break; case SDL_QUIT: case SDL_APP_TERMINATING: msg = new Message("quit"); @@ -433,6 +441,11 @@ Message *Event::convert(const SDL_Event &e) case SDL_APP_LOWMEMORY: msg = new Message("lowmemory"); break; +#if SDL_VERSION_ATLEAST(2, 0, 14) + case SDL_LOCALECHANGED: + msg = new Message("localechanged"); + break; +#endif default: break; } diff --git a/src/modules/filesystem/File.cpp b/src/modules/filesystem/File.cpp index c3fc539f6..6538c8f85 100644 --- a/src/modules/filesystem/File.cpp +++ b/src/modules/filesystem/File.cpp @@ -25,12 +25,17 @@ namespace love namespace filesystem { -love::Type File::type("File", &Object::type); +love::Type File::type("File", &Stream::type); File::~File() { } +FileData *File::read() +{ + return read(getSize()); +} + FileData *File::read(int64 size) { bool isopen = isOpen(); @@ -40,7 +45,6 @@ FileData *File::read(int64 size) int64 max = getSize(); int64 cur = tell(); - size = (size == ALL) ? max : size; if (size < 0) throw love::Exception("Invalid read size."); @@ -54,7 +58,7 @@ FileData *File::read(int64 size) if (cur + size > max) size = max - cur; - FileData *fileData = new FileData(size, getFilename()); + StrongRef fileData(new FileData(size, getFilename()), Acquire::NORETAIN); int64 bytesRead = read(fileData->getData(), size); if (bytesRead < 0 || (bytesRead == 0 && bytesRead != size)) @@ -65,23 +69,18 @@ FileData *File::read(int64 size) if (bytesRead < size) { - FileData *tmpFileData = new FileData(bytesRead, getFilename()); + StrongRef tmpFileData(new FileData(bytesRead, getFilename()), Acquire::NORETAIN); memcpy(tmpFileData->getData(), fileData->getData(), (size_t) bytesRead); - fileData->release(); fileData = tmpFileData; } if (!isopen) close(); + fileData->retain(); return fileData; } -bool File::write(const Data *data, int64 size) -{ - return write(data->getData(), (size == ALL) ? data->getSize() : size); -} - std::string File::getExtension() const { const std::string &filename = getFilename(); @@ -93,54 +92,22 @@ std::string File::getExtension() const return std::string(); } -bool File::getConstant(const char *in, Mode &out) +STRINGMAP_CLASS_BEGIN(File, File::Mode, File::MODE_MAX_ENUM, mode) { - return modes.find(in, out); + { "c", File::MODE_CLOSED }, + { "r", File::MODE_READ }, + { "w", File::MODE_WRITE }, + { "a", File::MODE_APPEND }, } +STRINGMAP_CLASS_END(File, File::Mode, File::MODE_MAX_ENUM, mode) -bool File::getConstant(Mode in, const char *&out) +STRINGMAP_CLASS_BEGIN(File, File::BufferMode, File::BUFFER_MAX_ENUM, bufferMode) { - return modes.find(in, out); + { "none", File::BUFFER_NONE }, + { "line", File::BUFFER_LINE }, + { "full", File::BUFFER_FULL }, } - -std::vector File::getConstants(Mode) -{ - return modes.getNames(); -} - -bool File::getConstant(const char *in, BufferMode &out) -{ - return bufferModes.find(in, out); -} - -bool File::getConstant(BufferMode in, const char *&out) -{ - return bufferModes.find(in, out); -} - -std::vector File::getConstants(BufferMode) -{ - return bufferModes.getNames(); -} - -StringMap::Entry File::modeEntries[] = -{ - { "c", MODE_CLOSED }, - { "r", MODE_READ }, - { "w", MODE_WRITE }, - { "a", MODE_APPEND }, -}; - -StringMap File::modes(File::modeEntries, sizeof(File::modeEntries)); - -StringMap::Entry File::bufferModeEntries[] = -{ - { "none", BUFFER_NONE }, - { "line", BUFFER_LINE }, - { "full", BUFFER_FULL }, -}; - -StringMap File::bufferModes(File::bufferModeEntries, sizeof(File::bufferModeEntries)); +STRINGMAP_CLASS_END(File, File::BufferMode, File::BUFFER_MAX_ENUM, bufferMode) } // filesystem } // love diff --git a/src/modules/filesystem/File.h b/src/modules/filesystem/File.h index 902730470..4b7a74cd5 100644 --- a/src/modules/filesystem/File.h +++ b/src/modules/filesystem/File.h @@ -27,6 +27,7 @@ // LOVE #include "common/Data.h" #include "common/Object.h" +#include "common/Stream.h" #include "common/StringMap.h" #include "common/int.h" #include "FileData.h" @@ -40,7 +41,7 @@ namespace filesystem * A File interface, providing generic means of reading from and * writing to files. **/ -class File : public Object +class File : public Stream { public: @@ -66,16 +67,19 @@ public: BUFFER_MAX_ENUM }; - /** - * Used to indicate ALL data in a file. - **/ - static const int64 ALL = -1; - /** * Destructor. **/ virtual ~File(); + // Implements Stream. + bool isReadable() const override { return getMode() == MODE_READ; } + bool isWritable() const override { return getMode() == MODE_WRITE || getMode() == MODE_APPEND; } + bool isSeekable() const override { return isOpen(); } + + using Stream::read; + using Stream::write; + /** * Opens the file in a certain mode. * @@ -96,53 +100,14 @@ public: **/ virtual bool isOpen() const = 0; - /** - * Gets the size of the file. - * - * @return The size of the file. - **/ - virtual int64 getSize() = 0; - /** * Reads data from the file and allocates a Data object. * - * @param size The number of bytes to attempt reading, or -1 for EOF. + * @param size The number of bytes to attempt reading. * @return A newly allocated Data object. **/ - virtual FileData *read(int64 size = ALL); - - /** - * Reads data into the destination buffer. - * - * @param dst The destination buffer. - * @param size The number of bytes to attempt reading. - * @return The number of bytes actually read. - **/ - virtual int64 read(void *dst, int64 size) = 0; - - /** - * Writes data into the File. - * - * @param data The source buffer. - * @param size The size of the buffer. - * @return True of success, false otherwise. - **/ - virtual bool write(const void *data, int64 size) = 0; - - /** - * Writes a Data object into the File. - * - * @param data The data object to write into the file. - * @param size The number of bytes to attempt writing, or -1 for everything. - * @return True of success, false otherwise. - **/ - virtual bool write(const Data *data, int64 size = ALL); - - /** - * Flushes the currently buffered file data to disk. Only applicable in - * write mode. - **/ - virtual bool flush() = 0; + FileData *read(int64 size) override; + FileData *read(); /** * Checks whether we are currently at end-of-file. @@ -151,21 +116,6 @@ public: **/ virtual bool isEOF() = 0; - /** - * Gets the current position in the File. - * - * @return The current byte position in the File. - **/ - virtual int64 tell() = 0; - - /** - * Seeks to a certain position in the File. - * - * @param pos The byte position in the file. - * @return True on success, false otherwise. - **/ - virtual bool seek(uint64 pos) = 0; - /** * Sets the buffering mode for the file. When buffering is enabled, the file * will not write to disk (or will pre-load data if in read mode) until the @@ -202,21 +152,8 @@ public: **/ virtual std::string getExtension() const; - static bool getConstant(const char *in, Mode &out); - static bool getConstant(Mode in, const char *&out); - static std::vector getConstants(Mode); - - static bool getConstant(const char *in, BufferMode &out); - static bool getConstant(BufferMode in, const char *&out); - static std::vector getConstants(BufferMode); - -private: - - static StringMap::Entry modeEntries[]; - static StringMap modes; - - static StringMap::Entry bufferModeEntries[]; - static StringMap bufferModes; + STRINGMAP_CLASS_DECLARE(Mode); + STRINGMAP_CLASS_DECLARE(BufferMode); }; // File diff --git a/src/modules/filesystem/Filesystem.h b/src/modules/filesystem/Filesystem.h index 4a381c75c..e2c78b7dc 100644 --- a/src/modules/filesystem/Filesystem.h +++ b/src/modules/filesystem/Filesystem.h @@ -165,9 +165,9 @@ public: virtual bool unmountFullPath(const char *fullpath) = 0; /** - * Creates a new file. + * Opens a new File object from the specified path, using the given mode. **/ - virtual File *newFile(const char *filename) const = 0; + virtual File *openFile(const char *filename, File::Mode mode) const = 0; /** * Creates a new FileData object. Data will be copied. @@ -216,6 +216,11 @@ public: **/ virtual std::string getRealDirectory(const char *filename) const = 0; + /** + * Gets whether anything exists at the specified path. + **/ + virtual bool exists(const char *filepath) const = 0; + /** * Gets information about the item at the specified filepath. Returns false * if nothing exists at the path. @@ -239,7 +244,8 @@ public: * @param filename The name of the file to read from. * @param size The size in bytes of the data to read. **/ - virtual FileData *read(const char *filename, int64 size = File::ALL) const = 0; + virtual FileData *read(const char *filename, int64 size) const = 0; + virtual FileData *read(const char *filename) const = 0; /** * Write data to a file. diff --git a/src/modules/filesystem/NativeFile.cpp b/src/modules/filesystem/NativeFile.cpp index fad7432fb..58753cfcf 100644 --- a/src/modules/filesystem/NativeFile.cpp +++ b/src/modules/filesystem/NativeFile.cpp @@ -39,13 +39,26 @@ namespace filesystem love::Type NativeFile::type("NativeFile", &File::type); -NativeFile::NativeFile(const std::string &filename) +NativeFile::NativeFile(const std::string &filename, Mode mode) : filename(filename) , file(nullptr) , mode(MODE_CLOSED) , bufferMode(BUFFER_NONE) , bufferSize(0) { + if (!open(mode)) + throw love::Exception("Could not open file at path %s", filename.c_str()); +} + +NativeFile::NativeFile(const NativeFile &other) + : filename(other.filename) + , file(nullptr) + , mode(MODE_CLOSED) + , bufferMode(other.bufferMode) + , bufferSize(other.bufferSize) +{ + if (!open(other.mode)) + throw love::Exception("Could not open file at path %s", filename.c_str()); } NativeFile::~NativeFile() @@ -54,10 +67,18 @@ NativeFile::~NativeFile() close(); } +NativeFile *NativeFile::clone() +{ + return new NativeFile(*this); +} + bool NativeFile::open(Mode newmode) { if (newmode == MODE_CLOSED) + { + close(); return true; + } // File already open? if (file != nullptr) @@ -197,15 +218,22 @@ int64 NativeFile::tell() #endif } -bool NativeFile::seek(uint64 pos) +bool NativeFile::seek(int64 pos, SeekOrigin origin) { if (file == nullptr) return false; + int forigin = SEEK_SET; + if (origin == SEEKORIGIN_CURRENT) + forigin = SEEK_CUR; + else if (origin == SEEKORIGIN_END) + forigin = SEEK_END; + + // TODO #ifdef LOVE_WINDOWS - return _fseeki64(file, (int64) pos, SEEK_SET) == 0; + return _fseeki64(file, pos, forigin) == 0; #else - return fseeko(file, (off_t) pos, SEEK_SET) == 0; + return fseeko(file, (off_t) pos, forigin) == 0; #endif } diff --git a/src/modules/filesystem/NativeFile.h b/src/modules/filesystem/NativeFile.h index 4a3e23e5c..9809784e8 100644 --- a/src/modules/filesystem/NativeFile.h +++ b/src/modules/filesystem/NativeFile.h @@ -39,22 +39,25 @@ public: static love::Type type; - NativeFile(const std::string &filename); + NativeFile(const std::string &filename, Mode mode); virtual ~NativeFile(); + // Implements Stream. + NativeFile *clone() override; + int64 read(void* dst, int64 size) override; + bool write(const void* data, int64 size) override; + bool flush() override; + int64 getSize() override; + int64 tell() override; + bool seek(int64 pos, SeekOrigin origin) override; + // Implements File. using File::read; using File::write; bool open(Mode mode) override; bool close() override; bool isOpen() const override; - int64 getSize() override; - int64 read(void *dst, int64 size) override; - bool write(const void *data, int64 size) override; - bool flush() override; bool isEOF() override; - int64 tell() override; - bool seek(uint64 pos) override; bool setBuffer(BufferMode bufmode, int64 size) override; BufferMode getBuffer(int64 &size) const override; Mode getMode() const override; @@ -62,6 +65,8 @@ public: private: + NativeFile(const NativeFile &other); + static const char *getModeString(Mode mode); std::string filename; diff --git a/src/modules/filesystem/physfs/File.cpp b/src/modules/filesystem/physfs/File.cpp index 2caa2fc93..181153d3e 100644 --- a/src/modules/filesystem/physfs/File.cpp +++ b/src/modules/filesystem/physfs/File.cpp @@ -41,13 +41,26 @@ static bool setupWriteDirectory() return fs != nullptr && fs->setupWriteDirectory(); } -File::File(const std::string &filename) +File::File(const std::string &filename, Mode mode) : filename(filename) , file(nullptr) , mode(MODE_CLOSED) , bufferMode(BUFFER_NONE) , bufferSize(0) { + if (!open(mode)) + throw love::Exception("Could not open file at path %s", filename.c_str()); +} + +File::File(const File &other) + : filename(other.filename) + , file(nullptr) + , mode(MODE_CLOSED) + , bufferMode(other.bufferMode) + , bufferSize(other.bufferSize) +{ + if (!open(other.mode)) + throw love::Exception("Could not open file at path %s", filename.c_str()); } File::~File() @@ -56,10 +69,18 @@ File::~File() close(); } +File *File::clone() +{ + return new File(*this); +} + bool File::open(Mode mode) { if (mode == MODE_CLOSED) + { + close(); return true; + } if (!PHYSFS_isInit()) throw love::Exception("PhysFS is not initialized."); @@ -151,10 +172,6 @@ int64 File::read(void *dst, int64 size) if (!file || mode != MODE_READ) throw love::Exception("File is not opened for reading."); - int64 max = (int64)PHYSFS_fileLength(file); - size = (size == ALL) ? max : size; - size = (size > max) ? max : size; - if (size < 0) throw love::Exception("Invalid read size."); @@ -207,8 +224,19 @@ int64 File::tell() return (int64) PHYSFS_tell(file); } -bool File::seek(uint64 pos) +bool File::seek(int64 pos, SeekOrigin origin) { + if (file != nullptr) + { + if (origin == SEEKORIGIN_CURRENT) + pos += tell(); + else if (origin == SEEKORIGIN_END) + pos += getSize(); + } + + if (pos < 0) + return false; + return file != nullptr && PHYSFS_seek(file, (PHYSFS_uint64) pos) != 0; } diff --git a/src/modules/filesystem/physfs/File.h b/src/modules/filesystem/physfs/File.h index 13dc543ca..40bad82a6 100644 --- a/src/modules/filesystem/physfs/File.h +++ b/src/modules/filesystem/physfs/File.h @@ -46,23 +46,26 @@ public: * Constructs an File with the given ilename. * @param filename The relative filepath of the file to load. **/ - File(const std::string &filename); + File(const std::string &filename, Mode mode); virtual ~File(); + // Implements Stream. + File *clone() override; + int64 read(void* dst, int64 size) override; + bool write(const void* data, int64 size) override; + bool flush() override; + int64 getSize() override; + bool seek(int64 pos, SeekOrigin origin) override; + int64 tell() override; + // Implements love::filesystem::File. using love::filesystem::File::read; using love::filesystem::File::write; bool open(Mode mode) override; bool close() override; bool isOpen() const override; - int64 getSize() override; - virtual int64 read(void *dst, int64 size) override; - bool write(const void *data, int64 size) override; - bool flush() override; bool isEOF() override; - int64 tell() override; - bool seek(uint64 pos) override; bool setBuffer(BufferMode bufmode, int64 size) override; BufferMode getBuffer(int64 &size) const override; Mode getMode() const override; @@ -70,6 +73,8 @@ public: private: + File(const File &other); + // filename std::string filename; diff --git a/src/modules/filesystem/physfs/Filesystem.cpp b/src/modules/filesystem/physfs/Filesystem.cpp index a56b0d527..27f58e826 100644 --- a/src/modules/filesystem/physfs/Filesystem.cpp +++ b/src/modules/filesystem/physfs/Filesystem.cpp @@ -496,9 +496,9 @@ bool Filesystem::unmount(Data *data) return false; } -love::filesystem::File *Filesystem::newFile(const char *filename) const +love::filesystem::File *Filesystem::openFile(const char *filename, File::Mode mode) const { - return new File(filename); + return new File(filename, mode); } std::string Filesystem::getFullCommonPath(CommonPath path) @@ -738,6 +738,14 @@ std::string Filesystem::getRealDirectory(const char *filename) const return std::string(dir); } +bool Filesystem::exists(const char *filepath) const +{ + if (!PHYSFS_isInit()) + return false; + + return PHYSFS_exists(filepath) != 0; +} + bool Filesystem::getInfo(const char *filepath, Info &info) const { if (!PHYSFS_isInit()) @@ -793,19 +801,23 @@ bool Filesystem::remove(const char *file) FileData *Filesystem::read(const char *filename, int64 size) const { - File file(filename); - - file.open(File::MODE_READ); + File file(filename, File::MODE_READ); // close() is called in the File destructor. return file.read(size); } +FileData* Filesystem::read(const char* filename) const +{ + File file(filename, File::MODE_READ); + + // close() is called in the File destructor. + return file.read(); +} + void Filesystem::write(const char *filename, const void *data, int64 size) const { - File file(filename); - - file.open(File::MODE_WRITE); + File file(filename, File::MODE_WRITE); // close() is called in the File destructor. if (!file.write(data, size)) @@ -814,9 +826,7 @@ void Filesystem::write(const char *filename, const void *data, int64 size) const void Filesystem::append(const char *filename, const void *data, int64 size) const { - File file(filename); - - file.open(File::MODE_APPEND); + File file(filename, File::MODE_APPEND); // close() is called in the File destructor. if (!file.write(data, size)) diff --git a/src/modules/filesystem/physfs/Filesystem.h b/src/modules/filesystem/physfs/Filesystem.h index 1e7854029..a1ec62657 100644 --- a/src/modules/filesystem/physfs/Filesystem.h +++ b/src/modules/filesystem/physfs/Filesystem.h @@ -71,7 +71,7 @@ public: bool unmount(CommonPath path) override; bool unmountFullPath(const char *fullpath) override; - love::filesystem::File *newFile(const char *filename) const override; + love::filesystem::File *openFile(const char *filename, File::Mode mode) const override; std::string getFullCommonPath(CommonPath path) override; const char *getWorkingDirectory() override; @@ -82,13 +82,15 @@ public: std::string getRealDirectory(const char *filename) const override; + bool exists(const char *filepath) const override; bool getInfo(const char *filepath, Info &info) const override; bool createDirectory(const char *dir) override; bool remove(const char *file) override; - FileData *read(const char *filename, int64 size = File::ALL) const override; + FileData *read(const char *filename, int64 size) const override; + FileData *read(const char *filename) const override; void write(const char *filename, const void *data, int64 size) const override; void append(const char *filename, const void *data, int64 size) const override; diff --git a/src/modules/filesystem/wrap_File.cpp b/src/modules/filesystem/wrap_File.cpp index 6300ac183..4581010d8 100644 --- a/src/modules/filesystem/wrap_File.cpp +++ b/src/modules/filesystem/wrap_File.cpp @@ -121,10 +121,12 @@ int w_File_read(lua_State *L) startidx = 3; } - int64 size = (int64) luaL_optnumber(L, startidx, (lua_Number) File::ALL); + int64 size = (int64) luaL_optnumber(L, startidx, -1); try { + if (size < 0) + size = file->getSize(); d.set(file->read(size), Acquire::NORETAIN); } catch (love::Exception &e) diff --git a/src/modules/filesystem/wrap_Filesystem.cpp b/src/modules/filesystem/wrap_Filesystem.cpp index d30566e47..4b53a86c7 100644 --- a/src/modules/filesystem/wrap_Filesystem.cpp +++ b/src/modules/filesystem/wrap_Filesystem.cpp @@ -227,34 +227,53 @@ int w_unmountCommonPath(lua_State *L) return 1; } -int w_newFile(lua_State *L) +int w_openFile(lua_State *L) { const char *filename = luaL_checkstring(L, 1); + const char *modestr = luaL_checkstring(L, 2); - const char *str = 0; File::Mode mode = File::MODE_CLOSED; + if (!File::getConstant(modestr, mode)) + return luax_enumerror(L, "file open mode", File::getConstants(mode), modestr); - if (lua_isstring(L, 2)) + File *t = nullptr; + try { - str = luaL_checkstring(L, 2); - if (!File::getConstant(str, mode)) - return luax_enumerror(L, "file open mode", File::getConstants(mode), str); + t = instance()->openFile(filename, mode); + } + catch (love::Exception &e) + { + return luax_ioError(L, "%s", e.what()); } - File *t = instance()->newFile(filename); + luax_pushtype(L, t); + t->release(); + return 1; +} - if (mode != File::MODE_CLOSED) +int w_newFile(lua_State* L) +{ + luax_markdeprecated(L, 1, "love.filesystem.newFile", API_FUNCTION, DEPRECATED_RENAMED, "love.filesystem.openFile"); + + const char* filename = luaL_checkstring(L, 1); + + File::Mode mode = File::MODE_CLOSED; + + if (!lua_isnoneornil(L, 2)) { - try - { - if (!t->open(mode)) - throw love::Exception("Could not open file."); - } - catch (love::Exception &e) - { - t->release(); - return luax_ioError(L, "%s", e.what()); - } + const char* modestr = luaL_checkstring(L, 2); + if (!File::getConstant(modestr, mode)) + return luax_enumerror(L, "file open mode", File::getConstants(mode), modestr); + } + + File* t = nullptr; + try + { + t = instance()->openFile(filename, mode); + } + catch (love::Exception& e) + { + return luax_ioError(L, "%s", e.what()); } luax_pushtype(L, t); @@ -268,7 +287,14 @@ File *luax_getfile(lua_State *L, int idx) if (lua_isstring(L, idx)) { const char *filename = luaL_checkstring(L, idx); - file = instance()->newFile(filename); + try + { + file = instance()->openFile(filename, File::MODE_CLOSED); + } + catch (love::Exception &e) + { + luax_ioError(L, "%s", e.what()); + } } else { @@ -343,6 +369,11 @@ Data *luax_getdata(lua_State *L, int idx) return data; } +bool luax_cangetfile(lua_State *L, int idx) +{ + return lua_isstring(L, idx) || luax_istype(L, idx, File::type); +} + bool luax_cangetfiledata(lua_State *L, int idx) { return lua_isstring(L, idx) || luax_istype(L, idx, File::type) || luax_istype(L, idx, FileData::type); @@ -471,6 +502,13 @@ int w_getExecutablePath(lua_State *L) return 1; } +int w_exists(lua_State *L) +{ + const char *path = luaL_checkstring(L, 1); + luax_pushboolean(L, instance()->exists(path)); + return 1; +} + int w_getInfo(lua_State *L) { const char *filepath = luaL_checkstring(L, 1); @@ -557,12 +595,15 @@ int w_read(lua_State *L) } const char *filename = luaL_checkstring(L, startidx + 0); - int64 len = (int64) luaL_optinteger(L, startidx + 1, File::ALL); + int64 len = (int64) luaL_optinteger(L, startidx + 1, -1); FileData *data = nullptr; try { - data = instance()->read(filename, len); + if (len >= 0) + data = instance()->read(filename, len); + else + data = instance()->read(filename); } catch (love::Exception &e) { @@ -655,16 +696,8 @@ int w_lines(lua_State *L) { if (lua_isstring(L, 1)) { - File *file = instance()->newFile(lua_tostring(L, 1)); - bool success = false; - - luax_catchexcept(L, [&](){ success = file->open(File::MODE_READ); }); - - if (!success) - { - file->release(); - return luaL_error(L, "Could not open file."); - } + File *file = nullptr; + luax_catchexcept(L, [&]() { file = instance()->openFile(lua_tostring(L, 1), File::MODE_READ); }); luax_pushtype(L, file); file->release(); @@ -806,6 +839,9 @@ int loader(lua_State *L) { std::string modulename = luax_checkstring(L, 1); + if (modulename.find('/') != std::string::npos) + luax_markdeprecated(L, 2, "character in require string (forward slashes), use dots instead.", API_CUSTOM); + for (char &c : modulename) { if (c == '.') @@ -954,7 +990,7 @@ static const luaL_Reg functions[] = { "unmount", w_unmount }, { "unmountFullPath", w_unmountFullPath }, { "unmountCommonPath", w_unmountCommonPath }, - { "newFile", w_newFile }, + { "openFile", w_openFile }, { "getFullCommonPath", w_getFullCommonPath }, { "getWorkingDirectory", w_getWorkingDirectory }, { "getUserDirectory", w_getUserDirectory }, @@ -971,6 +1007,7 @@ static const luaL_Reg functions[] = { "getDirectoryItems", w_getDirectoryItems }, { "lines", w_lines }, { "load", w_load }, + { "exists", w_exists }, { "getInfo", w_getInfo }, { "setSymlinksEnabled", w_setSymlinksEnabled }, { "areSymlinksEnabled", w_areSymlinksEnabled }, @@ -980,6 +1017,9 @@ static const luaL_Reg functions[] = { "getCRequirePath", w_getCRequirePath }, { "setCRequirePath", w_setCRequirePath }, + // Deprecated + { "newFile", w_newFile }, + { 0, 0 } }; diff --git a/src/modules/filesystem/wrap_Filesystem.h b/src/modules/filesystem/wrap_Filesystem.h index 5474d3717..6d1667bf2 100644 --- a/src/modules/filesystem/wrap_Filesystem.h +++ b/src/modules/filesystem/wrap_Filesystem.h @@ -40,7 +40,9 @@ namespace filesystem **/ FileData *luax_getfiledata(lua_State *L, int idx); bool luax_cangetfiledata(lua_State *L, int idx); + File *luax_getfile(lua_State *L, int idx); +bool luax_cangetfile(lua_State *L, int idx); Data *luax_getdata(lua_State *L, int idx); bool luax_cangetdata(lua_State *L, int idx); diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 87f279029..6ae6b9bf4 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -253,8 +253,10 @@ void Graphics::createQuadIndexBuffer() Buffer::Settings settings(BUFFERUSAGEFLAG_INDEX, BUFFERDATAUSAGE_STATIC); quadIndexBuffer = newBuffer(settings, DATAFORMAT_UINT16, nullptr, size, 0); - Buffer::Mapper map(*quadIndexBuffer); - fillIndices(TRIANGLEINDEX_QUADS, 0, LOVE_UINT16_MAX, (uint16 *) map.data); + { + Buffer::Mapper map(*quadIndexBuffer); + fillIndices(TRIANGLEINDEX_QUADS, 0, LOVE_UINT16_MAX, (uint16 *) map.data); + } quadIndexBuffer->setImmutable(true); } @@ -310,12 +312,18 @@ love::graphics::ParticleSystem *Graphics::newParticleSystem(Texture *texture, in return new ParticleSystem(texture, size); } -ShaderStage *Graphics::newShaderStage(ShaderStageType stage, const std::string &source, const Shader::SourceInfo &info) +ShaderStage *Graphics::newShaderStage(ShaderStageType stage, const std::string &source, const Shader::CompileOptions &options, const Shader::SourceInfo &info, bool cache) { ShaderStage *s = nullptr; std::string cachekey; - if (!source.empty()) + // Never cache if there are custom defines set... because hashing would get + // more complicated/expensive, and there shouldn't be a lot of duplicate + // shader stages with custom defines anyway. + if (!options.defines.empty()) + cache = false; + + if (cache && !source.empty()) { data::HashFunction::Value hashvalue; data::hash(data::HashFunction::FUNCTION_SHA1, source.c_str(), source.size(), hashvalue); @@ -333,16 +341,16 @@ ShaderStage *Graphics::newShaderStage(ShaderStageType stage, const std::string & if (s == nullptr) { bool glsles = usesGLSLES(); - std::string glsl = Shader::createShaderStageCode(this, stage, source, info, glsles, true); + std::string glsl = Shader::createShaderStageCode(this, stage, source, options, info, glsles, true); s = newShaderStageInternal(stage, cachekey, glsl, glsles); - if (!cachekey.empty()) + if (cache && !cachekey.empty()) cachedShaderStages[stage][cachekey] = s; } return s; } -Shader *Graphics::newShader(const std::vector &stagessource, bool vulkan) +Shader *Graphics::newShader(const std::vector &stagessource, const Shader::CompileOptions &options) { StrongRef stages[SHADERSTAGE_MAX_ENUM] = {}; @@ -353,7 +361,7 @@ Shader *Graphics::newShader(const std::vector &stagessource, bool v for (const std::string &source : stagessource) { Shader::SourceInfo info = Shader::getSourceInfo(source); - info.vulkan = vulkan; + info.vulkan = options.defines.find("vulkan") != options.defines.end(); bool isanystage = false; for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++) @@ -364,12 +372,12 @@ Shader *Graphics::newShader(const std::vector &stagessource, bool v if (info.stages[i] != Shader::ENTRYPOINT_NONE) { isanystage = true; - stages[i].set(newShaderStage((ShaderStageType) i, source, info), Acquire::NORETAIN); + stages[i].set(newShaderStage((ShaderStageType) i, source, options, info, true), Acquire::NORETAIN); } } if (!isanystage) - throw love::Exception("Could not parse shader code (missing 'position' or 'effect' function?)"); + throw love::Exception("Could not parse shader code (missing shader entry point function such as 'position' or 'effect')"); } for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++) @@ -379,7 +387,8 @@ Shader *Graphics::newShader(const std::vector &stagessource, bool v { const std::string &source = Shader::getDefaultCode(Shader::STANDARD_DEFAULT, stype); Shader::SourceInfo info = Shader::getSourceInfo(source); - stages[i].set(newShaderStage(stype, source, info), Acquire::NORETAIN); + Shader::CompileOptions opts; + stages[i].set(newShaderStage(stype, source, opts, info, true), Acquire::NORETAIN); } } @@ -387,7 +396,7 @@ Shader *Graphics::newShader(const std::vector &stagessource, bool v return newShaderInternal(stages); } -Shader *Graphics::newComputeShader(const std::string &source) +Shader *Graphics::newComputeShader(const std::string &source, const Shader::CompileOptions &options) { Shader::SourceInfo info = Shader::getSourceInfo(source); @@ -395,7 +404,11 @@ Shader *Graphics::newComputeShader(const std::string &source) throw love::Exception("Could not parse compute shader code (missing 'computemain' function?)"); StrongRef stages[SHADERSTAGE_MAX_ENUM]; - stages[SHADERSTAGE_COMPUTE].set(newShaderStage(SHADERSTAGE_COMPUTE, source, info)); + + // Don't bother caching compute shader intermediate source, since there + // shouldn't be much reuse. + stages[SHADERSTAGE_COMPUTE].set(newShaderStage(SHADERSTAGE_COMPUTE, source, options, info, false)); + return newShaderInternal(stages); } @@ -430,7 +443,7 @@ void Graphics::cleanupCachedShaderStage(ShaderStageType type, const std::string cachedShaderStages[type].erase(hashkey); } -bool Graphics::validateShader(bool gles, const std::vector &stagessource, std::string &err) +bool Graphics::validateShader(bool gles, const std::vector &stagessource, const Shader::CompileOptions &options, std::string &err) { StrongRef stages[SHADERSTAGE_MAX_ENUM] = {}; @@ -456,7 +469,7 @@ bool Graphics::validateShader(bool gles, const std::vector &stagess if (info.stages[i] != Shader::ENTRYPOINT_NONE) { isanystage = true; - std::string glsl = Shader::createShaderStageCode(this, stype, source, info, gles, false); + std::string glsl = Shader::createShaderStageCode(this, stype, source, options, info, gles, false); stages[i].set(new ShaderStageForValidation(this, stype, glsl, gles), Acquire::NORETAIN); } } diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 95005303f..08f9267c1 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -449,8 +449,8 @@ public: SpriteBatch *newSpriteBatch(Texture *texture, int size, BufferDataUsage usage); ParticleSystem *newParticleSystem(Texture *texture, int size); - Shader *newShader(const std::vector &stagessource, bool vulkan = false); - Shader *newComputeShader(const std::string &source); + Shader *newShader(const std::vector &stagessource, const Shader::CompileOptions &options); + Shader *newComputeShader(const std::string &source, const Shader::CompileOptions &options); virtual Buffer *newBuffer(const Buffer::Settings &settings, const std::vector &format, const void *data, size_t size, size_t arraylength) = 0; virtual Buffer *newBuffer(const Buffer::Settings &settings, DataFormat format, const void *data, size_t size, size_t arraylength); @@ -461,7 +461,7 @@ public: Text *newText(Font *font, const std::vector &text = {}); - bool validateShader(bool gles, const std::vector &stages, std::string &err); + bool validateShader(bool gles, const std::vector &stages, const Shader::CompileOptions &options, std::string &err); /** * Resets the current color, background color, line style, and so forth. @@ -798,7 +798,7 @@ public: /** * Gets whether the specified pixel format usage is supported. **/ - virtual bool isPixelFormatSupported(PixelFormat format, PixelFormatUsageFlags usage, bool sRGB = false) = 0; + virtual bool isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB = false) = 0; /** * Gets the renderer used by love.graphics. @@ -966,7 +966,7 @@ protected: {} }; - ShaderStage *newShaderStage(ShaderStageType stage, const std::string &source, const Shader::SourceInfo &info); + ShaderStage *newShaderStage(ShaderStageType stage, const std::string &source, const Shader::CompileOptions &options, const Shader::SourceInfo &info, bool cache); virtual ShaderStage *newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) = 0; virtual Shader *newShaderInternal(StrongRef stages[SHADERSTAGE_MAX_ENUM]) = 0; virtual StreamBuffer *newStreamBuffer(BufferUsage type, size_t size) = 0; diff --git a/src/modules/graphics/Shader.cpp b/src/modules/graphics/Shader.cpp index 148ea4896..375a985f1 100644 --- a/src/modules/graphics/Shader.cpp +++ b/src/modules/graphics/Shader.cpp @@ -573,7 +573,7 @@ Shader::SourceInfo Shader::getSourceInfo(const std::string &src) return info; } -std::string Shader::createShaderStageCode(Graphics *gfx, ShaderStageType stage, const std::string &code, const Shader::SourceInfo &info, bool gles, bool checksystemfeatures) +std::string Shader::createShaderStageCode(Graphics *gfx, ShaderStageType stage, const std::string &code, const CompileOptions &options, const Shader::SourceInfo &info, bool gles, bool checksystemfeatures) { if (info.language == Shader::LANGUAGE_MAX_ENUM) throw love::Exception("Invalid shader language"); @@ -630,8 +630,10 @@ std::string Shader::createShaderStageCode(Graphics *gfx, ShaderStageType stage, ss << "#define LOVE_GAMMA_CORRECT 1\n"; if (info.usesMRT) ss << "#define LOVE_MULTI_RENDER_TARGETS 1\n"; - if (info.vulkan) - ss << "#define USE_VULKAN\n"; + + for (const auto &def : options.defines) + ss << "#define " + def.first + " " + def.second + "\n"; + ss << glsl::global_syntax; ss << stageinfo.header; ss << stageinfo.uniforms; diff --git a/src/modules/graphics/Shader.h b/src/modules/graphics/Shader.h index ca3278a09..669db3503 100644 --- a/src/modules/graphics/Shader.h +++ b/src/modules/graphics/Shader.h @@ -107,6 +107,11 @@ public: ACCESS_WRITE = (1 << 1), }; + struct CompileOptions + { + std::map defines; + }; + struct SourceInfo { Language language; @@ -237,7 +242,7 @@ public: void getLocalThreadgroupSize(int *x, int *y, int *z); static SourceInfo getSourceInfo(const std::string &src); - static std::string createShaderStageCode(Graphics *gfx, ShaderStageType stage, const std::string &code, const SourceInfo &info, bool gles, bool checksystemfeatures); + static std::string createShaderStageCode(Graphics *gfx, ShaderStageType stage, const std::string &code, const CompileOptions &options, const SourceInfo &info, bool gles, bool checksystemfeatures); static bool validate(StrongRef stages[], std::string &err); diff --git a/src/modules/graphics/Texture.cpp b/src/modules/graphics/Texture.cpp index b0c1c0924..7da786e4d 100644 --- a/src/modules/graphics/Texture.cpp +++ b/src/modules/graphics/Texture.cpp @@ -232,8 +232,13 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices) if (mipmapsMode != MIPMAPS_NONE) mipmapCount = getTotalMipmapCount(pixelWidth, pixelHeight, depth); - if (mipmapsMode == MIPMAPS_AUTO && isPixelFormatDepthStencil(format)) - throw love::Exception("Automatic mipmap generation cannot be used for depth/stencil textures."); + const char *miperr = nullptr; + if (mipmapsMode == MIPMAPS_AUTO && !supportsGenerateMipmaps(miperr)) + { + const char *fstr = "unknown"; + love::getConstant(format, fstr); + throw love::Exception("Automatic mipmap generation is not supported for textures with the %s pixel format.", fstr); + } if (pixelWidth <= 0 || pixelHeight <= 0 || layers <= 0 || depth <= 0) throw love::Exception("Texture dimensions must be greater than 0."); @@ -511,19 +516,49 @@ void Texture::replacePixels(const void *data, size_t size, int slice, int mipmap generateMipmaps(); } -void Texture::generateMipmaps() +bool Texture::supportsGenerateMipmaps(const char *&outReason) const { - if (getMipmapCount() == 1 || getMipmapsMode() == MIPMAPS_NONE) - throw love::Exception("generateMipmaps can only be called on a Texture which was created with mipmaps enabled."); + if (getMipmapsMode() == MIPMAPS_NONE) + { + outReason = "generateMipmaps can only be called on a Texture which was created with mipmaps enabled."; + return false; + } if (isPixelFormatCompressed(format)) - throw love::Exception("generateMipmaps cannot be called on a compressed Texture."); + { + outReason = "generateMipmaps cannot be called on a compressed Texture."; + return false; + } if (isPixelFormatDepthStencil(format)) - throw love::Exception("generateMipmaps cannot be called on a depth/stencil Texture."); + { + outReason = "generateMipmaps cannot be called on a depth/stencil Texture."; + return false; + } if (isPixelFormatInteger(format)) - throw love::Exception("generateMipmaps cannot be called on an integer Texture."); + { + outReason = "generateMipmaps cannot be called on an integer Texture."; + return false; + } + + // This should be linear | rt because that's what metal needs, but the above + // code handles textures can't be used as RTs in metal. + auto gfx = Module::getInstance(Module::M_GRAPHICS); + if (gfx != nullptr && !gfx->isPixelFormatSupported(format, PIXELFORMATUSAGEFLAGS_LINEAR)) + { + outReason = "generateMipmaps cannot be called on textures with formats that don't support linear filtering on this system."; + return false; + } + + return true; +} + +void Texture::generateMipmaps() +{ + const char *err = nullptr; + if (!supportsGenerateMipmaps(err)) + throw love::Exception("%s", err); generateMipmapsInternal(); } diff --git a/src/modules/graphics/Texture.h b/src/modules/graphics/Texture.h index e79ad0528..415e4ace7 100644 --- a/src/modules/graphics/Texture.h +++ b/src/modules/graphics/Texture.h @@ -311,6 +311,7 @@ protected: void uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y); virtual void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) = 0; + bool supportsGenerateMipmaps(const char *&outReason) const; virtual void generateMipmapsInternal() = 0; virtual void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) = 0; diff --git a/src/modules/graphics/metal/Buffer.mm b/src/modules/graphics/metal/Buffer.mm index b4ac8ef5b..07cc47f3a 100644 --- a/src/modules/graphics/metal/Buffer.mm +++ b/src/modules/graphics/metal/Buffer.mm @@ -111,7 +111,7 @@ Buffer::~Buffer() void *Buffer::map(MapType /*map*/, size_t offset, size_t size) { @autoreleasepool { - if (size == 0) + if (size == 0 || isImmutable()) return nullptr; Range r(offset, size); @@ -160,7 +160,7 @@ void Buffer::unmap(size_t usedoffset, size_t usedsize) void Buffer::fill(size_t offset, size_t size, const void *data) { @autoreleasepool { - if (size == 0) + if (size == 0 || isImmutable()) return; size_t buffersize = getSize(); diff --git a/src/modules/graphics/metal/Graphics.h b/src/modules/graphics/metal/Graphics.h index 7591cff12..9274a0d6e 100644 --- a/src/modules/graphics/metal/Graphics.h +++ b/src/modules/graphics/metal/Graphics.h @@ -110,7 +110,7 @@ public: void setWireframe(bool enable) override; PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const override; - bool isPixelFormatSupported(PixelFormat format, PixelFormatUsageFlags usage, bool sRGB = false) override; + bool isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB = false) override; Renderer getRenderer() const override; bool usesGLSLES() const override; RendererInfo getRendererInfo() const override; diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index 4466eb51d..2704118d0 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -130,11 +130,6 @@ static inline id getMTLTexture(love::graphics::Texture *tex) return tex ? (__bridge id)(void *) tex->getHandle() : nil; } -static inline id getMTLSampler(love::graphics::Texture *tex) -{ - return tex ? (__bridge id)(void *) tex->getSamplerHandle() : nil; -} - static inline id getMTLRenderTarget(love::graphics::Texture *tex) { return tex ? (__bridge id)(void *) tex->getRenderTargetHandle() : nil; @@ -348,9 +343,10 @@ Graphics::Graphics() if (!Shader::standardShaders[i]) { std::vector stages; + Shader::CompileOptions opts; stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_VERTEX)); stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_PIXEL)); - Shader::standardShaders[i] = newShader(stages); + Shader::standardShaders[i] = newShader(stages, opts); } } @@ -1782,7 +1778,7 @@ PixelFormat Graphics::getSizedFormat(PixelFormat format, bool /*rendertarget*/, } } -bool Graphics::isPixelFormatSupported(PixelFormat format, PixelFormatUsageFlags usage, bool sRGB) +bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB) { bool rendertarget = (usage & PIXELFORMATUSAGEFLAGS_RENDERTARGET) != 0; bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0; diff --git a/src/modules/graphics/metal/Shader.mm b/src/modules/graphics/metal/Shader.mm index 7e8687e9f..d3060488f 100644 --- a/src/modules/graphics/metal/Shader.mm +++ b/src/modules/graphics/metal/Shader.mm @@ -232,11 +232,6 @@ static inline id getMTLTexture(love::graphics::Buffer *buffer) return buffer ? (__bridge id)(void *) buffer->getTexelBufferHandle() : nil; } -static inline id getMTLSampler(love::graphics::Texture *tex) -{ - return tex ? (__bridge id)(void *) tex->getSamplerHandle() : nil; -} - static inline id getMTLBuffer(love::graphics::Buffer *buffer) { return buffer ? (__bridge id)(void *) buffer->getHandle() : nil; @@ -313,6 +308,12 @@ Shader::Shader(id device, StrongRef stag bool forcedefault = false; bool forwardcompat = true; +#ifdef LOVE_IOS + defaultversion = 320; + defaultprofile = EEsProfile; + forcedefault = true; +#endif + if (!tshader->parse(&defaultTBuiltInResource, defaultversion, defaultprofile, forcedefault, forwardcompat, EShMsgSuppressWarnings)) { const char *stagename = "unknown"; diff --git a/src/modules/graphics/metal/Texture.mm b/src/modules/graphics/metal/Texture.mm index fcd01885c..0c2adf944 100644 --- a/src/modules/graphics/metal/Texture.mm +++ b/src/modules/graphics/metal/Texture.mm @@ -269,8 +269,6 @@ void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t s void Texture::generateMipmapsInternal() { @autoreleasepool { - // TODO: alternate method for non-color-renderable and non-filterable - // pixel formats. id encoder = Graphics::getInstance()->useBlitEncoder(); [encoder generateMipmapsForTexture:texture]; }} diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index cff45294d..df1b911fc 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -112,7 +112,7 @@ Graphics::Graphics() , bufferMapMemory(nullptr) , bufferMapMemorySize(2 * 1024 * 1024) , defaultBuffers() - , supportedFormats() + , pixelFormatUsage() { gl = OpenGL(); @@ -425,9 +425,10 @@ bool Graphics::setMode(void */*context*/, int width, int height, int pixelwidth, if (!Shader::standardShaders[i]) { std::vector stages; + Shader::CompileOptions opts; stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_VERTEX)); stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_PIXEL)); - Shader::standardShaders[i] = newShader(stages); + Shader::standardShaders[i] = newShader(stages, opts); } } catch (love::Exception &) @@ -1669,6 +1670,13 @@ void Graphics::initCapabilities() for (int i = 0; i < TEXTURE_MAX_ENUM; i++) capabilities.textureTypes[i] = gl.isTextureTypeSupported((TextureType) i); + + for (int i = 0; i < PIXELFORMAT_MAX_ENUM; i++) + { + auto format = (PixelFormat) i; + pixelFormatUsage[i][0] = computePixelFormatUsage(format, false); + pixelFormatUsage[i][1] = computePixelFormatUsage(format, true); + } } PixelFormat Graphics::getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const @@ -1696,111 +1704,97 @@ PixelFormat Graphics::getSizedFormat(PixelFormat format, bool rendertarget, bool } } -bool Graphics::isPixelFormatSupported(PixelFormat format, PixelFormatUsageFlags usage, bool sRGB) +uint32 Graphics::computePixelFormatUsage(PixelFormat format, bool readable) { - if (sRGB) - { - format = getSRGBPixelFormat(format); - sRGB = false; - } + uint32 usage = OpenGL::getPixelFormatUsageFlags(format); - bool rendertarget = (usage & PIXELFORMATUSAGEFLAGS_RENDERTARGET) != 0; - bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0; - bool computewrite = (usage & PIXELFORMATUSAGEFLAGS_COMPUTEWRITE) != 0; - - format = getSizedFormat(format, rendertarget, readable); - - OptionalBool &supported = supportedFormats[format][rendertarget ? 1 : 0][readable ? 1 : 0][computewrite ? 1 : 0][sRGB ? 1 : 0]; - - if (supported.hasValue) - return supported.value; - - uint32 supportedflags = OpenGL::getPixelFormatUsageFlags(format); - - if ((usage & supportedflags) != usage) - { - supported.set(false); - return supported.value; - } - - if (!rendertarget) - { - supported.set(true); - return supported.value; - } + if (readable && (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) == 0) + return 0; // Even though we might have the necessary OpenGL version or extension, // drivers are still allowed to throw FRAMEBUFFER_UNSUPPORTED when attaching // a texture to a FBO whose format the driver doesn't like. So we should // test with an actual FBO. - GLuint texture = 0; - GLuint renderbuffer = 0; - // Avoid the test for depth/stencil formats - not every GL version // guarantees support for depth/stencil-only render targets (which we would // need for the test below to work), and we already do some finagling in // convertPixelFormat to try to use the best-supported internal // depth/stencil format for a particular driver. - if (isPixelFormatDepthStencil(format)) + if ((usage & PIXELFORMATUSAGEFLAGS_RENDERTARGET) != 0 && !isPixelFormatDepthStencil(format)) { - supported.set(true); - return true; - } + GLuint texture = 0; + GLuint renderbuffer = 0; + bool sRGB = isPixelFormatSRGB(format); - OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, !readable, sRGB); + OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, !readable, sRGB); - GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); + GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); - GLuint fbo = 0; - glGenFramebuffers(1, &fbo); - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); - // Make sure at least something is bound to a color attachment. I believe - // this is required on ES2 but I'm not positive. - if (isPixelFormatDepthStencil(format)) - gl.framebufferTexture(GL_COLOR_ATTACHMENT0, TEXTURE_2D, gl.getDefaultTexture(TEXTURE_2D, DATA_BASETYPE_FLOAT), 0, 0, 0); - - if (readable) - { - glGenTextures(1, &texture); - gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false); - - SamplerState s; - s.minFilter = s.magFilter = SamplerState::FILTER_NEAREST; - gl.setSamplerState(TEXTURE_2D, s); - - gl.rawTexStorage(TEXTURE_2D, 1, format, sRGB, 1, 1); - } - else - { - glGenRenderbuffers(1, &renderbuffer); - glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); - glRenderbufferStorage(GL_RENDERBUFFER, fmt.internalformat, 1, 1); - } - - for (GLenum attachment : fmt.framebufferAttachments) - { - if (attachment == GL_NONE) - continue; + // Make sure at least something is bound to a color attachment. I believe + // this is required on ES2 but I'm not positive. + if (isPixelFormatDepthStencil(format)) + gl.framebufferTexture(GL_COLOR_ATTACHMENT0, TEXTURE_2D, gl.getDefaultTexture(TEXTURE_2D, DATA_BASETYPE_FLOAT), 0, 0, 0); if (readable) - gl.framebufferTexture(attachment, TEXTURE_2D, texture, 0, 0, 0); + { + glGenTextures(1, &texture); + gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false); + + SamplerState s; + s.minFilter = s.magFilter = SamplerState::FILTER_NEAREST; + gl.setSamplerState(TEXTURE_2D, s); + + gl.rawTexStorage(TEXTURE_2D, 1, format, sRGB, 1, 1); + } else - glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, renderbuffer); + { + glGenRenderbuffers(1, &renderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + glRenderbufferStorage(GL_RENDERBUFFER, fmt.internalformat, 1, 1); + } + + for (GLenum attachment : fmt.framebufferAttachments) + { + if (attachment == GL_NONE) + continue; + + if (readable) + gl.framebufferTexture(attachment, TEXTURE_2D, texture, 0, 0, 0); + else + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, renderbuffer); + } + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + usage &= ~PIXELFORMATUSAGEFLAGS_RENDERTARGET; + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); + gl.deleteFramebuffer(fbo); + + if (texture != 0) + gl.deleteTexture(texture); + + if (renderbuffer != 0) + glDeleteRenderbuffers(1, &renderbuffer); } - supported.set(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + return usage; +} - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); - gl.deleteFramebuffer(fbo); +bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB) +{ + if (sRGB) + format = getSRGBPixelFormat(format); - if (texture != 0) - gl.deleteTexture(texture); + bool rendertarget = (usage & PIXELFORMATUSAGEFLAGS_RENDERTARGET) != 0; + bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0; - if (renderbuffer != 0) - glDeleteRenderbuffers(1, &renderbuffer); + format = getSizedFormat(format, rendertarget, readable); - return supported.value; + return (usage & pixelFormatUsage[format][readable ? 1 : 0]) == usage; } } // opengl diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index b3d559bdd..f9bcb6523 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -106,7 +106,7 @@ public: void setWireframe(bool enable) override; PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const override; - bool isPixelFormatSupported(PixelFormat format, PixelFormatUsageFlags usage, bool sRGB = false) override; + bool isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB = false) override; Renderer getRenderer() const override; bool usesGLSLES() const override; RendererInfo getRendererInfo() const override; @@ -155,6 +155,8 @@ private: void setDebug(bool enable); + uint32 computePixelFormatUsage(PixelFormat format, bool readable); + std::unordered_map framebufferObjects; bool windowHasStencil; GLuint mainVAO; @@ -170,8 +172,8 @@ private: // Only needed for buffer types that can be bound to shaders. StrongRef defaultBuffers[BUFFERUSAGE_MAX_ENUM]; - // [rendertarget][readable][computewrite][srgb] - OptionalBool supportedFormats[PIXELFORMAT_MAX_ENUM][2][2][2][2]; + // [non-readable, readable] + uint32 pixelFormatUsage[PIXELFORMAT_MAX_ENUM][2]; }; // Graphics diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index ab3487728..13f184360 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -823,7 +823,7 @@ namespace love { std::vector stages; stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_VERTEX)); stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_PIXEL)); - Shader::standardShaders[i] = newShader(stages, true); + Shader::standardShaders[i] = newShader(stages, { { {"vulkan", "1"} } }); } } } diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index b264cfcc1..82904e835 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -68,7 +68,7 @@ namespace love { void setPointSize(float size) override { std::cout << "setPointSize "; } void setWireframe(bool enable) override { std::cout << "setWireframe "; } PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const override { std::cout << "getSizedFormat "; return format; } - bool isPixelFormatSupported(PixelFormat format, PixelFormatUsageFlags usage, bool sRGB = false) override { std::cout << "isPixelFormatSupported "; return true; } + bool isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB = false) override { std::cout << "isPixelFormatSupported "; return true; } Renderer getRenderer() const override { std::cout << "getRenderer "; return RENDERER_VULKAN; } bool usesGLSLES() const override { std::cout << "usesGLSES "; return false; } RendererInfo getRendererInfo() const override { std::cout << "getRendererInfo "; return {}; } diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index 70b1de307..d919c4130 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -1347,7 +1347,7 @@ int w_newParticleSystem(lua_State *L) return 1; } -static int w_getShaderSource(lua_State *L, int startidx, std::vector &stages) +static int w_getShaderSource(lua_State *L, int startidx, std::vector &stages, Shader::CompileOptions &options) { using namespace love::filesystem; @@ -1369,7 +1369,6 @@ static int w_getShaderSource(lua_State *L, int startidx, std::vector stages; - w_getShaderSource(L, 1, stages); + Shader::CompileOptions options; + w_getShaderSource(L, 1, stages, options); bool should_error = false; try { - Shader *shader = instance()->newShader(stages); + Shader *shader = instance()->newShader(stages, options); luax_pushtype(L, shader); shader->release(); } @@ -1446,12 +1488,13 @@ int w_newShader(lua_State *L) int w_newComputeShader(lua_State* L) { std::vector stages; - w_getShaderSource(L, 1, stages); + Shader::CompileOptions options; + w_getShaderSource(L, 1, stages, options); bool should_error = false; try { - Shader *shader = instance()->newComputeShader(stages[0]); + Shader *shader = instance()->newComputeShader(stages[0], options); luax_pushtype(L, shader); shader->release(); } @@ -1476,13 +1519,14 @@ int w_validateShader(lua_State *L) bool gles = luax_checkboolean(L, 1); std::vector stages; - w_getShaderSource(L, 2, stages); + Shader::CompileOptions options; + w_getShaderSource(L, 2, stages, options); bool success = true; std::string err; try { - success = instance()->validateShader(gles, stages, err); + success = instance()->validateShader(gles, stages, options, err); } catch (love::Exception &e) { diff --git a/src/modules/graphics/wrap_Graphics.lua b/src/modules/graphics/wrap_Graphics.lua index ea2b52579..8bb8a45f8 100644 --- a/src/modules/graphics/wrap_Graphics.lua +++ b/src/modules/graphics/wrap_Graphics.lua @@ -33,7 +33,7 @@ function love.graphics.newVideo(file, settings) local source, success if settings.audio ~= false and love.audio then - success, source = pcall(love.audio.newSource, video:getStream():getFilename(), "stream") + success, source = pcall(love.audio.newSource, video:getStream():getFilename(), "stream", "file") end if success then video:setSource(source) diff --git a/src/modules/image/CompressedImageData.cpp b/src/modules/image/CompressedImageData.cpp index 79c7b78f5..387632056 100644 --- a/src/modules/image/CompressedImageData.cpp +++ b/src/modules/image/CompressedImageData.cpp @@ -53,7 +53,7 @@ CompressedImageData::CompressedImageData(const std::list &forma if (format == PIXELFORMAT_UNKNOWN) throw love::Exception("Could not parse compressed data: Unknown format."); - if (dataImages.size() == 0 || memory->size == 0) + if (dataImages.size() == 0 || memory->getSize() == 0) throw love::Exception("Could not parse compressed data: No valid data?"); } @@ -61,8 +61,7 @@ CompressedImageData::CompressedImageData(const CompressedImageData &c) : format(c.format) , sRGB(c.sRGB) { - memory.set(new CompressedMemory(c.memory->size), Acquire::NORETAIN); - memcpy(memory->data, c.memory->data, memory->size); + memory.set(c.memory->clone(), Acquire::NORETAIN); for (const auto &i : c.dataImages) { @@ -83,12 +82,12 @@ CompressedImageData::~CompressedImageData() size_t CompressedImageData::getSize() const { - return memory->size; + return memory->getSize(); } void *CompressedImageData::getData() const { - return memory->data; + return memory->getData(); } int CompressedImageData::getMipmapCount() const diff --git a/src/modules/image/CompressedImageData.h b/src/modules/image/CompressedImageData.h index 8234d81aa..924afb740 100644 --- a/src/modules/image/CompressedImageData.h +++ b/src/modules/image/CompressedImageData.h @@ -103,7 +103,7 @@ protected: bool sRGB; // Single block of memory containing all of the sub-images. - StrongRef memory; + StrongRef memory; // Texture info for each mipmap level. std::vector> dataImages; diff --git a/src/modules/image/CompressedSlice.cpp b/src/modules/image/CompressedSlice.cpp index f185da994..fb1dd29c9 100644 --- a/src/modules/image/CompressedSlice.cpp +++ b/src/modules/image/CompressedSlice.cpp @@ -26,30 +26,12 @@ namespace love namespace image { -CompressedMemory::CompressedMemory(size_t size) - : data(nullptr) - , size(size) -{ - try - { - data = new uint8[size]; - } - catch (std::exception &) - { - throw love::Exception("Out of memory."); - } -} - -CompressedMemory::~CompressedMemory() -{ - delete[] data; -} - -CompressedSlice::CompressedSlice(PixelFormat format, int width, int height, CompressedMemory *memory, size_t offset, size_t size) +CompressedSlice::CompressedSlice(PixelFormat format, int width, int height, ByteData *memory, size_t offset, size_t size) : ImageDataBase(format, width, height) , memory(memory) , offset(offset) , dataSize(size) + , sRGB(false) { } @@ -58,6 +40,7 @@ CompressedSlice::CompressedSlice(const CompressedSlice &s) , memory(s.memory) , offset(s.offset) , dataSize(s.dataSize) + , sRGB(s.sRGB) { } diff --git a/src/modules/image/CompressedSlice.h b/src/modules/image/CompressedSlice.h index f322dd5eb..63004836b 100644 --- a/src/modules/image/CompressedSlice.h +++ b/src/modules/image/CompressedSlice.h @@ -23,7 +23,7 @@ // LOVE #include "common/int.h" #include "common/pixelformat.h" -#include "common/Object.h" +#include "data/ByteData.h" #include "ImageDataBase.h" namespace love @@ -31,17 +31,7 @@ namespace love namespace image { -class CompressedMemory : public Object -{ -public: - - CompressedMemory(size_t size); - virtual ~CompressedMemory(); - - uint8 *data; - size_t size; - -}; // CompressedMemory +using ByteData = love::data::ByteData; // Compressed image data can have multiple mipmap levels, each represented by a // sub-image. @@ -49,19 +39,19 @@ class CompressedSlice : public ImageDataBase { public: - CompressedSlice(PixelFormat format, int width, int height, CompressedMemory *memory, size_t offset, size_t size); + CompressedSlice(PixelFormat format, int width, int height, ByteData *memory, size_t offset, size_t size); CompressedSlice(const CompressedSlice &slice); virtual ~CompressedSlice(); CompressedSlice *clone() const override; - void *getData() const override { return memory->data + offset; } + void *getData() const override { return (uint8 *) memory->getData() + offset; } size_t getSize() const override { return dataSize; } bool isSRGB() const override { return sRGB; } size_t getOffset() const { return offset; } private: - StrongRef memory; + StrongRef memory; size_t offset; size_t dataSize; bool sRGB; diff --git a/src/modules/image/FormatHandler.cpp b/src/modules/image/FormatHandler.cpp index 1d37eabf5..9b4fc0050 100644 --- a/src/modules/image/FormatHandler.cpp +++ b/src/modules/image/FormatHandler.cpp @@ -60,7 +60,7 @@ bool FormatHandler::canParseCompressed(Data* /*data*/) return false; } -StrongRef FormatHandler::parseCompressed(Data* /*filedata*/, std::vector>& /*images*/, PixelFormat& /*format*/, bool& /*sRGB*/) +StrongRef FormatHandler::parseCompressed(Data* /*filedata*/, std::vector>& /*images*/, PixelFormat& /*format*/, bool& /*sRGB*/) { throw love::Exception("Compressed image parsing is not implemented for this format backend."); } diff --git a/src/modules/image/FormatHandler.h b/src/modules/image/FormatHandler.h index dd41d6568..2f3f63180 100644 --- a/src/modules/image/FormatHandler.h +++ b/src/modules/image/FormatHandler.h @@ -107,7 +107,7 @@ public: * * @return The single block of memory containing the parsed images. **/ - virtual StrongRef parseCompressed(Data *filedata, + virtual StrongRef parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB); diff --git a/src/modules/image/ImageData.cpp b/src/modules/image/ImageData.cpp index ec24d87f1..2a5be2819 100644 --- a/src/modules/image/ImageData.cpp +++ b/src/modules/image/ImageData.cpp @@ -43,7 +43,7 @@ ImageData::ImageData(int width, int height, PixelFormat format) : ImageDataBase(format, width, height) { if (!validPixelFormat(format)) - throw love::Exception("Unsupported pixel format for ImageData"); + throw love::Exception("ImageData does not support the %s pixel format.", getPixelFormatName(format)); create(width, height, format); @@ -55,7 +55,7 @@ ImageData::ImageData(int width, int height, PixelFormat format, void *data, bool : ImageDataBase(format, width, height) { if (!validPixelFormat(format)) - throw love::Exception("Unsupported pixel format for ImageData"); + throw love::Exception("ImageData does not support the %s pixel format.", getPixelFormatName(format)); if (own) this->data = (unsigned char *) data; @@ -196,11 +196,7 @@ love::filesystem::FileData *ImageData::encode(FormatHandler::EncodedFormat encod } if (encoder == nullptr || encodedimage.data == nullptr) - { - const char *fname = "unknown"; - love::getConstant(format, fname); - throw love::Exception("No suitable image encoder for %s format.", fname); - } + throw love::Exception("No suitable image encoder for the %s pixel format.", getPixelFormatName(format)); love::filesystem::FileData *filedata = nullptr; @@ -540,7 +536,7 @@ void ImageData::setPixel(int x, int y, const Colorf &c) Pixel *p = (Pixel *) (data + ((y * width + x) * pixelsize)); if (pixelSetFunction == nullptr) - throw love::Exception("Unhandled pixel format %d in ImageData::setPixel", format); + throw love::Exception("ImageData:setPixel does not currently support the %s pixel format.", getPixelFormatName(format)); Lock lock(mutex); @@ -556,7 +552,7 @@ void ImageData::getPixel(int x, int y, Colorf &c) const const Pixel *p = (const Pixel *) (data + ((y * width + x) * pixelsize)); if (pixelGetFunction == nullptr) - throw love::Exception("Unhandled pixel format %d in ImageData::setPixel", format); + throw love::Exception("ImageData:getPixel does not currently support the %s pixel format.", getPixelFormatName(format)); Lock lock(mutex); @@ -759,7 +755,7 @@ void ImageData::paste(ImageData *src, int dx, int dy, int sx, int sy, int sw, in else if (srcformat == PIXELFORMAT_RGBA32_FLOAT && dstformat == PIXELFORMAT_RGBA16_FLOAT) pasteRGBA32FtoRGBA16F(rowsrc, rowdst, sw); - else + else if (getfunction != nullptr && setfunction != nullptr) { // Slow path: convert src -> Colorf -> dst. Colorf c; @@ -771,6 +767,10 @@ void ImageData::paste(ImageData *src, int dx, int dy, int sx, int sy, int sw, in setfunction(c, dstp); } } + else if (getfunction == nullptr) + throw love::Exception("ImageData:paste does not currently support converting from the %s pixel format.", getPixelFormatName(srcformat)); + else + throw love::Exception("ImageData:paste does not currently support converting to the %s pixel format.", getPixelFormatName(dstformat)); } } } @@ -787,45 +787,7 @@ size_t ImageData::getPixelSize() const bool ImageData::validPixelFormat(PixelFormat format) { - switch (format) - { - case PIXELFORMAT_R8_UNORM: - case PIXELFORMAT_RG8_UNORM: - case PIXELFORMAT_RGBA8_UNORM: - case PIXELFORMAT_R16_UNORM: - case PIXELFORMAT_RG16_UNORM: - case PIXELFORMAT_RGBA16_UNORM: - case PIXELFORMAT_R16_FLOAT: - case PIXELFORMAT_RG16_FLOAT: - case PIXELFORMAT_RGBA16_FLOAT: - case PIXELFORMAT_R32_FLOAT: - case PIXELFORMAT_RG32_FLOAT: - case PIXELFORMAT_RGBA32_FLOAT: - case PIXELFORMAT_RGBA4_UNORM: - case PIXELFORMAT_RGB5A1_UNORM: - case PIXELFORMAT_RGB565_UNORM: - case PIXELFORMAT_RGB10A2_UNORM: - case PIXELFORMAT_RG11B10_FLOAT: - return true; - default: - return false; - } -} - -bool ImageData::canPaste(PixelFormat src, PixelFormat dst) -{ - if (src == dst) - return true; - - if (!(src == PIXELFORMAT_RGBA8_UNORM || src == PIXELFORMAT_RGBA16_UNORM - || src == PIXELFORMAT_RGBA16_FLOAT || src == PIXELFORMAT_RGBA32_FLOAT)) - return false; - - if (!(dst == PIXELFORMAT_RGBA8_UNORM || dst == PIXELFORMAT_RGBA16_UNORM - || dst == PIXELFORMAT_RGBA16_FLOAT || dst == PIXELFORMAT_RGBA32_FLOAT)) - return false; - - return true; + return isPixelFormatColor(format) && !isPixelFormatCompressed(format); } ImageData::PixelSetFunction ImageData::getPixelSetFunction(PixelFormat format) diff --git a/src/modules/image/ImageData.h b/src/modules/image/ImageData.h index e3e04c395..83769e797 100644 --- a/src/modules/image/ImageData.h +++ b/src/modules/image/ImageData.h @@ -124,7 +124,6 @@ public: PixelGetFunction getPixelGetFunction() const { return pixelGetFunction; } static bool validPixelFormat(PixelFormat format); - static bool canPaste(PixelFormat src, PixelFormat dst); static PixelSetFunction getPixelSetFunction(PixelFormat format); static PixelGetFunction getPixelGetFunction(PixelFormat format); diff --git a/src/modules/image/magpie/ASTCHandler.cpp b/src/modules/image/magpie/ASTCHandler.cpp index 6a5c1d12e..817025759 100644 --- a/src/modules/image/magpie/ASTCHandler.cpp +++ b/src/modules/image/magpie/ASTCHandler.cpp @@ -105,7 +105,7 @@ bool ASTCHandler::canParseCompressed(Data *data) return true; } -StrongRef ASTCHandler::parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) +StrongRef ASTCHandler::parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) { if (!canParseCompressed(filedata)) throw love::Exception("Could not decode compressed data (not an .astc file?)"); @@ -125,15 +125,15 @@ StrongRef ASTCHandler::parseCompressed(Data *filedata, std::ve uint32 blocksY = (sizeY + header.blockdimY - 1) / header.blockdimY; uint32 blocksZ = (sizeZ + header.blockdimZ - 1) / header.blockdimZ; - size_t totalsize = blocksX * blocksY * blocksZ * 16; + size_t totalsize = (size_t) blocksX * blocksY * blocksZ * 16; if (totalsize + sizeof(header) > filedata->getSize()) throw love::Exception("Could not parse .astc file: file is too small."); - StrongRef memory(new CompressedMemory(totalsize), Acquire::NORETAIN); + StrongRef memory(new ByteData(totalsize, false), Acquire::NORETAIN); // .astc files only store a single mipmap level. - memcpy(memory->data, (uint8 *) filedata->getData() + sizeof(ASTCHeader), totalsize); + memcpy(memory->getData(), (uint8 *) filedata->getData() + sizeof(ASTCHeader), totalsize); images.emplace_back(new CompressedSlice(cformat, sizeX, sizeY, memory, 0, totalsize), Acquire::NORETAIN); diff --git a/src/modules/image/magpie/ASTCHandler.h b/src/modules/image/magpie/ASTCHandler.h index 6bd797d93..c6daba32f 100644 --- a/src/modules/image/magpie/ASTCHandler.h +++ b/src/modules/image/magpie/ASTCHandler.h @@ -43,7 +43,7 @@ public: // Implements FormatHandler. bool canParseCompressed(Data *data) override; - StrongRef parseCompressed(Data *filedata, + StrongRef parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) override; diff --git a/src/modules/image/magpie/KTXHandler.cpp b/src/modules/image/magpie/KTXHandler.cpp index 177742d7f..8fc2e52c6 100644 --- a/src/modules/image/magpie/KTXHandler.cpp +++ b/src/modules/image/magpie/KTXHandler.cpp @@ -298,7 +298,7 @@ bool KTXHandler::canParseCompressed(Data *data) return true; } -StrongRef KTXHandler::parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) +StrongRef KTXHandler::parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) { if (!canParseCompressed(filedata)) throw love::Exception("Could not decode compressed data (not a KTX file?)"); @@ -354,8 +354,7 @@ StrongRef KTXHandler::parseCompressed(Data *filedata, std::vec fileoffset += mipsizepadded; } - StrongRef memory; - memory.set(new CompressedMemory(totalsize), Acquire::NORETAIN); + StrongRef memory(new ByteData(totalsize, false), Acquire::NORETAIN); // Reset the file offset to the start of the file's image data. fileoffset = sizeof(KTXHeader) + header.bytesOfKeyValueData; @@ -376,7 +375,7 @@ StrongRef KTXHandler::parseCompressed(Data *filedata, std::vec int width = (int) std::max(header.pixelWidth >> i, 1u); int height = (int) std::max(header.pixelHeight >> i, 1u); - memcpy(memory->data + dataoffset, filebytes + fileoffset, mipsize); + memcpy((uint8 *) memory->getData() + dataoffset, filebytes + fileoffset, mipsize); auto slice = new CompressedSlice(cformat, width, height, memory, dataoffset, mipsize); images.push_back(slice); diff --git a/src/modules/image/magpie/KTXHandler.h b/src/modules/image/magpie/KTXHandler.h index 599d3a3a5..4a7bede72 100644 --- a/src/modules/image/magpie/KTXHandler.h +++ b/src/modules/image/magpie/KTXHandler.h @@ -42,7 +42,7 @@ public: // Implements FormatHandler. bool canParseCompressed(Data *data) override; - StrongRef parseCompressed(Data *filedata, + StrongRef parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) override; diff --git a/src/modules/image/magpie/PKMHandler.cpp b/src/modules/image/magpie/PKMHandler.cpp index e513599bc..8f276d257 100644 --- a/src/modules/image/magpie/PKMHandler.cpp +++ b/src/modules/image/magpie/PKMHandler.cpp @@ -114,7 +114,7 @@ bool PKMHandler::canParseCompressed(Data *data) return true; } -StrongRef PKMHandler::parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) +StrongRef PKMHandler::parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) { if (!canParseCompressed(filedata)) throw love::Exception("Could not decode compressed data (not a PKM file?)"); @@ -135,11 +135,10 @@ StrongRef PKMHandler::parseCompressed(Data *filedata, std::vec // The rest of the file after the header is all texture data. size_t totalsize = filedata->getSize() - sizeof(PKMHeader); - StrongRef memory; - memory.set(new CompressedMemory(totalsize), Acquire::NORETAIN); + StrongRef memory(new ByteData(totalsize, false), Acquire::NORETAIN); // PKM files only store a single mipmap level. - memcpy(memory->data, (uint8 *) filedata->getData() + sizeof(PKMHeader), totalsize); + memcpy(memory->getData(), (uint8 *) filedata->getData() + sizeof(PKMHeader), totalsize); // TODO: verify whether glCompressedTexImage works properly with the unpadded // width and height values (extended == padded.) diff --git a/src/modules/image/magpie/PKMHandler.h b/src/modules/image/magpie/PKMHandler.h index 76c1e89e5..0752340ac 100644 --- a/src/modules/image/magpie/PKMHandler.h +++ b/src/modules/image/magpie/PKMHandler.h @@ -42,7 +42,7 @@ public: // Implements FormatHandler. bool canParseCompressed(Data *data) override; - StrongRef parseCompressed(Data *filedata, + StrongRef parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) override; diff --git a/src/modules/image/magpie/PVRHandler.cpp b/src/modules/image/magpie/PVRHandler.cpp index aa5d089a6..b752bec5f 100644 --- a/src/modules/image/magpie/PVRHandler.cpp +++ b/src/modules/image/magpie/PVRHandler.cpp @@ -475,7 +475,7 @@ bool PVRHandler::canParseCompressed(Data *data) return false; } -StrongRef PVRHandler::parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) +StrongRef PVRHandler::parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) { if (!canParseCompressed(filedata)) throw love::Exception("Could not decode compressed data (not a PVR file?)"); @@ -525,8 +525,8 @@ StrongRef PVRHandler::parseCompressed(Data *filedata, std::vec if (filedata->getSize() < fileoffset + totalsize) throw love::Exception("Could not parse PVR file: invalid size calculation."); - StrongRef memory; - memory.set(new CompressedMemory(totalsize), Acquire::NORETAIN); + ; + StrongRef memory(new ByteData(totalsize, false), Acquire::NORETAIN); size_t curoffset = 0; const uint8 *filebytes = (uint8 *) filedata->getData() + fileoffset; @@ -541,7 +541,7 @@ StrongRef PVRHandler::parseCompressed(Data *filedata, std::vec int width = std::max((int) header3.width >> i, 1); int height = std::max((int) header3.height >> i, 1); - memcpy(memory->data + curoffset, filebytes + curoffset, mipsize); + memcpy((uint8 *) memory->getData() + curoffset, filebytes + curoffset, mipsize); auto slice = new CompressedSlice(cformat, width, height, memory, curoffset, mipsize); images.push_back(slice); diff --git a/src/modules/image/magpie/PVRHandler.h b/src/modules/image/magpie/PVRHandler.h index 8db4db322..11f612bb2 100644 --- a/src/modules/image/magpie/PVRHandler.h +++ b/src/modules/image/magpie/PVRHandler.h @@ -40,7 +40,7 @@ public: // Implements FormatHandler. bool canParseCompressed(Data *data) override; - StrongRef parseCompressed(Data *filedata, + StrongRef parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) override; diff --git a/src/modules/image/magpie/ddsHandler.cpp b/src/modules/image/magpie/ddsHandler.cpp index c0701e6a4..dd19e7c46 100644 --- a/src/modules/image/magpie/ddsHandler.cpp +++ b/src/modules/image/magpie/ddsHandler.cpp @@ -229,7 +229,7 @@ bool DDSHandler::canParseCompressed(Data *data) return dds::isCompressedDDS(data->getData(), data->getSize()); } -StrongRef DDSHandler::parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) +StrongRef DDSHandler::parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) { if (!dds::isCompressedDDS(filedata->getData(), filedata->getSize())) throw love::Exception("Could not decode compressed data (not a DDS file?)"); @@ -238,7 +238,6 @@ StrongRef DDSHandler::parseCompressed(Data *filedata, std::vec bool isSRGB = false; bool bgra = false; - StrongRef memory; size_t dataSize = 0; images.clear(); @@ -261,7 +260,7 @@ StrongRef DDSHandler::parseCompressed(Data *filedata, std::vec dataSize += img->dataSize; } - memory.set(new CompressedMemory(dataSize), Acquire::NORETAIN); + StrongRef memory(new ByteData(dataSize, false), Acquire::NORETAIN); size_t dataOffset = 0; @@ -272,7 +271,7 @@ StrongRef DDSHandler::parseCompressed(Data *filedata, std::vec const dds::Image *img = parser.getImageData(i); // Copy the mipmap image from the FileData to our block of memory. - memcpy(memory->data + dataOffset, img->data, img->dataSize); + memcpy((uint8 *) memory->getData() + dataOffset, img->data, img->dataSize); auto slice = new CompressedSlice(texformat, img->width, img->height, memory, dataOffset, img->dataSize); images.emplace_back(slice, Acquire::NORETAIN); diff --git a/src/modules/image/magpie/ddsHandler.h b/src/modules/image/magpie/ddsHandler.h index 0af73b9de..3d957911a 100644 --- a/src/modules/image/magpie/ddsHandler.h +++ b/src/modules/image/magpie/ddsHandler.h @@ -46,7 +46,7 @@ public: bool canDecode(Data *data) override; DecodedImage decode(Data *data) override; bool canParseCompressed(Data *data) override; - StrongRef parseCompressed(Data *filedata, + StrongRef parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) override; diff --git a/src/modules/image/wrap_ImageData.lua b/src/modules/image/wrap_ImageData.lua index 5d5cac8a9..5092944da 100644 --- a/src/modules/image/wrap_ImageData.lua +++ b/src/modules/image/wrap_ImageData.lua @@ -368,9 +368,9 @@ local objectcache = setmetatable({}, { width = width, height = height, format = format, - pointer = ffi.cast(conv.pointer, imagedata:getFFIPointer()), - tolua = conv.tolua, - fromlua = conv.fromlua, + pointer = conv == nil and nil or ffi.cast(conv.pointer, imagedata:getFFIPointer()), + tolua = conv == nil and nil or conv.tolua, + fromlua = conv == nil and nil or conv.fromlua, } self[imagedata] = p @@ -395,6 +395,8 @@ function ImageData:_mapPixelUnsafe(func, ix, iy, iw, ih) local p = objectcache[self] local idw, idh = p.width, p.height + if p.pointer == nil then error("ImageData:mapPixel does not currently support the "..p.format.." pixel format.", 2) end + ix = floor(ix) iy = floor(iy) iw = floor(iw) @@ -423,6 +425,8 @@ function ImageData:getPixel(x, y) local p = objectcache[self] if not inside(x, y, p.width, p.height) then error("Attempt to get out-of-range pixel!", 2) end + if p.pointer == nil then error("ImageData:getPixel does not currently support the "..p.format.." pixel format.", 2) end + ffifuncs.lockMutex(self) local pixel = p.pointer[y * p.width + x] local r, g, b, a = p.tolua(pixel) @@ -451,6 +455,8 @@ function ImageData:setPixel(x, y, r, g, b, a) local p = objectcache[self] if not inside(x, y, p.width, p.height) then error("Attempt to set out-of-range pixel!", 2) end + if p.pointer == nil then error("ImageData:setPixel does not currently support the "..p.format.." pixel format.", 2) end + ffifuncs.lockMutex(self) p.fromlua(p.pointer[y * p.width + x], r, g, b, a) ffifuncs.unlockMutex(self) diff --git a/src/modules/keyboard/Keyboard.cpp b/src/modules/keyboard/Keyboard.cpp index 47f5baf69..05c4bed70 100644 --- a/src/modules/keyboard/Keyboard.cpp +++ b/src/modules/keyboard/Keyboard.cpp @@ -47,6 +47,16 @@ bool Keyboard::getConstant(Scancode in, const char *&out) return scancodes.find(in, out); } +bool Keyboard::getConstant(const char *in, ModifierKey &out) +{ + return modifiers.find(in, out); +} + +bool Keyboard::getConstant(ModifierKey in, const char *&out) +{ + return modifiers.find(in, out); +} + StringMap::Entry Keyboard::keyEntries[] = { {"unknown", Keyboard::KEY_UNKNOWN}, @@ -521,5 +531,15 @@ StringMap::Entry Keyboard::scan StringMap Keyboard::scancodes(Keyboard::scancodeEntries, sizeof(Keyboard::scancodeEntries)); +StringMap::Entry Keyboard::modifierEntries[] = +{ + {"numlock", MODKEY_NUMLOCK}, + {"capslock", MODKEY_CAPSLOCK}, + {"scrolllock", MODKEY_SCROLLLOCK}, + {"mode", MODKEY_MODE}, +}; + +StringMap Keyboard::modifiers(Keyboard::modifierEntries, sizeof(Keyboard::modifierEntries)); + } // keyboard } // love diff --git a/src/modules/keyboard/Keyboard.h b/src/modules/keyboard/Keyboard.h index d28c561e8..73ea3faeb 100644 --- a/src/modules/keyboard/Keyboard.h +++ b/src/modules/keyboard/Keyboard.h @@ -519,6 +519,18 @@ public: SCANCODE_MAX_ENUM }; + /** + * Modifier keys. These are special keys that temporarily modifies the normal function of a button when active. + **/ + enum ModifierKey { + MODKEY_NUMLOCK, + MODKEY_CAPSLOCK, + MODKEY_SCROLLLOCK, + MODKEY_MODE, + + MODKEY_MAX_ENUM + }; + virtual ~Keyboard() {} // Implements Module. @@ -550,6 +562,13 @@ public: **/ virtual bool isScancodeDown(const std::vector &scancodelist) const = 0; + /** + * Checks whether specific modifier key is active or not. + * @param key Modifier key to check. + * @return Whether the specified modifier key is active. + **/ + virtual bool isModifierActive(ModifierKey key) const = 0; + /** * Gets the key corresponding to the specified scancode according to the * current keyboard layout. @@ -592,6 +611,9 @@ public: static bool getConstant(const char *in, Scancode &out); static bool getConstant(Scancode in, const char *&out); + static bool getConstant(const char *in, ModifierKey &out); + static bool getConstant(ModifierKey in, const char *&out); + private: static StringMap::Entry keyEntries[]; @@ -600,6 +622,9 @@ private: static StringMap::Entry scancodeEntries[]; static StringMap scancodes; + static StringMap::Entry modifierEntries[]; + static StringMap modifiers; + }; // Keyboard } // keyboard diff --git a/src/modules/keyboard/sdl/Keyboard.cpp b/src/modules/keyboard/sdl/Keyboard.cpp index 51150e7fe..1abb0858d 100644 --- a/src/modules/keyboard/sdl/Keyboard.cpp +++ b/src/modules/keyboard/sdl/Keyboard.cpp @@ -18,9 +18,16 @@ * 3. This notice may not be removed or altered from any source distribution. **/ +#include + #include "Keyboard.h" #include "window/Window.h" +// SDL before 2.0.18 lack KMOD_SCROLL. Use KMOD_RESERVED instead. +#if !SDL_VERSION_ATLEAST(2, 0, 18) +#define KMOD_SCROLL KMOD_RESERVED +#endif // !SDL_VERSION_ATLEAST(2, 0, 18) + namespace love { namespace keyboard @@ -78,6 +85,25 @@ bool Keyboard::isScancodeDown(const std::vector &scancodelist) const return false; } +bool Keyboard::isModifierActive(ModifierKey key) const +{ + int modstate = SDL_GetModState(); + + switch (key) + { + case MODKEY_NUMLOCK: + return (modstate & KMOD_NUM) != 0; + case MODKEY_CAPSLOCK: + return (modstate & KMOD_CAPS) != 0; + case MODKEY_SCROLLLOCK: + return (modstate & KMOD_SCROLL) != 0; + case MODKEY_MODE: + return (modstate & KMOD_MODE) != 0; + } + + return false; +} + Keyboard::Key Keyboard::getKeyFromScancode(Scancode scancode) const { SDL_Scancode sdlscancode = SDL_SCANCODE_UNKNOWN; diff --git a/src/modules/keyboard/sdl/Keyboard.h b/src/modules/keyboard/sdl/Keyboard.h index e3e889f86..776b9789e 100644 --- a/src/modules/keyboard/sdl/Keyboard.h +++ b/src/modules/keyboard/sdl/Keyboard.h @@ -48,6 +48,7 @@ public: bool hasKeyRepeat() const; bool isDown(const std::vector &keylist) const; bool isScancodeDown(const std::vector &scancodelist) const; + bool isModifierActive(ModifierKey key) const; Key getKeyFromScancode(Scancode scancode) const; Scancode getScancodeFromKey(Key key) const; diff --git a/src/modules/keyboard/wrap_Keyboard.cpp b/src/modules/keyboard/wrap_Keyboard.cpp index 1e0def63c..64c9728b1 100644 --- a/src/modules/keyboard/wrap_Keyboard.cpp +++ b/src/modules/keyboard/wrap_Keyboard.cpp @@ -187,6 +187,17 @@ int w_hasScreenKeyboard(lua_State *L) return 1; } +int w_isModifierActive(lua_State* L) +{ + const char *keystr = luaL_checkstring(L, 1); + Keyboard::ModifierKey key; + if (!Keyboard::getConstant(keystr, key)) + return luax_enumerror(L, "modifier keys", keystr); + + luax_pushboolean(L, instance()->isModifierActive(key)); + return 1; +} + // List of functions to wrap. static const luaL_Reg functions[] = { @@ -199,6 +210,7 @@ static const luaL_Reg functions[] = { "isScancodeDown", w_isScancodeDown }, { "getScancodeFromKey", w_getScancodeFromKey }, { "getKeyFromScancode", w_getKeyFromScancode }, + { "isModifierActive", w_isModifierActive }, { 0, 0 } }; diff --git a/src/modules/love/boot.lua b/src/modules/love/boot.lua index d06b4e4c3..736950476 100644 --- a/src/modules/love/boot.lua +++ b/src/modules/love/boot.lua @@ -37,6 +37,7 @@ end local no_game_code = false local invalid_game_path = nil +local main_file = "main.lua" -- This can't be overridden. function love.boot() @@ -80,6 +81,9 @@ function love.boot() love.setDeprecationOutput(not love.filesystem.isFused()) + main_file = "main.lua" + local custom_main_file = false + local identity = "" if not can_has_game and o.game.set and o.game.arg[1] then local nouri = o.game.arg[1] @@ -89,6 +93,14 @@ function love.boot() end local full_source = love.path.getFull(nouri) + local source_leaf = love.path.leaf(full_source) + + if source_leaf:match("%.lua$") then + main_file = source_leaf + custom_main_file = true + full_source = love.path.getFull(full_source:sub(1, -(#source_leaf + 1))) + end + can_has_game = pcall(love.filesystem.setSource, full_source) if not can_has_game then @@ -104,7 +116,7 @@ function love.boot() -- Try to use the archive containing main.lua as the identity name. It -- might not be available, in which case the fallbacks above are used. - local realdir = love.filesystem.getRealDirectory("main.lua") + local realdir = love.filesystem.getRealDirectory(main_file) if realdir then identity = love.path.leaf(realdir) end @@ -118,7 +130,7 @@ function love.boot() -- before the save directory (the identity should be appended.) pcall(love.filesystem.setIdentity, identity, true) - if can_has_game and not (love.filesystem.getInfo("main.lua") or love.filesystem.getInfo("conf.lua")) then + if can_has_game and not (love.filesystem.getInfo(main_file) or (not custom_main_file and love.filesystem.getInfo("conf.lua"))) then no_game_code = true end @@ -145,7 +157,7 @@ function love.init() minheight = 1, fullscreen = false, fullscreentype = "desktop", - display = 1, + displayindex = 1, vsync = 1, msaa = 0, borderless = false, @@ -362,13 +374,16 @@ function love.init() if love.filesystem then love.filesystem._setAndroidSaveExternal(c.externalstorage) love.filesystem.setIdentity(c.identity or love.filesystem.getIdentity(), c.appendidentity) - if love.filesystem.getInfo("main.lua") then - require("main") + if love.filesystem.getInfo(main_file) then + require(main_file:gsub("%.lua$", "")) end end if no_game_code then - error("No code to run\nYour game might be packaged incorrectly.\nMake sure main.lua is at the top level of the zip.") + local opts = love.arg.options + local gamepath = opts.game.set and opts.game.arg[1] or "" + local gamestr = gamepath == "" and "" or " at "..gamepath + error("No code to run"..gamestr.."\nYour game might be packaged incorrectly.\nMake sure "..main_file.." is at the top level of the zip or folder.") elseif invalid_game_path then error("Cannot load game at path '" .. invalid_game_path .. "'.\nMake sure a folder exists at the specified path.") end diff --git a/src/modules/love/callbacks.lua b/src/modules/love/callbacks.lua index 66735695c..79b1b3e97 100644 --- a/src/modules/love/callbacks.lua +++ b/src/modules/love/callbacks.lua @@ -112,6 +112,12 @@ function love.createhandlers() directorydropped = function (dir) if love.directorydropped then return love.directorydropped(dir) end end, + dropbegan = function () + if love.dropbegan then return love.dropbegan() end + end, + dropcompleted = function () + if love.dropcompleted then return love.dropcompleted() end + end, lowmemory = function () if love.lowmemory then love.lowmemory() end collectgarbage() @@ -120,6 +126,9 @@ function love.createhandlers() displayrotated = function (display, orient) if love.displayrotated then return love.displayrotated(display, orient) end end, + localechanged = function () + if love.localechanged then return love.localechanged() end + end, }, { __index = function(self, name) error("Unknown event: " .. name) diff --git a/src/modules/sound/Decoder.cpp b/src/modules/sound/Decoder.cpp index 1182cb68a..355b783c9 100644 --- a/src/modules/sound/Decoder.cpp +++ b/src/modules/sound/Decoder.cpp @@ -29,13 +29,16 @@ namespace sound love::Type Decoder::type("Decoder", &Object::type); -Decoder::Decoder(Data *data, int bufferSize) - : data(data) +Decoder::Decoder(Stream *stream, int bufferSize) + : stream(stream) , bufferSize(bufferSize) , sampleRate(DEFAULT_SAMPLE_RATE) , buffer(0) , eof(false) { + if (!stream->isReadable() || !stream->isSeekable()) + throw love::Exception("Decoder input stream must be readable and seekable."); + buffer = new char[bufferSize]; } @@ -65,5 +68,12 @@ bool Decoder::isFinished() return eof; } +STRINGMAP_CLASS_BEGIN(Decoder, Decoder::StreamSource, Decoder::STREAM_MAX_ENUM, streamSource) +{ + { "memory", Decoder::STREAM_MEMORY }, + { "file", Decoder::STREAM_FILE }, +} +STRINGMAP_CLASS_END(Decoder, Decoder::StreamSource, Decoder::STREAM_MAX_ENUM, streamSource) + } // sound } // love diff --git a/src/modules/sound/Decoder.h b/src/modules/sound/Decoder.h index f3452567f..1c91be281 100644 --- a/src/modules/sound/Decoder.h +++ b/src/modules/sound/Decoder.h @@ -23,7 +23,8 @@ // LOVE #include "common/Object.h" -#include "filesystem/File.h" +#include "common/Stream.h" +#include "common/StringMap.h" #include @@ -39,9 +40,16 @@ class Decoder : public Object { public: + enum StreamSource + { + STREAM_MEMORY, + STREAM_FILE, + STREAM_MAX_ENUM + }; + static love::Type type; - Decoder(Data *data, int bufferSize); + Decoder(Stream *stream, int bufferSize); virtual ~Decoder(); /** @@ -143,11 +151,12 @@ public: **/ virtual double getDuration() = 0; + STRINGMAP_CLASS_DECLARE(StreamSource); + protected: - // The encoded data. This should be replaced with buffered file - // reads in the future. - StrongRef data; + // A readable stream containing the encoded data. + StrongRef stream; // When the decoder decodes data incrementally, it writes // this many bytes at a time (at most). diff --git a/src/modules/sound/Sound.h b/src/modules/sound/Sound.h index 30ec0cb9d..8cfe378a6 100644 --- a/src/modules/sound/Sound.h +++ b/src/modules/sound/Sound.h @@ -23,7 +23,7 @@ // LOVE #include "common/Module.h" -#include "filesystem/File.h" +#include "common/Stream.h" #include "SoundData.h" #include "Decoder.h" @@ -83,11 +83,11 @@ public: /** * Attempts to find a decoder for the encoded sound data in the * specified file. - * @param file The file with encoded sound data. + * @param stream The readable Stream with encoded sound data. * @param bufferSize The size of each decoded chunk. * @return A Decoder object on success, or zero if no decoder could be found. **/ - virtual Decoder *newDecoder(filesystem::FileData *file, int bufferSize) = 0; + virtual Decoder *newDecoder(Stream *stream, int bufferSize) = 0; }; // Sound diff --git a/src/modules/sound/lullaby/CoreAudioDecoder.cpp b/src/modules/sound/lullaby/CoreAudioDecoder.cpp index d1b117dd0..499ecc43e 100644 --- a/src/modules/sound/lullaby/CoreAudioDecoder.cpp +++ b/src/modules/sound/lullaby/CoreAudioDecoder.cpp @@ -34,37 +34,23 @@ namespace lullaby { // Callbacks -namespace +static OSStatus readFunc(void *inClientData, SInt64 inPosition, UInt32 requestCount, void *buffer, UInt32 *actualCount) { -OSStatus readFunc(void *inClientData, SInt64 inPosition, UInt32 requestCount, void *buffer, UInt32 *actualCount) -{ - Data *data = (Data *) inClientData; - SInt64 bytesLeft = data->getSize() - inPosition; + auto stream = (Stream *) inClientData; + int64 readbytes = stream->read(buffer, requestCount); - if (bytesLeft > 0) - { - UInt32 actualSize = bytesLeft >= requestCount ? requestCount : (UInt32) bytesLeft; - memcpy(buffer, (char *) data->getData() + inPosition, actualSize); - *actualCount = actualSize; - } - else - { - *actualCount = 0; - return kAudioFilePositionError; - } - - return noErr; + *actualCount = (UInt32) readbytes; + return readbytes > 0 ? noErr : kAudioFilePositionError; } -SInt64 getSizeFunc(void *inClientData) +static SInt64 getSizeFunc(void *inClientData) { - Data *data = (Data *) inClientData; - return data->getSize(); + auto stream = (Stream *) inClientData; + return stream->getSize(); } -} // anonymous namespace -CoreAudioDecoder::CoreAudioDecoder(Data *data, int bufferSize) - : Decoder(data, bufferSize) +CoreAudioDecoder::CoreAudioDecoder(Stream *stream, int bufferSize) + : Decoder(stream, bufferSize) , audioFile(nullptr) , extAudioFile(nullptr) , inputInfo() @@ -75,23 +61,23 @@ CoreAudioDecoder::CoreAudioDecoder(Data *data, int bufferSize) { OSStatus err = noErr; - // Open the file represented by the Data. - err = AudioFileOpenWithCallbacks(data, readFunc, nullptr, getSizeFunc, nullptr, kAudioFileMP3Type, &audioFile); + // Open the file represented by the Stream. + err = AudioFileOpenWithCallbacks(stream, readFunc, nullptr, getSizeFunc, nullptr, kAudioFileMP3Type, &audioFile); if (err != noErr) - throw love::Exception("Could open audio file for decoding."); + throw love::Exception("Could not open audio file for decoding with CoreAudio."); // We want to use the Extended AudioFile API. err = ExtAudioFileWrapAudioFileID(audioFile, false, &extAudioFile); if (err != noErr) - throw love::Exception("Could open audio file for decoding."); + throw love::Exception("Could not open audio file for decoding with CoreAudio."); // Get the format of the audio data. UInt32 propertySize = sizeof(inputInfo); err = ExtAudioFileGetProperty(extAudioFile, kExtAudioFileProperty_FileDataFormat, &propertySize, &inputInfo); if (err != noErr) - throw love::Exception("Could not determine file format."); + throw love::Exception("Could not determine CoreAudio file format."); // Set the output format to 16 bit signed integer (native-endian) data. // Keep the channel count and sample rate of the source format. @@ -116,7 +102,7 @@ CoreAudioDecoder::CoreAudioDecoder(Data *data, int bufferSize) err = ExtAudioFileSetProperty(extAudioFile, kExtAudioFileProperty_ClientDataFormat, propertySize, &outputInfo); if (err != noErr) - throw love::Exception("Could not set decoder properties."); + throw love::Exception("Could not set CoreAudio decoder properties."); } catch (love::Exception &) { @@ -143,59 +129,10 @@ void CoreAudioDecoder::closeAudioFile() audioFile = nullptr; } -bool CoreAudioDecoder::accepts(const std::string &ext) -{ - UInt32 size = 0; - std::vector types; - - // Get the size in bytes of the type array we're about to get. - OSStatus err = AudioFileGetGlobalInfoSize(kAudioFileGlobalInfo_ReadableTypes, sizeof(UInt32), nullptr, &size); - if (err != noErr) - return false; - - types.resize(size / sizeof(UInt32)); - - // Get an array of supported types. - err = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_ReadableTypes, 0, nullptr, &size, &types[0]); - if (err != noErr) - return false; - - // Turn the extension string into a CFStringRef. - CFStringRef extstr = CFStringCreateWithCString(nullptr, ext.c_str(), kCFStringEncodingUTF8); - - CFArrayRef exts = nullptr; - size = sizeof(CFArrayRef); - - for (UInt32 type : types) - { - // Get the extension strings for the type. - err = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_ExtensionsForType, sizeof(UInt32), &type, &size, &exts); - if (err != noErr) - continue; - - // A type can have more than one extension string. - for (CFIndex i = 0; i < CFArrayGetCount(exts); i++) - { - CFStringRef value = (CFStringRef) CFArrayGetValueAtIndex(exts, i); - - if (CFStringCompare(extstr, value, 0) == kCFCompareEqualTo) - { - CFRelease(extstr); - CFRelease(exts); - return true; - } - } - - CFRelease(exts); - } - - CFRelease(extstr); - return false; -} - love::sound::Decoder *CoreAudioDecoder::clone() { - return new CoreAudioDecoder(data.get(), bufferSize); + StrongRef s(stream->clone(), Acquire::NORETAIN); + return new CoreAudioDecoder(s, bufferSize); } int CoreAudioDecoder::decode() diff --git a/src/modules/sound/lullaby/CoreAudioDecoder.h b/src/modules/sound/lullaby/CoreAudioDecoder.h index d0380f136..9153dd2c4 100644 --- a/src/modules/sound/lullaby/CoreAudioDecoder.h +++ b/src/modules/sound/lullaby/CoreAudioDecoder.h @@ -26,7 +26,7 @@ #ifdef LOVE_SUPPORT_COREAUDIO // LOVE -#include "common/Data.h" +#include "common/Stream.h" #include "sound/Decoder.h" // Core Audio @@ -47,19 +47,17 @@ class CoreAudioDecoder : public Decoder { public: - CoreAudioDecoder(Data *data, int bufferSize); + CoreAudioDecoder(Stream *stream, int bufferSize); virtual ~CoreAudioDecoder(); - static bool accepts(const std::string &ext); - - love::sound::Decoder *clone(); - int decode(); - bool seek(double s); - bool rewind(); - bool isSeekable(); - int getChannelCount() const; - int getBitDepth() const; - double getDuration(); + love::sound::Decoder *clone() override; + int decode() override; + bool seek(double s) override; + bool rewind() override; + bool isSeekable() override; + int getChannelCount() const override; + int getBitDepth() const override; + double getDuration() override; private: diff --git a/src/modules/sound/lullaby/FLACDecoder.cpp b/src/modules/sound/lullaby/FLACDecoder.cpp index b416f30a9..d45386490 100644 --- a/src/modules/sound/lullaby/FLACDecoder.cpp +++ b/src/modules/sound/lullaby/FLACDecoder.cpp @@ -22,6 +22,7 @@ #include "FLACDecoder.h" #include +#include #include "common/Exception.h" namespace love @@ -31,10 +32,24 @@ namespace sound namespace lullaby { -FLACDecoder::FLACDecoder(Data *data, int nbufferSize) -: Decoder(data, nbufferSize) +static size_t onRead(void *pUserData, void *pBufferOut, size_t bytesToRead) { - flac = drflac_open_memory(data->getData(), data->getSize(), nullptr); + auto stream = (Stream *) pUserData; + int64 read = stream->read(pBufferOut, bytesToRead); + return std::max(0, read); +} + +static drflac_bool32 onSeek(void* pUserData, int offset, drflac_seek_origin origin) +{ + auto stream = (Stream *) pUserData; + auto seekorigin = origin == drflac_seek_origin_current ? Stream::SEEKORIGIN_CURRENT : Stream::SEEKORIGIN_BEGIN; + return stream->seek(offset, seekorigin) ? DRFLAC_TRUE : DRFLAC_FALSE; +} + +FLACDecoder::FLACDecoder(Stream *stream, int nbufferSize) + : Decoder(stream, nbufferSize) +{ + flac = drflac_open(onRead, onSeek, stream, nullptr); if (flac == nullptr) throw love::Exception("Could not load FLAC file"); } @@ -44,29 +59,10 @@ FLACDecoder::~FLACDecoder() drflac_close(flac); } -bool FLACDecoder::accepts(const std::string &ext) -{ - // dr_flac supports FLAC encapsulated in Ogg, but unfortunately - // LOVE detects .ogg extension as Vorbis. It would be a good idea - // to always probe in the future (see #1487 and commit ccf9e63). - // Please remove once it's no longer the case. - static const std::string supported[] = - { - "flac", "ogg", "" - }; - - for (int i = 0; !(supported[i].empty()); i++) - { - if (supported[i].compare(ext) == 0) - return true; - } - - return false; -} - love::sound::Decoder *FLACDecoder::clone() { - return new FLACDecoder(data.get(), bufferSize); + StrongRef s(stream->clone(), Acquire::NORETAIN); + return new FLACDecoder(s, bufferSize); } int FLACDecoder::decode() diff --git a/src/modules/sound/lullaby/FLACDecoder.h b/src/modules/sound/lullaby/FLACDecoder.h index 4ddae3191..7e5fcdc30 100644 --- a/src/modules/sound/lullaby/FLACDecoder.h +++ b/src/modules/sound/lullaby/FLACDecoder.h @@ -22,7 +22,7 @@ #define LOVE_SOUND_LULLABY_FLAC_DECODER_H // LOVE -#include "common/Data.h" +#include "common/Stream.h" #include "sound/Decoder.h" #include "dr/dr_flac.h" @@ -38,23 +38,22 @@ namespace lullaby class FLACDecoder : public Decoder { public: - FLACDecoder(Data *data, int bufferSize); + FLACDecoder(Stream *stream, int bufferSize); ~FLACDecoder(); - static bool accepts(const std::string &ext); - love::sound::Decoder *clone(); - int decode(); - bool seek(double s); - bool rewind(); - bool isSeekable(); - int getChannelCount() const; - int getBitDepth() const; - int getSampleRate() const; - double getDuration(); + love::sound::Decoder *clone() override; + int decode() override; + bool seek(double s) override; + bool rewind() override; + bool isSeekable() override; + int getChannelCount() const override; + int getBitDepth() const override; + int getSampleRate() const override; + double getDuration() override; private: drflac *flac; -}; // Decoder +}; // FLACDecoder } // lullaby } // sound diff --git a/src/modules/sound/lullaby/GmeDecoder.cpp b/src/modules/sound/lullaby/GmeDecoder.cpp deleted file mode 100644 index fbf4b5bfa..000000000 --- a/src/modules/sound/lullaby/GmeDecoder.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright (c) 2006-2022 LOVE Development Team - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - **/ - -#include "common/config.h" - -#ifdef LOVE_SUPPORT_GME - -#include "common/Exception.h" -#include "GmeDecoder.h" - -namespace love -{ -namespace sound -{ -namespace lullaby -{ - -GmeDecoder::GmeDecoder(Data *data, int bufferSize) - : Decoder(data, bufferSize) - , emu(0) - , num_tracks(0) - , cur_track(0) -{ - void *d = data->getData(); - int s = data->getSize(); - - if (gme_open_data(d, s, &emu, sampleRate) != 0) - throw love::Exception("Could not open game music file"); - - num_tracks = gme_track_count(emu); - - try - { - if (num_tracks <= 0) - throw love::Exception("Game music file has no tracks"); - - if (gme_start_track(emu, cur_track) != 0) - throw love::Exception("Could not start game music playback"); - } - catch (love::Exception &) - { - gme_delete(emu); - throw; - } -} - -GmeDecoder::~GmeDecoder() -{ - if (emu) - gme_delete(emu); -} - -bool GmeDecoder::accepts(const std::string &ext) -{ - static const std::string supported[] = - { - "ay", "gbs", "gym", "hes", "kss", "nsf", - "nsfe", "sap", "spc", "vgm", "vgz", "" - }; - - for (int i = 0; !(supported[i].empty()); i++) - { - if (supported[i].compare(ext) == 0) - return true; - } - - return false; -} - -love::sound::Decoder *GmeDecoder::clone() -{ - return new GmeDecoder(data.get(), bufferSize); -} - -int GmeDecoder::decode() -{ - short *sbuf = static_cast(buffer); - int size = bufferSize / sizeof(short); - - if (gme_play(emu, size, sbuf) != 0) - throw love::Exception("Error while decoding game music"); - - if (!eof && gme_track_ended(emu)) - { - // Start the next track if this one ended. - if (cur_track < num_tracks - 1) - gme_start_track(emu, ++cur_track); - else - eof = true; - } - - return bufferSize; -} - -bool GmeDecoder::seek(double s) -{ - return gme_seek(emu, static_cast(s * 1000.0)) != 0; -} - -bool GmeDecoder::rewind() -{ - // If we're in the first track, rewind. - if (cur_track == 0) - return gme_seek(emu, 0) == 0; - else - { - // Otherwise, start from the first track again. - cur_track = 0; - return gme_start_track(emu, cur_track) == 0; - } -} - -bool GmeDecoder::isSeekable() -{ - return true; -} - -int GmeDecoder::getChannelCount() const -{ - return 2; -} - -int GmeDecoder::getBitDepth() const -{ - return 16; -} - -double GmeDecoder::getDuration() -{ - return -1; -} - -} // lullaby -} // sound -} // love - -#endif // LOVE_SUPPORT_GME diff --git a/src/modules/sound/lullaby/MP3Decoder.cpp b/src/modules/sound/lullaby/MP3Decoder.cpp index 76b2228fb..1c73aa10b 100644 --- a/src/modules/sound/lullaby/MP3Decoder.cpp +++ b/src/modules/sound/lullaby/MP3Decoder.cpp @@ -21,6 +21,7 @@ #define DR_MP3_IMPLEMENTATION #define DR_MP3_NO_STDIO #include "MP3Decoder.h" +#include "common/Exception.h" namespace love { @@ -29,11 +30,121 @@ namespace sound namespace lullaby { -MP3Decoder::MP3Decoder(Data *data, int bufferSize) -: Decoder(data, bufferSize) +// Copied from dr_mp3 function drmp3_hdr_valid() +static bool isMP3HeaderValid(const uint8 *h) { + return + // Sync bits + h[0] == 0xff && + ((h[1] & 0xF0) == 0xf0 || (h[1] & 0xFE) == 0xe2) && + // Check layer + (DRMP3_HDR_GET_LAYER(h) != 0) && + // Check bitrate + (DRMP3_HDR_GET_BITRATE(h) != 15) && + // Check sample rate + (DRMP3_HDR_GET_SAMPLE_RATE(h) != 3); +} + +static int64 findFirstValidHeader(Stream* stream) +{ + constexpr size_t LOOKUP_SIZE = 16384; + + std::vector data(LOOKUP_SIZE); + uint8 header[10]; + uint8 *dataPtr = data.data(); + int64 buffer = 0; + int64 offset = 0; + + if (stream->read(header, 10) < 10) + return -1; + + if (memcmp(header, "TAG", 3) == 0) + { + // ID3v1 tag is always 128 bytes long + if (!stream->seek(128, Stream::SEEKORIGIN_BEGIN)) + return -1; + + buffer = stream->read(dataPtr, LOOKUP_SIZE); + offset = 128; + } + else if (memcmp(header, "ID3", 3) == 0) + { + // ID3v2 tag header is 10 bytes long, but we're + // only interested on how much we should skip. + int64 off = + header[9] | + ((int64) header[8] << 7) | + ((int64) header[7] << 14) | + ((int64) header[6] << 21); + + if (!stream->seek(off, Stream::SEEKORIGIN_CURRENT)) + return -1; + + buffer = stream->read(dataPtr, LOOKUP_SIZE); + offset = off + 10; + } + else + { + // Copy the rest to data buffer + memcpy(dataPtr, header, 10); + buffer = 10 + stream->read(dataPtr + 10, LOOKUP_SIZE - 10); + } + + // Look for mp3 data + for (int i = 0; i < buffer - 4; i++, offset++) + { + if (isMP3HeaderValid(dataPtr++)) + { + stream->seek(offset, Stream::SEEKORIGIN_BEGIN); + return offset; + } + } + + // No valid MP3 frame found in first 16KB data + return -1; +} + +size_t MP3Decoder::onRead(void *pUserData, void *pBufferOut, size_t bytesToRead) +{ + auto decoder = (MP3Decoder *) pUserData; + int64 read = decoder->stream->read(pBufferOut, bytesToRead); + return std::max(0, read); +} + +drmp3_bool32 MP3Decoder::onSeek(void *pUserData, int offset, drmp3_seek_origin origin) +{ + auto decoder = (MP3Decoder *) pUserData; + int64 pos = decoder->offset; + + // Due to possible offsets, we have to calculate the position ourself. + switch (origin) + { + case drmp3_seek_origin_start: + pos += offset; + break; + case drmp3_seek_origin_current: + pos = decoder->stream->tell() + offset; + break; + default: + return DRMP3_FALSE; + } + + if (pos < decoder->offset) + return DRMP3_FALSE; + + return decoder->stream->seek(pos, Stream::SEEKORIGIN_BEGIN) ? DRMP3_TRUE : DRMP3_FALSE; +} + +MP3Decoder::MP3Decoder(Stream *stream, int bufferSize) +: Decoder(stream, bufferSize) +{ + // Check for possible ID3 tag and skip it if necessary. + offset = findFirstValidHeader(stream); + if (offset == -1) + throw love::Exception("Could not find first valid mp3 header."); + // initialize mp3 handle - if(drmp3_init_memory(&mp3, data->getData(), data->getSize(), nullptr, nullptr) == 0) + if (!drmp3_init(&mp3, onRead, onSeek, this, nullptr, nullptr)) throw love::Exception("Could not read mp3 data."); sampleRate = mp3.sampleRate; @@ -43,25 +154,24 @@ MP3Decoder::MP3Decoder(Data *data, int bufferSize) if (!drmp3_get_mp3_and_pcm_frame_count(&mp3, &mp3FrameCount, &pcmCount)) { drmp3_uninit(&mp3); - throw love::Exception("Could not calculate duration."); + throw love::Exception("Could not calculate mp3 duration."); } duration = ((double) pcmCount) / ((double) mp3.sampleRate); // create seek table - uint32_t mp3FrameInt = mp3FrameCount; - seekTable.resize(mp3FrameCount, {0ULL, 0ULL, 0, 0}); + drmp3_uint32 mp3FrameInt = (drmp3_uint32) mp3FrameCount; + seekTable.resize((size_t) mp3FrameCount, {0ULL, 0ULL, 0, 0}); if (!drmp3_calculate_seek_points(&mp3, &mp3FrameInt, seekTable.data())) { drmp3_uninit(&mp3); - throw love::Exception("Could not calculate seek table"); + throw love::Exception("Could not calculate mp3 seek table"); } - mp3FrameInt = mp3FrameInt > mp3FrameCount ? mp3FrameCount : mp3FrameInt; // bind seek table if (!drmp3_bind_seek_table(&mp3, mp3FrameInt, seekTable.data())) { drmp3_uninit(&mp3); - throw love::Exception("Could not bind seek table"); + throw love::Exception("Could not bind mp3 seek table"); } } @@ -70,25 +180,10 @@ MP3Decoder::~MP3Decoder() drmp3_uninit(&mp3); } -bool MP3Decoder::accepts(const std::string &ext) -{ - static const std::string supported[] = - { - "mp3", "" - }; - - for (int i = 0; !(supported[i].empty()); i++) - { - if (supported[i].compare(ext) == 0) - return true; - } - - return false; -} - love::sound::Decoder *MP3Decoder::clone() { - return new MP3Decoder(data, bufferSize); + StrongRef s(stream->clone(), Acquire::NORETAIN); + return new MP3Decoder(s, bufferSize); } int MP3Decoder::decode() @@ -105,7 +200,7 @@ int MP3Decoder::decode() bool MP3Decoder::seek(double s) { - drmp3_uint64 targetSample = s * mp3.sampleRate; + drmp3_uint64 targetSample = (drmp3_uint64) (s * mp3.sampleRate); drmp3_bool32 success = drmp3_seek_to_pcm_frame(&mp3, targetSample); if (success) diff --git a/src/modules/sound/lullaby/MP3Decoder.h b/src/modules/sound/lullaby/MP3Decoder.h index 7a95564df..84c9444e6 100644 --- a/src/modules/sound/lullaby/MP3Decoder.h +++ b/src/modules/sound/lullaby/MP3Decoder.h @@ -22,7 +22,7 @@ #define LOVE_SOUND_LULLABY_MP3_DECODER_H // LOVE -#include "common/Data.h" +#include "common/Stream.h" #include "sound/Decoder.h" // dr_mp3 @@ -41,25 +41,28 @@ class MP3Decoder: public love::sound::Decoder { public: - MP3Decoder(Data *data, int bufsize); + MP3Decoder(Stream *stream, int bufsize); virtual ~MP3Decoder(); - static bool accepts(const std::string &ext); - love::sound::Decoder *clone(); - int decode(); - bool seek(double s); - bool rewind(); - bool isSeekable(); - int getChannelCount() const; - int getBitDepth() const; - double getDuration(); + love::sound::Decoder *clone() override; + int decode() override; + bool seek(double s) override; + bool rewind() override; + bool isSeekable() override; + int getChannelCount() const override; + int getBitDepth() const override; + double getDuration() override; private: + static size_t onRead(void *pUserData, void *pBufferOut, size_t bytesToRead); + static drmp3_bool32 onSeek(void *pUserData, int offset, drmp3_seek_origin origin); // MP3 handle drmp3 mp3; // Used for fast seeking std::vector seekTable; + // Position of first MP3 frame found + int64 offset; double duration; }; // MP3Decoder diff --git a/src/modules/sound/lullaby/ModPlugDecoder.cpp b/src/modules/sound/lullaby/ModPlugDecoder.cpp index 269e934c9..440aa44d8 100644 --- a/src/modules/sound/lullaby/ModPlugDecoder.cpp +++ b/src/modules/sound/lullaby/ModPlugDecoder.cpp @@ -23,6 +23,7 @@ #ifndef LOVE_NO_MODPLUG #include "common/Exception.h" +#include "common/Data.h" namespace love { @@ -31,12 +32,11 @@ namespace sound namespace lullaby { -ModPlugDecoder::ModPlugDecoder(Data *data, int bufferSize) - : Decoder(data, bufferSize) +ModPlugDecoder::ModPlugDecoder(Stream *stream, int bufferSize) + : Decoder(stream, bufferSize) , plug(0) , duration(-2.0) { - // Set some ModPlug settings. settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION; settings.mChannels = 2; @@ -60,10 +60,29 @@ ModPlugDecoder::ModPlugDecoder(Data *data, int bufferSize) ModPlug_SetSettings(&settings); - // Load the module. - plug = ModPlug_Load(data->getData(), (int) data->getSize()); + // ModPlug has no streaming API. Miserable. + // We don't want to load the entire stream immediately if it's big, because + // it might not be compatible with ModPlug. So we just try to load 4MB and + // see if that works, and then load the whole thing if it does. + if (stream->getSize() > 1024 * 1024 * 4) + { + data.set(stream->read(1024 * 1024 * 4), Acquire::NORETAIN); - if (plug == 0) + plug = ModPlug_Load(data->getData(), (int)data->getSize()); + + if (plug == nullptr) + throw love::Exception("Could not load file with ModPlug."); + + stream->seek(0); + ModPlug_Unload(plug); + } + + data.set(stream->read(stream->getSize()), Acquire::NORETAIN); + + // Load the module. + plug = ModPlug_Load(data->getData(), (int)data->getSize()); + + if (plug == nullptr) throw love::Exception("Could not load file with ModPlug."); // set master volume for delicate ears @@ -72,33 +91,14 @@ ModPlugDecoder::ModPlugDecoder(Data *data, int bufferSize) ModPlugDecoder::~ModPlugDecoder() { - if (plug != 0) + if (plug != nullptr) ModPlug_Unload(plug); } -bool ModPlugDecoder::accepts(const std::string &ext) -{ - static const std::string supported[] = - { - "699", "abc", "amf", "ams", "dbm", "dmf", - "dsm", "far", "it", "j2b", "mdl", "med", - "mid", "mod", "mt2", "mtm", "okt", "pat", - "psm", "s3m", "stm", "ult", "umx", "xm", - "" - }; - - for (int i = 0; !(supported[i].empty()); i++) - { - if (supported[i].compare(ext) == 0) - return true; - } - - return false; -} - love::sound::Decoder *ModPlugDecoder::clone() { - return new ModPlugDecoder(data.get(), bufferSize); + StrongRef s(stream->clone(), Acquire::NORETAIN); + return new ModPlugDecoder(s, bufferSize); } int ModPlugDecoder::decode() diff --git a/src/modules/sound/lullaby/ModPlugDecoder.h b/src/modules/sound/lullaby/ModPlugDecoder.h index ec45d47b3..b99a2d0e6 100644 --- a/src/modules/sound/lullaby/ModPlugDecoder.h +++ b/src/modules/sound/lullaby/ModPlugDecoder.h @@ -26,7 +26,7 @@ #ifndef LOVE_NO_MODPLUG // LOVE -#include "common/Data.h" +#include "common/Stream.h" #include "sound/Decoder.h" // libmodplug @@ -47,28 +47,28 @@ class ModPlugDecoder : public Decoder { public: - ModPlugDecoder(Data *data, int bufferSize); + ModPlugDecoder(Stream *stream, int bufferSize); virtual ~ModPlugDecoder(); - static bool accepts(const std::string &ext); - - love::sound::Decoder *clone(); - int decode(); - bool seek(double s); - bool rewind(); - bool isSeekable(); - int getChannelCount() const; - int getBitDepth() const; - double getDuration(); + love::sound::Decoder *clone() override; + int decode() override; + bool seek(double s) override; + bool rewind() override; + bool isSeekable() override; + int getChannelCount() const override; + int getBitDepth() const override; + double getDuration() override; private: + StrongRef data; + ModPlugFile *plug; ModPlug_Settings settings; double duration; -}; // Decoder +}; // ModPlugDecoder } // lullaby } // sound diff --git a/src/modules/sound/lullaby/Sound.cpp b/src/modules/sound/lullaby/Sound.cpp index 8ab449d45..84aaa08ce 100644 --- a/src/modules/sound/lullaby/Sound.cpp +++ b/src/modules/sound/lullaby/Sound.cpp @@ -27,7 +27,6 @@ #include "ModPlugDecoder.h" #include "VorbisDecoder.h" -#include "GmeDecoder.h" #include "WaveDecoder.h" #include "FLACDecoder.h" #include "MP3Decoder.h" @@ -38,21 +37,16 @@ struct DecoderImpl { - love::sound::Decoder *(*create)(love::filesystem::FileData *data, int bufferSize); - bool (*accepts)(const std::string& ext); + love::sound::Decoder *(*create)(love::Stream *stream, int bufferSize); }; template DecoderImpl DecoderImplFor() { DecoderImpl decoderImpl; - decoderImpl.create = [](love::filesystem::FileData *data, int bufferSize) -> love::sound::Decoder* + decoderImpl.create = [](love::Stream *stream, int bufferSize) -> love::sound::Decoder* { - return new DecoderType(data, bufferSize); - }; - decoderImpl.accepts = [](const std::string& ext) -> bool - { - return DecoderType::accepts(ext); + return new DecoderType(stream, bufferSize); }; return decoderImpl; } @@ -77,43 +71,29 @@ const char *Sound::getName() const return "love.sound.lullaby"; } -sound::Decoder *Sound::newDecoder(love::filesystem::FileData *data, int bufferSize) +sound::Decoder *Sound::newDecoder(Stream *stream, int bufferSize) { - std::string ext = data->getExtension(); - std::transform(ext.begin(), ext.end(), ext.begin(), tolower); - std::vector possibleDecoders = { -#ifndef LOVE_NO_MODPLUG - DecoderImplFor(), -#endif // LOVE_NO_MODPLUG - DecoderImplFor(), + DecoderImplFor(), + DecoderImplFor(), DecoderImplFor(), -#ifdef LOVE_SUPPORT_GME - DecoderImplFor(), -#endif // LOVE_SUPPORT_GME #ifdef LOVE_SUPPORT_COREAUDIO DecoderImplFor(), #endif - DecoderImplFor(), - DecoderImplFor(), - // DecoderImplFor(), + DecoderImplFor(), +#ifndef LOVE_NO_MODPLUG + DecoderImplFor(), // Last because it doesn't work well with Streams. +#endif }; - // First find a matching decoder based on extension - for (DecoderImpl &possibleDecoder : possibleDecoders) - { - if (possibleDecoder.accepts(ext)) - return possibleDecoder.create(data, bufferSize); - } - - // If that fails, start probing instead std::stringstream decodingErrors; decodingErrors << "Failed to determine file type:\n"; for (DecoderImpl &possibleDecoder : possibleDecoders) { try { - sound::Decoder *decoder = possibleDecoder.create(data, bufferSize); + stream->seek(0); + sound::Decoder *decoder = possibleDecoder.create(stream, bufferSize); return decoder; } catch (love::Exception &e) @@ -122,8 +102,8 @@ sound::Decoder *Sound::newDecoder(love::filesystem::FileData *data, int bufferSi } } - // Probing failed too, bail with the accumulated errors - throw love::Exception(decodingErrors.str().c_str()); + std::string errors = decodingErrors.str(); + throw love::Exception("No suitable audio decoders found.\n%s", errors.c_str()); // Unreachable, but here to prevent (possible) warnings return nullptr; diff --git a/src/modules/sound/lullaby/Sound.h b/src/modules/sound/lullaby/Sound.h index a79392b84..028740020 100644 --- a/src/modules/sound/lullaby/Sound.h +++ b/src/modules/sound/lullaby/Sound.h @@ -45,21 +45,14 @@ class Sound : public love::sound::Sound { public: - /** - * Constructor. Initializes relevant libraries. - **/ Sound(); - - /** - * Destructor. Deinitializes relevant libraries. - **/ virtual ~Sound(); /// @copydoc love::Module::getName - const char *getName() const; + const char *getName() const override; /// @copydoc love::sound::Sound::newDecoder - sound::Decoder *newDecoder(love::filesystem::FileData *file, int bufferSize); + sound::Decoder *newDecoder(Stream *stream, int bufferSize) override; }; // Sound diff --git a/src/modules/sound/lullaby/VorbisDecoder.cpp b/src/modules/sound/lullaby/VorbisDecoder.cpp index a955bc474..6aeac42b9 100644 --- a/src/modules/sound/lullaby/VorbisDecoder.cpp +++ b/src/modules/sound/lullaby/VorbisDecoder.cpp @@ -31,132 +31,65 @@ namespace sound namespace lullaby { -/** - * CALLBACK FUNCTIONS - **/ -static int vorbisClose(void * /* ptr to the data that the vorbis files need*/) +static int vorbisClose(void *) { // Does nothing (handled elsewhere) return 1; } -static size_t vorbisRead(void *ptr /* ptr to the data that the vorbis files need*/, - size_t byteSize /* how big a byte is*/, - size_t sizeToRead /* How much we can read*/, - void *datasource /* this is a pointer to the data we passed into ov_open_callbacks (our SOggFile struct*/) +static size_t vorbisRead(void *ptr, size_t byteSize, size_t sizeToRead, void *datasource) { - size_t spaceToEOF; // How much more we can read till we hit the EOF marker - size_t actualSizeToRead; // How much data we are actually going to read from memory - SOggFile *vorbisData; // Our vorbis data, for the typecast - - // Get the data in the right format - vorbisData = (SOggFile *)datasource; - - // Calculate how much we need to read. This can be sizeToRead*byteSize or less depending on how near the EOF marker we are - spaceToEOF = vorbisData->dataSize - vorbisData->dataRead; - if ((sizeToRead*byteSize) < spaceToEOF) - actualSizeToRead = (sizeToRead*byteSize); - else - actualSizeToRead = spaceToEOF; - - // A simple copy of the data from memory to the datastruct that the vorbis libs will use - if (actualSizeToRead) - { - // Copy the data from the start of the file PLUS how much we have already read in - memcpy(ptr, (const char *)vorbisData->dataPtr + vorbisData->dataRead, actualSizeToRead); - // Increase by how much we have read by - vorbisData->dataRead += (actualSizeToRead); - } - - // Return how much we read (in the same way fread would) - return actualSizeToRead; + auto stream = (Stream *) datasource; + return stream->read(ptr, byteSize * sizeToRead); } -static int vorbisSeek(void *datasource /* ptr to the data that the vorbis files need*/, - ogg_int64_t offset /*offset from the point we wish to seek to*/, - int whence /*where we want to seek to*/) +static int vorbisSeek(void *datasource, ogg_int64_t offset, int whence) { - int64 spaceToEOF; // How much more we can read till we hit the EOF marker - int64 actualOffset; // How much we can actually offset it by - SOggFile *vorbisData; // The data we passed in (for the typecast) + auto stream = (Stream *) datasource; + auto origin = Stream::SEEKORIGIN_BEGIN; - // Get the data in the right format - vorbisData = (SOggFile *) datasource; - - // Goto where we wish to seek to switch (whence) { - case SEEK_SET: // Seek to the start of the data file - // Make sure we are not going to the end of the file - if (vorbisData->dataSize >= offset) - actualOffset = offset; - else - actualOffset = vorbisData->dataSize; - // Set where we now are - vorbisData->dataRead = (int)actualOffset; + case SEEK_SET: + origin = Stream::SEEKORIGIN_BEGIN; break; - case SEEK_CUR: // Seek from where we are - // Make sure we dont go past the end - spaceToEOF = vorbisData->dataSize - vorbisData->dataRead; - if (offset < spaceToEOF) - actualOffset = (offset); - else - actualOffset = spaceToEOF; - // Seek from our currrent location - vorbisData->dataRead += actualOffset; + case SEEK_CUR: + origin = Stream::SEEKORIGIN_CURRENT; break; - case SEEK_END: // Seek from the end of the file - if (offset < 0) - vorbisData->dataRead = vorbisData->dataSize + offset; - else - vorbisData->dataRead = vorbisData->dataSize; + case SEEK_END: + origin = Stream::SEEKORIGIN_END; break; default: break; }; - return 0; + return stream->seek(offset, origin) ? 0 : -1; } -static long vorbisTell(void *datasource /* ptr to the data that the vorbis files need*/) +static long vorbisTell(void *datasource) { - SOggFile *vorbisData; - vorbisData = (SOggFile *) datasource; - return vorbisData->dataRead; + auto stream = (Stream *) datasource; + return (long) stream->tell(); } /** * END CALLBACK FUNCTIONS **/ -VorbisDecoder::VorbisDecoder(Data *data, int bufferSize) - : Decoder(data, bufferSize) +VorbisDecoder::VorbisDecoder(Stream *stream, int bufferSize) + : Decoder(stream, bufferSize) , duration(-2.0) { - // Initialize callbacks - vorbisCallbacks.close_func = vorbisClose; - vorbisCallbacks.seek_func = vorbisSeek; - vorbisCallbacks.read_func = vorbisRead; - vorbisCallbacks.tell_func = vorbisTell; - - // Check endianness -#ifdef LOVE_BIG_ENDIAN - endian = 1; -#else - endian = 0; -#endif - - // Initialize OGG file - oggFile.dataPtr = (const char *) data->getData(); - oggFile.dataSize = data->getSize(); - oggFile.dataRead = 0; + ov_callbacks callbacks = {}; + callbacks.close_func = vorbisClose; + callbacks.seek_func = vorbisSeek; + callbacks.read_func = vorbisRead; + callbacks.tell_func = vorbisTell; // Open Vorbis handle - if (ov_open_callbacks(&oggFile, &handle, NULL, 0, vorbisCallbacks) < 0) + if (ov_open_callbacks(stream, &handle, nullptr, 0, callbacks) < 0) throw love::Exception("Could not read Ogg bitstream"); - // Get info and comments vorbisInfo = ov_info(&handle, -1); - vorbisComment = ov_comment(&handle, -1); } VorbisDecoder::~VorbisDecoder() @@ -164,31 +97,22 @@ VorbisDecoder::~VorbisDecoder() ov_clear(&handle); } -bool VorbisDecoder::accepts(const std::string &ext) -{ - static const std::string supported[] = - { - "ogg", "oga", "ogv", "" - }; - - for (int i = 0; !(supported[i].empty()); i++) - { - if (supported[i].compare(ext) == 0) - return true; - } - - return false; -} - love::sound::Decoder *VorbisDecoder::clone() { - return new VorbisDecoder(data.get(), bufferSize); + StrongRef s(stream->clone(), Acquire::NORETAIN); + return new VorbisDecoder(s, bufferSize); } int VorbisDecoder::decode() { int size = 0; +#ifdef LOVE_BIG_ENDIAN + int endian = 1; +#else + int endian = 0; +#endif + while (size < bufferSize) { long result = ov_read(&handle, (char *) buffer + size, bufferSize - size, endian, (getBitDepth() == 16 ? 2 : 1), 1, 0); diff --git a/src/modules/sound/lullaby/VorbisDecoder.h b/src/modules/sound/lullaby/VorbisDecoder.h index dd58097ce..88f21e489 100644 --- a/src/modules/sound/lullaby/VorbisDecoder.h +++ b/src/modules/sound/lullaby/VorbisDecoder.h @@ -22,7 +22,7 @@ #define LOVE_SOUND_LULLABY_VORBIS_DECODER_H // LOVE -#include "common/Data.h" +#include "common/Stream.h" #include "common/int.h" #include "sound/Decoder.h" @@ -38,41 +38,29 @@ namespace sound namespace lullaby { -// Struct for handling data -struct SOggFile -{ - const char *dataPtr; // Pointer to the data in memory - int64 dataSize; // Size of the data - int64 dataRead; // How much we've read so far -}; - class VorbisDecoder : public Decoder { public: - VorbisDecoder(Data *data, int bufferSize); + VorbisDecoder(Stream *stream, int bufferSize); virtual ~VorbisDecoder(); - static bool accepts(const std::string &ext); - - love::sound::Decoder *clone(); - int decode(); - bool seek(double s); - bool rewind(); - bool isSeekable(); - int getChannelCount() const; - int getBitDepth() const; - int getSampleRate() const; - double getDuration(); + love::sound::Decoder *clone() override; + int decode() override; + bool seek(double s) override; + bool rewind() override; + bool isSeekable() override; + int getChannelCount() const override; + int getBitDepth() const override; + int getSampleRate() const override; + double getDuration() override; private: - SOggFile oggFile; // (see struct) - ov_callbacks vorbisCallbacks; // Callbacks used to read the file from mem - OggVorbis_File handle; // Handle to the file - vorbis_info *vorbisInfo; // Info - vorbis_comment *vorbisComment; // Comments - int endian; // Endianness + + OggVorbis_File handle; + vorbis_info *vorbisInfo; double duration; + }; // VorbisDecoder } // lullaby diff --git a/src/modules/sound/lullaby/WaveDecoder.cpp b/src/modules/sound/lullaby/WaveDecoder.cpp index c7b680409..012e4d42d 100644 --- a/src/modules/sound/lullaby/WaveDecoder.cpp +++ b/src/modules/sound/lullaby/WaveDecoder.cpp @@ -34,40 +34,32 @@ namespace lullaby // Callbacks static wuff_sint32 read_callback(void *userdata, wuff_uint8 *buffer, size_t *size) { - WaveFile *input = (WaveFile *) userdata; - size_t bytes_left = input->size - input->offset; - size_t target_size = *size < bytes_left ? *size : bytes_left; - memcpy(buffer, input->data + input->offset, target_size); - input->offset += target_size; - *size = target_size; + auto stream = (Stream *) userdata; + size_t readsize = stream->read(buffer, *size); + *size = readsize; return WUFF_SUCCESS; } static wuff_sint32 seek_callback(void *userdata, wuff_uint64 offset) { - WaveFile *input = (WaveFile *)userdata; - input->offset = (size_t) (offset < input->size ? offset : input->size); + auto stream = (Stream *) userdata; + stream->seek(offset, Stream::SEEKORIGIN_BEGIN); return WUFF_SUCCESS; } static wuff_sint32 tell_callback(void *userdata, wuff_uint64 *offset) { - WaveFile *input = (WaveFile *)userdata; - *offset = input->offset; + auto stream = (Stream *) userdata; + *offset = stream->tell(); return WUFF_SUCCESS; } -wuff_callback WaveDecoderCallbacks = {read_callback, seek_callback, tell_callback}; +static wuff_callback WaveDecoderCallbacks = {read_callback, seek_callback, tell_callback}; - -WaveDecoder::WaveDecoder(Data *data, int bufferSize) - : Decoder(data, bufferSize) +WaveDecoder::WaveDecoder(Stream *stream, int bufferSize) + : Decoder(stream, bufferSize) { - dataFile.data = (char *) data->getData(); - dataFile.size = data->getSize(); - dataFile.offset = 0; - - int wuff_status = wuff_open(&handle, &WaveDecoderCallbacks, &dataFile); + int wuff_status = wuff_open(&handle, &WaveDecoderCallbacks, stream); if (wuff_status < 0) throw love::Exception("Could not open WAVE"); @@ -78,13 +70,13 @@ WaveDecoder::WaveDecoder(Data *data, int bufferSize) throw love::Exception("Could not retrieve WAVE stream info"); if (info.channels > 2) - throw love::Exception("Multichannel audio not supported"); + throw love::Exception("WAVE Multichannel audio not supported"); if (info.format != WUFF_FORMAT_PCM_U8 && info.format != WUFF_FORMAT_PCM_S16) { wuff_status = wuff_format(handle, WUFF_FORMAT_PCM_S16); if (wuff_status < 0) - throw love::Exception("Could not set output format"); + throw love::Exception("Could not set WAVE output format"); } } catch (love::Exception &) @@ -99,25 +91,10 @@ WaveDecoder::~WaveDecoder() wuff_close(handle); } -bool WaveDecoder::accepts(const std::string &ext) -{ - static const std::string supported[] = - { - "wav", "" - }; - - for (int i = 0; !(supported[i].empty()); i++) - { - if (supported[i].compare(ext) == 0) - return true; - } - - return false; -} - love::sound::Decoder *WaveDecoder::clone() { - return new WaveDecoder(data.get(), bufferSize); + StrongRef s(stream->clone(), Acquire::NORETAIN); + return new WaveDecoder(s, bufferSize); } int WaveDecoder::decode() diff --git a/src/modules/sound/lullaby/WaveDecoder.h b/src/modules/sound/lullaby/WaveDecoder.h index 90832214a..9f7ddbeb4 100644 --- a/src/modules/sound/lullaby/WaveDecoder.h +++ b/src/modules/sound/lullaby/WaveDecoder.h @@ -22,7 +22,7 @@ #define LOVE_SOUND_LULLABY_WAVE_DECODER_H // LOVE -#include "common/Data.h" +#include "common/Stream.h" #include "sound/Decoder.h" #include "libraries/Wuff/wuff.h" @@ -34,36 +34,25 @@ namespace sound namespace lullaby { -// Struct for handling data -struct WaveFile -{ - char *data; - size_t size; - size_t offset; -}; - class WaveDecoder : public Decoder { public: - WaveDecoder(Data *data, int bufferSize); + WaveDecoder(Stream *stream, int bufferSize); virtual ~WaveDecoder(); - static bool accepts(const std::string &ext); - - love::sound::Decoder *clone(); - int decode(); - bool seek(double s); - bool rewind(); - bool isSeekable(); - int getChannelCount() const; - int getBitDepth() const; - int getSampleRate() const; - double getDuration(); + love::sound::Decoder *clone() override; + int decode() override; + bool seek(double s) override; + bool rewind() override; + bool isSeekable() override; + int getChannelCount() const override; + int getBitDepth() const override; + int getSampleRate() const override; + double getDuration() override; private: - WaveFile dataFile; wuff_handle *handle; wuff_info info; diff --git a/src/modules/sound/wrap_Sound.cpp b/src/modules/sound/wrap_Sound.cpp index 8c229c9a3..364374880 100644 --- a/src/modules/sound/wrap_Sound.cpp +++ b/src/modules/sound/wrap_Sound.cpp @@ -21,6 +21,7 @@ #include "wrap_Sound.h" #include "filesystem/wrap_Filesystem.h" +#include "data/DataStream.h" // Implementations. #include "lullaby/Sound.h" @@ -34,18 +35,50 @@ namespace sound int w_newDecoder(lua_State *L) { - love::filesystem::FileData *data = love::filesystem::luax_getfiledata(L, 1); - int bufferSize = (int) luaL_optinteger(L, 2, Decoder::DEFAULT_BUFFER_SIZE); + int bufferSize = (int)luaL_optinteger(L, 2, Decoder::DEFAULT_BUFFER_SIZE); + love::Stream *stream = nullptr; + + if (love::filesystem::luax_cangetfile(L, 1)) + { + Decoder::StreamSource source = Decoder::STREAM_FILE; + + const char* sourcestr = lua_isnoneornil(L, 3) ? nullptr : luaL_checkstring(L, 3); + if (sourcestr != nullptr && !Decoder::getConstant(sourcestr, source)) + return luax_enumerror(L, "stream type", Decoder::getConstants(source), sourcestr); + + if (source == Decoder::STREAM_FILE) + { + auto file = love::filesystem::luax_getfile(L, 1); + luax_catchexcept(L, [&]() { file->open(love::filesystem::File::MODE_READ); }); + stream = file; + } + else + { + luax_catchexcept(L, [&]() + { + StrongRef data(love::filesystem::luax_getfiledata(L, 1), Acquire::NORETAIN); + stream = new data::DataStream(data); + }); + } + + } + else if (luax_istype(L, 1, Data::type)) + { + Data *data = luax_checktype(L, 1); + luax_catchexcept(L, [&]() { stream = new data::DataStream(data); }); + } + else + { + stream = luax_checktype(L, 1); + stream->retain(); + } Decoder *t = nullptr; luax_catchexcept(L, - [&]() { t = instance()->newDecoder(data, bufferSize); }, - [&](bool) { data->release(); } + [&]() { t = instance()->newDecoder(stream, bufferSize); }, + [&](bool) { stream->release(); } ); - if (t == nullptr) - return luaL_error(L, "Extension \"%s\" not supported.", data->getExtension().c_str()); - luax_pushtype(L, t); t->release(); return 1; diff --git a/src/modules/system/System.cpp b/src/modules/system/System.cpp index 55980b4a2..800bf5866 100644 --- a/src/modules/system/System.cpp +++ b/src/modules/system/System.cpp @@ -164,7 +164,7 @@ bool System::openURL(const std::string &url) const #endif - return (int) result > 32; + return (ptrdiff_t) result > 32; #endif } diff --git a/src/modules/system/System.h b/src/modules/system/System.h index 7b2e6c7b5..8a40f59e9 100644 --- a/src/modules/system/System.h +++ b/src/modules/system/System.h @@ -114,6 +114,19 @@ public: **/ bool hasBackgroundMusic() const; + /** + * Gets the list of locales in order of user preference. + * + * The returned string from this function has format of + * xx_YY where 'xx' is ISO-639 language code and 'YY' is + * the ISO-3166 country code if available. If country + * code is unavailable, then it simply returns the language. + * + * @return List user preferred locales or empty if the current + * platform does not support this function. + */ + virtual std::vector getPreferredLocales() const = 0; + static bool getConstant(const char *in, PowerState &out); static bool getConstant(PowerState in, const char *&out); diff --git a/src/modules/system/sdl/System.cpp b/src/modules/system/sdl/System.cpp index acfaf316b..df0c476cf 100644 --- a/src/modules/system/sdl/System.cpp +++ b/src/modules/system/sdl/System.cpp @@ -25,6 +25,8 @@ // SDL #include #include +#include +#include namespace love { @@ -90,6 +92,30 @@ love::system::System::PowerState System::getPowerInfo(int &seconds, int &percent return state; } +std::vector System::getPreferredLocales() const +{ + std::vector result; + +#if SDL_VERSION_ATLEAST(2, 0, 14) + SDL_Locale *locales = SDL_GetPreferredLocales(); + + if (locales) + { + for (SDL_Locale* locale = locales; locale->language != nullptr; locale++) + { + if (locale->country) + result.push_back(std::string(locale->language) + "_" + std::string(locale->country)); + else + result.push_back(locale->language); + } + + SDL_free(locales); + } +#endif + + return result; +} + EnumMap::Entry System::powerEntries[] = { {System::POWER_UNKNOWN, SDL_POWERSTATE_UNKNOWN}, diff --git a/src/modules/system/sdl/System.h b/src/modules/system/sdl/System.h index 3f74b085c..0f0258dd4 100644 --- a/src/modules/system/sdl/System.h +++ b/src/modules/system/sdl/System.h @@ -51,6 +51,7 @@ public: std::string getClipboardText() const; PowerState getPowerInfo(int &seconds, int &percent) const; + std::vector getPreferredLocales() const override; private: diff --git a/src/modules/system/wrap_System.cpp b/src/modules/system/wrap_System.cpp index ac4df081b..d7d3209bd 100644 --- a/src/modules/system/wrap_System.cpp +++ b/src/modules/system/wrap_System.cpp @@ -101,6 +101,22 @@ int w_hasBackgroundMusic(lua_State *L) return 1; } +int w_getPreferredLocales(lua_State* L) +{ + int i = 1; + std::vector locales = instance()->getPreferredLocales(); + + lua_createtable(L, locales.size(), 0); + + for (const std::string& str: locales) + { + luax_pushstring(L, str); + lua_rawseti(L, -2, i++); + } + + return 1; +} + static const luaL_Reg functions[] = { { "getOS", w_getOS }, @@ -111,6 +127,7 @@ static const luaL_Reg functions[] = { "openURL", w_openURL }, { "vibrate", w_vibrate }, { "hasBackgroundMusic", w_hasBackgroundMusic }, + { "getPreferredLocales", w_getPreferredLocales }, { 0, 0 } }; diff --git a/src/modules/thread/LuaThread.cpp b/src/modules/thread/LuaThread.cpp index 2a6cb12cc..9ae4ab0bd 100644 --- a/src/modules/thread/LuaThread.cpp +++ b/src/modules/thread/LuaThread.cpp @@ -37,6 +37,7 @@ love::Type LuaThread::type("Thread", &Threadable::type); LuaThread::LuaThread(const std::string &name, love::Data *code) : code(code) , name(name) + , haserror(false) { threadName = name; } @@ -48,6 +49,7 @@ LuaThread::~LuaThread() void LuaThread::threadFunction() { error.clear(); + haserror = false; lua_State *L = luaL_newstate(); luaL_openlibs(L); @@ -71,7 +73,10 @@ void LuaThread::threadFunction() int tracebackidx = lua_gettop(L); if (luaL_loadbuffer(L, (const char *) code->getData(), code->getSize(), name.c_str()) != 0) + { error = luax_tostring(L, -1); + haserror = true; + } else { int pushedargs = (int) args.size(); @@ -82,18 +87,27 @@ void LuaThread::threadFunction() args.clear(); if (lua_pcall(L, pushedargs, 0, tracebackidx) != 0) + { error = luax_tostring(L, -1); + haserror = true; + } } lua_close(L); - if (!error.empty()) + if (haserror) onError(); } bool LuaThread::start(const std::vector &args) { + if (isRunning()) + return false; + this->args = args; + error.clear(); + haserror = false; + return Threadable::start(); } @@ -104,9 +118,6 @@ const std::string &LuaThread::getError() const void LuaThread::onError() { - if (error.empty()) - return; - auto eventmodule = Module::getInstance(Module::M_EVENT); if (!eventmodule) return; diff --git a/src/modules/thread/LuaThread.h b/src/modules/thread/LuaThread.h index e8d625cb7..9e707b784 100644 --- a/src/modules/thread/LuaThread.h +++ b/src/modules/thread/LuaThread.h @@ -46,6 +46,7 @@ public: virtual ~LuaThread(); void threadFunction(); const std::string &getError() const; + bool hasError() const { return haserror; } bool start(const std::vector &args); @@ -56,6 +57,7 @@ private: StrongRef code; std::string name; std::string error; + bool haserror; std::vector args; diff --git a/src/modules/thread/sdl/Thread.cpp b/src/modules/thread/sdl/Thread.cpp index cfc63e31f..71f28e4ac 100644 --- a/src/modules/thread/sdl/Thread.cpp +++ b/src/modules/thread/sdl/Thread.cpp @@ -44,7 +44,7 @@ bool Thread::start() { #if defined(LOVE_LINUX) // Temporarly block signals, as the thread inherits this mask - love::thread::disableSignals(); + love::thread::ScopedDisableSignals disableSignals; #endif Lock l(mutex); @@ -55,9 +55,6 @@ bool Thread::start() thread = SDL_CreateThread(thread_runner, t->getThreadName(), this); running = (thread != nullptr); -#if defined(LOVE_LINUX) - love::thread::reenableSignals(); -#endif return running; } diff --git a/src/modules/thread/threads.h b/src/modules/thread/threads.h index 5f607bade..827ca8586 100644 --- a/src/modules/thread/threads.h +++ b/src/modules/thread/threads.h @@ -132,6 +132,12 @@ Thread *newThread(Threadable *t); #if defined(LOVE_LINUX) void disableSignals(); void reenableSignals(); + +struct ScopedDisableSignals +{ + ScopedDisableSignals() { disableSignals(); } + ~ScopedDisableSignals() { reenableSignals(); } +}; #endif } // thread diff --git a/src/modules/thread/wrap_LuaThread.cpp b/src/modules/thread/wrap_LuaThread.cpp index db29c1a65..fe15befbc 100644 --- a/src/modules/thread/wrap_LuaThread.cpp +++ b/src/modules/thread/wrap_LuaThread.cpp @@ -63,11 +63,10 @@ int w_Thread_wait(lua_State *L) int w_Thread_getError(lua_State *L) { LuaThread *t = luax_checkthread(L, 1); - std::string err = t->getError(); - if (err.empty()) - lua_pushnil(L); + if (t->hasError()) + luax_pushstring(L, t->getError()); else - luax_pushstring(L, err); + lua_pushnil(L); return 1; } diff --git a/src/modules/video/Video.h b/src/modules/video/Video.h index 3de49f0d8..11d158980 100644 --- a/src/modules/video/Video.h +++ b/src/modules/video/Video.h @@ -23,7 +23,6 @@ // LOVE #include "common/Module.h" -#include "common/Stream.h" #include "filesystem/File.h" #include "VideoStream.h" diff --git a/src/modules/video/VideoStream.cpp b/src/modules/video/VideoStream.cpp index 2edb8707c..bb2fae705 100644 --- a/src/modules/video/VideoStream.cpp +++ b/src/modules/video/VideoStream.cpp @@ -27,7 +27,7 @@ namespace love namespace video { -love::Type VideoStream::type("VideoStream", &Stream::type); +love::Type VideoStream::type("VideoStream", &Object::type); void VideoStream::setSync(VideoStream::FrameSync *frameSync) { diff --git a/src/modules/video/VideoStream.h b/src/modules/video/VideoStream.h index fe507a6d0..10d1fa3ae 100644 --- a/src/modules/video/VideoStream.h +++ b/src/modules/video/VideoStream.h @@ -22,7 +22,7 @@ #define LOVE_VIDEO_VIDEOSTREAM_H // LOVE -#include "common/Stream.h" +#include "common/Object.h" #include "audio/Source.h" #include "thread/threads.h" @@ -31,7 +31,7 @@ namespace love namespace video { -class VideoStream : public Stream +class VideoStream : public love::Object { public: @@ -39,6 +39,28 @@ public: virtual ~VideoStream() {} + /** + * A callback, gets called when some Stream consumer exhausts the data + **/ + virtual void fillBackBuffer() {} + + /** + * Get the front buffer, Streams are supposed to be (at least) double-buffered + **/ + virtual const void* getFrontBuffer() const = 0; + + /** + * Get the size of any (and in particular the front) buffer + **/ + virtual size_t getSize() const = 0; + + /** + * Swap buffers. Returns true if there is new data in the front buffer, + * false otherwise. + * NOTE: If there is no back buffer ready, this call must be ignored + **/ + virtual bool swapBuffers() = 0; + virtual int getWidth() const = 0; virtual int getHeight() const = 0; virtual const std::string &getFilename() const = 0; diff --git a/src/modules/window/Window.cpp b/src/modules/window/Window.cpp index 5133a29d3..c2a69df2d 100644 --- a/src/modules/window/Window.cpp +++ b/src/modules/window/Window.cpp @@ -114,6 +114,7 @@ StringMap::Entry Window::settingEntri {"minheight", SETTING_MIN_HEIGHT}, {"borderless", SETTING_BORDERLESS}, {"centered", SETTING_CENTERED}, + {"displayindex", SETTING_DISPLAYINDEX}, {"display", SETTING_DISPLAY}, {"highdpi", SETTING_HIGHDPI}, {"usedpiscale", SETTING_USE_DPISCALE}, diff --git a/src/modules/window/Window.h b/src/modules/window/Window.h index 333bafeee..01b468f6f 100644 --- a/src/modules/window/Window.h +++ b/src/modules/window/Window.h @@ -69,7 +69,8 @@ public: SETTING_MIN_HEIGHT, SETTING_BORDERLESS, SETTING_CENTERED, - SETTING_DISPLAY, + SETTING_DISPLAYINDEX, + SETTING_DISPLAY, // Deprecated SETTING_HIGHDPI, // Deprecated SETTING_USE_DPISCALE, SETTING_REFRESHRATE, @@ -264,7 +265,7 @@ struct WindowSettings int minheight = 1; bool borderless = false; bool centered = true; - int display = 0; + int displayindex = 0; bool usedpiscale = true; double refreshrate = 0.0; bool useposition = false; diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index 3324a3ffc..260cbfaf6 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/src/modules/window/sdl/Window.cpp @@ -498,13 +498,13 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) f.minwidth = std::max(f.minwidth, 1); f.minheight = std::max(f.minheight, 1); - f.display = std::min(std::max(f.display, 0), getDisplayCount() - 1); + f.displayindex = std::min(std::max(f.displayindex, 0), getDisplayCount() - 1); // Use the desktop resolution if a width or height of 0 is specified. if (width == 0 || height == 0) { SDL_DisplayMode mode = {}; - SDL_GetDesktopDisplayMode(f.display, &mode); + SDL_GetDesktopDisplayMode(f.displayindex, &mode); width = mode.w; height = mode.h; } @@ -529,16 +529,16 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) { // The position needs to be in the global coordinate space. SDL_Rect displaybounds = {}; - SDL_GetDisplayBounds(f.display, &displaybounds); + SDL_GetDisplayBounds(f.displayindex, &displaybounds); x += displaybounds.x; y += displaybounds.y; } else { if (f.centered) - x = y = SDL_WINDOWPOS_CENTERED_DISPLAY(f.display); + x = y = SDL_WINDOWPOS_CENTERED_DISPLAY(f.displayindex); else - x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(f.display); + x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(f.displayindex); } SDL_DisplayMode fsmode = {0, width, height, 0, nullptr}; @@ -546,12 +546,12 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) if (f.fullscreen && f.fstype == FULLSCREEN_EXCLUSIVE) { // Fullscreen window creation will bug out if no mode can be used. - if (SDL_GetClosestDisplayMode(f.display, &fsmode, &fsmode) == nullptr) + if (SDL_GetClosestDisplayMode(f.displayindex, &fsmode, &fsmode) == nullptr) { // GetClosestDisplayMode will fail if we request a size larger // than the largest available display mode, so we'll try to use // the largest (first) mode in that case. - if (SDL_GetDisplayMode(f.display, 0, &fsmode) < 0) + if (SDL_GetDisplayMode(f.displayindex, 0, &fsmode) < 0) return false; } } @@ -629,7 +629,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) // Enforce minimum window dimensions. SDL_SetWindowMinimumSize(window, f.minwidth, f.minheight); - if (this->settings.display != f.display || f.useposition || f.centered) + if (this->settings.displayindex != f.displayindex || f.useposition || f.centered) SDL_SetWindowPosition(window, x, y); SDL_RaiseWindow(window); @@ -682,7 +682,17 @@ bool Window::onSizeChanged(int width, int height) windowWidth = width; windowHeight = height; - SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight); + if (glcontext != nullptr) + SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight); +#ifdef LOVE_GRAPHICS_METAL + else if (metalView != nullptr) + SDL_Metal_GetDrawableSize(window, &pixelWidth, &pixelHeight); +#endif + else + { + pixelWidth = width; + pixelHeight = height; + } if (graphics.get()) { @@ -739,7 +749,7 @@ void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphi settings.borderless = (wflags & SDL_WINDOW_BORDERLESS) != 0; settings.centered = newsettings.centered; - getPosition(settings.x, settings.y, settings.display); + getPosition(settings.x, settings.y, settings.displayindex); setHighDPIAllowed((wflags & SDL_WINDOW_ALLOW_HIGHDPI) != 0); @@ -757,7 +767,7 @@ void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphi settings.depth = newsettings.depth; SDL_DisplayMode dmode = {}; - SDL_GetCurrentDisplayMode(settings.display, &dmode); + SDL_GetCurrentDisplayMode(settings.displayindex, &dmode); // May be 0 if the refresh rate can't be determined. settings.refreshrate = (double) dmode.refresh_rate; @@ -865,11 +875,6 @@ bool Window::setFullscreen(bool fullscreen, FullscreenType fstype) SDL_GL_MakeCurrent(window, glcontext); updateSettings(newsettings, true); - - // This gets un-set when we exit fullscreen (at least in macOS). - if (!fullscreen) - SDL_SetWindowMinimumSize(window, settings.minwidth, settings.minheight); - return true; } diff --git a/src/modules/window/wrap_Window.cpp b/src/modules/window/wrap_Window.cpp index a1720e6c2..59b20fee5 100644 --- a/src/modules/window/wrap_Window.cpp +++ b/src/modules/window/wrap_Window.cpp @@ -74,9 +74,17 @@ static int readWindowSettings(lua_State *L, int idx, WindowSettings &settings) settings.minheight = luax_intflag(L, idx, settingName(Window::SETTING_MIN_HEIGHT), settings.minheight); settings.borderless = luax_boolflag(L, idx, settingName(Window::SETTING_BORDERLESS), settings.borderless); settings.centered = luax_boolflag(L, idx, settingName(Window::SETTING_CENTERED), settings.centered); - settings.display = luax_intflag(L, idx, settingName(Window::SETTING_DISPLAY), settings.display+1) - 1; settings.usedpiscale = luax_boolflag(L, idx, settingName(Window::SETTING_USE_DPISCALE), settings.usedpiscale); + settings.displayindex = luax_intflag(L, idx, settingName(Window::SETTING_DISPLAYINDEX), settings.displayindex + 1) - 1; + lua_getfield(L, idx, settingName(Window::SETTING_DISPLAY)); + if (!lua_isnoneornil(L, -1)) + { + luax_markdeprecated(L, 1, "window.display", API_FIELD, DEPRECATED_REPLACED, "displayindex field"); + settings.displayindex = (int) luaL_checkinteger(L, -1) - 1; + } + lua_pop(L, 1); + lua_getfield(L, idx, settingName(Window::SETTING_HIGHDPI)); if (!lua_isnoneornil(L, -1)) { @@ -208,8 +216,8 @@ int w_getMode(lua_State *L) lua_setfield(L, -2, settingName(Window::SETTING_CENTERED)); // Display index is 0-based internally and 1-based in Lua. - lua_pushinteger(L, settings.display + 1); - lua_setfield(L, -2, settingName(Window::SETTING_DISPLAY)); + lua_pushinteger(L, settings.displayindex + 1); + lua_setfield(L, -2, settingName(Window::SETTING_DISPLAYINDEX)); luax_pushboolean(L, settings.usedpiscale); lua_setfield(L, -2, settingName(Window::SETTING_USE_DPISCALE));