Browse Source

Merge remote-tracking branch 'origin/12.0-development' into vulkan

niki 3 years ago
parent
commit
75a6bc9b5d
100 changed files with 1523 additions and 1327 deletions
  1. 0 106
      Android.mk
  2. 21 4
      CMakeLists.txt
  3. 3 0
      changes.txt
  4. 1 4
      platform/unix/configure.ac
  5. 0 5
      platform/unix/deps.m4
  6. 13 13
      platform/xcode/liblove.xcodeproj/project.pbxproj
  7. 50 1
      src/common/Stream.cpp
  8. 64 13
      src/common/Stream.h
  9. 1 1
      src/common/android.cpp
  10. 2 0
      src/common/config.h
  11. 13 12
      src/common/deprecation.cpp
  12. 12 0
      src/common/pixelformat.cpp
  13. 10 0
      src/common/pixelformat.h
  14. 5 1
      src/libraries/luahttps/src/lua/main.cpp
  15. 18 20
      src/modules/audio/openal/Audio.cpp
  16. 27 14
      src/modules/audio/wrap_Audio.cpp
  17. 3 2
      src/modules/data/ByteData.cpp
  18. 1 1
      src/modules/data/ByteData.h
  19. 139 0
      src/modules/data/DataStream.cpp
  20. 33 37
      src/modules/data/DataStream.h
  21. 14 1
      src/modules/event/sdl/Event.cpp
  22. 20 53
      src/modules/filesystem/File.cpp
  23. 15 78
      src/modules/filesystem/File.h
  24. 9 3
      src/modules/filesystem/Filesystem.h
  25. 32 4
      src/modules/filesystem/NativeFile.cpp
  26. 12 7
      src/modules/filesystem/NativeFile.h
  27. 34 6
      src/modules/filesystem/physfs/File.cpp
  28. 12 7
      src/modules/filesystem/physfs/File.h
  29. 21 11
      src/modules/filesystem/physfs/Filesystem.cpp
  30. 4 2
      src/modules/filesystem/physfs/Filesystem.h
  31. 3 1
      src/modules/filesystem/wrap_File.cpp
  32. 72 32
      src/modules/filesystem/wrap_Filesystem.cpp
  33. 2 0
      src/modules/filesystem/wrap_Filesystem.h
  34. 28 15
      src/modules/graphics/Graphics.cpp
  35. 5 5
      src/modules/graphics/Graphics.h
  36. 5 3
      src/modules/graphics/Shader.cpp
  37. 6 1
      src/modules/graphics/Shader.h
  38. 43 8
      src/modules/graphics/Texture.cpp
  39. 1 0
      src/modules/graphics/Texture.h
  40. 2 2
      src/modules/graphics/metal/Buffer.mm
  41. 1 1
      src/modules/graphics/metal/Graphics.h
  42. 3 7
      src/modules/graphics/metal/Graphics.mm
  43. 6 5
      src/modules/graphics/metal/Shader.mm
  44. 0 2
      src/modules/graphics/metal/Texture.mm
  45. 73 79
      src/modules/graphics/opengl/Graphics.cpp
  46. 5 3
      src/modules/graphics/opengl/Graphics.h
  47. 1 1
      src/modules/graphics/vulkan/Graphics.cpp
  48. 1 1
      src/modules/graphics/vulkan/Graphics.h
  49. 52 8
      src/modules/graphics/wrap_Graphics.cpp
  50. 1 1
      src/modules/graphics/wrap_Graphics.lua
  51. 4 5
      src/modules/image/CompressedImageData.cpp
  52. 1 1
      src/modules/image/CompressedImageData.h
  53. 3 20
      src/modules/image/CompressedSlice.cpp
  54. 5 15
      src/modules/image/CompressedSlice.h
  55. 1 1
      src/modules/image/FormatHandler.cpp
  56. 1 1
      src/modules/image/FormatHandler.h
  57. 11 49
      src/modules/image/ImageData.cpp
  58. 0 1
      src/modules/image/ImageData.h
  59. 4 4
      src/modules/image/magpie/ASTCHandler.cpp
  60. 1 1
      src/modules/image/magpie/ASTCHandler.h
  61. 3 4
      src/modules/image/magpie/KTXHandler.cpp
  62. 1 1
      src/modules/image/magpie/KTXHandler.h
  63. 3 4
      src/modules/image/magpie/PKMHandler.cpp
  64. 1 1
      src/modules/image/magpie/PKMHandler.h
  65. 4 4
      src/modules/image/magpie/PVRHandler.cpp
  66. 1 1
      src/modules/image/magpie/PVRHandler.h
  67. 3 4
      src/modules/image/magpie/ddsHandler.cpp
  68. 1 1
      src/modules/image/magpie/ddsHandler.h
  69. 9 3
      src/modules/image/wrap_ImageData.lua
  70. 20 0
      src/modules/keyboard/Keyboard.cpp
  71. 25 0
      src/modules/keyboard/Keyboard.h
  72. 26 0
      src/modules/keyboard/sdl/Keyboard.cpp
  73. 1 0
      src/modules/keyboard/sdl/Keyboard.h
  74. 12 0
      src/modules/keyboard/wrap_Keyboard.cpp
  75. 21 6
      src/modules/love/boot.lua
  76. 9 0
      src/modules/love/callbacks.lua
  77. 12 2
      src/modules/sound/Decoder.cpp
  78. 14 5
      src/modules/sound/Decoder.h
  79. 3 3
      src/modules/sound/Sound.h
  80. 18 81
      src/modules/sound/lullaby/CoreAudioDecoder.cpp
  81. 10 12
      src/modules/sound/lullaby/CoreAudioDecoder.h
  82. 20 24
      src/modules/sound/lullaby/FLACDecoder.cpp
  83. 12 13
      src/modules/sound/lullaby/FLACDecoder.h
  84. 0 154
      src/modules/sound/lullaby/GmeDecoder.cpp
  85. 122 27
      src/modules/sound/lullaby/MP3Decoder.cpp
  86. 14 11
      src/modules/sound/lullaby/MP3Decoder.h
  87. 27 27
      src/modules/sound/lullaby/ModPlugDecoder.cpp
  88. 13 13
      src/modules/sound/lullaby/ModPlugDecoder.h
  89. 14 34
      src/modules/sound/lullaby/Sound.cpp
  90. 2 9
      src/modules/sound/lullaby/Sound.h
  91. 33 109
      src/modules/sound/lullaby/VorbisDecoder.cpp
  92. 15 27
      src/modules/sound/lullaby/VorbisDecoder.h
  93. 15 38
      src/modules/sound/lullaby/WaveDecoder.cpp
  94. 11 22
      src/modules/sound/lullaby/WaveDecoder.h
  95. 40 7
      src/modules/sound/wrap_Sound.cpp
  96. 1 1
      src/modules/system/System.cpp
  97. 13 0
      src/modules/system/System.h
  98. 26 0
      src/modules/system/sdl/System.cpp
  99. 1 0
      src/modules/system/sdl/System.h
  100. 17 0
      src/modules/system/wrap_System.cpp

+ 0 - 106
Android.mk

@@ -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)

+ 21 - 4
CMakeLists.txt

@@ -36,6 +36,11 @@ set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
 
 
 set (CMAKE_CXX_STANDARD 17)
 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)
 if(MSVC)
 	set(LOVE_CONSOLE_EXE_NAME lovec)
 	set(LOVE_CONSOLE_EXE_NAME lovec)
 endif()
 endif()
@@ -48,11 +53,15 @@ else()
 	set(LOVE_TARGET_PLATFORM x86)
 	set(LOVE_TARGET_PLATFORM x86)
 endif()
 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(LOVE_JIT)
 	if(APPLE)
 	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()
 	endif()
 	message(STATUS "LuaJIT: Enabled")
 	message(STATUS "LuaJIT: Enabled")
 else()
 else()
@@ -238,6 +247,8 @@ endfunction()
 #
 #
 
 
 set(LOVE_SRC_COMMON
 set(LOVE_SRC_COMMON
+	src/common/android.cpp
+	src/common/android.h
 	src/common/b64.cpp
 	src/common/b64.cpp
 	src/common/b64.h
 	src/common/b64.h
 	src/common/Color.h
 	src/common/Color.h
@@ -290,6 +301,9 @@ if (APPLE)
 	set(LOVE_SRC_COMMON ${LOVE_SRC_COMMON}
 	set(LOVE_SRC_COMMON ${LOVE_SRC_COMMON}
 		src/common/macosx.mm
 		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()
 endif()
 
 
 source_group("common" FILES ${LOVE_SRC_COMMON})
 source_group("common" FILES ${LOVE_SRC_COMMON})
@@ -364,6 +378,8 @@ set(LOVE_SRC_MODULE_DATA
 	src/modules/data/Compressor.h
 	src/modules/data/Compressor.h
 	src/modules/data/DataModule.cpp
 	src/modules/data/DataModule.cpp
 	src/modules/data/DataModule.h
 	src/modules/data/DataModule.h
+	src/modules/data/DataStream.cpp
+	src/modules/data/DataStream.h
 	src/modules/data/DataView.cpp
 	src/modules/data/DataView.cpp
 	src/modules/data/DataView.h
 	src/modules/data/DataView.h
 	src/modules/data/HashFunction.cpp
 	src/modules/data/HashFunction.cpp
@@ -901,8 +917,6 @@ set(LOVE_SRC_MODULE_SOUND_ROOT
 set(LOVE_SRC_MODULE_SOUND_LULLABY
 set(LOVE_SRC_MODULE_SOUND_LULLABY
 	src/modules/sound/lullaby/FLACDecoder.cpp
 	src/modules/sound/lullaby/FLACDecoder.cpp
 	src/modules/sound/lullaby/FLACDecoder.h
 	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.cpp
 	src/modules/sound/lullaby/ModPlugDecoder.h
 	src/modules/sound/lullaby/ModPlugDecoder.h
 	src/modules/sound/lullaby/MP3Decoder.h
 	src/modules/sound/lullaby/MP3Decoder.h
@@ -1644,6 +1658,7 @@ if(APPLE)
 	set(LOVE_SRC_3P_PHYSFS ${LOVE_SRC_3P_PHYSFS}
 	set(LOVE_SRC_3P_PHYSFS ${LOVE_SRC_3P_PHYSFS}
 		src/libraries/physfs/physfs_platform_apple.m
 		src/libraries/physfs/physfs_platform_apple.m
 	)
 	)
+	set(LOVE_LINK_LIBRARIES ${LOVE_LINK_LIBRARIES} "-framework IOKit")
 endif()
 endif()
 
 
 add_library(love_3p_physfs ${LOVE_SRC_3P_PHYSFS})
 add_library(love_3p_physfs ${LOVE_SRC_3P_PHYSFS})
@@ -1764,6 +1779,7 @@ set(LOVE_LIB_SRC
 )
 )
 
 
 include_directories(
 include_directories(
+	BEFORE
 	src
 	src
 	src/libraries
 	src/libraries
 	src/libraries/box2D
 	src/libraries/box2D
@@ -1792,6 +1808,7 @@ if(ANDROID)
 	# as shared library, so change the library name and add love.cpp
 	# as shared library, so change the library name and add love.cpp
 	set(LOVE_LIB_NAME ${LOVE_EXE_NAME})
 	set(LOVE_LIB_NAME ${LOVE_EXE_NAME})
 	set(LOVE_LIB_SRC ${LOVE_LIB_SRC} src/love.cpp)
 	set(LOVE_LIB_SRC ${LOVE_LIB_SRC} src/love.cpp)
+	set(LOVE_LINK_LIBRARIES ${LOVE_LINK_LIBRARIES} android)
 endif()
 endif()
 
 
 add_library(${LOVE_LIB_NAME} SHARED ${LOVE_LIB_SRC} ${LOVE_RC})
 add_library(${LOVE_LIB_NAME} SHARED ${LOVE_LIB_SRC} ${LOVE_RC})

+ 3 - 0
changes.txt

@@ -42,6 +42,9 @@ Released: N/A
 * Added a variant of love.graphics.setColorMask which accepts a single boolean.
 * Added a variant of love.graphics.setColorMask which accepts a single boolean.
 * Added new 'clampone' wrap mode.
 * Added new 'clampone' wrap mode.
 * Added a variant of Font:getWidth which takes a codepoint number argument.
 * 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 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.
 * Changed the Texture class and implementation to no longer have separate Canvas and Image subclasses.

+ 1 - 4
platform/unix/configure.ac

@@ -54,9 +54,6 @@ with_clean_luaversion=`printf ${with_luaversion} | sed 's/\.//g'`
 # Generated sources for enabling/disabling modules
 # Generated sources for enabling/disabling modules
 m4_include([configure-modules-pre.ac])
 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
 # Dependencies we always use
 ACLOVE_DEP_LUA
 ACLOVE_DEP_LUA
 ACLOVE_DEP_SDL2
 ACLOVE_DEP_SDL2
@@ -72,12 +69,12 @@ AS_VAR_IF([enable_module_sound], [yes], [
 	ACLOVE_DEP_VORBISFILE
 	ACLOVE_DEP_VORBISFILE
 ], [])
 ], [])
 AS_VAR_IF([enable_module_video], [yes], [ACLOVE_DEP_THEORA], [])
 AS_VAR_IF([enable_module_video], [yes], [ACLOVE_DEP_THEORA], [])
-AS_VAR_IF([enable_gme], [yes], [ACLOVE_DEP_GME], [])
 
 
 # Add flags for optional libraries
 # 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-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-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-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
 # 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])
 AS_VAR_IF([enable_module_filesystem], [yes], [enable_library_physfs=yes], [enable_library_physfs=no])

+ 0 - 5
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],[-D_FILE_OFFSET_BITS=64]),
 		AC_SUBST([FILE_OFFSET],[]))])
 		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
 # For enet
 AC_DEFUN([ACLOVE_SOCKLEN_T], [
 AC_DEFUN([ACLOVE_SOCKLEN_T], [
 	AC_CHECK_TYPE([socklen_t], [AC_DEFINE([HAS_SOCKLEN_T], [1], [Define if socklen_t exists.] )], ,
 	AC_CHECK_TYPE([socklen_t], [AC_DEFINE([HAS_SOCKLEN_T], [1], [Define if socklen_t exists.] )], ,

+ 13 - 13
platform/xcode/liblove.xcodeproj/project.pbxproj

@@ -526,9 +526,6 @@
 		FA0B7E8B1A95902C000E1D17 /* FLACDecoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C821A95902C000E1D17 /* FLACDecoder.cpp */; };
 		FA0B7E8B1A95902C000E1D17 /* FLACDecoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C821A95902C000E1D17 /* FLACDecoder.cpp */; };
 		FA0B7E8C1A95902C000E1D17 /* 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 */; };
 		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 */; };
 		FA0B7E911A95902C000E1D17 /* ModPlugDecoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C861A95902C000E1D17 /* ModPlugDecoder.cpp */; };
 		FA0B7E921A95902C000E1D17 /* 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 */; };
 		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 */; };
 		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 */; };
 		FA6A2B7B1F60B8250074C308 /* wrap_ByteData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6A2B781F60B8250074C308 /* wrap_ByteData.cpp */; };
 		FA6BDE5C1F31725300786805 /* Color.h in Headers */ = {isa = PBXBuildFile; fileRef = FA6BDE5B1F31725300786805 /* Color.h */; };
 		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 */; };
 		FA76344A1E28722A0066EF9E /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7634481E28722A0066EF9E /* StreamBuffer.cpp */; };
 		FA76344B1E28722A0066EF9E /* 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 */; };
 		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 = "<group>"; };
 		FA0B7C801A95902C000E1D17 /* Decoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Decoder.cpp; sourceTree = "<group>"; };
 		FA0B7C821A95902C000E1D17 /* FLACDecoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FLACDecoder.cpp; sourceTree = "<group>"; };
 		FA0B7C821A95902C000E1D17 /* FLACDecoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FLACDecoder.cpp; sourceTree = "<group>"; };
 		FA0B7C831A95902C000E1D17 /* FLACDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLACDecoder.h; sourceTree = "<group>"; };
 		FA0B7C831A95902C000E1D17 /* FLACDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLACDecoder.h; sourceTree = "<group>"; };
-		FA0B7C841A95902C000E1D17 /* GmeDecoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GmeDecoder.cpp; sourceTree = "<group>"; };
-		FA0B7C851A95902C000E1D17 /* GmeDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GmeDecoder.h; sourceTree = "<group>"; };
 		FA0B7C861A95902C000E1D17 /* ModPlugDecoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ModPlugDecoder.cpp; sourceTree = "<group>"; };
 		FA0B7C861A95902C000E1D17 /* ModPlugDecoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ModPlugDecoder.cpp; sourceTree = "<group>"; };
 		FA0B7C871A95902C000E1D17 /* ModPlugDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModPlugDecoder.h; sourceTree = "<group>"; };
 		FA0B7C871A95902C000E1D17 /* ModPlugDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModPlugDecoder.h; sourceTree = "<group>"; };
 		FA0B7C8A1A95902C000E1D17 /* Sound.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Sound.cpp; sourceTree = "<group>"; };
 		FA0B7C8A1A95902C000E1D17 /* Sound.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Sound.cpp; sourceTree = "<group>"; };
@@ -1909,6 +1907,8 @@
 		FA6A2B771F60B8250074C308 /* wrap_ByteData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_ByteData.h; sourceTree = "<group>"; };
 		FA6A2B771F60B8250074C308 /* wrap_ByteData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_ByteData.h; sourceTree = "<group>"; };
 		FA6A2B781F60B8250074C308 /* wrap_ByteData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_ByteData.cpp; sourceTree = "<group>"; };
 		FA6A2B781F60B8250074C308 /* wrap_ByteData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_ByteData.cpp; sourceTree = "<group>"; };
 		FA6BDE5B1F31725300786805 /* Color.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Color.h; sourceTree = "<group>"; };
 		FA6BDE5B1F31725300786805 /* Color.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Color.h; sourceTree = "<group>"; };
+		FA6BDF8C281219E900240F2A /* DataStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DataStream.cpp; sourceTree = "<group>"; };
+		FA6BDF8D281219E900240F2A /* DataStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataStream.h; sourceTree = "<group>"; };
 		FA7634481E28722A0066EF9E /* StreamBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StreamBuffer.cpp; sourceTree = "<group>"; };
 		FA7634481E28722A0066EF9E /* StreamBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StreamBuffer.cpp; sourceTree = "<group>"; };
 		FA7634491E28722A0066EF9E /* StreamBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamBuffer.h; sourceTree = "<group>"; };
 		FA7634491E28722A0066EF9E /* StreamBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamBuffer.h; sourceTree = "<group>"; };
 		FA7DA04C1C16874A0056B200 /* wrap_Math.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wrap_Math.lua; sourceTree = "<group>"; };
 		FA7DA04C1C16874A0056B200 /* wrap_Math.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wrap_Math.lua; sourceTree = "<group>"; };
@@ -3199,8 +3199,6 @@
 				FA0B7C7F1A95902C000E1D17 /* CoreAudioDecoder.h */,
 				FA0B7C7F1A95902C000E1D17 /* CoreAudioDecoder.h */,
 				FA0B7C821A95902C000E1D17 /* FLACDecoder.cpp */,
 				FA0B7C821A95902C000E1D17 /* FLACDecoder.cpp */,
 				FA0B7C831A95902C000E1D17 /* FLACDecoder.h */,
 				FA0B7C831A95902C000E1D17 /* FLACDecoder.h */,
-				FA0B7C841A95902C000E1D17 /* GmeDecoder.cpp */,
-				FA0B7C851A95902C000E1D17 /* GmeDecoder.h */,
 				FA0B7C861A95902C000E1D17 /* ModPlugDecoder.cpp */,
 				FA0B7C861A95902C000E1D17 /* ModPlugDecoder.cpp */,
 				FA0B7C871A95902C000E1D17 /* ModPlugDecoder.h */,
 				FA0B7C871A95902C000E1D17 /* ModPlugDecoder.h */,
 				FA522D4B23F9FE370059EE3C /* MP3Decoder.cpp */,
 				FA522D4B23F9FE370059EE3C /* MP3Decoder.cpp */,
@@ -3762,6 +3760,8 @@
 				FACA02E31F5E396B0084B28F /* Compressor.h */,
 				FACA02E31F5E396B0084B28F /* Compressor.h */,
 				FACA02E41F5E396B0084B28F /* DataModule.cpp */,
 				FACA02E41F5E396B0084B28F /* DataModule.cpp */,
 				FACA02E51F5E396B0084B28F /* DataModule.h */,
 				FACA02E51F5E396B0084B28F /* DataModule.h */,
+				FA6BDF8C281219E900240F2A /* DataStream.cpp */,
+				FA6BDF8D281219E900240F2A /* DataStream.h */,
 				FA6A2B681F5F7F560074C308 /* DataView.cpp */,
 				FA6A2B681F5F7F560074C308 /* DataView.cpp */,
 				FA6A2B691F5F7F560074C308 /* DataView.h */,
 				FA6A2B691F5F7F560074C308 /* DataView.h */,
 				FACA02E61F5E396B0084B28F /* HashFunction.cpp */,
 				FACA02E61F5E396B0084B28F /* HashFunction.cpp */,
@@ -4023,7 +4023,6 @@
 				FAF140871E20934C00F898D2 /* parseVersions.h in Headers */,
 				FAF140871E20934C00F898D2 /* parseVersions.h in Headers */,
 				FA0B7AC61A958EA3000E1D17 /* types.h in Headers */,
 				FA0B7AC61A958EA3000E1D17 /* types.h in Headers */,
 				FA0B7DBD1A95902C000E1D17 /* Joystick.h in Headers */,
 				FA0B7DBD1A95902C000E1D17 /* Joystick.h in Headers */,
-				FA0B7E901A95902C000E1D17 /* GmeDecoder.h in Headers */,
 				FA0B7D0E1A95902C000E1D17 /* wrap_Filesystem.h in Headers */,
 				FA0B7D0E1A95902C000E1D17 /* wrap_Filesystem.h in Headers */,
 				FA0B7EE41A95902D000E1D17 /* Window.h in Headers */,
 				FA0B7EE41A95902D000E1D17 /* Window.h in Headers */,
 				FA0B7CFC1A95902C000E1D17 /* Filesystem.h in Headers */,
 				FA0B7CFC1A95902C000E1D17 /* Filesystem.h in Headers */,
@@ -4370,6 +4369,7 @@
 				217DFC121D9F6D490055D849 /* usocket.h in Headers */,
 				217DFC121D9F6D490055D849 /* usocket.h in Headers */,
 				217DFC081D9F6D490055D849 /* udp.h in Headers */,
 				217DFC081D9F6D490055D849 /* udp.h in Headers */,
 				FA0B7DCF1A95902C000E1D17 /* wrap_Keyboard.h in Headers */,
 				FA0B7DCF1A95902C000E1D17 /* wrap_Keyboard.h in Headers */,
+				FA6BDF90281219E900240F2A /* DataStream.h in Headers */,
 				FAF6C9DB23C2DE2900D7B5BC /* SpvBuilder.h in Headers */,
 				FAF6C9DB23C2DE2900D7B5BC /* SpvBuilder.h in Headers */,
 				FA0B7EA21A95902C000E1D17 /* Sound.h in Headers */,
 				FA0B7EA21A95902C000E1D17 /* Sound.h in Headers */,
 				FA0B7B331A958EA3000E1D17 /* wuff_config.h in Headers */,
 				FA0B7B331A958EA3000E1D17 /* wuff_config.h in Headers */,
@@ -4517,7 +4517,6 @@
 				FADF54081E3D78F700012CC0 /* Video.cpp in Sources */,
 				FADF54081E3D78F700012CC0 /* Video.cpp in Sources */,
 				FA9D8DD81DEF8411002CD881 /* Data.cpp in Sources */,
 				FA9D8DD81DEF8411002CD881 /* Data.cpp in Sources */,
 				FABDA9992552448300B5C523 /* b2_contact_manager.cpp in Sources */,
 				FABDA9992552448300B5C523 /* b2_contact_manager.cpp in Sources */,
-				FA0B7E8F1A95902C000E1D17 /* GmeDecoder.cpp in Sources */,
 				FADF542B1E3DAADA00012CC0 /* wrap_Mesh.cpp in Sources */,
 				FADF542B1E3DAADA00012CC0 /* wrap_Mesh.cpp in Sources */,
 				FA0B7CD71A95902C000E1D17 /* Audio.cpp in Sources */,
 				FA0B7CD71A95902C000E1D17 /* Audio.cpp in Sources */,
 				FA0B7AC01A958EA3000E1D17 /* host.c in Sources */,
 				FA0B7AC01A958EA3000E1D17 /* host.c in Sources */,
@@ -4702,6 +4701,7 @@
 				FAE64A922071364B00BC7981 /* physfs_platform_winrt.cpp in Sources */,
 				FAE64A922071364B00BC7981 /* physfs_platform_winrt.cpp in Sources */,
 				FABDA97A2552448200B5C523 /* b2_joint.cpp in Sources */,
 				FABDA97A2552448200B5C523 /* b2_joint.cpp in Sources */,
 				FA4F2C0F1DE936FE00CA37D7 /* timeout.c in Sources */,
 				FA4F2C0F1DE936FE00CA37D7 /* timeout.c in Sources */,
+				FA6BDF8F281219E900240F2A /* DataStream.cpp in Sources */,
 				FA59A2D31C06481400328DBA /* ParticleSystem.cpp in Sources */,
 				FA59A2D31C06481400328DBA /* ParticleSystem.cpp in Sources */,
 				FA0B7E131A95902C000E1D17 /* GearJoint.cpp in Sources */,
 				FA0B7E131A95902C000E1D17 /* GearJoint.cpp in Sources */,
 				FABDA99B2552448300B5C523 /* b2_polygon_contact.cpp in Sources */,
 				FABDA99B2552448300B5C523 /* b2_polygon_contact.cpp in Sources */,
@@ -4940,7 +4940,6 @@
 				FABDA9982552448300B5C523 /* b2_contact_manager.cpp in Sources */,
 				FABDA9982552448300B5C523 /* b2_contact_manager.cpp in Sources */,
 				FADF542A1E3DAADA00012CC0 /* wrap_Mesh.cpp in Sources */,
 				FADF542A1E3DAADA00012CC0 /* wrap_Mesh.cpp in Sources */,
 				FA0B7D2B1A95902C000E1D17 /* wrap_Rasterizer.cpp in Sources */,
 				FA0B7D2B1A95902C000E1D17 /* wrap_Rasterizer.cpp in Sources */,
-				FA0B7E8E1A95902C000E1D17 /* GmeDecoder.cpp in Sources */,
 				FA0B7CD61A95902C000E1D17 /* Audio.cpp in Sources */,
 				FA0B7CD61A95902C000E1D17 /* Audio.cpp in Sources */,
 				FA3C5E421F8C368C0003C579 /* ShaderStage.cpp in Sources */,
 				FA3C5E421F8C368C0003C579 /* ShaderStage.cpp in Sources */,
 				FA0B7EAF1A95902C000E1D17 /* System.cpp in Sources */,
 				FA0B7EAF1A95902C000E1D17 /* System.cpp in Sources */,
@@ -5122,6 +5121,7 @@
 				FABDA9932552448300B5C523 /* b2_polygon_circle_contact.cpp in Sources */,
 				FABDA9932552448300B5C523 /* b2_polygon_circle_contact.cpp in Sources */,
 				FAC7CD851FE35E95006A60C7 /* physfs_unicode.c in Sources */,
 				FAC7CD851FE35E95006A60C7 /* physfs_unicode.c in Sources */,
 				FA6A2B7A1F60B8250074C308 /* wrap_ByteData.cpp in Sources */,
 				FA6A2B7A1F60B8250074C308 /* wrap_ByteData.cpp in Sources */,
+				FA6BDF8E281219E900240F2A /* DataStream.cpp in Sources */,
 				FAF140551E20934C00F898D2 /* Link.cpp in Sources */,
 				FAF140551E20934C00F898D2 /* Link.cpp in Sources */,
 				FABDA9792552448200B5C523 /* b2_joint.cpp in Sources */,
 				FABDA9792552448200B5C523 /* b2_joint.cpp in Sources */,
 				FAF140841E20934C00F898D2 /* ParseHelper.cpp in Sources */,
 				FAF140841E20934C00F898D2 /* ParseHelper.cpp in Sources */,
@@ -5399,7 +5399,6 @@
 					"\"$(SRCROOT)/../../src/libraries/enet/libenet/include\"",
 					"\"$(SRCROOT)/../../src/libraries/enet/libenet/include\"",
 				);
 				);
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
-				LD_RUNPATH_SEARCH_PATHS = "@rpath";
 				LIBRARY_SEARCH_PATHS = "";
 				LIBRARY_SEARCH_PATHS = "";
 				MACOSX_DEPLOYMENT_TARGET = 10.9;
 				MACOSX_DEPLOYMENT_TARGET = 10.9;
 				ONLY_ACTIVE_ARCH = NO;
 				ONLY_ACTIVE_ARCH = NO;
@@ -5465,7 +5464,6 @@
 					"\"$(SRCROOT)/../../src/libraries/enet/libenet/include\"",
 					"\"$(SRCROOT)/../../src/libraries/enet/libenet/include\"",
 				);
 				);
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
-				LD_RUNPATH_SEARCH_PATHS = "@rpath";
 				LIBRARY_SEARCH_PATHS = "";
 				LIBRARY_SEARCH_PATHS = "";
 				MACOSX_DEPLOYMENT_TARGET = 10.9;
 				MACOSX_DEPLOYMENT_TARGET = 10.9;
 				ONLY_ACTIVE_ARCH = YES;
 				ONLY_ACTIVE_ARCH = YES;
@@ -5603,7 +5601,6 @@
 					"\"$(SRCROOT)/../../src/libraries/enet/libenet/include\"",
 					"\"$(SRCROOT)/../../src/libraries/enet/libenet/include\"",
 				);
 				);
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
-				LD_RUNPATH_SEARCH_PATHS = "@rpath";
 				LIBRARY_SEARCH_PATHS = "";
 				LIBRARY_SEARCH_PATHS = "";
 				LLVM_LTO = YES;
 				LLVM_LTO = YES;
 				MACOSX_DEPLOYMENT_TARGET = 10.9;
 				MACOSX_DEPLOYMENT_TARGET = 10.9;
@@ -5637,6 +5634,7 @@
 				);
 				);
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
+				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../";
 				LIBRARY_SEARCH_PATHS = (
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/ios/libraries/freetype",
 					"$(PROJECT_DIR)/ios/libraries/freetype",
@@ -5675,6 +5673,7 @@
 				);
 				);
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
+				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../";
 				LIBRARY_SEARCH_PATHS = (
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/ios/libraries/freetype",
 					"$(PROJECT_DIR)/ios/libraries/freetype",
@@ -5714,6 +5713,7 @@
 				);
 				);
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				INFOPLIST_FILE = "macosx/liblove-macosx.plist";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
 				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
+				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../../../";
 				LIBRARY_SEARCH_PATHS = (
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/ios/libraries/freetype",
 					"$(PROJECT_DIR)/ios/libraries/freetype",

+ 50 - 1
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
  * This software is provided 'as-is', without any express or implied
  * warranty.  In no event will the authors be held liable for any damages
  * warranty.  In no event will the authors be held liable for any damages
@@ -20,10 +20,59 @@
 
 
 // LOVE
 // LOVE
 #include "Stream.h"
 #include "Stream.h"
+#include "Data.h"
+#include "data/ByteData.h"
+#include "Exception.h"
 
 
 namespace love
 namespace love
 {
 {
 
 
 love::Type Stream::type("Stream", &Object::type);
 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<data::ByteData> 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
 } // love

+ 64 - 13
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
  * This software is provided 'as-is', without any express or implied
  * warranty.  In no event will the authors be held liable for any damages
  * warranty.  In no event will the authors be held liable for any damages
@@ -24,41 +24,92 @@
 // LOVE
 // LOVE
 #include <stddef.h>
 #include <stddef.h>
 #include "Object.h"
 #include "Object.h"
+#include "int.h"
 
 
 namespace love
 namespace love
 {
 {
 
 
+class Data;
+
 class Stream : public Object
 class Stream : public Object
 {
 {
 public:
 public:
+
+	enum SeekOrigin
+	{
+		SEEKORIGIN_BEGIN,
+		SEEKORIGIN_CURRENT,
+		SEEKORIGIN_END,
+		SEEKORIGIN_MAX_ENUM
+	};
+
 	static love::Type type;
 	static love::Type type;
 
 
 	virtual ~Stream() {}
 	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;
+
+	/**
+	 * Gets whether read() is supported for this Stream.
+	 **/
+	virtual bool isReadable() const = 0;
+
+	/**
+	 * Gets whether write() is supported for this Stream.
+	 **/
+	virtual bool isWritable() const = 0;
+
+	/**
+	 * Gets whether seek(), tell(), and getSize() are supported for this Stream.
+	 **/
+	virtual bool isSeekable() const = 0;
 
 
 	/**
 	/**
-	 * A callback, gets called when some Stream consumer exhausts the data
+	 * Reads data into the destination buffer, and returns the number of bytes
+	 * actually read.
 	 **/
 	 **/
-	virtual void fillBackBuffer() {}
+	virtual int64 read(void *dst, int64 size) = 0;
 
 
 	/**
 	/**
-	 * Get the front buffer, Streams are supposed to be (at least) double-buffered
+	 * Reads data into a new Data object.
 	 **/
 	 **/
-	virtual const void *getFrontBuffer() const = 0;
+	virtual Data *read(int64 size);
 
 
 	/**
 	/**
-	 * Get the size of any (and in particular the front) buffer
+	 * Writes data from the source buffer into the Stream.
 	 **/
 	 **/
-	virtual size_t getSize() const = 0;
+	virtual bool write(const void *src, int64 size) = 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
+	 * Writes data from the source Data object into the Stream.
 	 **/
 	 **/
-	virtual bool swapBuffers() = 0;
+	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
 }; // Stream
 
 
 } // love
 } // love

+ 1 - 1
src/common/android.cpp

@@ -35,7 +35,7 @@
 #include <sys/types.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <unistd.h>
 
 
-#include "physfs.h"
+#include "libraries/physfs/physfs.h"
 
 
 namespace love
 namespace love
 {
 {

+ 2 - 0
src/common/config.h

@@ -38,6 +38,8 @@
 #endif
 #endif
 #if defined(__ANDROID__)
 #if defined(__ANDROID__)
 #	define LOVE_ANDROID 1
 #	define LOVE_ANDROID 1
+// Needed for ENet
+#	define HAS_SOCKLEN_T 1
 #endif
 #endif
 #if defined(__APPLE__)
 #if defined(__APPLE__)
 #	include <TargetConditionals.h>
 #	include <TargetConditionals.h>

+ 13 - 12
src/common/deprecation.cpp

@@ -24,6 +24,7 @@
 
 
 #include <atomic>
 #include <atomic>
 #include <map>
 #include <map>
+#include <sstream>
 
 
 namespace love
 namespace love
 {
 {
@@ -97,32 +98,32 @@ bool isDeprecationOutputEnabled()
 
 
 std::string getDeprecationNotice(const DeprecationInfo &info, bool usewhere)
 std::string getDeprecationNotice(const DeprecationInfo &info, bool usewhere)
 {
 {
-	std::string notice;
+	std::stringstream notice;
 
 
 	if (usewhere)
 	if (usewhere)
-		notice += info.where;
+		notice << info.where;
 
 
-	notice += "Using deprecated ";
+	notice << "Using deprecated ";
 
 
 	if (info.apiType == API_FUNCTION)
 	if (info.apiType == API_FUNCTION)
-		notice += "function ";
+		notice << "function ";
 	else if (info.apiType == API_METHOD)
 	else if (info.apiType == API_METHOD)
-		notice += "method ";
+		notice << "method ";
 	else if (info.apiType == API_CALLBACK)
 	else if (info.apiType == API_CALLBACK)
-		notice += "callback ";
+		notice << "callback ";
 	else if (info.apiType == API_FIELD)
 	else if (info.apiType == API_FIELD)
-		notice += "field ";
+		notice << "field ";
 	else if (info.apiType == API_CONSTANT)
 	else if (info.apiType == API_CONSTANT)
-		notice += "constant ";
+		notice << "constant ";
 
 
-	notice += info.name;
+	notice << info.name;
 
 
 	if (info.type == DEPRECATED_REPLACED && !info.replacement.empty())
 	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())
 	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()
 GetDeprecated::GetDeprecated()

+ 12 - 0
src/common/pixelformat.cpp

@@ -240,11 +240,23 @@ const PixelFormatInfo &getPixelFormatInfo(PixelFormat format)
 	return formatInfo[format];
 	return formatInfo[format];
 }
 }
 
 
+const char *getPixelFormatName(PixelFormat format)
+{
+	const char *name = "unknown";
+	getConstant(format, name);
+	return name;
+}
+
 bool isPixelFormatCompressed(PixelFormat format)
 bool isPixelFormatCompressed(PixelFormat format)
 {
 {
 	return formatInfo[format].compressed;
 	return formatInfo[format].compressed;
 }
 }
 
 
+bool isPixelFormatColor(PixelFormat format)
+{
+	return formatInfo[format].color;
+}
+
 bool isPixelFormatDepthStencil(PixelFormat format)
 bool isPixelFormatDepthStencil(PixelFormat format)
 {
 {
 	const PixelFormatInfo &info = formatInfo[format];
 	const PixelFormatInfo &info = formatInfo[format];

+ 10 - 0
src/common/pixelformat.h

@@ -157,11 +157,21 @@ bool getConstant(const char *in, PixelFormat &out);
 
 
 const PixelFormatInfo &getPixelFormatInfo(PixelFormat format);
 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.
  * Gets whether the specified pixel format is a compressed type.
  **/
  **/
 bool isPixelFormatCompressed(PixelFormat format);
 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.
  * Gets whether the specified pixel format is a depth or stencil type.
  **/
  **/

+ 5 - 1
src/libraries/luahttps/src/lua/main.cpp

@@ -1,4 +1,8 @@
-#include <lua.hpp>
+extern "C"
+{
+#include <lua.h>
+#include <lauxlib.h>
+}
 
 
 #include "../common/HTTPS.h"
 #include "../common/HTTPS.h"
 #include "../common/config.h"
 #include "../common/config.h"

+ 18 - 20
src/modules/audio/openal/Audio.cpp

@@ -100,11 +100,6 @@ Audio::Audio()
 	, poolThread(nullptr)
 	, poolThread(nullptr)
 	, distanceModel(DISTANCE_INVERSE_CLAMPED)
 	, 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
 	// Before opening new device, check if recording
 	// is requested.
 	// is requested.
 	if (getRequestRecordingPermission())
 	if (getRequestRecordingPermission())
@@ -114,29 +109,32 @@ Audio::Audio()
 			requestRecordingPermission();
 			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
 #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
 #else
-	ALint *attribs = nullptr;
+		ALint *attribs = nullptr;
 #endif
 #endif
 
 
-	context = alcCreateContext(device, attribs);
-
-	if (context == nullptr)
-		throw love::Exception("Could not create context.");
+		context = alcCreateContext(device, attribs);
 
 
-	if (!alcMakeContextCurrent(context) || alcGetError(device) != ALC_NO_ERROR)
-		throw love::Exception("Could not make context current.");
+		if (context == nullptr)
+			throw love::Exception("Could not create context.");
 
 
-#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
 #ifdef ALC_EXT_EFX
 	initializeEFX();
 	initializeEFX();

+ 27 - 14
src/modules/audio/wrap_Audio.cpp

@@ -20,6 +20,7 @@
 
 
 // LOVE
 // LOVE
 #include "wrap_Audio.h"
 #include "wrap_Audio.h"
+#include "filesystem/wrap_Filesystem.h"
 
 
 #include "openal/Audio.h"
 #include "openal/Audio.h"
 #include "null/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.");
 			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))
 	if (stype == Source::TYPE_STATIC && luax_istype(L, 1, love::sound::Decoder::type))
 		luax_convobj(L, 1, "sound", "newSoundData");
 		luax_convobj(L, 1, "sound", "newSoundData");
@@ -84,20 +100,17 @@ int w_newSource(lua_State *L)
 
 
 int w_newQueueableSource(lua_State *L)
 int w_newQueueableSource(lua_State *L)
 {
 {
-	Source *t = nullptr;
+	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);
 
 
-	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));
-	});
+	Source *t = nullptr;
+	luax_catchexcept(L, [&]() { t = instance()->newSource(samplerate, bitdepth, channels, buffers); });
 
 
-	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<Source*> readSourceList(lua_State *L, int n)
 static std::vector<Source*> readSourceList(lua_State *L, int n)

+ 3 - 2
src/modules/data/ByteData.cpp

@@ -31,11 +31,12 @@ namespace data
 
 
 love::Type ByteData::type("ByteData", &Data::type);
 love::Type ByteData::type("ByteData", &Data::type);
 
 
-ByteData::ByteData(size_t size)
+ByteData::ByteData(size_t size, bool clear)
 	: size(size)
 	: size(size)
 {
 {
 	create();
 	create();
-	memset(data, 0, size);
+	if (clear)
+		memset(data, 0, size);
 }
 }
 
 
 ByteData::ByteData(const void *d, size_t size)
 ByteData::ByteData(const void *d, size_t size)

+ 1 - 1
src/modules/data/ByteData.h

@@ -35,7 +35,7 @@ public:
 
 
 	static love::Type type;
 	static love::Type type;
 
 
-	ByteData(size_t size);
+	ByteData(size_t size, bool clear = true);
 	ByteData(const void *d, size_t size);
 	ByteData(const void *d, size_t size);
 	ByteData(void *d, size_t size, bool own);
 	ByteData(void *d, size_t size, bool own);
 	ByteData(const ByteData &d);
 	ByteData(const ByteData &d);

+ 139 - 0
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 <algorithm>
+
+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<int64>(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<int64>(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

+ 33 - 37
src/modules/sound/lullaby/GmeDecoder.h → src/modules/data/DataStream.h

@@ -18,56 +18,52 @@
  * 3. This notice may not be removed or altered from any source distribution.
  * 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 <Game_Music_Emu/gme.h>
-#else
-#include <gme.h>
-#endif
+#include "common/Stream.h"
 
 
 namespace love
 namespace love
 {
 {
-namespace sound
-{
-namespace lullaby
+namespace data
 {
 {
 
 
-class GmeDecoder : public Decoder
+class DataStream : public love::Stream
 {
 {
 public:
 public:
 
 
-	GmeDecoder(Data *data, int bufferSize);
-	virtual ~GmeDecoder();
+	static love::Type type;
+
+	DataStream(Data *data);
+	virtual ~DataStream();
+
+	// Implements Stream.
+	DataStream *clone() override;
+
+	bool isReadable() const override;
+	bool isWritable() const override;
+	bool isSeekable() const override;
 
 
-	static bool accepts(const std::string &ext);
+	int64 read(void* data, int64 size) override;
+	bool write(const void* data, int64 size) override;
 
 
-	love::sound::Decoder *clone();
-	int decode();
-	bool seek(double s);
-	bool rewind();
-	bool isSeekable();
-	int getChannelCount() const;
-	int getBitDepth() const;
-	double getDuration();
+	bool flush() override;
+
+	int64 getSize() override;
+
+	bool seek(int64 pos, SeekOrigin origin = SEEKORIGIN_BEGIN) override;
+	int64 tell() override;
 
 
 private:
 private:
-	Music_Emu *emu;
-	int num_tracks;
-	int cur_track;
-}; // Decoder
 
 
-} // lullaby
-} // sound
-} // love
+	DataStream(const DataStream &other);
 
 
-#endif // LOVE_SUPPORT_GME
+	StrongRef<Data> data;
+	const uint8 *memory;
+	uint8 *writableMemory;
+	size_t offset;
+	size_t size;
 
 
-#endif // LOVE_SOUND_LULLABY_GME_DECODER_H
+}; // DataStream
+
+} // data
+} // love

+ 14 - 1
src/modules/event/sdl/Event.cpp

@@ -35,6 +35,8 @@
 
 
 #include <cmath>
 #include <cmath>
 
 
+#include <SDL_version.h>
+
 namespace love
 namespace love
 {
 {
 namespace event
 namespace event
@@ -418,7 +420,7 @@ Message *Event::convert(const SDL_Event &e)
 			}
 			}
 			else
 			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);
 				vargs.emplace_back(&love::filesystem::NativeFile::type, file);
 				msg = new Message("filedropped", vargs);
 				msg = new Message("filedropped", vargs);
 				file->release();
 				file->release();
@@ -426,6 +428,12 @@ Message *Event::convert(const SDL_Event &e)
 		}
 		}
 		SDL_free(e.drop.file);
 		SDL_free(e.drop.file);
 		break;
 		break;
+	case SDL_DROPBEGIN:
+		msg = new Message("dropbegan");
+		break;
+	case SDL_DROPCOMPLETE:
+		msg = new Message("dropcompleted");
+		break;
 	case SDL_QUIT:
 	case SDL_QUIT:
 	case SDL_APP_TERMINATING:
 	case SDL_APP_TERMINATING:
 		msg = new Message("quit");
 		msg = new Message("quit");
@@ -433,6 +441,11 @@ Message *Event::convert(const SDL_Event &e)
 	case SDL_APP_LOWMEMORY:
 	case SDL_APP_LOWMEMORY:
 		msg = new Message("lowmemory");
 		msg = new Message("lowmemory");
 		break;
 		break;
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+	case SDL_LOCALECHANGED:
+		msg = new Message("localechanged");
+		break;
+#endif
 	default:
 	default:
 		break;
 		break;
 	}
 	}

+ 20 - 53
src/modules/filesystem/File.cpp

@@ -25,12 +25,17 @@ namespace love
 namespace filesystem
 namespace filesystem
 {
 {
 
 
-love::Type File::type("File", &Object::type);
+love::Type File::type("File", &Stream::type);
 
 
 File::~File()
 File::~File()
 {
 {
 }
 }
 
 
+FileData *File::read()
+{
+	return read(getSize());
+}
+
 FileData *File::read(int64 size)
 FileData *File::read(int64 size)
 {
 {
 	bool isopen = isOpen();
 	bool isopen = isOpen();
@@ -40,7 +45,6 @@ FileData *File::read(int64 size)
 
 
 	int64 max = getSize();
 	int64 max = getSize();
 	int64 cur = tell();
 	int64 cur = tell();
-	size = (size == ALL) ? max : size;
 
 
 	if (size < 0)
 	if (size < 0)
 		throw love::Exception("Invalid read size.");
 		throw love::Exception("Invalid read size.");
@@ -54,7 +58,7 @@ FileData *File::read(int64 size)
 	if (cur + size > max)
 	if (cur + size > max)
 		size = max - cur;
 		size = max - cur;
 
 
-	FileData *fileData = new FileData(size, getFilename());
+	StrongRef<FileData> fileData(new FileData(size, getFilename()), Acquire::NORETAIN);
 	int64 bytesRead = read(fileData->getData(), size);
 	int64 bytesRead = read(fileData->getData(), size);
 
 
 	if (bytesRead < 0 || (bytesRead == 0 && bytesRead != size))
 	if (bytesRead < 0 || (bytesRead == 0 && bytesRead != size))
@@ -65,23 +69,18 @@ FileData *File::read(int64 size)
 
 
 	if (bytesRead < size)
 	if (bytesRead < size)
 	{
 	{
-		FileData *tmpFileData = new FileData(bytesRead, getFilename());
+		StrongRef<FileData> tmpFileData(new FileData(bytesRead, getFilename()), Acquire::NORETAIN);
 		memcpy(tmpFileData->getData(), fileData->getData(), (size_t) bytesRead);
 		memcpy(tmpFileData->getData(), fileData->getData(), (size_t) bytesRead);
-		fileData->release();
 		fileData = tmpFileData;
 		fileData = tmpFileData;
 	}
 	}
 
 
 	if (!isopen)
 	if (!isopen)
 		close();
 		close();
 
 
+	fileData->retain();
 	return fileData;
 	return fileData;
 }
 }
 
 
-bool File::write(const Data *data, int64 size)
-{
-	return write(data->getData(), (size == ALL) ? data->getSize() : size);
-}
-
 std::string File::getExtension() const
 std::string File::getExtension() const
 {
 {
 	const std::string &filename = getFilename();
 	const std::string &filename = getFilename();
@@ -93,54 +92,22 @@ std::string File::getExtension() const
 		return std::string();
 		return std::string();
 }
 }
 
 
-bool File::getConstant(const char *in, Mode &out)
-{
-	return modes.find(in, out);
-}
-
-bool File::getConstant(Mode in, const char *&out)
-{
-	return modes.find(in, out);
-}
-
-std::vector<std::string> File::getConstants(Mode)
+STRINGMAP_CLASS_BEGIN(File, File::Mode, File::MODE_MAX_ENUM, mode)
 {
 {
-	return modes.getNames();
+	{ "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(const char *in, BufferMode &out)
+STRINGMAP_CLASS_BEGIN(File, File::BufferMode, File::BUFFER_MAX_ENUM, bufferMode)
 {
 {
-	return bufferModes.find(in, out);
+	{ "none", File::BUFFER_NONE },
+	{ "line", File::BUFFER_LINE },
+	{ "full", File::BUFFER_FULL },
 }
 }
-
-bool File::getConstant(BufferMode in, const char *&out)
-{
-	return bufferModes.find(in, out);
-}
-
-std::vector<std::string> File::getConstants(BufferMode)
-{
-	return bufferModes.getNames();
-}
-
-StringMap<File::Mode, File::MODE_MAX_ENUM>::Entry File::modeEntries[] =
-{
-	{ "c", MODE_CLOSED },
-	{ "r", MODE_READ   },
-	{ "w", MODE_WRITE  },
-	{ "a", MODE_APPEND },
-};
-
-StringMap<File::Mode, File::MODE_MAX_ENUM> File::modes(File::modeEntries, sizeof(File::modeEntries));
-
-StringMap<File::BufferMode, File::BUFFER_MAX_ENUM>::Entry File::bufferModeEntries[] =
-{
-	{ "none", BUFFER_NONE },
-	{ "line", BUFFER_LINE },
-	{ "full", BUFFER_FULL },
-};
-
-StringMap<File::BufferMode, File::BUFFER_MAX_ENUM> File::bufferModes(File::bufferModeEntries, sizeof(File::bufferModeEntries));
+STRINGMAP_CLASS_END(File, File::BufferMode, File::BUFFER_MAX_ENUM, bufferMode)
 
 
 } // filesystem
 } // filesystem
 } // love
 } // love

+ 15 - 78
src/modules/filesystem/File.h

@@ -27,6 +27,7 @@
 // LOVE
 // LOVE
 #include "common/Data.h"
 #include "common/Data.h"
 #include "common/Object.h"
 #include "common/Object.h"
+#include "common/Stream.h"
 #include "common/StringMap.h"
 #include "common/StringMap.h"
 #include "common/int.h"
 #include "common/int.h"
 #include "FileData.h"
 #include "FileData.h"
@@ -40,7 +41,7 @@ namespace filesystem
  * A File interface, providing generic means of reading from and
  * A File interface, providing generic means of reading from and
  * writing to files.
  * writing to files.
  **/
  **/
-class File : public Object
+class File : public Stream
 {
 {
 public:
 public:
 
 
@@ -66,16 +67,19 @@ public:
 		BUFFER_MAX_ENUM
 		BUFFER_MAX_ENUM
 	};
 	};
 
 
-	/**
-	 * Used to indicate ALL data in a file.
-	 **/
-	static const int64 ALL = -1;
-
 	/**
 	/**
 	 * Destructor.
 	 * Destructor.
 	 **/
 	 **/
 	virtual ~File();
 	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.
 	 * Opens the file in a certain mode.
 	 *
 	 *
@@ -96,53 +100,14 @@ public:
 	 **/
 	 **/
 	virtual bool isOpen() const = 0;
 	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.
 	 * Reads data from the file and allocates a Data object.
 	 *
 	 *
-	 * @param size The number of bytes to attempt reading, or -1 for EOF.
-	 * @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.
 	 * @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.
+	 * @return A newly allocated Data object.
 	 **/
 	 **/
-	virtual bool flush() = 0;
+	FileData *read(int64 size) override;
+	FileData *read();
 
 
 	/**
 	/**
 	 * Checks whether we are currently at end-of-file.
 	 * Checks whether we are currently at end-of-file.
@@ -151,21 +116,6 @@ public:
 	 **/
 	 **/
 	virtual bool isEOF() = 0;
 	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
 	 * 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
 	 * 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;
 	virtual std::string getExtension() const;
 
 
-	static bool getConstant(const char *in, Mode &out);
-	static bool getConstant(Mode in, const char *&out);
-	static std::vector<std::string> getConstants(Mode);
-
-	static bool getConstant(const char *in, BufferMode &out);
-	static bool getConstant(BufferMode in, const char *&out);
-	static std::vector<std::string> getConstants(BufferMode);
-
-private:
-
-	static StringMap<Mode, MODE_MAX_ENUM>::Entry modeEntries[];
-	static StringMap<Mode, MODE_MAX_ENUM> modes;
-
-	static StringMap<BufferMode, BUFFER_MAX_ENUM>::Entry bufferModeEntries[];
-	static StringMap<BufferMode, BUFFER_MAX_ENUM> bufferModes;
+	STRINGMAP_CLASS_DECLARE(Mode);
+	STRINGMAP_CLASS_DECLARE(BufferMode);
 
 
 }; // File
 }; // File
 
 

+ 9 - 3
src/modules/filesystem/Filesystem.h

@@ -165,9 +165,9 @@ public:
 	virtual bool unmountFullPath(const char *fullpath) = 0;
 	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.
 	 * Creates a new FileData object. Data will be copied.
@@ -216,6 +216,11 @@ public:
 	 **/
 	 **/
 	virtual std::string getRealDirectory(const char *filename) const = 0;
 	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
 	 * Gets information about the item at the specified filepath. Returns false
 	 * if nothing exists at the path.
 	 * if nothing exists at the path.
@@ -239,7 +244,8 @@ public:
 	 * @param filename The name of the file to read from.
 	 * @param filename The name of the file to read from.
 	 * @param size The size in bytes of the data to read.
 	 * @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.
 	 * Write data to a file.

+ 32 - 4
src/modules/filesystem/NativeFile.cpp

@@ -39,13 +39,26 @@ namespace filesystem
 
 
 love::Type NativeFile::type("NativeFile", &File::type);
 love::Type NativeFile::type("NativeFile", &File::type);
 
 
-NativeFile::NativeFile(const std::string &filename)
+NativeFile::NativeFile(const std::string &filename, Mode mode)
 	: filename(filename)
 	: filename(filename)
 	, file(nullptr)
 	, file(nullptr)
 	, mode(MODE_CLOSED)
 	, mode(MODE_CLOSED)
 	, bufferMode(BUFFER_NONE)
 	, bufferMode(BUFFER_NONE)
 	, bufferSize(0)
 	, 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()
 NativeFile::~NativeFile()
@@ -54,10 +67,18 @@ NativeFile::~NativeFile()
 		close();
 		close();
 }
 }
 
 
+NativeFile *NativeFile::clone()
+{
+	return new NativeFile(*this);
+}
+
 bool NativeFile::open(Mode newmode)
 bool NativeFile::open(Mode newmode)
 {
 {
 	if (newmode == MODE_CLOSED)
 	if (newmode == MODE_CLOSED)
+	{
+		close();
 		return true;
 		return true;
+	}
 
 
 	// File already open?
 	// File already open?
 	if (file != nullptr)
 	if (file != nullptr)
@@ -197,15 +218,22 @@ int64 NativeFile::tell()
 #endif
 #endif
 }
 }
 
 
-bool NativeFile::seek(uint64 pos)
+bool NativeFile::seek(int64 pos, SeekOrigin origin)
 {
 {
 	if (file == nullptr)
 	if (file == nullptr)
 		return false;
 		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
 #ifdef LOVE_WINDOWS
-	return _fseeki64(file, (int64) pos, SEEK_SET) == 0;
+	return _fseeki64(file, pos, forigin) == 0;
 #else
 #else
-	return fseeko(file, (off_t) pos, SEEK_SET) == 0;
+	return fseeko(file, (off_t) pos, forigin) == 0;
 #endif
 #endif
 }
 }
 
 

+ 12 - 7
src/modules/filesystem/NativeFile.h

@@ -39,22 +39,25 @@ public:
 
 
 	static love::Type type;
 	static love::Type type;
 
 
-	NativeFile(const std::string &filename);
+	NativeFile(const std::string &filename, Mode mode);
 	virtual ~NativeFile();
 	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.
 	// Implements File.
 	using File::read;
 	using File::read;
 	using File::write;
 	using File::write;
 	bool open(Mode mode) override;
 	bool open(Mode mode) override;
 	bool close() override;
 	bool close() override;
 	bool isOpen() const 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;
 	bool isEOF() override;
-	int64 tell() override;
-	bool seek(uint64 pos) override;
 	bool setBuffer(BufferMode bufmode, int64 size) override;
 	bool setBuffer(BufferMode bufmode, int64 size) override;
 	BufferMode getBuffer(int64 &size) const override;
 	BufferMode getBuffer(int64 &size) const override;
 	Mode getMode() const override;
 	Mode getMode() const override;
@@ -62,6 +65,8 @@ public:
 
 
 private:
 private:
 
 
+	NativeFile(const NativeFile &other);
+
 	static const char *getModeString(Mode mode);
 	static const char *getModeString(Mode mode);
 
 
 	std::string filename;
 	std::string filename;

+ 34 - 6
src/modules/filesystem/physfs/File.cpp

@@ -41,13 +41,26 @@ static bool setupWriteDirectory()
 	return fs != nullptr && fs->setupWriteDirectory();
 	return fs != nullptr && fs->setupWriteDirectory();
 }
 }
 
 
-File::File(const std::string &filename)
+File::File(const std::string &filename, Mode mode)
 	: filename(filename)
 	: filename(filename)
 	, file(nullptr)
 	, file(nullptr)
 	, mode(MODE_CLOSED)
 	, mode(MODE_CLOSED)
 	, bufferMode(BUFFER_NONE)
 	, bufferMode(BUFFER_NONE)
 	, bufferSize(0)
 	, 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()
 File::~File()
@@ -56,10 +69,18 @@ File::~File()
 		close();
 		close();
 }
 }
 
 
+File *File::clone()
+{
+	return new File(*this);
+}
+
 bool File::open(Mode mode)
 bool File::open(Mode mode)
 {
 {
 	if (mode == MODE_CLOSED)
 	if (mode == MODE_CLOSED)
+	{
+		close();
 		return true;
 		return true;
+	}
 
 
 	if (!PHYSFS_isInit())
 	if (!PHYSFS_isInit())
 		throw love::Exception("PhysFS is not initialized.");
 		throw love::Exception("PhysFS is not initialized.");
@@ -151,10 +172,6 @@ int64 File::read(void *dst, int64 size)
 	if (!file || mode != MODE_READ)
 	if (!file || mode != MODE_READ)
 		throw love::Exception("File is not opened for reading.");
 		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)
 	if (size < 0)
 		throw love::Exception("Invalid read size.");
 		throw love::Exception("Invalid read size.");
 
 
@@ -207,8 +224,19 @@ int64 File::tell()
 	return (int64) PHYSFS_tell(file);
 	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;
 	return file != nullptr && PHYSFS_seek(file, (PHYSFS_uint64) pos) != 0;
 }
 }
 
 

+ 12 - 7
src/modules/filesystem/physfs/File.h

@@ -46,23 +46,26 @@ public:
 	 * Constructs an File with the given ilename.
 	 * Constructs an File with the given ilename.
 	 * @param filename The relative filepath of the file to load.
 	 * @param filename The relative filepath of the file to load.
 	 **/
 	 **/
-	File(const std::string &filename);
+	File(const std::string &filename, Mode mode);
 
 
 	virtual ~File();
 	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.
 	// Implements love::filesystem::File.
 	using love::filesystem::File::read;
 	using love::filesystem::File::read;
 	using love::filesystem::File::write;
 	using love::filesystem::File::write;
 	bool open(Mode mode) override;
 	bool open(Mode mode) override;
 	bool close() override;
 	bool close() override;
 	bool isOpen() const 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;
 	bool isEOF() override;
-	int64 tell() override;
-	bool seek(uint64 pos) override;
 	bool setBuffer(BufferMode bufmode, int64 size) override;
 	bool setBuffer(BufferMode bufmode, int64 size) override;
 	BufferMode getBuffer(int64 &size) const override;
 	BufferMode getBuffer(int64 &size) const override;
 	Mode getMode() const override;
 	Mode getMode() const override;
@@ -70,6 +73,8 @@ public:
 
 
 private:
 private:
 
 
+	File(const File &other);
+
 	// filename
 	// filename
 	std::string filename;
 	std::string filename;
 
 

+ 21 - 11
src/modules/filesystem/physfs/Filesystem.cpp

@@ -496,9 +496,9 @@ bool Filesystem::unmount(Data *data)
 	return false;
 	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)
 std::string Filesystem::getFullCommonPath(CommonPath path)
@@ -738,6 +738,14 @@ std::string Filesystem::getRealDirectory(const char *filename) const
 	return std::string(dir);
 	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
 bool Filesystem::getInfo(const char *filepath, Info &info) const
 {
 {
 	if (!PHYSFS_isInit())
 	if (!PHYSFS_isInit())
@@ -793,19 +801,23 @@ bool Filesystem::remove(const char *file)
 
 
 FileData *Filesystem::read(const char *filename, int64 size) const
 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.
 	// close() is called in the File destructor.
 	return file.read(size);
 	return file.read(size);
 }
 }
 
 
-void Filesystem::write(const char *filename, const void *data, int64 size) const
+FileData* Filesystem::read(const char* filename) const
 {
 {
-	File file(filename);
+	File file(filename, File::MODE_READ);
 
 
-	file.open(File::MODE_WRITE);
+	// 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::MODE_WRITE);
 
 
 	// close() is called in the File destructor.
 	// close() is called in the File destructor.
 	if (!file.write(data, size))
 	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
 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.
 	// close() is called in the File destructor.
 	if (!file.write(data, size))
 	if (!file.write(data, size))

+ 4 - 2
src/modules/filesystem/physfs/Filesystem.h

@@ -71,7 +71,7 @@ public:
 	bool unmount(CommonPath path) override;
 	bool unmount(CommonPath path) override;
 	bool unmountFullPath(const char *fullpath) 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;
 	std::string getFullCommonPath(CommonPath path) override;
 	const char *getWorkingDirectory() override;
 	const char *getWorkingDirectory() override;
@@ -82,13 +82,15 @@ public:
 
 
 	std::string getRealDirectory(const char *filename) const override;
 	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 getInfo(const char *filepath, Info &info) const override;
 
 
 	bool createDirectory(const char *dir) override;
 	bool createDirectory(const char *dir) override;
 
 
 	bool remove(const char *file) 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 write(const char *filename, const void *data, int64 size) const override;
 	void append(const char *filename, const void *data, int64 size) const override;
 	void append(const char *filename, const void *data, int64 size) const override;
 
 

+ 3 - 1
src/modules/filesystem/wrap_File.cpp

@@ -121,10 +121,12 @@ int w_File_read(lua_State *L)
 		startidx = 3;
 		startidx = 3;
 	}
 	}
 
 
-	int64 size = (int64) luaL_optnumber(L, startidx, (lua_Number) File::ALL);
+	int64 size = (int64) luaL_optnumber(L, startidx, -1);
 
 
 	try
 	try
 	{
 	{
+		if (size < 0)
+			size = file->getSize();
 		d.set(file->read(size), Acquire::NORETAIN);
 		d.set(file->read(size), Acquire::NORETAIN);
 	}
 	}
 	catch (love::Exception &e)
 	catch (love::Exception &e)

+ 72 - 32
src/modules/filesystem/wrap_Filesystem.cpp

@@ -227,34 +227,53 @@ int w_unmountCommonPath(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-int w_newFile(lua_State *L)
+int w_openFile(lua_State *L)
 {
 {
 	const char *filename = luaL_checkstring(L, 1);
 	const char *filename = luaL_checkstring(L, 1);
+	const char *modestr = luaL_checkstring(L, 2);
 
 
-	const char *str = 0;
 	File::Mode mode = File::MODE_CLOSED;
 	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;
+}
+
+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);
 
 
-	if (mode != File::MODE_CLOSED)
+	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);
 	luax_pushtype(L, t);
@@ -268,7 +287,14 @@ File *luax_getfile(lua_State *L, int idx)
 	if (lua_isstring(L, idx))
 	if (lua_isstring(L, idx))
 	{
 	{
 		const char *filename = luaL_checkstring(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
 	else
 	{
 	{
@@ -343,6 +369,11 @@ Data *luax_getdata(lua_State *L, int idx)
 	return data;
 	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)
 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);
 	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;
 	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)
 int w_getInfo(lua_State *L)
 {
 {
 	const char *filepath = luaL_checkstring(L, 1);
 	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);
 	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;
 	FileData *data = nullptr;
 	try
 	try
 	{
 	{
-		data = instance()->read(filename, len);
+		if (len >= 0)
+			data = instance()->read(filename, len);
+		else
+			data = instance()->read(filename);
 	}
 	}
 	catch (love::Exception &e)
 	catch (love::Exception &e)
 	{
 	{
@@ -655,16 +696,8 @@ int w_lines(lua_State *L)
 {
 {
 	if (lua_isstring(L, 1))
 	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);
 		luax_pushtype(L, file);
 		file->release();
 		file->release();
@@ -806,6 +839,9 @@ int loader(lua_State *L)
 {
 {
 	std::string modulename = luax_checkstring(L, 1);
 	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)
 	for (char &c : modulename)
 	{
 	{
 		if (c == '.')
 		if (c == '.')
@@ -954,7 +990,7 @@ static const luaL_Reg functions[] =
 	{ "unmount", w_unmount },
 	{ "unmount", w_unmount },
 	{ "unmountFullPath", w_unmountFullPath },
 	{ "unmountFullPath", w_unmountFullPath },
 	{ "unmountCommonPath", w_unmountCommonPath },
 	{ "unmountCommonPath", w_unmountCommonPath },
-	{ "newFile", w_newFile },
+	{ "openFile", w_openFile },
 	{ "getFullCommonPath", w_getFullCommonPath },
 	{ "getFullCommonPath", w_getFullCommonPath },
 	{ "getWorkingDirectory", w_getWorkingDirectory },
 	{ "getWorkingDirectory", w_getWorkingDirectory },
 	{ "getUserDirectory", w_getUserDirectory },
 	{ "getUserDirectory", w_getUserDirectory },
@@ -971,6 +1007,7 @@ static const luaL_Reg functions[] =
 	{ "getDirectoryItems", w_getDirectoryItems },
 	{ "getDirectoryItems", w_getDirectoryItems },
 	{ "lines", w_lines },
 	{ "lines", w_lines },
 	{ "load", w_load },
 	{ "load", w_load },
+	{ "exists", w_exists },
 	{ "getInfo", w_getInfo },
 	{ "getInfo", w_getInfo },
 	{ "setSymlinksEnabled", w_setSymlinksEnabled },
 	{ "setSymlinksEnabled", w_setSymlinksEnabled },
 	{ "areSymlinksEnabled", w_areSymlinksEnabled },
 	{ "areSymlinksEnabled", w_areSymlinksEnabled },
@@ -980,6 +1017,9 @@ static const luaL_Reg functions[] =
 	{ "getCRequirePath", w_getCRequirePath },
 	{ "getCRequirePath", w_getCRequirePath },
 	{ "setCRequirePath", w_setCRequirePath },
 	{ "setCRequirePath", w_setCRequirePath },
 
 
+	// Deprecated
+	{ "newFile", w_newFile },
+
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };
 
 

+ 2 - 0
src/modules/filesystem/wrap_Filesystem.h

@@ -40,7 +40,9 @@ namespace filesystem
  **/
  **/
 FileData *luax_getfiledata(lua_State *L, int idx);
 FileData *luax_getfiledata(lua_State *L, int idx);
 bool luax_cangetfiledata(lua_State *L, int idx);
 bool luax_cangetfiledata(lua_State *L, int idx);
+
 File *luax_getfile(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);
 Data *luax_getdata(lua_State *L, int idx);
 bool luax_cangetdata(lua_State *L, int idx);
 bool luax_cangetdata(lua_State *L, int idx);

+ 28 - 15
src/modules/graphics/Graphics.cpp

@@ -253,8 +253,10 @@ void Graphics::createQuadIndexBuffer()
 	Buffer::Settings settings(BUFFERUSAGEFLAG_INDEX, BUFFERDATAUSAGE_STATIC);
 	Buffer::Settings settings(BUFFERUSAGEFLAG_INDEX, BUFFERDATAUSAGE_STATIC);
 	quadIndexBuffer = newBuffer(settings, DATAFORMAT_UINT16, nullptr, size, 0);
 	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);
 	quadIndexBuffer->setImmutable(true);
 }
 }
@@ -310,12 +312,18 @@ love::graphics::ParticleSystem *Graphics::newParticleSystem(Texture *texture, in
 	return new ParticleSystem(texture, size);
 	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;
 	ShaderStage *s = nullptr;
 	std::string cachekey;
 	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::HashFunction::Value hashvalue;
 		data::hash(data::HashFunction::FUNCTION_SHA1, source.c_str(), source.size(), 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)
 	if (s == nullptr)
 	{
 	{
 		bool glsles = usesGLSLES();
 		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);
 		s = newShaderStageInternal(stage, cachekey, glsl, glsles);
-		if (!cachekey.empty())
+		if (cache && !cachekey.empty())
 			cachedShaderStages[stage][cachekey] = s;
 			cachedShaderStages[stage][cachekey] = s;
 	}
 	}
 
 
 	return s;
 	return s;
 }
 }
 
 
-Shader *Graphics::newShader(const std::vector<std::string> &stagessource, bool vulkan)
+Shader *Graphics::newShader(const std::vector<std::string> &stagessource, const Shader::CompileOptions &options)
 {
 {
 	StrongRef<ShaderStage> stages[SHADERSTAGE_MAX_ENUM] = {};
 	StrongRef<ShaderStage> stages[SHADERSTAGE_MAX_ENUM] = {};
 
 
@@ -353,7 +361,7 @@ Shader *Graphics::newShader(const std::vector<std::string> &stagessource, bool v
 	for (const std::string &source : stagessource)
 	for (const std::string &source : stagessource)
 	{
 	{
 		Shader::SourceInfo info = Shader::getSourceInfo(source);
 		Shader::SourceInfo info = Shader::getSourceInfo(source);
-		info.vulkan = vulkan;
+		info.vulkan = options.defines.find("vulkan") != options.defines.end();
 		bool isanystage = false;
 		bool isanystage = false;
 
 
 		for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++)
 		for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++)
@@ -364,12 +372,12 @@ Shader *Graphics::newShader(const std::vector<std::string> &stagessource, bool v
 			if (info.stages[i] != Shader::ENTRYPOINT_NONE)
 			if (info.stages[i] != Shader::ENTRYPOINT_NONE)
 			{
 			{
 				isanystage = true;
 				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)
 		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++)
 	for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++)
@@ -379,7 +387,8 @@ Shader *Graphics::newShader(const std::vector<std::string> &stagessource, bool v
 		{
 		{
 			const std::string &source = Shader::getDefaultCode(Shader::STANDARD_DEFAULT, stype);
 			const std::string &source = Shader::getDefaultCode(Shader::STANDARD_DEFAULT, stype);
 			Shader::SourceInfo info = Shader::getSourceInfo(source);
 			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<std::string> &stagessource, bool v
 	return newShaderInternal(stages);
 	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);
 	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?)");
 		throw love::Exception("Could not parse compute shader code (missing 'computemain' function?)");
 
 
 	StrongRef<ShaderStage> stages[SHADERSTAGE_MAX_ENUM];
 	StrongRef<ShaderStage> 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);
 	return newShaderInternal(stages);
 }
 }
 
 
@@ -430,7 +443,7 @@ void Graphics::cleanupCachedShaderStage(ShaderStageType type, const std::string
 	cachedShaderStages[type].erase(hashkey);
 	cachedShaderStages[type].erase(hashkey);
 }
 }
 
 
-bool Graphics::validateShader(bool gles, const std::vector<std::string> &stagessource, std::string &err)
+bool Graphics::validateShader(bool gles, const std::vector<std::string> &stagessource, const Shader::CompileOptions &options, std::string &err)
 {
 {
 	StrongRef<ShaderStage> stages[SHADERSTAGE_MAX_ENUM] = {};
 	StrongRef<ShaderStage> stages[SHADERSTAGE_MAX_ENUM] = {};
 
 
@@ -456,7 +469,7 @@ bool Graphics::validateShader(bool gles, const std::vector<std::string> &stagess
 			if (info.stages[i] != Shader::ENTRYPOINT_NONE)
 			if (info.stages[i] != Shader::ENTRYPOINT_NONE)
 			{
 			{
 				isanystage = true;
 				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);
 				stages[i].set(new ShaderStageForValidation(this, stype, glsl, gles), Acquire::NORETAIN);
 			}
 			}
 		}
 		}

+ 5 - 5
src/modules/graphics/Graphics.h

@@ -449,8 +449,8 @@ public:
 	SpriteBatch *newSpriteBatch(Texture *texture, int size, BufferDataUsage usage);
 	SpriteBatch *newSpriteBatch(Texture *texture, int size, BufferDataUsage usage);
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 
 
-	Shader *newShader(const std::vector<std::string> &stagessource, bool vulkan = false);
-	Shader *newComputeShader(const std::string &source);
+	Shader *newShader(const std::vector<std::string> &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<Buffer::DataDeclaration> &format, const void *data, size_t size, size_t arraylength) = 0;
 	virtual Buffer *newBuffer(const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &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);
 	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<Font::ColoredString> &text = {});
 	Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
 
 
-	bool validateShader(bool gles, const std::vector<std::string> &stages, std::string &err);
+	bool validateShader(bool gles, const std::vector<std::string> &stages, const Shader::CompileOptions &options, std::string &err);
 
 
 	/**
 	/**
 	 * Resets the current color, background color, line style, and so forth.
 	 * 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.
 	 * 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.
 	 * 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 ShaderStage *newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) = 0;
 	virtual Shader *newShaderInternal(StrongRef<ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) = 0;
 	virtual Shader *newShaderInternal(StrongRef<ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) = 0;
 	virtual StreamBuffer *newStreamBuffer(BufferUsage type, size_t size) = 0;
 	virtual StreamBuffer *newStreamBuffer(BufferUsage type, size_t size) = 0;

+ 5 - 3
src/modules/graphics/Shader.cpp

@@ -573,7 +573,7 @@ Shader::SourceInfo Shader::getSourceInfo(const std::string &src)
 	return info;
 	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)
 	if (info.language == Shader::LANGUAGE_MAX_ENUM)
 		throw love::Exception("Invalid shader language");
 		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";
 		ss << "#define LOVE_GAMMA_CORRECT 1\n";
 	if (info.usesMRT)
 	if (info.usesMRT)
 		ss << "#define LOVE_MULTI_RENDER_TARGETS 1\n";
 		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 << glsl::global_syntax;
 	ss << stageinfo.header;
 	ss << stageinfo.header;
 	ss << stageinfo.uniforms;
 	ss << stageinfo.uniforms;

+ 6 - 1
src/modules/graphics/Shader.h

@@ -107,6 +107,11 @@ public:
 		ACCESS_WRITE = (1 << 1),
 		ACCESS_WRITE = (1 << 1),
 	};
 	};
 
 
+	struct CompileOptions
+	{
+		std::map<std::string, std::string> defines;
+	};
+
 	struct SourceInfo
 	struct SourceInfo
 	{
 	{
 		Language language;
 		Language language;
@@ -237,7 +242,7 @@ public:
 	void getLocalThreadgroupSize(int *x, int *y, int *z);
 	void getLocalThreadgroupSize(int *x, int *y, int *z);
 
 
 	static SourceInfo getSourceInfo(const std::string &src);
 	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<ShaderStage> stages[], std::string &err);
 	static bool validate(StrongRef<ShaderStage> stages[], std::string &err);
 
 

+ 43 - 8
src/modules/graphics/Texture.cpp

@@ -232,8 +232,13 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices)
 	if (mipmapsMode != MIPMAPS_NONE)
 	if (mipmapsMode != MIPMAPS_NONE)
 		mipmapCount = getTotalMipmapCount(pixelWidth, pixelHeight, depth);
 		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)
 	if (pixelWidth <= 0 || pixelHeight <= 0 || layers <= 0 || depth <= 0)
 		throw love::Exception("Texture dimensions must be greater than 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();
 		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))
 	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))
 	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))
 	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<Graphics>(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();
 	generateMipmapsInternal();
 }
 }

+ 1 - 0
src/modules/graphics/Texture.h

@@ -311,6 +311,7 @@ protected:
 	void uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y);
 	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;
 	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 generateMipmapsInternal() = 0;
 	virtual void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) = 0;
 	virtual void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) = 0;
 
 

+ 2 - 2
src/modules/graphics/metal/Buffer.mm

@@ -111,7 +111,7 @@ Buffer::~Buffer()
 
 
 void *Buffer::map(MapType /*map*/, size_t offset, size_t size)
 void *Buffer::map(MapType /*map*/, size_t offset, size_t size)
 { @autoreleasepool {
 { @autoreleasepool {
-	if (size == 0)
+	if (size == 0 || isImmutable())
 		return nullptr;
 		return nullptr;
 
 
 	Range r(offset, size);
 	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)
 void Buffer::fill(size_t offset, size_t size, const void *data)
 { @autoreleasepool {
 { @autoreleasepool {
-	if (size == 0)
+	if (size == 0 || isImmutable())
 		return;
 		return;
 
 
 	size_t buffersize = getSize();
 	size_t buffersize = getSize();

+ 1 - 1
src/modules/graphics/metal/Graphics.h

@@ -110,7 +110,7 @@ public:
 	void setWireframe(bool enable) override;
 	void setWireframe(bool enable) override;
 	
 	
 	PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const 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;
 	Renderer getRenderer() const override;
 	bool usesGLSLES() const override;
 	bool usesGLSLES() const override;
 	RendererInfo getRendererInfo() const override;
 	RendererInfo getRendererInfo() const override;

+ 3 - 7
src/modules/graphics/metal/Graphics.mm

@@ -130,11 +130,6 @@ static inline id<MTLTexture> getMTLTexture(love::graphics::Texture *tex)
 	return tex ? (__bridge id<MTLTexture>)(void *) tex->getHandle() : nil;
 	return tex ? (__bridge id<MTLTexture>)(void *) tex->getHandle() : nil;
 }
 }
 
 
-static inline id<MTLSamplerState> getMTLSampler(love::graphics::Texture *tex)
-{
-	return tex ? (__bridge id<MTLSamplerState>)(void *) tex->getSamplerHandle() : nil;
-}
-
 static inline id<MTLTexture> getMTLRenderTarget(love::graphics::Texture *tex)
 static inline id<MTLTexture> getMTLRenderTarget(love::graphics::Texture *tex)
 {
 {
 	return tex ? (__bridge id<MTLTexture>)(void *) tex->getRenderTargetHandle() : nil;
 	return tex ? (__bridge id<MTLTexture>)(void *) tex->getRenderTargetHandle() : nil;
@@ -348,9 +343,10 @@ Graphics::Graphics()
 		if (!Shader::standardShaders[i])
 		if (!Shader::standardShaders[i])
 		{
 		{
 			std::vector<std::string> stages;
 			std::vector<std::string> stages;
+			Shader::CompileOptions opts;
 			stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_VERTEX));
 			stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_VERTEX));
 			stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_PIXEL));
 			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 rendertarget = (usage & PIXELFORMATUSAGEFLAGS_RENDERTARGET) != 0;
 	bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0;
 	bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0;

+ 6 - 5
src/modules/graphics/metal/Shader.mm

@@ -232,11 +232,6 @@ static inline id<MTLTexture> getMTLTexture(love::graphics::Buffer *buffer)
 	return buffer ? (__bridge id<MTLTexture>)(void *) buffer->getTexelBufferHandle() : nil;
 	return buffer ? (__bridge id<MTLTexture>)(void *) buffer->getTexelBufferHandle() : nil;
 }
 }
 
 
-static inline id<MTLSamplerState> getMTLSampler(love::graphics::Texture *tex)
-{
-	return tex ? (__bridge id<MTLSamplerState>)(void *) tex->getSamplerHandle() : nil;
-}
-
 static inline id<MTLBuffer> getMTLBuffer(love::graphics::Buffer *buffer)
 static inline id<MTLBuffer> getMTLBuffer(love::graphics::Buffer *buffer)
 {
 {
 	return buffer ? (__bridge id<MTLBuffer>)(void *) buffer->getHandle() : nil;
 	return buffer ? (__bridge id<MTLBuffer>)(void *) buffer->getHandle() : nil;
@@ -313,6 +308,12 @@ Shader::Shader(id<MTLDevice> device, StrongRef<love::graphics::ShaderStage> stag
 		bool forcedefault = false;
 		bool forcedefault = false;
 		bool forwardcompat = true;
 		bool forwardcompat = true;
 
 
+#ifdef LOVE_IOS
+		defaultversion = 320;
+		defaultprofile = EEsProfile;
+		forcedefault = true;
+#endif
+
 		if (!tshader->parse(&defaultTBuiltInResource, defaultversion, defaultprofile, forcedefault, forwardcompat, EShMsgSuppressWarnings))
 		if (!tshader->parse(&defaultTBuiltInResource, defaultversion, defaultprofile, forcedefault, forwardcompat, EShMsgSuppressWarnings))
 		{
 		{
 			const char *stagename = "unknown";
 			const char *stagename = "unknown";

+ 0 - 2
src/modules/graphics/metal/Texture.mm

@@ -269,8 +269,6 @@ void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t s
 
 
 void Texture::generateMipmapsInternal()
 void Texture::generateMipmapsInternal()
 { @autoreleasepool {
 { @autoreleasepool {
-	// TODO: alternate method for non-color-renderable and non-filterable
-	// pixel formats.
 	id<MTLBlitCommandEncoder> encoder = Graphics::getInstance()->useBlitEncoder();
 	id<MTLBlitCommandEncoder> encoder = Graphics::getInstance()->useBlitEncoder();
 	[encoder generateMipmapsForTexture:texture];
 	[encoder generateMipmapsForTexture:texture];
 }}
 }}

+ 73 - 79
src/modules/graphics/opengl/Graphics.cpp

@@ -112,7 +112,7 @@ Graphics::Graphics()
 	, bufferMapMemory(nullptr)
 	, bufferMapMemory(nullptr)
 	, bufferMapMemorySize(2 * 1024 * 1024)
 	, bufferMapMemorySize(2 * 1024 * 1024)
 	, defaultBuffers()
 	, defaultBuffers()
-	, supportedFormats()
+	, pixelFormatUsage()
 {
 {
 	gl = OpenGL();
 	gl = OpenGL();
 
 
@@ -425,9 +425,10 @@ bool Graphics::setMode(void */*context*/, int width, int height, int pixelwidth,
 			if (!Shader::standardShaders[i])
 			if (!Shader::standardShaders[i])
 			{
 			{
 				std::vector<std::string> stages;
 				std::vector<std::string> stages;
+				Shader::CompileOptions opts;
 				stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_VERTEX));
 				stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_VERTEX));
 				stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_PIXEL));
 				stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_PIXEL));
-				Shader::standardShaders[i] = newShader(stages);
+				Shader::standardShaders[i] = newShader(stages, opts);
 			}
 			}
 		}
 		}
 		catch (love::Exception &)
 		catch (love::Exception &)
@@ -1669,6 +1670,13 @@ void Graphics::initCapabilities()
 
 
 	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
 	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
 		capabilities.textureTypes[i] = gl.isTextureTypeSupported((TextureType) 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
 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;
-	}
-
-	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 usage = OpenGL::getPixelFormatUsageFlags(format);
 
 
-	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,
 	// Even though we might have the necessary OpenGL version or extension,
 	// drivers are still allowed to throw FRAMEBUFFER_UNSUPPORTED when attaching
 	// 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
 	// a texture to a FBO whose format the driver doesn't like. So we should
 	// test with an actual FBO.
 	// test with an actual FBO.
-	GLuint texture = 0;
-	GLuint renderbuffer = 0;
-
 	// Avoid the test for depth/stencil formats - not every GL version
 	// Avoid the test for depth/stencil formats - not every GL version
 	// guarantees support for depth/stencil-only render targets (which we would
 	// 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
 	// need for the test below to work), and we already do some finagling in
 	// convertPixelFormat to try to use the best-supported internal
 	// convertPixelFormat to try to use the best-supported internal
 	// depth/stencil format for a particular driver.
 	// 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);
+		// 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);
+		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);
+			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);
-	}
+			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;
+		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 (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
 } // opengl

+ 5 - 3
src/modules/graphics/opengl/Graphics.h

@@ -106,7 +106,7 @@ public:
 	void setWireframe(bool enable) override;
 	void setWireframe(bool enable) override;
 
 
 	PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const 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;
 	Renderer getRenderer() const override;
 	bool usesGLSLES() const override;
 	bool usesGLSLES() const override;
 	RendererInfo getRendererInfo() const override;
 	RendererInfo getRendererInfo() const override;
@@ -155,6 +155,8 @@ private:
 
 
 	void setDebug(bool enable);
 	void setDebug(bool enable);
 
 
+	uint32 computePixelFormatUsage(PixelFormat format, bool readable);
+
 	std::unordered_map<RenderTargets, GLuint, CachedFBOHasher> framebufferObjects;
 	std::unordered_map<RenderTargets, GLuint, CachedFBOHasher> framebufferObjects;
 	bool windowHasStencil;
 	bool windowHasStencil;
 	GLuint mainVAO;
 	GLuint mainVAO;
@@ -170,8 +172,8 @@ private:
 	// Only needed for buffer types that can be bound to shaders.
 	// Only needed for buffer types that can be bound to shaders.
 	StrongRef<love::graphics::Buffer> defaultBuffers[BUFFERUSAGE_MAX_ENUM];
 	StrongRef<love::graphics::Buffer> 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
 }; // Graphics
 
 

+ 1 - 1
src/modules/graphics/vulkan/Graphics.cpp

@@ -823,7 +823,7 @@ namespace love {
 						std::vector<std::string> stages;
 						std::vector<std::string> stages;
 						stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_VERTEX));
 						stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_VERTEX));
 						stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_PIXEL));
 						stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_PIXEL));
-						Shader::standardShaders[i] = newShader(stages, true);
+						Shader::standardShaders[i] = newShader(stages, { { {"vulkan", "1"} } });
 					}
 					}
 				}
 				}
 			}
 			}

+ 1 - 1
src/modules/graphics/vulkan/Graphics.h

@@ -68,7 +68,7 @@ namespace love {
 				void setPointSize(float size) override { std::cout << "setPointSize "; }
 				void setPointSize(float size) override { std::cout << "setPointSize "; }
 				void setWireframe(bool enable) override { std::cout << "setWireframe "; }
 				void setWireframe(bool enable) override { std::cout << "setWireframe "; }
 				PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const override { std::cout << "getSizedFormat "; return format; }
 				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; }
 				Renderer getRenderer() const override { std::cout << "getRenderer "; return RENDERER_VULKAN; }
 				bool usesGLSLES() const override { std::cout << "usesGLSES "; return false; }
 				bool usesGLSLES() const override { std::cout << "usesGLSES "; return false; }
 				RendererInfo getRendererInfo() const override { std::cout << "getRendererInfo "; return {}; }
 				RendererInfo getRendererInfo() const override { std::cout << "getRendererInfo "; return {}; }

+ 52 - 8
src/modules/graphics/wrap_Graphics.cpp

@@ -1347,7 +1347,7 @@ int w_newParticleSystem(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-static int w_getShaderSource(lua_State *L, int startidx, std::vector<std::string> &stages)
+static int w_getShaderSource(lua_State *L, int startidx, std::vector<std::string> &stages, Shader::CompileOptions &options)
 {
 {
 	using namespace love::filesystem;
 	using namespace love::filesystem;
 
 
@@ -1369,7 +1369,6 @@ static int w_getShaderSource(lua_State *L, int startidx, std::vector<std::string
 
 
 				lua_replace(L, i);
 				lua_replace(L, i);
 			}
 			}
-
 			continue;
 			continue;
 		}
 		}
 
 
@@ -1412,18 +1411,61 @@ static int w_getShaderSource(lua_State *L, int startidx, std::vector<std::string
 	if (has_arg2)
 	if (has_arg2)
 		stages.push_back(luax_checkstring(L, startidx + 1));
 		stages.push_back(luax_checkstring(L, startidx + 1));
 
 
+	int optionsidx = has_arg2 ? startidx + 2 : startidx + 1;
+	if (!lua_isnoneornil(L, optionsidx))
+	{
+		luaL_checktype(L, optionsidx, LUA_TTABLE);
+		lua_getfield(L, optionsidx, "defines");
+		if (!lua_isnoneornil(L, -1))
+		{
+			if (!lua_istable(L, -1))
+				luaL_argerror(L, optionsidx, "expected 'defines' field to be a table");
+
+			lua_pushnil(L);
+			while (lua_next(L, -2))
+			{
+				std::string defname;
+				std::string defval;
+
+				if (lua_type(L, -2) == LUA_TNUMBER && lua_type(L, -1) == LUA_TSTRING)
+					defname = luaL_checkstring(L, -1);
+				else if (lua_type(L, -2) != LUA_TSTRING)
+					luaL_argerror(L, optionsidx, "all fields in the 'defines' table must use string keys.");
+				else
+				{
+					defname = luaL_checkstring(L, -2);
+					if (lua_type(L, -1) == LUA_TBOOLEAN)
+						defval = luax_toboolean(L, -1) ? "1" : "0";
+					else
+					{
+						const char *val = lua_tostring(L, -1);
+						if (val == nullptr)
+							luaL_argerror(L, optionsidx, "'defines' table values must be strings, numbers, or booleans.");
+						defval = val;
+					}
+				}
+
+				options.defines[defname] = defval;
+
+				lua_pop(L, 1);
+			}
+		}
+		lua_pop(L, 1);
+	}
+
 	return 0;
 	return 0;
 }
 }
 
 
 int w_newShader(lua_State *L)
 int w_newShader(lua_State *L)
 {
 {
 	std::vector<std::string> stages;
 	std::vector<std::string> stages;
-	w_getShaderSource(L, 1, stages);
+	Shader::CompileOptions options;
+	w_getShaderSource(L, 1, stages, options);
 
 
 	bool should_error = false;
 	bool should_error = false;
 	try
 	try
 	{
 	{
-		Shader *shader = instance()->newShader(stages);
+		Shader *shader = instance()->newShader(stages, options);
 		luax_pushtype(L, shader);
 		luax_pushtype(L, shader);
 		shader->release();
 		shader->release();
 	}
 	}
@@ -1446,12 +1488,13 @@ int w_newShader(lua_State *L)
 int w_newComputeShader(lua_State* L)
 int w_newComputeShader(lua_State* L)
 {
 {
 	std::vector<std::string> stages;
 	std::vector<std::string> stages;
-	w_getShaderSource(L, 1, stages);
+	Shader::CompileOptions options;
+	w_getShaderSource(L, 1, stages, options);
 
 
 	bool should_error = false;
 	bool should_error = false;
 	try
 	try
 	{
 	{
-		Shader *shader = instance()->newComputeShader(stages[0]);
+		Shader *shader = instance()->newComputeShader(stages[0], options);
 		luax_pushtype(L, shader);
 		luax_pushtype(L, shader);
 		shader->release();
 		shader->release();
 	}
 	}
@@ -1476,13 +1519,14 @@ int w_validateShader(lua_State *L)
 	bool gles = luax_checkboolean(L, 1);
 	bool gles = luax_checkboolean(L, 1);
 
 
 	std::vector<std::string> stages;
 	std::vector<std::string> stages;
-	w_getShaderSource(L, 2, stages);
+	Shader::CompileOptions options;
+	w_getShaderSource(L, 2, stages, options);
 
 
 	bool success = true;
 	bool success = true;
 	std::string err;
 	std::string err;
 	try
 	try
 	{
 	{
-		success = instance()->validateShader(gles, stages, err);
+		success = instance()->validateShader(gles, stages, options, err);
 	}
 	}
 	catch (love::Exception &e)
 	catch (love::Exception &e)
 	{
 	{

+ 1 - 1
src/modules/graphics/wrap_Graphics.lua

@@ -33,7 +33,7 @@ function love.graphics.newVideo(file, settings)
 	local source, success
 	local source, success
 
 
 	if settings.audio ~= false and love.audio then
 	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
 	end
 	if success then
 	if success then
 		video:setSource(source)
 		video:setSource(source)

+ 4 - 5
src/modules/image/CompressedImageData.cpp

@@ -53,7 +53,7 @@ CompressedImageData::CompressedImageData(const std::list<FormatHandler *> &forma
 	if (format == PIXELFORMAT_UNKNOWN)
 	if (format == PIXELFORMAT_UNKNOWN)
 		throw love::Exception("Could not parse compressed data: Unknown format.");
 		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?");
 		throw love::Exception("Could not parse compressed data: No valid data?");
 }
 }
 
 
@@ -61,8 +61,7 @@ CompressedImageData::CompressedImageData(const CompressedImageData &c)
 	: format(c.format)
 	: format(c.format)
 	, sRGB(c.sRGB)
 	, 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)
 	for (const auto &i : c.dataImages)
 	{
 	{
@@ -83,12 +82,12 @@ CompressedImageData::~CompressedImageData()
 
 
 size_t CompressedImageData::getSize() const
 size_t CompressedImageData::getSize() const
 {
 {
-	return memory->size;
+	return memory->getSize();
 }
 }
 
 
 void *CompressedImageData::getData() const
 void *CompressedImageData::getData() const
 {
 {
-	return memory->data;
+	return memory->getData();
 }
 }
 
 
 int CompressedImageData::getMipmapCount() const
 int CompressedImageData::getMipmapCount() const

+ 1 - 1
src/modules/image/CompressedImageData.h

@@ -103,7 +103,7 @@ protected:
 	bool sRGB;
 	bool sRGB;
 
 
 	// Single block of memory containing all of the sub-images.
 	// Single block of memory containing all of the sub-images.
-	StrongRef<CompressedMemory> memory;
+	StrongRef<ByteData> memory;
 
 
 	// Texture info for each mipmap level.
 	// Texture info for each mipmap level.
 	std::vector<StrongRef<CompressedSlice>> dataImages;
 	std::vector<StrongRef<CompressedSlice>> dataImages;

+ 3 - 20
src/modules/image/CompressedSlice.cpp

@@ -26,30 +26,12 @@ namespace love
 namespace image
 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)
 	: ImageDataBase(format, width, height)
 	, memory(memory)
 	, memory(memory)
 	, offset(offset)
 	, offset(offset)
 	, dataSize(size)
 	, dataSize(size)
+	, sRGB(false)
 {
 {
 }
 }
 
 
@@ -58,6 +40,7 @@ CompressedSlice::CompressedSlice(const CompressedSlice &s)
 	, memory(s.memory)
 	, memory(s.memory)
 	, offset(s.offset)
 	, offset(s.offset)
 	, dataSize(s.dataSize)
 	, dataSize(s.dataSize)
+	, sRGB(s.sRGB)
 {
 {
 }
 }
 
 

+ 5 - 15
src/modules/image/CompressedSlice.h

@@ -23,7 +23,7 @@
 // LOVE
 // LOVE
 #include "common/int.h"
 #include "common/int.h"
 #include "common/pixelformat.h"
 #include "common/pixelformat.h"
-#include "common/Object.h"
+#include "data/ByteData.h"
 #include "ImageDataBase.h"
 #include "ImageDataBase.h"
 
 
 namespace love
 namespace love
@@ -31,17 +31,7 @@ namespace love
 namespace image
 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
 // Compressed image data can have multiple mipmap levels, each represented by a
 // sub-image.
 // sub-image.
@@ -49,19 +39,19 @@ class CompressedSlice : public ImageDataBase
 {
 {
 public:
 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);
 	CompressedSlice(const CompressedSlice &slice);
 	virtual ~CompressedSlice();
 	virtual ~CompressedSlice();
 
 
 	CompressedSlice *clone() const override;
 	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; }
 	size_t getSize() const override { return dataSize; }
 	bool isSRGB() const override { return sRGB; }
 	bool isSRGB() const override { return sRGB; }
 	size_t getOffset() const { return offset; }
 	size_t getOffset() const { return offset; }
 
 
 private:
 private:
 
 
-	StrongRef<CompressedMemory> memory;
+	StrongRef<ByteData> memory;
 	size_t offset;
 	size_t offset;
 	size_t dataSize;
 	size_t dataSize;
 	bool sRGB;
 	bool sRGB;

+ 1 - 1
src/modules/image/FormatHandler.cpp

@@ -60,7 +60,7 @@ bool FormatHandler::canParseCompressed(Data* /*data*/)
 	return false;
 	return false;
 }
 }
 
 
-StrongRef<CompressedMemory> FormatHandler::parseCompressed(Data* /*filedata*/, std::vector<StrongRef<CompressedSlice>>& /*images*/, PixelFormat& /*format*/, bool& /*sRGB*/)
+StrongRef<ByteData> FormatHandler::parseCompressed(Data* /*filedata*/, std::vector<StrongRef<CompressedSlice>>& /*images*/, PixelFormat& /*format*/, bool& /*sRGB*/)
 {
 {
 	throw love::Exception("Compressed image parsing is not implemented for this format backend.");
 	throw love::Exception("Compressed image parsing is not implemented for this format backend.");
 }
 }

+ 1 - 1
src/modules/image/FormatHandler.h

@@ -107,7 +107,7 @@ public:
 	 *
 	 *
 	 * @return The single block of memory containing the parsed images.
 	 * @return The single block of memory containing the parsed images.
 	 **/
 	 **/
-	virtual StrongRef<CompressedMemory> parseCompressed(Data *filedata,
+	virtual StrongRef<ByteData> parseCompressed(Data *filedata,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        PixelFormat &format, bool &sRGB);
 	        PixelFormat &format, bool &sRGB);
 
 

+ 11 - 49
src/modules/image/ImageData.cpp

@@ -43,7 +43,7 @@ ImageData::ImageData(int width, int height, PixelFormat format)
 	: ImageDataBase(format, width, height)
 	: ImageDataBase(format, width, height)
 {
 {
 	if (!validPixelFormat(format))
 	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);
 	create(width, height, format);
 
 
@@ -55,7 +55,7 @@ ImageData::ImageData(int width, int height, PixelFormat format, void *data, bool
 	: ImageDataBase(format, width, height)
 	: ImageDataBase(format, width, height)
 {
 {
 	if (!validPixelFormat(format))
 	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)
 	if (own)
 		this->data = (unsigned char *) data;
 		this->data = (unsigned char *) data;
@@ -196,11 +196,7 @@ love::filesystem::FileData *ImageData::encode(FormatHandler::EncodedFormat encod
 	}
 	}
 
 
 	if (encoder == nullptr || encodedimage.data == nullptr)
 	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;
 	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));
 	Pixel *p = (Pixel *) (data + ((y * width + x) * pixelsize));
 
 
 	if (pixelSetFunction == nullptr)
 	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);
 	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));
 	const Pixel *p = (const Pixel *) (data + ((y * width + x) * pixelsize));
 
 
 	if (pixelGetFunction == nullptr)
 	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);
 	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)
 			else if (srcformat == PIXELFORMAT_RGBA32_FLOAT && dstformat == PIXELFORMAT_RGBA16_FLOAT)
 				pasteRGBA32FtoRGBA16F(rowsrc, rowdst, sw);
 				pasteRGBA32FtoRGBA16F(rowsrc, rowdst, sw);
 
 
-			else
+			else if (getfunction != nullptr && setfunction != nullptr)
 			{
 			{
 				// Slow path: convert src -> Colorf -> dst.
 				// Slow path: convert src -> Colorf -> dst.
 				Colorf c;
 				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);
 					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)
 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)
 ImageData::PixelSetFunction ImageData::getPixelSetFunction(PixelFormat format)

+ 0 - 1
src/modules/image/ImageData.h

@@ -124,7 +124,6 @@ public:
 	PixelGetFunction getPixelGetFunction() const { return pixelGetFunction; }
 	PixelGetFunction getPixelGetFunction() const { return pixelGetFunction; }
 
 
 	static bool validPixelFormat(PixelFormat format);
 	static bool validPixelFormat(PixelFormat format);
-	static bool canPaste(PixelFormat src, PixelFormat dst);
 
 
 	static PixelSetFunction getPixelSetFunction(PixelFormat format);
 	static PixelSetFunction getPixelSetFunction(PixelFormat format);
 	static PixelGetFunction getPixelGetFunction(PixelFormat format);
 	static PixelGetFunction getPixelGetFunction(PixelFormat format);

+ 4 - 4
src/modules/image/magpie/ASTCHandler.cpp

@@ -105,7 +105,7 @@ bool ASTCHandler::canParseCompressed(Data *data)
 	return true;
 	return true;
 }
 }
 
 
-StrongRef<CompressedMemory> ASTCHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
+StrongRef<ByteData> ASTCHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
 {
 {
 	if (!canParseCompressed(filedata))
 	if (!canParseCompressed(filedata))
 		throw love::Exception("Could not decode compressed data (not an .astc file?)");
 		throw love::Exception("Could not decode compressed data (not an .astc file?)");
@@ -125,15 +125,15 @@ StrongRef<CompressedMemory> ASTCHandler::parseCompressed(Data *filedata, std::ve
 	uint32 blocksY = (sizeY + header.blockdimY - 1) / header.blockdimY;
 	uint32 blocksY = (sizeY + header.blockdimY - 1) / header.blockdimY;
 	uint32 blocksZ = (sizeZ + header.blockdimZ - 1) / header.blockdimZ;
 	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())
 	if (totalsize + sizeof(header) > filedata->getSize())
 		throw love::Exception("Could not parse .astc file: file is too small.");
 		throw love::Exception("Could not parse .astc file: file is too small.");
 
 
-	StrongRef<CompressedMemory> memory(new CompressedMemory(totalsize), Acquire::NORETAIN);
+	StrongRef<ByteData> memory(new ByteData(totalsize, false), Acquire::NORETAIN);
 
 
 	// .astc files only store a single mipmap level.
 	// .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);
 	images.emplace_back(new CompressedSlice(cformat, sizeX, sizeY, memory, 0, totalsize), Acquire::NORETAIN);
 
 

+ 1 - 1
src/modules/image/magpie/ASTCHandler.h

@@ -43,7 +43,7 @@ public:
 	// Implements FormatHandler.
 	// Implements FormatHandler.
 	bool canParseCompressed(Data *data) override;
 	bool canParseCompressed(Data *data) override;
 
 
-	StrongRef<CompressedMemory> parseCompressed(Data *filedata,
+	StrongRef<ByteData> parseCompressed(Data *filedata,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        PixelFormat &format, bool &sRGB) override;
 	        PixelFormat &format, bool &sRGB) override;
 
 

+ 3 - 4
src/modules/image/magpie/KTXHandler.cpp

@@ -298,7 +298,7 @@ bool KTXHandler::canParseCompressed(Data *data)
 	return true;
 	return true;
 }
 }
 
 
-StrongRef<CompressedMemory> KTXHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
+StrongRef<ByteData> KTXHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
 {
 {
 	if (!canParseCompressed(filedata))
 	if (!canParseCompressed(filedata))
 		throw love::Exception("Could not decode compressed data (not a KTX file?)");
 		throw love::Exception("Could not decode compressed data (not a KTX file?)");
@@ -354,8 +354,7 @@ StrongRef<CompressedMemory> KTXHandler::parseCompressed(Data *filedata, std::vec
 		fileoffset += mipsizepadded;
 		fileoffset += mipsizepadded;
 	}
 	}
 
 
-	StrongRef<CompressedMemory> memory;
-	memory.set(new CompressedMemory(totalsize), Acquire::NORETAIN);
+	StrongRef<ByteData> memory(new ByteData(totalsize, false), Acquire::NORETAIN);
 
 
 	// Reset the file offset to the start of the file's image data.
 	// Reset the file offset to the start of the file's image data.
 	fileoffset = sizeof(KTXHeader) + header.bytesOfKeyValueData;
 	fileoffset = sizeof(KTXHeader) + header.bytesOfKeyValueData;
@@ -376,7 +375,7 @@ StrongRef<CompressedMemory> KTXHandler::parseCompressed(Data *filedata, std::vec
 		int width = (int) std::max(header.pixelWidth >> i, 1u);
 		int width = (int) std::max(header.pixelWidth >> i, 1u);
 		int height = (int) std::max(header.pixelHeight >> 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);
 		auto slice = new CompressedSlice(cformat, width, height, memory, dataoffset, mipsize);
 		images.push_back(slice);
 		images.push_back(slice);

+ 1 - 1
src/modules/image/magpie/KTXHandler.h

@@ -42,7 +42,7 @@ public:
 	// Implements FormatHandler.
 	// Implements FormatHandler.
 	bool canParseCompressed(Data *data) override;
 	bool canParseCompressed(Data *data) override;
 
 
-	StrongRef<CompressedMemory> parseCompressed(Data *filedata,
+	StrongRef<ByteData> parseCompressed(Data *filedata,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        PixelFormat &format, bool &sRGB) override;
 	        PixelFormat &format, bool &sRGB) override;
 
 

+ 3 - 4
src/modules/image/magpie/PKMHandler.cpp

@@ -114,7 +114,7 @@ bool PKMHandler::canParseCompressed(Data *data)
 	return true;
 	return true;
 }
 }
 
 
-StrongRef<CompressedMemory> PKMHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
+StrongRef<ByteData> PKMHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
 {
 {
 	if (!canParseCompressed(filedata))
 	if (!canParseCompressed(filedata))
 		throw love::Exception("Could not decode compressed data (not a PKM file?)");
 		throw love::Exception("Could not decode compressed data (not a PKM file?)");
@@ -135,11 +135,10 @@ StrongRef<CompressedMemory> PKMHandler::parseCompressed(Data *filedata, std::vec
 	// The rest of the file after the header is all texture data.
 	// The rest of the file after the header is all texture data.
 	size_t totalsize = filedata->getSize() - sizeof(PKMHeader);
 	size_t totalsize = filedata->getSize() - sizeof(PKMHeader);
 
 
-	StrongRef<CompressedMemory> memory;
-	memory.set(new CompressedMemory(totalsize), Acquire::NORETAIN);
+	StrongRef<ByteData> memory(new ByteData(totalsize, false), Acquire::NORETAIN);
 
 
 	// PKM files only store a single mipmap level.
 	// 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
 	// TODO: verify whether glCompressedTexImage works properly with the unpadded
 	// width and height values (extended == padded.)
 	// width and height values (extended == padded.)

+ 1 - 1
src/modules/image/magpie/PKMHandler.h

@@ -42,7 +42,7 @@ public:
 	// Implements FormatHandler.
 	// Implements FormatHandler.
 	bool canParseCompressed(Data *data) override;
 	bool canParseCompressed(Data *data) override;
 
 
-	StrongRef<CompressedMemory> parseCompressed(Data *filedata,
+	StrongRef<ByteData> parseCompressed(Data *filedata,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        PixelFormat &format, bool &sRGB) override;
 	        PixelFormat &format, bool &sRGB) override;
 
 

+ 4 - 4
src/modules/image/magpie/PVRHandler.cpp

@@ -475,7 +475,7 @@ bool PVRHandler::canParseCompressed(Data *data)
 	return false;
 	return false;
 }
 }
 
 
-StrongRef<CompressedMemory> PVRHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
+StrongRef<ByteData> PVRHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
 {
 {
 	if (!canParseCompressed(filedata))
 	if (!canParseCompressed(filedata))
 		throw love::Exception("Could not decode compressed data (not a PVR file?)");
 		throw love::Exception("Could not decode compressed data (not a PVR file?)");
@@ -525,8 +525,8 @@ StrongRef<CompressedMemory> PVRHandler::parseCompressed(Data *filedata, std::vec
 	if (filedata->getSize() < fileoffset + totalsize)
 	if (filedata->getSize() < fileoffset + totalsize)
 		throw love::Exception("Could not parse PVR file: invalid size calculation.");
 		throw love::Exception("Could not parse PVR file: invalid size calculation.");
 
 
-	StrongRef<CompressedMemory> memory;
-	memory.set(new CompressedMemory(totalsize), Acquire::NORETAIN);
+	;
+	StrongRef<ByteData> memory(new ByteData(totalsize, false), Acquire::NORETAIN);
 
 
 	size_t curoffset = 0;
 	size_t curoffset = 0;
 	const uint8 *filebytes = (uint8 *) filedata->getData() + fileoffset;
 	const uint8 *filebytes = (uint8 *) filedata->getData() + fileoffset;
@@ -541,7 +541,7 @@ StrongRef<CompressedMemory> PVRHandler::parseCompressed(Data *filedata, std::vec
 		int width = std::max((int) header3.width >> i, 1);
 		int width = std::max((int) header3.width >> i, 1);
 		int height = std::max((int) header3.height >> 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);
 		auto slice = new CompressedSlice(cformat, width, height, memory, curoffset, mipsize);
 		images.push_back(slice);
 		images.push_back(slice);

+ 1 - 1
src/modules/image/magpie/PVRHandler.h

@@ -40,7 +40,7 @@ public:
 	// Implements FormatHandler.
 	// Implements FormatHandler.
 	bool canParseCompressed(Data *data) override;
 	bool canParseCompressed(Data *data) override;
 
 
-	StrongRef<CompressedMemory> parseCompressed(Data *filedata,
+	StrongRef<ByteData> parseCompressed(Data *filedata,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        PixelFormat &format, bool &sRGB) override;
 	        PixelFormat &format, bool &sRGB) override;
 
 

+ 3 - 4
src/modules/image/magpie/ddsHandler.cpp

@@ -229,7 +229,7 @@ bool DDSHandler::canParseCompressed(Data *data)
 	return dds::isCompressedDDS(data->getData(), data->getSize());
 	return dds::isCompressedDDS(data->getData(), data->getSize());
 }
 }
 
 
-StrongRef<CompressedMemory> DDSHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
+StrongRef<ByteData> DDSHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
 {
 {
 	if (!dds::isCompressedDDS(filedata->getData(), filedata->getSize()))
 	if (!dds::isCompressedDDS(filedata->getData(), filedata->getSize()))
 		throw love::Exception("Could not decode compressed data (not a DDS file?)");
 		throw love::Exception("Could not decode compressed data (not a DDS file?)");
@@ -238,7 +238,6 @@ StrongRef<CompressedMemory> DDSHandler::parseCompressed(Data *filedata, std::vec
 	bool isSRGB = false;
 	bool isSRGB = false;
 	bool bgra = false;
 	bool bgra = false;
 
 
-	StrongRef<CompressedMemory> memory;
 	size_t dataSize = 0;
 	size_t dataSize = 0;
 
 
 	images.clear();
 	images.clear();
@@ -261,7 +260,7 @@ StrongRef<CompressedMemory> DDSHandler::parseCompressed(Data *filedata, std::vec
 		dataSize += img->dataSize;
 		dataSize += img->dataSize;
 	}
 	}
 
 
-	memory.set(new CompressedMemory(dataSize), Acquire::NORETAIN);
+	StrongRef<ByteData> memory(new ByteData(dataSize, false), Acquire::NORETAIN);
 
 
 	size_t dataOffset = 0;
 	size_t dataOffset = 0;
 
 
@@ -272,7 +271,7 @@ StrongRef<CompressedMemory> DDSHandler::parseCompressed(Data *filedata, std::vec
 		const dds::Image *img = parser.getImageData(i);
 		const dds::Image *img = parser.getImageData(i);
 
 
 		// Copy the mipmap image from the FileData to our block of memory.
 		// 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);
 		auto slice = new CompressedSlice(texformat, img->width, img->height, memory, dataOffset, img->dataSize);
 		images.emplace_back(slice, Acquire::NORETAIN);
 		images.emplace_back(slice, Acquire::NORETAIN);

+ 1 - 1
src/modules/image/magpie/ddsHandler.h

@@ -46,7 +46,7 @@ public:
 	bool canDecode(Data *data) override;
 	bool canDecode(Data *data) override;
 	DecodedImage decode(Data *data) override;
 	DecodedImage decode(Data *data) override;
 	bool canParseCompressed(Data *data) override;
 	bool canParseCompressed(Data *data) override;
-	StrongRef<CompressedMemory> parseCompressed(Data *filedata,
+	StrongRef<ByteData> parseCompressed(Data *filedata,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        PixelFormat &format, bool &sRGB) override;
 	        PixelFormat &format, bool &sRGB) override;
 
 

+ 9 - 3
src/modules/image/wrap_ImageData.lua

@@ -368,9 +368,9 @@ local objectcache = setmetatable({}, {
 			width = width,
 			width = width,
 			height = height,
 			height = height,
 			format = format,
 			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
 		self[imagedata] = p
@@ -395,6 +395,8 @@ function ImageData:_mapPixelUnsafe(func, ix, iy, iw, ih)
 	local p = objectcache[self]
 	local p = objectcache[self]
 	local idw, idh = p.width, p.height
 	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)
 	ix = floor(ix)
 	iy = floor(iy)
 	iy = floor(iy)
 	iw = floor(iw)
 	iw = floor(iw)
@@ -423,6 +425,8 @@ function ImageData:getPixel(x, y)
 	local p = objectcache[self]
 	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 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)
 	ffifuncs.lockMutex(self)
 	local pixel = p.pointer[y * p.width + x]
 	local pixel = p.pointer[y * p.width + x]
 	local r, g, b, a = p.tolua(pixel)
 	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]
 	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 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)
 	ffifuncs.lockMutex(self)
 	p.fromlua(p.pointer[y * p.width + x], r, g, b, a)
 	p.fromlua(p.pointer[y * p.width + x], r, g, b, a)
 	ffifuncs.unlockMutex(self)
 	ffifuncs.unlockMutex(self)

+ 20 - 0
src/modules/keyboard/Keyboard.cpp

@@ -47,6 +47,16 @@ bool Keyboard::getConstant(Scancode in, const char *&out)
 	return scancodes.find(in, 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<Keyboard::Key, Keyboard::KEY_MAX_ENUM>::Entry Keyboard::keyEntries[] =
 StringMap<Keyboard::Key, Keyboard::KEY_MAX_ENUM>::Entry Keyboard::keyEntries[] =
 {
 {
 	{"unknown", Keyboard::KEY_UNKNOWN},
 	{"unknown", Keyboard::KEY_UNKNOWN},
@@ -521,5 +531,15 @@ StringMap<Keyboard::Scancode, Keyboard::SCANCODE_MAX_ENUM>::Entry Keyboard::scan
 
 
 StringMap<Keyboard::Scancode, Keyboard::SCANCODE_MAX_ENUM> Keyboard::scancodes(Keyboard::scancodeEntries, sizeof(Keyboard::scancodeEntries));
 StringMap<Keyboard::Scancode, Keyboard::SCANCODE_MAX_ENUM> Keyboard::scancodes(Keyboard::scancodeEntries, sizeof(Keyboard::scancodeEntries));
 
 
+StringMap<Keyboard::ModifierKey, Keyboard::MODKEY_MAX_ENUM>::Entry Keyboard::modifierEntries[] =
+{
+	{"numlock", MODKEY_NUMLOCK},
+	{"capslock", MODKEY_CAPSLOCK},
+	{"scrolllock", MODKEY_SCROLLLOCK},
+	{"mode", MODKEY_MODE},
+};
+
+StringMap<Keyboard::ModifierKey, Keyboard::MODKEY_MAX_ENUM> Keyboard::modifiers(Keyboard::modifierEntries, sizeof(Keyboard::modifierEntries));
+
 } // keyboard
 } // keyboard
 } // love
 } // love

+ 25 - 0
src/modules/keyboard/Keyboard.h

@@ -519,6 +519,18 @@ public:
 		SCANCODE_MAX_ENUM
 		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() {}
 	virtual ~Keyboard() {}
 
 
 	// Implements Module.
 	// Implements Module.
@@ -550,6 +562,13 @@ public:
 	 **/
 	 **/
 	virtual bool isScancodeDown(const std::vector<Scancode> &scancodelist) const = 0;
 	virtual bool isScancodeDown(const std::vector<Scancode> &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
 	 * Gets the key corresponding to the specified scancode according to the
 	 * current keyboard layout.
 	 * current keyboard layout.
@@ -592,6 +611,9 @@ public:
 	static bool getConstant(const char *in, Scancode &out);
 	static bool getConstant(const char *in, Scancode &out);
 	static bool getConstant(Scancode in, const char *&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:
 private:
 
 
 	static StringMap<Key, KEY_MAX_ENUM>::Entry keyEntries[];
 	static StringMap<Key, KEY_MAX_ENUM>::Entry keyEntries[];
@@ -600,6 +622,9 @@ private:
 	static StringMap<Scancode, SCANCODE_MAX_ENUM>::Entry scancodeEntries[];
 	static StringMap<Scancode, SCANCODE_MAX_ENUM>::Entry scancodeEntries[];
 	static StringMap<Scancode, SCANCODE_MAX_ENUM> scancodes;
 	static StringMap<Scancode, SCANCODE_MAX_ENUM> scancodes;
 
 
+	static StringMap<ModifierKey, MODKEY_MAX_ENUM>::Entry modifierEntries[];
+	static StringMap<ModifierKey, MODKEY_MAX_ENUM> modifiers;
+
 }; // Keyboard
 }; // Keyboard
 
 
 } // keyboard
 } // keyboard

+ 26 - 0
src/modules/keyboard/sdl/Keyboard.cpp

@@ -18,9 +18,16 @@
  * 3. This notice may not be removed or altered from any source distribution.
  * 3. This notice may not be removed or altered from any source distribution.
  **/
  **/
 
 
+#include <SDL_version.h>
+
 #include "Keyboard.h"
 #include "Keyboard.h"
 #include "window/Window.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 love
 {
 {
 namespace keyboard
 namespace keyboard
@@ -78,6 +85,25 @@ bool Keyboard::isScancodeDown(const std::vector<Scancode> &scancodelist) const
 	return false;
 	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
 Keyboard::Key Keyboard::getKeyFromScancode(Scancode scancode) const
 {
 {
 	SDL_Scancode sdlscancode = SDL_SCANCODE_UNKNOWN;
 	SDL_Scancode sdlscancode = SDL_SCANCODE_UNKNOWN;

+ 1 - 0
src/modules/keyboard/sdl/Keyboard.h

@@ -48,6 +48,7 @@ public:
 	bool hasKeyRepeat() const;
 	bool hasKeyRepeat() const;
 	bool isDown(const std::vector<Key> &keylist) const;
 	bool isDown(const std::vector<Key> &keylist) const;
 	bool isScancodeDown(const std::vector<Scancode> &scancodelist) const;
 	bool isScancodeDown(const std::vector<Scancode> &scancodelist) const;
+	bool isModifierActive(ModifierKey key) const;
 
 
 	Key getKeyFromScancode(Scancode scancode) const;
 	Key getKeyFromScancode(Scancode scancode) const;
 	Scancode getScancodeFromKey(Key key) const;
 	Scancode getScancodeFromKey(Key key) const;

+ 12 - 0
src/modules/keyboard/wrap_Keyboard.cpp

@@ -187,6 +187,17 @@ int w_hasScreenKeyboard(lua_State *L)
 	return 1;
 	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.
 // List of functions to wrap.
 static const luaL_Reg functions[] =
 static const luaL_Reg functions[] =
 {
 {
@@ -199,6 +210,7 @@ static const luaL_Reg functions[] =
 	{ "isScancodeDown", w_isScancodeDown },
 	{ "isScancodeDown", w_isScancodeDown },
 	{ "getScancodeFromKey", w_getScancodeFromKey },
 	{ "getScancodeFromKey", w_getScancodeFromKey },
 	{ "getKeyFromScancode", w_getKeyFromScancode },
 	{ "getKeyFromScancode", w_getKeyFromScancode },
+	{ "isModifierActive", w_isModifierActive },
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };
 
 

+ 21 - 6
src/modules/love/boot.lua

@@ -37,6 +37,7 @@ end
 
 
 local no_game_code = false
 local no_game_code = false
 local invalid_game_path = nil
 local invalid_game_path = nil
+local main_file = "main.lua"
 
 
 -- This can't be overridden.
 -- This can't be overridden.
 function love.boot()
 function love.boot()
@@ -80,6 +81,9 @@ function love.boot()
 
 
 	love.setDeprecationOutput(not love.filesystem.isFused())
 	love.setDeprecationOutput(not love.filesystem.isFused())
 
 
+	main_file = "main.lua"
+	local custom_main_file = false
+
 	local identity = ""
 	local identity = ""
 	if not can_has_game and o.game.set and o.game.arg[1] then
 	if not can_has_game and o.game.set and o.game.arg[1] then
 		local nouri = o.game.arg[1]
 		local nouri = o.game.arg[1]
@@ -89,6 +93,14 @@ function love.boot()
 		end
 		end
 
 
 		local full_source = love.path.getFull(nouri)
 		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)
 		can_has_game = pcall(love.filesystem.setSource, full_source)
 
 
 		if not can_has_game then
 		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
 	-- 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.
 	-- 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
 	if realdir then
 		identity = love.path.leaf(realdir)
 		identity = love.path.leaf(realdir)
 	end
 	end
@@ -118,7 +130,7 @@ function love.boot()
 	-- before the save directory (the identity should be appended.)
 	-- before the save directory (the identity should be appended.)
 	pcall(love.filesystem.setIdentity, identity, true)
 	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
 		no_game_code = true
 	end
 	end
 
 
@@ -145,7 +157,7 @@ function love.init()
 			minheight = 1,
 			minheight = 1,
 			fullscreen = false,
 			fullscreen = false,
 			fullscreentype = "desktop",
 			fullscreentype = "desktop",
-			display = 1,
+			displayindex = 1,
 			vsync = 1,
 			vsync = 1,
 			msaa = 0,
 			msaa = 0,
 			borderless = false,
 			borderless = false,
@@ -362,13 +374,16 @@ function love.init()
 	if love.filesystem then
 	if love.filesystem then
 		love.filesystem._setAndroidSaveExternal(c.externalstorage)
 		love.filesystem._setAndroidSaveExternal(c.externalstorage)
 		love.filesystem.setIdentity(c.identity or love.filesystem.getIdentity(), c.appendidentity)
 		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
 	end
 	end
 
 
 	if no_game_code then
 	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
 	elseif invalid_game_path then
 		error("Cannot load game at path '" .. invalid_game_path .. "'.\nMake sure a folder exists at the specified path.")
 		error("Cannot load game at path '" .. invalid_game_path .. "'.\nMake sure a folder exists at the specified path.")
 	end
 	end

+ 9 - 0
src/modules/love/callbacks.lua

@@ -112,6 +112,12 @@ function love.createhandlers()
 		directorydropped = function (dir)
 		directorydropped = function (dir)
 			if love.directorydropped then return love.directorydropped(dir) end
 			if love.directorydropped then return love.directorydropped(dir) end
 		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 ()
 		lowmemory = function ()
 			if love.lowmemory then love.lowmemory() end
 			if love.lowmemory then love.lowmemory() end
 			collectgarbage()
 			collectgarbage()
@@ -120,6 +126,9 @@ function love.createhandlers()
 		displayrotated = function (display, orient)
 		displayrotated = function (display, orient)
 			if love.displayrotated then return love.displayrotated(display, orient) end
 			if love.displayrotated then return love.displayrotated(display, orient) end
 		end,
 		end,
+		localechanged = function ()
+			if love.localechanged then return love.localechanged() end
+		end,
 	}, {
 	}, {
 		__index = function(self, name)
 		__index = function(self, name)
 			error("Unknown event: " .. name)
 			error("Unknown event: " .. name)

+ 12 - 2
src/modules/sound/Decoder.cpp

@@ -29,13 +29,16 @@ namespace sound
 
 
 love::Type Decoder::type("Decoder", &Object::type);
 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)
 	, bufferSize(bufferSize)
 	, sampleRate(DEFAULT_SAMPLE_RATE)
 	, sampleRate(DEFAULT_SAMPLE_RATE)
 	, buffer(0)
 	, buffer(0)
 	, eof(false)
 	, eof(false)
 {
 {
+	if (!stream->isReadable() || !stream->isSeekable())
+		throw love::Exception("Decoder input stream must be readable and seekable.");
+
 	buffer = new char[bufferSize];
 	buffer = new char[bufferSize];
 }
 }
 
 
@@ -65,5 +68,12 @@ bool Decoder::isFinished()
 	return eof;
 	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
 } // sound
 } // love
 } // love

+ 14 - 5
src/modules/sound/Decoder.h

@@ -23,7 +23,8 @@
 
 
 // LOVE
 // LOVE
 #include "common/Object.h"
 #include "common/Object.h"
-#include "filesystem/File.h"
+#include "common/Stream.h"
+#include "common/StringMap.h"
 
 
 #include <string>
 #include <string>
 
 
@@ -39,9 +40,16 @@ class Decoder : public Object
 {
 {
 public:
 public:
 
 
+	enum StreamSource
+	{
+		STREAM_MEMORY,
+		STREAM_FILE,
+		STREAM_MAX_ENUM
+	};
+
 	static love::Type type;
 	static love::Type type;
 
 
-	Decoder(Data *data, int bufferSize);
+	Decoder(Stream *stream, int bufferSize);
 	virtual ~Decoder();
 	virtual ~Decoder();
 
 
 	/**
 	/**
@@ -143,11 +151,12 @@ public:
 	 **/
 	 **/
 	virtual double getDuration() = 0;
 	virtual double getDuration() = 0;
 
 
+	STRINGMAP_CLASS_DECLARE(StreamSource);
+
 protected:
 protected:
 
 
-	// The encoded data. This should be replaced with buffered file
-	// reads in the future.
-	StrongRef<Data> data;
+	// A readable stream containing the encoded data.
+	StrongRef<Stream> stream;
 
 
 	// When the decoder decodes data incrementally, it writes
 	// When the decoder decodes data incrementally, it writes
 	// this many bytes at a time (at most).
 	// this many bytes at a time (at most).

+ 3 - 3
src/modules/sound/Sound.h

@@ -23,7 +23,7 @@
 
 
 // LOVE
 // LOVE
 #include "common/Module.h"
 #include "common/Module.h"
-#include "filesystem/File.h"
+#include "common/Stream.h"
 
 
 #include "SoundData.h"
 #include "SoundData.h"
 #include "Decoder.h"
 #include "Decoder.h"
@@ -83,11 +83,11 @@ public:
 	/**
 	/**
 	 * Attempts to find a decoder for the encoded sound data in the
 	 * Attempts to find a decoder for the encoded sound data in the
 	 * specified file.
 	 * 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.
 	 * @param bufferSize The size of each decoded chunk.
 	 * @return A Decoder object on success, or zero if no decoder could be found.
 	 * @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
 }; // Sound
 
 

+ 18 - 81
src/modules/sound/lullaby/CoreAudioDecoder.cpp

@@ -34,37 +34,23 @@ namespace lullaby
 {
 {
 
 
 // Callbacks
 // 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;
-
-	if (bytesLeft > 0)
-	{
-		UInt32 actualSize = bytesLeft >= requestCount ? requestCount : (UInt32) bytesLeft;
-		memcpy(buffer, (char *) data->getData() + inPosition, actualSize);
-		*actualCount = actualSize;
-	}
-	else
-	{
-		*actualCount = 0;
-		return kAudioFilePositionError;
-	}
+	auto stream = (Stream *) inClientData;
+	int64 readbytes = stream->read(buffer, requestCount);
 
 
-	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)
 	, audioFile(nullptr)
 	, extAudioFile(nullptr)
 	, extAudioFile(nullptr)
 	, inputInfo()
 	, inputInfo()
@@ -75,23 +61,23 @@ CoreAudioDecoder::CoreAudioDecoder(Data *data, int bufferSize)
 	{
 	{
 		OSStatus err = noErr;
 		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)
 		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.
 		// We want to use the Extended AudioFile API.
 		err = ExtAudioFileWrapAudioFileID(audioFile, false, &extAudioFile);
 		err = ExtAudioFileWrapAudioFileID(audioFile, false, &extAudioFile);
 
 
 		if (err != noErr)
 		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.
 		// Get the format of the audio data.
 		UInt32 propertySize = sizeof(inputInfo);
 		UInt32 propertySize = sizeof(inputInfo);
 		err = ExtAudioFileGetProperty(extAudioFile, kExtAudioFileProperty_FileDataFormat, &propertySize, &inputInfo);
 		err = ExtAudioFileGetProperty(extAudioFile, kExtAudioFileProperty_FileDataFormat, &propertySize, &inputInfo);
 
 
 		if (err != noErr)
 		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.
 		// Set the output format to 16 bit signed integer (native-endian) data.
 		// Keep the channel count and sample rate of the source format.
 		// 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);
 		err = ExtAudioFileSetProperty(extAudioFile, kExtAudioFileProperty_ClientDataFormat, propertySize, &outputInfo);
 
 
 		if (err != noErr)
 		if (err != noErr)
-			throw love::Exception("Could not set decoder properties.");
+			throw love::Exception("Could not set CoreAudio decoder properties.");
 	}
 	}
 	catch (love::Exception &)
 	catch (love::Exception &)
 	{
 	{
@@ -143,59 +129,10 @@ void CoreAudioDecoder::closeAudioFile()
 	audioFile = nullptr;
 	audioFile = nullptr;
 }
 }
 
 
-bool CoreAudioDecoder::accepts(const std::string &ext)
-{
-	UInt32 size = 0;
-	std::vector<UInt32> 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()
 love::sound::Decoder *CoreAudioDecoder::clone()
 {
 {
-	return new CoreAudioDecoder(data.get(), bufferSize);
+	StrongRef<Stream> s(stream->clone(), Acquire::NORETAIN);
+	return new CoreAudioDecoder(s, bufferSize);
 }
 }
 
 
 int CoreAudioDecoder::decode()
 int CoreAudioDecoder::decode()

+ 10 - 12
src/modules/sound/lullaby/CoreAudioDecoder.h

@@ -26,7 +26,7 @@
 #ifdef LOVE_SUPPORT_COREAUDIO
 #ifdef LOVE_SUPPORT_COREAUDIO
 
 
 // LOVE
 // LOVE
-#include "common/Data.h"
+#include "common/Stream.h"
 #include "sound/Decoder.h"
 #include "sound/Decoder.h"
 
 
 // Core Audio
 // Core Audio
@@ -47,19 +47,17 @@ class CoreAudioDecoder : public Decoder
 {
 {
 public:
 public:
 
 
-	CoreAudioDecoder(Data *data, int bufferSize);
+	CoreAudioDecoder(Stream *stream, int bufferSize);
 	virtual ~CoreAudioDecoder();
 	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:
 private:
 
 

+ 20 - 24
src/modules/sound/lullaby/FLACDecoder.cpp

@@ -22,6 +22,7 @@
 #include "FLACDecoder.h"
 #include "FLACDecoder.h"
 
 
 #include <set>
 #include <set>
+#include <algorithm>
 #include "common/Exception.h"
 #include "common/Exception.h"
 
 
 namespace love
 namespace love
@@ -31,42 +32,37 @@ namespace sound
 namespace lullaby
 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);
-	if (flac == nullptr)
-		throw love::Exception("Could not load FLAC file");
+	auto stream = (Stream *) pUserData;
+	int64 read = stream->read(pBufferOut, bytesToRead);
+	return std::max<int64>(0, read);
 }
 }
 
 
-FLACDecoder::~FLACDecoder()
+static drflac_bool32 onSeek(void* pUserData, int offset, drflac_seek_origin origin)
 {
 {
-	drflac_close(flac);
+	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;
 }
 }
 
 
-bool FLACDecoder::accepts(const std::string &ext)
+FLACDecoder::FLACDecoder(Stream *stream, int nbufferSize)
+	: Decoder(stream, nbufferSize)
 {
 {
-	// 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;
-	}
+	flac = drflac_open(onRead, onSeek, stream, nullptr);
+	if (flac == nullptr)
+		throw love::Exception("Could not load FLAC file");
+}
 
 
-	return false;
+FLACDecoder::~FLACDecoder()
+{
+	drflac_close(flac);
 }
 }
 
 
 love::sound::Decoder *FLACDecoder::clone()
 love::sound::Decoder *FLACDecoder::clone()
 {
 {
-	return new FLACDecoder(data.get(), bufferSize);
+	StrongRef<Stream> s(stream->clone(), Acquire::NORETAIN);
+	return new FLACDecoder(s, bufferSize);
 }
 }
 
 
 int FLACDecoder::decode()
 int FLACDecoder::decode()

+ 12 - 13
src/modules/sound/lullaby/FLACDecoder.h

@@ -22,7 +22,7 @@
 #define LOVE_SOUND_LULLABY_FLAC_DECODER_H
 #define LOVE_SOUND_LULLABY_FLAC_DECODER_H
 
 
 // LOVE
 // LOVE
-#include "common/Data.h"
+#include "common/Stream.h"
 #include "sound/Decoder.h"
 #include "sound/Decoder.h"
 
 
 #include "dr/dr_flac.h"
 #include "dr/dr_flac.h"
@@ -38,23 +38,22 @@ namespace lullaby
 class FLACDecoder : public Decoder
 class FLACDecoder : public Decoder
 {
 {
 public:
 public:
-	FLACDecoder(Data *data, int bufferSize);
+	FLACDecoder(Stream *stream, int bufferSize);
 	~FLACDecoder();
 	~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:
 private:
 	drflac *flac;
 	drflac *flac;
-}; // Decoder
+}; // FLACDecoder
 
 
 } // lullaby
 } // lullaby
 } // sound
 } // sound

+ 0 - 154
src/modules/sound/lullaby/GmeDecoder.cpp

@@ -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<short*>(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<long>(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

+ 122 - 27
src/modules/sound/lullaby/MP3Decoder.cpp

@@ -21,6 +21,7 @@
 #define DR_MP3_IMPLEMENTATION
 #define DR_MP3_IMPLEMENTATION
 #define DR_MP3_NO_STDIO
 #define DR_MP3_NO_STDIO
 #include "MP3Decoder.h"
 #include "MP3Decoder.h"
+#include "common/Exception.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -29,11 +30,121 @@ namespace sound
 namespace lullaby
 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<uint8> 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<int64>(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
 	// 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.");
 		throw love::Exception("Could not read mp3 data.");
 
 
 	sampleRate = mp3.sampleRate;
 	sampleRate = mp3.sampleRate;
@@ -43,25 +154,24 @@ MP3Decoder::MP3Decoder(Data *data, int bufferSize)
 	if (!drmp3_get_mp3_and_pcm_frame_count(&mp3, &mp3FrameCount, &pcmCount))
 	if (!drmp3_get_mp3_and_pcm_frame_count(&mp3, &mp3FrameCount, &pcmCount))
 	{
 	{
 		drmp3_uninit(&mp3);
 		drmp3_uninit(&mp3);
-		throw love::Exception("Could not calculate duration.");
+		throw love::Exception("Could not calculate mp3 duration.");
 	}
 	}
 	duration = ((double) pcmCount) / ((double) mp3.sampleRate);
 	duration = ((double) pcmCount) / ((double) mp3.sampleRate);
 
 
 	// create seek table
 	// 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()))
 	if (!drmp3_calculate_seek_points(&mp3, &mp3FrameInt, seekTable.data()))
 	{
 	{
 		drmp3_uninit(&mp3);
 		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
 	// bind seek table
 	if (!drmp3_bind_seek_table(&mp3, mp3FrameInt, seekTable.data()))
 	if (!drmp3_bind_seek_table(&mp3, mp3FrameInt, seekTable.data()))
 	{
 	{
 		drmp3_uninit(&mp3);
 		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);
 	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()
 love::sound::Decoder *MP3Decoder::clone()
 {
 {
-	return new MP3Decoder(data, bufferSize);
+	StrongRef<Stream> s(stream->clone(), Acquire::NORETAIN);
+	return new MP3Decoder(s, bufferSize);
 }
 }
 
 
 int MP3Decoder::decode()
 int MP3Decoder::decode()
@@ -105,7 +200,7 @@ int MP3Decoder::decode()
 
 
 bool MP3Decoder::seek(double s)
 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);
 	drmp3_bool32 success = drmp3_seek_to_pcm_frame(&mp3, targetSample);
 
 
 	if (success)
 	if (success)

+ 14 - 11
src/modules/sound/lullaby/MP3Decoder.h

@@ -22,7 +22,7 @@
 #define LOVE_SOUND_LULLABY_MP3_DECODER_H
 #define LOVE_SOUND_LULLABY_MP3_DECODER_H
 
 
 // LOVE
 // LOVE
-#include "common/Data.h"
+#include "common/Stream.h"
 #include "sound/Decoder.h"
 #include "sound/Decoder.h"
 
 
 // dr_mp3
 // dr_mp3
@@ -41,25 +41,28 @@ class MP3Decoder: public love::sound::Decoder
 {
 {
 public:
 public:
 
 
-	MP3Decoder(Data *data, int bufsize);
+	MP3Decoder(Stream *stream, int bufsize);
 	virtual ~MP3Decoder();
 	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:
 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
 	// MP3 handle
 	drmp3 mp3;
 	drmp3 mp3;
 	// Used for fast seeking
 	// Used for fast seeking
 	std::vector<drmp3_seek_point> seekTable;
 	std::vector<drmp3_seek_point> seekTable;
+	// Position of first MP3 frame found
+	int64 offset;
 
 
 	double duration;
 	double duration;
 }; // MP3Decoder
 }; // MP3Decoder

+ 27 - 27
src/modules/sound/lullaby/ModPlugDecoder.cpp

@@ -23,6 +23,7 @@
 #ifndef LOVE_NO_MODPLUG
 #ifndef LOVE_NO_MODPLUG
 
 
 #include "common/Exception.h"
 #include "common/Exception.h"
+#include "common/Data.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -31,12 +32,11 @@ namespace sound
 namespace lullaby
 namespace lullaby
 {
 {
 
 
-ModPlugDecoder::ModPlugDecoder(Data *data, int bufferSize)
-	: Decoder(data, bufferSize)
+ModPlugDecoder::ModPlugDecoder(Stream *stream, int bufferSize)
+	: Decoder(stream, bufferSize)
 	, plug(0)
 	, plug(0)
 	, duration(-2.0)
 	, duration(-2.0)
 {
 {
-
 	// Set some ModPlug settings.
 	// Set some ModPlug settings.
 	settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION;
 	settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION;
 	settings.mChannels = 2;
 	settings.mChannels = 2;
@@ -60,10 +60,29 @@ ModPlugDecoder::ModPlugDecoder(Data *data, int bufferSize)
 
 
 	ModPlug_SetSettings(&settings);
 	ModPlug_SetSettings(&settings);
 
 
+	// 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);
+
+		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.
 	// Load the module.
-	plug = ModPlug_Load(data->getData(), (int) data->getSize());
+	plug = ModPlug_Load(data->getData(), (int)data->getSize());
 
 
-	if (plug == 0)
+	if (plug == nullptr)
 		throw love::Exception("Could not load file with ModPlug.");
 		throw love::Exception("Could not load file with ModPlug.");
 
 
 	// set master volume for delicate ears
 	// set master volume for delicate ears
@@ -72,33 +91,14 @@ ModPlugDecoder::ModPlugDecoder(Data *data, int bufferSize)
 
 
 ModPlugDecoder::~ModPlugDecoder()
 ModPlugDecoder::~ModPlugDecoder()
 {
 {
-	if (plug != 0)
+	if (plug != nullptr)
 		ModPlug_Unload(plug);
 		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()
 love::sound::Decoder *ModPlugDecoder::clone()
 {
 {
-	return new ModPlugDecoder(data.get(), bufferSize);
+	StrongRef<Stream> s(stream->clone(), Acquire::NORETAIN);
+	return new ModPlugDecoder(s, bufferSize);
 }
 }
 
 
 int ModPlugDecoder::decode()
 int ModPlugDecoder::decode()

+ 13 - 13
src/modules/sound/lullaby/ModPlugDecoder.h

@@ -26,7 +26,7 @@
 #ifndef LOVE_NO_MODPLUG
 #ifndef LOVE_NO_MODPLUG
 
 
 // LOVE
 // LOVE
-#include "common/Data.h"
+#include "common/Stream.h"
 #include "sound/Decoder.h"
 #include "sound/Decoder.h"
 
 
 // libmodplug
 // libmodplug
@@ -47,28 +47,28 @@ class ModPlugDecoder : public Decoder
 {
 {
 public:
 public:
 
 
-	ModPlugDecoder(Data *data, int bufferSize);
+	ModPlugDecoder(Stream *stream, int bufferSize);
 	virtual ~ModPlugDecoder();
 	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:
 private:
 
 
+	StrongRef<Data> data;
+
 	ModPlugFile *plug;
 	ModPlugFile *plug;
 	ModPlug_Settings settings;
 	ModPlug_Settings settings;
 
 
 	double duration;
 	double duration;
 
 
-}; // Decoder
+}; // ModPlugDecoder
 
 
 } // lullaby
 } // lullaby
 } // sound
 } // sound

+ 14 - 34
src/modules/sound/lullaby/Sound.cpp

@@ -27,7 +27,6 @@
 
 
 #include "ModPlugDecoder.h"
 #include "ModPlugDecoder.h"
 #include "VorbisDecoder.h"
 #include "VorbisDecoder.h"
-#include "GmeDecoder.h"
 #include "WaveDecoder.h"
 #include "WaveDecoder.h"
 #include "FLACDecoder.h"
 #include "FLACDecoder.h"
 #include "MP3Decoder.h"
 #include "MP3Decoder.h"
@@ -38,21 +37,16 @@
 
 
 struct DecoderImpl
 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<typename DecoderType>
 template<typename DecoderType>
 DecoderImpl DecoderImplFor()
 DecoderImpl DecoderImplFor()
 {
 {
 	DecoderImpl decoderImpl;
 	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;
 	return decoderImpl;
 }
 }
@@ -77,43 +71,29 @@ const char *Sound::getName() const
 	return "love.sound.lullaby";
 	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<DecoderImpl> possibleDecoders = {
 	std::vector<DecoderImpl> possibleDecoders = {
-#ifndef LOVE_NO_MODPLUG
-		DecoderImplFor<ModPlugDecoder>(),
-#endif // LOVE_NO_MODPLUG
-		DecoderImplFor<MP3Decoder>(),
+		DecoderImplFor<WaveDecoder>(),
+		DecoderImplFor<FLACDecoder>(),
 		DecoderImplFor<VorbisDecoder>(),
 		DecoderImplFor<VorbisDecoder>(),
-#ifdef LOVE_SUPPORT_GME
-		DecoderImplFor<GmeDecoder>(),
-#endif // LOVE_SUPPORT_GME
 #ifdef LOVE_SUPPORT_COREAUDIO
 #ifdef LOVE_SUPPORT_COREAUDIO
 		DecoderImplFor<CoreAudioDecoder>(),
 		DecoderImplFor<CoreAudioDecoder>(),
 #endif
 #endif
-		DecoderImplFor<WaveDecoder>(),
-		DecoderImplFor<FLACDecoder>(),
-		// DecoderImplFor<OtherDecoder>(),
+		DecoderImplFor<MP3Decoder>(),
+#ifndef LOVE_NO_MODPLUG
+		DecoderImplFor<ModPlugDecoder>(), // 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;
 	std::stringstream decodingErrors;
 	decodingErrors << "Failed to determine file type:\n";
 	decodingErrors << "Failed to determine file type:\n";
 	for (DecoderImpl &possibleDecoder : possibleDecoders)
 	for (DecoderImpl &possibleDecoder : possibleDecoders)
 	{
 	{
 		try
 		try
 		{
 		{
-			sound::Decoder *decoder = possibleDecoder.create(data, bufferSize);
+			stream->seek(0);
+			sound::Decoder *decoder = possibleDecoder.create(stream, bufferSize);
 			return decoder;
 			return decoder;
 		}
 		}
 		catch (love::Exception &e)
 		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
 	// Unreachable, but here to prevent (possible) warnings
 	return nullptr;
 	return nullptr;

+ 2 - 9
src/modules/sound/lullaby/Sound.h

@@ -45,21 +45,14 @@ class Sound : public love::sound::Sound
 {
 {
 public:
 public:
 
 
-	/**
-	 * Constructor. Initializes relevant libraries.
-	 **/
 	Sound();
 	Sound();
-
-	/**
-	 * Destructor. Deinitializes relevant libraries.
-	 **/
 	virtual ~Sound();
 	virtual ~Sound();
 
 
 	/// @copydoc love::Module::getName
 	/// @copydoc love::Module::getName
-	const char *getName() const;
+	const char *getName() const override;
 
 
 	/// @copydoc love::sound::Sound::newDecoder
 	/// @copydoc love::sound::Sound::newDecoder
-	sound::Decoder *newDecoder(love::filesystem::FileData *file, int bufferSize);
+	sound::Decoder *newDecoder(Stream *stream, int bufferSize) override;
 
 
 }; // Sound
 }; // Sound
 
 

+ 33 - 109
src/modules/sound/lullaby/VorbisDecoder.cpp

@@ -31,132 +31,65 @@ namespace sound
 namespace lullaby
 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)
 	// Does nothing (handled elsewhere)
 	return 1;
 	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)
-
-	// Get the data in the right format
-	vorbisData = (SOggFile *) datasource;
+	auto stream = (Stream *) datasource;
+	auto origin = Stream::SEEKORIGIN_BEGIN;
 
 
-	// Goto where we wish to seek to
 	switch (whence)
 	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;
 		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;
 		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;
 		break;
 	default:
 	default:
 		break;
 		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
  * END CALLBACK FUNCTIONS
  **/
  **/
 
 
-VorbisDecoder::VorbisDecoder(Data *data, int bufferSize)
-	: Decoder(data, bufferSize)
+VorbisDecoder::VorbisDecoder(Stream *stream, int bufferSize)
+	: Decoder(stream, bufferSize)
 	, duration(-2.0)
 	, 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
 	// 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");
 		throw love::Exception("Could not read Ogg bitstream");
 
 
-	// Get info and comments
 	vorbisInfo = ov_info(&handle, -1);
 	vorbisInfo = ov_info(&handle, -1);
-	vorbisComment = ov_comment(&handle, -1);
 }
 }
 
 
 VorbisDecoder::~VorbisDecoder()
 VorbisDecoder::~VorbisDecoder()
@@ -164,31 +97,22 @@ VorbisDecoder::~VorbisDecoder()
 	ov_clear(&handle);
 	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()
 love::sound::Decoder *VorbisDecoder::clone()
 {
 {
-	return new VorbisDecoder(data.get(), bufferSize);
+	StrongRef<Stream> s(stream->clone(), Acquire::NORETAIN);
+	return new VorbisDecoder(s, bufferSize);
 }
 }
 
 
 int VorbisDecoder::decode()
 int VorbisDecoder::decode()
 {
 {
 	int size = 0;
 	int size = 0;
 
 
+#ifdef LOVE_BIG_ENDIAN
+	int endian = 1;
+#else
+	int endian = 0;
+#endif
+
 	while (size < bufferSize)
 	while (size < bufferSize)
 	{
 	{
 		long result = ov_read(&handle, (char *) buffer + size, bufferSize - size, endian, (getBitDepth() == 16 ? 2 : 1), 1, 0);
 		long result = ov_read(&handle, (char *) buffer + size, bufferSize - size, endian, (getBitDepth() == 16 ? 2 : 1), 1, 0);

+ 15 - 27
src/modules/sound/lullaby/VorbisDecoder.h

@@ -22,7 +22,7 @@
 #define LOVE_SOUND_LULLABY_VORBIS_DECODER_H
 #define LOVE_SOUND_LULLABY_VORBIS_DECODER_H
 
 
 // LOVE
 // LOVE
-#include "common/Data.h"
+#include "common/Stream.h"
 #include "common/int.h"
 #include "common/int.h"
 #include "sound/Decoder.h"
 #include "sound/Decoder.h"
 
 
@@ -38,41 +38,29 @@ namespace sound
 namespace lullaby
 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
 class VorbisDecoder : public Decoder
 {
 {
 public:
 public:
 
 
-	VorbisDecoder(Data *data, int bufferSize);
+	VorbisDecoder(Stream *stream, int bufferSize);
 	virtual ~VorbisDecoder();
 	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:
 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;
 	double duration;
+
 }; // VorbisDecoder
 }; // VorbisDecoder
 
 
 } // lullaby
 } // lullaby

+ 15 - 38
src/modules/sound/lullaby/WaveDecoder.cpp

@@ -34,40 +34,32 @@ namespace lullaby
 // Callbacks
 // Callbacks
 static wuff_sint32 read_callback(void *userdata, wuff_uint8 *buffer, size_t *size)
 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;
 	return WUFF_SUCCESS;
 }
 }
 
 
 static wuff_sint32 seek_callback(void *userdata, wuff_uint64 offset)
 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;
 	return WUFF_SUCCESS;
 }
 }
 
 
 static wuff_sint32 tell_callback(void *userdata, wuff_uint64 *offset)
 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;
 	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)
 	if (wuff_status < 0)
 		throw love::Exception("Could not open WAVE");
 		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");
 			throw love::Exception("Could not retrieve WAVE stream info");
 
 
 		if (info.channels > 2)
 		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)
 		if (info.format != WUFF_FORMAT_PCM_U8 && info.format != WUFF_FORMAT_PCM_S16)
 		{
 		{
 			wuff_status = wuff_format(handle, WUFF_FORMAT_PCM_S16);
 			wuff_status = wuff_format(handle, WUFF_FORMAT_PCM_S16);
 			if (wuff_status < 0)
 			if (wuff_status < 0)
-				throw love::Exception("Could not set output format");
+				throw love::Exception("Could not set WAVE output format");
 		}
 		}
 	}
 	}
 	catch (love::Exception &)
 	catch (love::Exception &)
@@ -99,25 +91,10 @@ WaveDecoder::~WaveDecoder()
 	wuff_close(handle);
 	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()
 love::sound::Decoder *WaveDecoder::clone()
 {
 {
-	return new WaveDecoder(data.get(), bufferSize);
+	StrongRef<Stream> s(stream->clone(), Acquire::NORETAIN);
+	return new WaveDecoder(s, bufferSize);
 }
 }
 
 
 int WaveDecoder::decode()
 int WaveDecoder::decode()

+ 11 - 22
src/modules/sound/lullaby/WaveDecoder.h

@@ -22,7 +22,7 @@
 #define LOVE_SOUND_LULLABY_WAVE_DECODER_H
 #define LOVE_SOUND_LULLABY_WAVE_DECODER_H
 
 
 // LOVE
 // LOVE
-#include "common/Data.h"
+#include "common/Stream.h"
 #include "sound/Decoder.h"
 #include "sound/Decoder.h"
 
 
 #include "libraries/Wuff/wuff.h"
 #include "libraries/Wuff/wuff.h"
@@ -34,36 +34,25 @@ namespace sound
 namespace lullaby
 namespace lullaby
 {
 {
 
 
-// Struct for handling data
-struct WaveFile
-{
-	char *data;
-	size_t size;
-	size_t offset;
-};
-
 class WaveDecoder : public Decoder
 class WaveDecoder : public Decoder
 {
 {
 public:
 public:
 
 
-	WaveDecoder(Data *data, int bufferSize);
+	WaveDecoder(Stream *stream, int bufferSize);
 	virtual ~WaveDecoder();
 	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:
 private:
 
 
-	WaveFile dataFile;
 	wuff_handle *handle;
 	wuff_handle *handle;
 	wuff_info info;
 	wuff_info info;
 
 

+ 40 - 7
src/modules/sound/wrap_Sound.cpp

@@ -21,6 +21,7 @@
 #include "wrap_Sound.h"
 #include "wrap_Sound.h"
 
 
 #include "filesystem/wrap_Filesystem.h"
 #include "filesystem/wrap_Filesystem.h"
+#include "data/DataStream.h"
 
 
 // Implementations.
 // Implementations.
 #include "lullaby/Sound.h"
 #include "lullaby/Sound.h"
@@ -34,18 +35,50 @@ namespace sound
 
 
 int w_newDecoder(lua_State *L)
 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<love::filesystem::FileData> 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<Data>(L, 1);
+		luax_catchexcept(L, [&]() { stream = new data::DataStream(data); });
+	}
+	else
+	{
+		stream = luax_checktype<Stream>(L, 1);
+		stream->retain();
+	}	
 
 
 	Decoder *t = nullptr;
 	Decoder *t = nullptr;
 	luax_catchexcept(L,
 	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);
 	luax_pushtype(L, t);
 	t->release();
 	t->release();
 	return 1;
 	return 1;

+ 1 - 1
src/modules/system/System.cpp

@@ -164,7 +164,7 @@ bool System::openURL(const std::string &url) const
 
 
 #endif
 #endif
 
 
-	return (int) result > 32;
+	return (ptrdiff_t) result > 32;
 
 
 #endif
 #endif
 }
 }

+ 13 - 0
src/modules/system/System.h

@@ -114,6 +114,19 @@ public:
 	 **/
 	 **/
 	bool hasBackgroundMusic() const;
 	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<std::string> getPreferredLocales() const = 0;
+
 	static bool getConstant(const char *in, PowerState &out);
 	static bool getConstant(const char *in, PowerState &out);
 	static bool getConstant(PowerState in, const char *&out);
 	static bool getConstant(PowerState in, const char *&out);
 
 

+ 26 - 0
src/modules/system/sdl/System.cpp

@@ -25,6 +25,8 @@
 // SDL
 // SDL
 #include <SDL_clipboard.h>
 #include <SDL_clipboard.h>
 #include <SDL_cpuinfo.h>
 #include <SDL_cpuinfo.h>
+#include <SDL_locale.h>
+#include <SDL_version.h>
 
 
 namespace love
 namespace love
 {
 {
@@ -90,6 +92,30 @@ love::system::System::PowerState System::getPowerInfo(int &seconds, int &percent
 	return state;
 	return state;
 }
 }
 
 
+std::vector<std::string> System::getPreferredLocales() const
+{
+	std::vector<std::string> 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<System::PowerState, SDL_PowerState, System::POWER_MAX_ENUM>::Entry System::powerEntries[] =
 EnumMap<System::PowerState, SDL_PowerState, System::POWER_MAX_ENUM>::Entry System::powerEntries[] =
 {
 {
 	{System::POWER_UNKNOWN, SDL_POWERSTATE_UNKNOWN},
 	{System::POWER_UNKNOWN, SDL_POWERSTATE_UNKNOWN},

+ 1 - 0
src/modules/system/sdl/System.h

@@ -51,6 +51,7 @@ public:
 	std::string getClipboardText() const;
 	std::string getClipboardText() const;
 
 
 	PowerState getPowerInfo(int &seconds, int &percent) const;
 	PowerState getPowerInfo(int &seconds, int &percent) const;
+	std::vector<std::string> getPreferredLocales() const override;
 
 
 private:
 private:
 
 

+ 17 - 0
src/modules/system/wrap_System.cpp

@@ -101,6 +101,22 @@ int w_hasBackgroundMusic(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_getPreferredLocales(lua_State* L)
+{
+	int i = 1;
+	std::vector<std::string> 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[] =
 static const luaL_Reg functions[] =
 {
 {
 	{ "getOS", w_getOS },
 	{ "getOS", w_getOS },
@@ -111,6 +127,7 @@ static const luaL_Reg functions[] =
 	{ "openURL", w_openURL },
 	{ "openURL", w_openURL },
 	{ "vibrate", w_vibrate },
 	{ "vibrate", w_vibrate },
 	{ "hasBackgroundMusic", w_hasBackgroundMusic },
 	{ "hasBackgroundMusic", w_hasBackgroundMusic },
+	{ "getPreferredLocales", w_getPreferredLocales },
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };
 
 

Some files were not shown because too many files changed in this diff