Browse Source

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

niki 3 years ago
parent
commit
4c415479c0
55 changed files with 2096 additions and 536 deletions
  1. 6 0
      CMakeLists.txt
  2. 1 1
      license.txt
  3. 37 1
      platform/xcode/liblove.xcodeproj/project.pbxproj
  4. 1 1
      platform/xcode/love.xcodeproj/project.pbxproj
  5. 191 131
      src/common/android.cpp
  6. 21 7
      src/common/android.h
  7. 171 77
      src/libraries/dr/dr_flac.h
  8. 5 0
      src/modules/audio/Audio.cpp
  9. 15 0
      src/modules/audio/Audio.h
  10. 9 0
      src/modules/audio/null/Audio.cpp
  11. 3 0
      src/modules/audio/null/Audio.h
  12. 65 5
      src/modules/audio/openal/Audio.cpp
  13. 5 0
      src/modules/audio/openal/Audio.h
  14. 50 2
      src/modules/audio/openal/Pool.cpp
  15. 7 1
      src/modules/audio/openal/Pool.h
  16. 73 21
      src/modules/audio/wrap_Audio.cpp
  17. 20 1
      src/modules/filesystem/NativeFile.cpp
  18. 10 26
      src/modules/filesystem/physfs/Filesystem.cpp
  19. 26 30
      src/modules/filesystem/wrap_Filesystem.cpp
  20. 3 2
      src/modules/graphics/Buffer.cpp
  21. 5 4
      src/modules/graphics/Buffer.h
  22. 166 1
      src/modules/graphics/Graphics.cpp
  23. 37 3
      src/modules/graphics/Graphics.h
  24. 229 0
      src/modules/graphics/GraphicsReadback.cpp
  25. 112 0
      src/modules/graphics/GraphicsReadback.h
  26. 0 41
      src/modules/graphics/Texture.cpp
  27. 0 3
      src/modules/graphics/Texture.h
  28. 1 1
      src/modules/graphics/metal/Buffer.h
  29. 38 24
      src/modules/graphics/metal/Buffer.mm
  30. 4 0
      src/modules/graphics/metal/Graphics.h
  31. 14 16
      src/modules/graphics/metal/Graphics.mm
  32. 54 0
      src/modules/graphics/metal/GraphicsReadback.h
  33. 143 0
      src/modules/graphics/metal/GraphicsReadback.mm
  34. 0 1
      src/modules/graphics/metal/Texture.h
  35. 0 46
      src/modules/graphics/metal/Texture.mm
  36. 35 8
      src/modules/graphics/opengl/Buffer.cpp
  37. 3 1
      src/modules/graphics/opengl/Buffer.h
  38. 16 0
      src/modules/graphics/opengl/FenceSync.cpp
  39. 1 0
      src/modules/graphics/opengl/FenceSync.h
  40. 15 16
      src/modules/graphics/opengl/Graphics.cpp
  41. 4 0
      src/modules/graphics/opengl/Graphics.h
  42. 126 0
      src/modules/graphics/opengl/GraphicsReadback.cpp
  43. 56 0
      src/modules/graphics/opengl/GraphicsReadback.h
  44. 1 1
      src/modules/graphics/opengl/OpenGL.cpp
  45. 33 51
      src/modules/graphics/opengl/Texture.cpp
  46. 2 2
      src/modules/graphics/opengl/Texture.h
  47. 4 4
      src/modules/graphics/vertex.cpp
  48. 1 1
      src/modules/graphics/vertex.h
  49. 126 0
      src/modules/graphics/wrap_Graphics.cpp
  50. 1 0
      src/modules/graphics/wrap_Graphics.h
  51. 95 0
      src/modules/graphics/wrap_GraphicsReadback.cpp
  52. 36 0
      src/modules/graphics/wrap_GraphicsReadback.h
  53. 12 6
      src/modules/graphics/wrap_Texture.cpp
  54. 2 0
      src/modules/keyboard/sdl/Keyboard.cpp
  55. 5 0
      src/modules/love/callbacks.lua

+ 6 - 0
CMakeLists.txt

@@ -515,6 +515,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT
 	src/modules/graphics/Font.h
 	src/modules/graphics/Font.h
 	src/modules/graphics/Graphics.cpp
 	src/modules/graphics/Graphics.cpp
 	src/modules/graphics/Graphics.h
 	src/modules/graphics/Graphics.h
+	src/modules/graphics/GraphicsReadback.cpp
+	src/modules/graphics/GraphicsReadback.h
 	src/modules/graphics/Mesh.cpp
 	src/modules/graphics/Mesh.cpp
 	src/modules/graphics/Mesh.h
 	src/modules/graphics/Mesh.h
 	src/modules/graphics/ParticleSystem.cpp
 	src/modules/graphics/ParticleSystem.cpp
@@ -550,6 +552,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT
 	src/modules/graphics/wrap_Font.h
 	src/modules/graphics/wrap_Font.h
 	src/modules/graphics/wrap_Graphics.cpp
 	src/modules/graphics/wrap_Graphics.cpp
 	src/modules/graphics/wrap_Graphics.h
 	src/modules/graphics/wrap_Graphics.h
+	src/modules/graphics/wrap_GraphicsReadback.cpp
+	src/modules/graphics/wrap_GraphicsReadback.h
 	src/modules/graphics/wrap_Mesh.cpp
 	src/modules/graphics/wrap_Mesh.cpp
 	src/modules/graphics/wrap_Mesh.h
 	src/modules/graphics/wrap_Mesh.h
 	src/modules/graphics/wrap_ParticleSystem.cpp
 	src/modules/graphics/wrap_ParticleSystem.cpp
@@ -575,6 +579,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL
 	src/modules/graphics/opengl/FenceSync.h
 	src/modules/graphics/opengl/FenceSync.h
 	src/modules/graphics/opengl/Graphics.cpp
 	src/modules/graphics/opengl/Graphics.cpp
 	src/modules/graphics/opengl/Graphics.h
 	src/modules/graphics/opengl/Graphics.h
+	src/modules/graphics/opengl/GraphicsReadback.cpp
+	src/modules/graphics/opengl/GraphicsReadback.h
 	src/modules/graphics/opengl/OpenGL.cpp
 	src/modules/graphics/opengl/OpenGL.cpp
 	src/modules/graphics/opengl/OpenGL.h
 	src/modules/graphics/opengl/OpenGL.h
 	src/modules/graphics/opengl/Shader.cpp
 	src/modules/graphics/opengl/Shader.cpp

+ 1 - 1
license.txt

@@ -88,7 +88,7 @@ This distribution contains code from the following projects (full license text b
 
 
  - dr_flac
  - dr_flac
 	Website: https://github.com/mackron/dr_libs
 	Website: https://github.com/mackron/dr_libs
-	Source download: https://github.com/mackron/dr_libs/blob/c5e5355/dr_flac.h
+	Source download: https://github.com/mackron/dr_libs/blob/15f37e3/dr_flac.h
 	License: MIT/Expat
 	License: MIT/Expat
 	Copyright 2018 David Reid
 	Copyright 2018 David Reid
 
 

+ 37 - 1
platform/xcode/liblove.xcodeproj/project.pbxproj

@@ -823,6 +823,8 @@
 		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 */; };
+		FA6BDF89280B62A000240F2A /* GraphicsReadback.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF88280B62A000240F2A /* GraphicsReadback.mm */; };
+		FA6BDF8A280B62A000240F2A /* GraphicsReadback.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF88280B62A000240F2A /* GraphicsReadback.mm */; };
 		FA6BDF8E281219E900240F2A /* DataStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF8C281219E900240F2A /* DataStream.cpp */; };
 		FA6BDF8E281219E900240F2A /* DataStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6BDF8C281219E900240F2A /* DataStream.cpp */; };
 		FA6BDF8F281219E900240F2A /* 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 */; };
 		FA6BDF90281219E900240F2A /* DataStream.h in Headers */ = {isa = PBXBuildFile; fileRef = FA6BDF8D281219E900240F2A /* DataStream.h */; };
@@ -832,6 +834,14 @@
 		FA7E9207277E120900C24CB2 /* theora.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA7E9206277E120900C24CB2 /* theora.xcframework */; };
 		FA7E9207277E120900C24CB2 /* theora.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA7E9206277E120900C24CB2 /* theora.xcframework */; };
 		FA84DE612778D7F3002674C6 /* SpirvIntrinsics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */; };
 		FA84DE612778D7F3002674C6 /* SpirvIntrinsics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */; };
 		FA84DE622778D7F3002674C6 /* SpirvIntrinsics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */; };
 		FA84DE622778D7F3002674C6 /* SpirvIntrinsics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */; };
+		FA84DE6627791C36002674C6 /* GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6427791C36002674C6 /* GraphicsReadback.cpp */; };
+		FA84DE6727791C36002674C6 /* GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6427791C36002674C6 /* GraphicsReadback.cpp */; };
+		FA84DE6827791C36002674C6 /* GraphicsReadback.h in Headers */ = {isa = PBXBuildFile; fileRef = FA84DE6527791C36002674C6 /* GraphicsReadback.h */; };
+		FA84DE6B277943F6002674C6 /* GraphicsReadback.h in Headers */ = {isa = PBXBuildFile; fileRef = FA84DE69277943F6002674C6 /* GraphicsReadback.h */; };
+		FA84DE6C277943F6002674C6 /* GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6A277943F6002674C6 /* GraphicsReadback.cpp */; };
+		FA84DE6D277943F6002674C6 /* GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6A277943F6002674C6 /* GraphicsReadback.cpp */; };
+		FA84DE7127795E22002674C6 /* wrap_GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6F27795E22002674C6 /* wrap_GraphicsReadback.cpp */; };
+		FA84DE7227795E22002674C6 /* wrap_GraphicsReadback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE6F27795E22002674C6 /* wrap_GraphicsReadback.cpp */; };
 		FA84DE76277CB3D5002674C6 /* SDL2.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE75277CB3D4002674C6 /* SDL2.xcframework */; };
 		FA84DE76277CB3D5002674C6 /* SDL2.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE75277CB3D4002674C6 /* SDL2.xcframework */; };
 		FA84DE7A277D4C88002674C6 /* modplug.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE79277D4C88002674C6 /* modplug.xcframework */; };
 		FA84DE7A277D4C88002674C6 /* modplug.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE79277D4C88002674C6 /* modplug.xcframework */; };
 		FA84DE7C277E045E002674C6 /* ogg.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE7B277E045E002674C6 /* ogg.xcframework */; };
 		FA84DE7C277E045E002674C6 /* ogg.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE7B277E045E002674C6 /* ogg.xcframework */; };
@@ -1907,6 +1917,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>"; };
+		FA6BDF88280B62A000240F2A /* GraphicsReadback.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = GraphicsReadback.mm; sourceTree = "<group>"; };
+		FA6BDF8B280B62B600240F2A /* GraphicsReadback.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GraphicsReadback.h; sourceTree = "<group>"; };
 		FA6BDF8C281219E900240F2A /* DataStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DataStream.cpp; 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>"; };
 		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>"; };
@@ -1917,6 +1929,12 @@
 		FA84DE5E2778D7DC002674C6 /* glslang_c_interface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glslang_c_interface.h; sourceTree = "<group>"; };
 		FA84DE5E2778D7DC002674C6 /* glslang_c_interface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glslang_c_interface.h; sourceTree = "<group>"; };
 		FA84DE5F2778D7DC002674C6 /* glslang_c_shader_types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glslang_c_shader_types.h; sourceTree = "<group>"; };
 		FA84DE5F2778D7DC002674C6 /* glslang_c_shader_types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glslang_c_shader_types.h; sourceTree = "<group>"; };
 		FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpirvIntrinsics.cpp; sourceTree = "<group>"; };
 		FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpirvIntrinsics.cpp; sourceTree = "<group>"; };
+		FA84DE6427791C36002674C6 /* GraphicsReadback.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = GraphicsReadback.cpp; sourceTree = "<group>"; };
+		FA84DE6527791C36002674C6 /* GraphicsReadback.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GraphicsReadback.h; sourceTree = "<group>"; };
+		FA84DE69277943F6002674C6 /* GraphicsReadback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GraphicsReadback.h; sourceTree = "<group>"; };
+		FA84DE6A277943F6002674C6 /* GraphicsReadback.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GraphicsReadback.cpp; sourceTree = "<group>"; };
+		FA84DE6E27795E22002674C6 /* wrap_GraphicsReadback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_GraphicsReadback.h; sourceTree = "<group>"; };
+		FA84DE6F27795E22002674C6 /* wrap_GraphicsReadback.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_GraphicsReadback.cpp; sourceTree = "<group>"; };
 		FA84DE75277CB3D4002674C6 /* SDL2.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = SDL2.xcframework; path = ios/libraries/SDL2.xcframework; sourceTree = "<group>"; };
 		FA84DE75277CB3D4002674C6 /* SDL2.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = SDL2.xcframework; path = ios/libraries/SDL2.xcframework; sourceTree = "<group>"; };
 		FA84DE79277D4C88002674C6 /* modplug.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = modplug.xcframework; path = ios/libraries/modplug.xcframework; sourceTree = "<group>"; };
 		FA84DE79277D4C88002674C6 /* modplug.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = modplug.xcframework; path = ios/libraries/modplug.xcframework; sourceTree = "<group>"; };
 		FA84DE7B277E045E002674C6 /* ogg.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ogg.xcframework; path = ios/libraries/ogg.xcframework; sourceTree = "<group>"; };
 		FA84DE7B277E045E002674C6 /* ogg.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ogg.xcframework; path = ios/libraries/ogg.xcframework; sourceTree = "<group>"; };
@@ -2819,6 +2837,8 @@
 				FA1BA09C1E16CFCE00AA2803 /* Font.h */,
 				FA1BA09C1E16CFCE00AA2803 /* Font.h */,
 				FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */,
 				FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */,
 				FA0B7B8B1A95902C000E1D17 /* Graphics.h */,
 				FA0B7B8B1A95902C000E1D17 /* Graphics.h */,
+				FA84DE6427791C36002674C6 /* GraphicsReadback.cpp */,
+				FA84DE6527791C36002674C6 /* GraphicsReadback.h */,
 				FADF54231E3DA5BA00012CC0 /* Mesh.cpp */,
 				FADF54231E3DA5BA00012CC0 /* Mesh.cpp */,
 				FADF54241E3DA5BA00012CC0 /* Mesh.h */,
 				FADF54241E3DA5BA00012CC0 /* Mesh.h */,
 				FA18CECC23DBC6E000263725 /* metal */,
 				FA18CECC23DBC6E000263725 /* metal */,
@@ -2857,6 +2877,8 @@
 				FADF54391E3DAFF700012CC0 /* wrap_Graphics.cpp */,
 				FADF54391E3DAFF700012CC0 /* wrap_Graphics.cpp */,
 				FADF543A1E3DAFF700012CC0 /* wrap_Graphics.h */,
 				FADF543A1E3DAFF700012CC0 /* wrap_Graphics.h */,
 				FADF54371E3DAFBA00012CC0 /* wrap_Graphics.lua */,
 				FADF54371E3DAFBA00012CC0 /* wrap_Graphics.lua */,
+				FA84DE6F27795E22002674C6 /* wrap_GraphicsReadback.cpp */,
+				FA84DE6E27795E22002674C6 /* wrap_GraphicsReadback.h */,
 				FADF54281E3DAADA00012CC0 /* wrap_Mesh.cpp */,
 				FADF54281E3DAADA00012CC0 /* wrap_Mesh.cpp */,
 				FADF54291E3DAADA00012CC0 /* wrap_Mesh.h */,
 				FADF54291E3DAADA00012CC0 /* wrap_Mesh.h */,
 				FADF541E1E3DA52C00012CC0 /* wrap_ParticleSystem.cpp */,
 				FADF541E1E3DA52C00012CC0 /* wrap_ParticleSystem.cpp */,
@@ -2887,6 +2909,8 @@
 				FA28EBD41E352DB5003446F4 /* FenceSync.h */,
 				FA28EBD41E352DB5003446F4 /* FenceSync.h */,
 				FA0B7B911A95902C000E1D17 /* Graphics.cpp */,
 				FA0B7B911A95902C000E1D17 /* Graphics.cpp */,
 				FA0B7B921A95902C000E1D17 /* Graphics.h */,
 				FA0B7B921A95902C000E1D17 /* Graphics.h */,
+				FA84DE6A277943F6002674C6 /* GraphicsReadback.cpp */,
+				FA84DE69277943F6002674C6 /* GraphicsReadback.h */,
 				FA0B7B971A95902C000E1D17 /* OpenGL.cpp */,
 				FA0B7B971A95902C000E1D17 /* OpenGL.cpp */,
 				FA0B7B981A95902C000E1D17 /* OpenGL.h */,
 				FA0B7B981A95902C000E1D17 /* OpenGL.h */,
 				FA0B7B9D1A95902C000E1D17 /* Shader.cpp */,
 				FA0B7B9D1A95902C000E1D17 /* Shader.cpp */,
@@ -3354,6 +3378,8 @@
 				FA18CEE823DBC8D400263725 /* Buffer.mm */,
 				FA18CEE823DBC8D400263725 /* Buffer.mm */,
 				FA18CED523DBC6E000263725 /* Graphics.h */,
 				FA18CED523DBC6E000263725 /* Graphics.h */,
 				FA18CED323DBC6E000263725 /* Graphics.mm */,
 				FA18CED323DBC6E000263725 /* Graphics.mm */,
+				FA6BDF8B280B62B600240F2A /* GraphicsReadback.h */,
+				FA6BDF88280B62A000240F2A /* GraphicsReadback.mm */,
 				FA18CED423DBC6E000263725 /* Metal.h */,
 				FA18CED423DBC6E000263725 /* Metal.h */,
 				FA18CED023DBC6E000263725 /* Metal.mm */,
 				FA18CED023DBC6E000263725 /* Metal.mm */,
 				FA18CECD23DBC6E000263725 /* Shader.h */,
 				FA18CECD23DBC6E000263725 /* Shader.h */,
@@ -4338,6 +4364,7 @@
 				FABDA9C92552448300B5C523 /* b2_distance.h in Headers */,
 				FABDA9C92552448300B5C523 /* b2_distance.h in Headers */,
 				FA24348921D401CB00B8918A /* pch.h in Headers */,
 				FA24348921D401CB00B8918A /* pch.h in Headers */,
 				FAF140991E20934C00F898D2 /* PpTokens.h in Headers */,
 				FAF140991E20934C00F898D2 /* PpTokens.h in Headers */,
+				FA84DE6827791C36002674C6 /* GraphicsReadback.h in Headers */,
 				FAF1405B1E20934C00F898D2 /* InfoSink.h in Headers */,
 				FAF1405B1E20934C00F898D2 /* InfoSink.h in Headers */,
 				FA18CF2423DCF67900263725 /* spirv_cpp.hpp in Headers */,
 				FA18CF2423DCF67900263725 /* spirv_cpp.hpp in Headers */,
 				FA0B7B321A958EA3000E1D17 /* wuff.h in Headers */,
 				FA0B7B321A958EA3000E1D17 /* wuff.h in Headers */,
@@ -4394,6 +4421,7 @@
 				FAC7CD961FE755B4006A60C7 /* lz4opt.h in Headers */,
 				FAC7CD961FE755B4006A60C7 /* lz4opt.h in Headers */,
 				FA9D8DD31DEB56C3002CD881 /* pixelformat.h in Headers */,
 				FA9D8DD31DEB56C3002CD881 /* pixelformat.h in Headers */,
 				FABDA9DE2552448300B5C523 /* b2_prismatic_joint.h in Headers */,
 				FABDA9DE2552448300B5C523 /* b2_prismatic_joint.h in Headers */,
+				FA84DE6B277943F6002674C6 /* GraphicsReadback.h in Headers */,
 				FAF140A61E20934C00F898D2 /* ScanContext.h in Headers */,
 				FAF140A61E20934C00F898D2 /* ScanContext.h in Headers */,
 				FABDA97B2552448200B5C523 /* b2_chain_polygon_contact.h in Headers */,
 				FABDA97B2552448200B5C523 /* b2_chain_polygon_contact.h in Headers */,
 				FA0B7E4A1A95902C000E1D17 /* wrap_DistanceJoint.h in Headers */,
 				FA0B7E4A1A95902C000E1D17 /* wrap_DistanceJoint.h in Headers */,
@@ -4468,7 +4496,7 @@
 		08FB7793FE84155DC02AAC07 /* Project object */ = {
 		08FB7793FE84155DC02AAC07 /* Project object */ = {
 			isa = PBXProject;
 			isa = PBXProject;
 			attributes = {
 			attributes = {
-				LastUpgradeCheck = 1310;
+				LastUpgradeCheck = 1340;
 				TargetAttributes = {
 				TargetAttributes = {
 					FA0B78DC1A958B90000E1D17 = {
 					FA0B78DC1A958B90000E1D17 = {
 						CreatedOnToolsVersion = 6.1.1;
 						CreatedOnToolsVersion = 6.1.1;
@@ -4548,6 +4576,7 @@
 				FA18CF4723DD1A8100263725 /* ShaderStage.mm in Sources */,
 				FA18CF4723DD1A8100263725 /* ShaderStage.mm in Sources */,
 				FA0B7ECC1A95902C000E1D17 /* wrap_Channel.cpp in Sources */,
 				FA0B7ECC1A95902C000E1D17 /* wrap_Channel.cpp in Sources */,
 				FA0B7E6D1A95902C000E1D17 /* wrap_RevoluteJoint.cpp in Sources */,
 				FA0B7E6D1A95902C000E1D17 /* wrap_RevoluteJoint.cpp in Sources */,
+				FA84DE7227795E22002674C6 /* wrap_GraphicsReadback.cpp in Sources */,
 				FACA02FA1F5E397B0084B28F /* DataModule.cpp in Sources */,
 				FACA02FA1F5E397B0084B28F /* DataModule.cpp in Sources */,
 				FA0B7E641A95902C000E1D17 /* wrap_PolygonShape.cpp in Sources */,
 				FA0B7E641A95902C000E1D17 /* wrap_PolygonShape.cpp in Sources */,
 				FA4F2C031DE936C200CA37D7 /* auxiliar.c in Sources */,
 				FA4F2C031DE936C200CA37D7 /* auxiliar.c in Sources */,
@@ -4619,6 +4648,7 @@
 				FADF53FE1E3D74F200012CC0 /* Text.cpp in Sources */,
 				FADF53FE1E3D74F200012CC0 /* Text.cpp in Sources */,
 				FA0B7D191A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */,
 				FA0B7D191A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */,
 				FAC271E723B5B5B400C200D3 /* renderstate.cpp in Sources */,
 				FAC271E723B5B5B400C200D3 /* renderstate.cpp in Sources */,
+				FA84DE6727791C36002674C6 /* GraphicsReadback.cpp in Sources */,
 				FA0B7CFB1A95902C000E1D17 /* Filesystem.cpp in Sources */,
 				FA0B7CFB1A95902C000E1D17 /* Filesystem.cpp in Sources */,
 				FABDA9F82552448300B5C523 /* b2_dynamic_tree.cpp in Sources */,
 				FABDA9F82552448300B5C523 /* b2_dynamic_tree.cpp in Sources */,
 				FABDA98B2552448300B5C523 /* b2_revolute_joint.cpp in Sources */,
 				FABDA98B2552448300B5C523 /* b2_revolute_joint.cpp in Sources */,
@@ -4651,6 +4681,7 @@
 				FABDA9A32552448300B5C523 /* b2_friction_joint.cpp in Sources */,
 				FABDA9A32552448300B5C523 /* b2_friction_joint.cpp in Sources */,
 				FAF140AD1E20934C00F898D2 /* Versions.cpp in Sources */,
 				FAF140AD1E20934C00F898D2 /* Versions.cpp in Sources */,
 				FA0B793C1A958E3B000E1D17 /* runtime.cpp in Sources */,
 				FA0B793C1A958E3B000E1D17 /* runtime.cpp in Sources */,
+				FA6BDF8A280B62A000240F2A /* GraphicsReadback.mm in Sources */,
 				FAF140DC1E20934C00F898D2 /* InitializeDll.cpp in Sources */,
 				FAF140DC1E20934C00F898D2 /* InitializeDll.cpp in Sources */,
 				FA0B7DBC1A95902C000E1D17 /* Joystick.cpp in Sources */,
 				FA0B7DBC1A95902C000E1D17 /* Joystick.cpp in Sources */,
 				FA0B7DAF1A95902C000E1D17 /* wrap_CompressedImageData.cpp in Sources */,
 				FA0B7DAF1A95902C000E1D17 /* wrap_CompressedImageData.cpp in Sources */,
@@ -4766,6 +4797,7 @@
 				FA0B7CF81A95902C000E1D17 /* FileData.cpp in Sources */,
 				FA0B7CF81A95902C000E1D17 /* FileData.cpp in Sources */,
 				FA0B7DA61A95902C000E1D17 /* PNGHandler.cpp in Sources */,
 				FA0B7DA61A95902C000E1D17 /* PNGHandler.cpp in Sources */,
 				FAF6C9F523C2DE2900D7B5BC /* Logger.cpp in Sources */,
 				FAF6C9F523C2DE2900D7B5BC /* Logger.cpp in Sources */,
+				FA84DE6D277943F6002674C6 /* GraphicsReadback.cpp in Sources */,
 				FAE64A932071365100BC7981 /* physfs_platform_haiku.cpp in Sources */,
 				FAE64A932071365100BC7981 /* physfs_platform_haiku.cpp in Sources */,
 				FA0B7E981A95902C000E1D17 /* Sound.cpp in Sources */,
 				FA0B7E981A95902C000E1D17 /* Sound.cpp in Sources */,
 				FA0B7E371A95902C000E1D17 /* WheelJoint.cpp in Sources */,
 				FA0B7E371A95902C000E1D17 /* WheelJoint.cpp in Sources */,
@@ -4969,6 +5001,7 @@
 				FAF140A71E20934C00F898D2 /* ShaderLang.cpp in Sources */,
 				FAF140A71E20934C00F898D2 /* ShaderLang.cpp in Sources */,
 				FAC271E623B5B5B400C200D3 /* renderstate.cpp in Sources */,
 				FAC271E623B5B5B400C200D3 /* renderstate.cpp in Sources */,
 				FA1BA09D1E16CFCE00AA2803 /* Font.cpp in Sources */,
 				FA1BA09D1E16CFCE00AA2803 /* Font.cpp in Sources */,
+				FA84DE7127795E22002674C6 /* wrap_GraphicsReadback.cpp in Sources */,
 				FA0B7E6C1A95902C000E1D17 /* wrap_RevoluteJoint.cpp in Sources */,
 				FA0B7E6C1A95902C000E1D17 /* wrap_RevoluteJoint.cpp in Sources */,
 				FA0B7E631A95902C000E1D17 /* wrap_PolygonShape.cpp in Sources */,
 				FA0B7E631A95902C000E1D17 /* wrap_PolygonShape.cpp in Sources */,
 				FAC7CD7B1FE35E95006A60C7 /* physfs_platform_unix.c in Sources */,
 				FAC7CD7B1FE35E95006A60C7 /* physfs_platform_unix.c in Sources */,
@@ -5039,6 +5072,7 @@
 				FADF54341E3DAE6E00012CC0 /* wrap_SpriteBatch.cpp in Sources */,
 				FADF54341E3DAE6E00012CC0 /* wrap_SpriteBatch.cpp in Sources */,
 				FA0B7E0C1A95902C000E1D17 /* Fixture.cpp in Sources */,
 				FA0B7E0C1A95902C000E1D17 /* Fixture.cpp in Sources */,
 				FA0B7D181A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */,
 				FA0B7D181A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */,
+				FA84DE6627791C36002674C6 /* GraphicsReadback.cpp in Sources */,
 				FA0B7CFA1A95902C000E1D17 /* Filesystem.cpp in Sources */,
 				FA0B7CFA1A95902C000E1D17 /* Filesystem.cpp in Sources */,
 				FABDA9F72552448300B5C523 /* b2_dynamic_tree.cpp in Sources */,
 				FABDA9F72552448300B5C523 /* b2_dynamic_tree.cpp in Sources */,
 				FABDA98A2552448300B5C523 /* b2_revolute_joint.cpp in Sources */,
 				FABDA98A2552448300B5C523 /* b2_revolute_joint.cpp in Sources */,
@@ -5071,6 +5105,7 @@
 				FABDA9A22552448300B5C523 /* b2_friction_joint.cpp in Sources */,
 				FABDA9A22552448300B5C523 /* b2_friction_joint.cpp in Sources */,
 				217DFC0D1D9F6D490055D849 /* unixudp.c in Sources */,
 				217DFC0D1D9F6D490055D849 /* unixudp.c in Sources */,
 				FA0B7CDC1A95902C000E1D17 /* Source.cpp in Sources */,
 				FA0B7CDC1A95902C000E1D17 /* Source.cpp in Sources */,
+				FA6BDF89280B62A000240F2A /* GraphicsReadback.mm in Sources */,
 				FA0B7DC41A95902C000E1D17 /* wrap_JoystickModule.cpp in Sources */,
 				FA0B7DC41A95902C000E1D17 /* wrap_JoystickModule.cpp in Sources */,
 				FA0B7E6F1A95902C000E1D17 /* wrap_RopeJoint.cpp in Sources */,
 				FA0B7E6F1A95902C000E1D17 /* wrap_RopeJoint.cpp in Sources */,
 				FA24348721D401CB00B8918A /* attribute.cpp in Sources */,
 				FA24348721D401CB00B8918A /* attribute.cpp in Sources */,
@@ -5190,6 +5225,7 @@
 				FA0B7DA51A95902C000E1D17 /* PNGHandler.cpp in Sources */,
 				FA0B7DA51A95902C000E1D17 /* PNGHandler.cpp in Sources */,
 				FA0B7B371A958EA3000E1D17 /* wuff_internal.c in Sources */,
 				FA0B7B371A958EA3000E1D17 /* wuff_internal.c in Sources */,
 				FA0B7E971A95902C000E1D17 /* Sound.cpp in Sources */,
 				FA0B7E971A95902C000E1D17 /* Sound.cpp in Sources */,
+				FA84DE6C277943F6002674C6 /* GraphicsReadback.cpp in Sources */,
 				FA4F2B791DE0125B00CA37D7 /* xxhash.c in Sources */,
 				FA4F2B791DE0125B00CA37D7 /* xxhash.c in Sources */,
 				FA0B7E361A95902C000E1D17 /* WheelJoint.cpp in Sources */,
 				FA0B7E361A95902C000E1D17 /* WheelJoint.cpp in Sources */,
 				FADF542F1E3DABF600012CC0 /* SpriteBatch.cpp in Sources */,
 				FADF542F1E3DABF600012CC0 /* SpriteBatch.cpp in Sources */,

+ 1 - 1
platform/xcode/love.xcodeproj/project.pbxproj

@@ -328,7 +328,7 @@
 		29B97313FDCFA39411CA2CEA /* Project object */ = {
 		29B97313FDCFA39411CA2CEA /* Project object */ = {
 			isa = PBXProject;
 			isa = PBXProject;
 			attributes = {
 			attributes = {
-				LastUpgradeCheck = 1310;
+				LastUpgradeCheck = 1340;
 				TargetAttributes = {
 				TargetAttributes = {
 					FA0B7F051A95AAF3000E1D17 = {
 					FA0B7F051A95AAF3000E1D17 = {
 						CreatedOnToolsVersion = 6.1.1;
 						CreatedOnToolsVersion = 6.1.1;

+ 191 - 131
src/common/android.cpp

@@ -19,6 +19,7 @@
  **/
  **/
 
 
 #include "android.h"
 #include "android.h"
+#include "Object.h"
 
 
 #ifdef LOVE_ANDROID
 #ifdef LOVE_ANDROID
 
 
@@ -45,13 +46,11 @@ namespace android
 void setImmersive(bool immersive_active)
 void setImmersive(bool immersive_active)
 {
 {
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
-
 	jobject activity = (jobject) SDL_AndroidGetActivity();
 	jobject activity = (jobject) SDL_AndroidGetActivity();
+	jclass clazz = env->GetObjectClass(activity);
 
 
-	jclass clazz(env->GetObjectClass(activity));
-	jmethodID method_id = env->GetMethodID(clazz, "setImmersiveMode", "(Z)V");
-
-	env->CallVoidMethod(activity, method_id, immersive_active);
+	static jmethodID setImmersiveMethod = env->GetMethodID(clazz, "setImmersiveMode", "(Z)V");
+	env->CallVoidMethod(activity, setImmersiveMethod, immersive_active);
 
 
 	env->DeleteLocalRef(activity);
 	env->DeleteLocalRef(activity);
 	env->DeleteLocalRef(clazz);
 	env->DeleteLocalRef(clazz);
@@ -60,18 +59,16 @@ void setImmersive(bool immersive_active)
 bool getImmersive()
 bool getImmersive()
 {
 {
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
-
 	jobject activity = (jobject) SDL_AndroidGetActivity();
 	jobject activity = (jobject) SDL_AndroidGetActivity();
+	jclass clazz = env->GetObjectClass(activity);
 
 
-	jclass clazz(env->GetObjectClass(activity));
-	jmethodID method_id = env->GetMethodID(clazz, "getImmersiveMode", "()Z");
-
-	jboolean immersive_active = env->CallBooleanMethod(activity, method_id);
+	static jmethodID getImmersiveMethod = env->GetMethodID(clazz, "getImmersiveMode", "()Z");
+	jboolean immersiveActive = env->CallBooleanMethod(activity, getImmersiveMethod);
 
 
 	env->DeleteLocalRef(activity);
 	env->DeleteLocalRef(activity);
 	env->DeleteLocalRef(clazz);
 	env->DeleteLocalRef(clazz);
 
 
-	return immersive_active;
+	return immersiveActive;
 }
 }
 
 
 double getScreenScale()
 double getScreenScale()
@@ -81,16 +78,13 @@ double getScreenScale()
 	if (result == -1.)
 	if (result == -1.)
 	{
 	{
 		JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
 		JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
-		jclass activity = env->FindClass("org/love2d/android/GameActivity");
-
-		jmethodID getMetrics = env->GetStaticMethodID(activity, "getMetrics", "()Landroid/util/DisplayMetrics;");
-		jobject metrics = env->CallStaticObjectMethod(activity, getMetrics);
-		jclass metricsClass = env->GetObjectClass(metrics);
+		jobject activity = (jobject) SDL_AndroidGetActivity();
+		jclass clazz = env->GetObjectClass(activity);
 
 
-		result = env->GetFloatField(metrics, env->GetFieldID(metricsClass, "density", "F"));
+		jmethodID getDPIMethod = env->GetMethodID(clazz, "getDPIScale", "()F");
+		result = (double) env->CallFloatMethod(activity, getDPIMethod);
 
 
-		env->DeleteLocalRef(metricsClass);
-		env->DeleteLocalRef(metrics);
+		env->DeleteLocalRef(clazz);
 		env->DeleteLocalRef(activity);
 		env->DeleteLocalRef(activity);
 	}
 	}
 
 
@@ -101,8 +95,10 @@ bool getSafeArea(int &top, int &left, int &bottom, int &right)
 {
 {
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
 	jobject activity = (jobject) SDL_AndroidGetActivity();
 	jobject activity = (jobject) SDL_AndroidGetActivity();
-	jclass clazz(env->GetObjectClass(activity));
-	jmethodID methodID = env->GetMethodID(clazz, "initializeSafeArea", "()Z");
+	jclass clazz = env->GetObjectClass(activity);
+
+	static jmethodID methodID = env->GetMethodID(clazz, "getSafeArea", "()Z");
+
 	bool hasSafeArea = false;
 	bool hasSafeArea = false;
 
 
 	if (methodID == nullptr)
 	if (methodID == nullptr)
@@ -122,64 +118,33 @@ bool getSafeArea(int &top, int &left, int &bottom, int &right)
 	return hasSafeArea;
 	return hasSafeArea;
 }
 }
 
 
-const char *getSelectedGameFile()
-{
-	static const char *path = NULL;
-
-	if (path)
-	{
-		delete path;
-		path = NULL;
-	}
-
-	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
-	jclass activity = env->FindClass("org/love2d/android/GameActivity");
-
-	jmethodID getGamePath = env->GetStaticMethodID(activity, "getGamePath", "()Ljava/lang/String;");
-	jstring gamePath = (jstring) env->CallStaticObjectMethod(activity, getGamePath);
-	const char *utf = env->GetStringUTFChars(gamePath, 0);
-	if (utf)
-	{
-		path = SDL_strdup(utf);
-		env->ReleaseStringUTFChars(gamePath, utf);
-	}
-
-	env->DeleteLocalRef(gamePath);
-	env->DeleteLocalRef(activity);
-
-	return path;
-}
-
 bool openURL(const std::string &url)
 bool openURL(const std::string &url)
 {
 {
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
-	jclass activity = env->FindClass("org/love2d/android/GameActivity");
-
-	jmethodID openURL = env->GetStaticMethodID(activity, "openURLFromLOVE", "(Ljava/lang/String;)Z");
-
-	if (openURL == nullptr)
-	{
-		env->ExceptionClear();
-		openURL = env->GetStaticMethodID(activity, "openURL", "(Ljava/lang/String;)Z");
-	}
+	jobject activity = (jobject) SDL_AndroidGetActivity();
+	jclass clazz = env->GetObjectClass(activity);
 
 
-	jstring url_jstring = (jstring) env->NewStringUTF(url.c_str());
+	static jmethodID openURL = env->GetMethodID(clazz, "openURLFromLOVE", "(Ljava/lang/String;)Z");
 
 
-	jboolean result = env->CallStaticBooleanMethod(activity, openURL, url_jstring);
+	jstring jstringURL = env->NewStringUTF(url.c_str());
+	jboolean result = env->CallBooleanMethod(clazz, openURL, jstringURL);
 
 
-	env->DeleteLocalRef(url_jstring);
+	env->DeleteLocalRef(jstringURL);
+	env->DeleteLocalRef(clazz);
 	env->DeleteLocalRef(activity);
 	env->DeleteLocalRef(activity);
-	return result;
+	return (bool) result;
 }
 }
 
 
 void vibrate(double seconds)
 void vibrate(double seconds)
 {
 {
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
-	jclass activity = env->FindClass("org/love2d/android/GameActivity");
+	jobject activity = (jobject) SDL_AndroidGetActivity();
+	jclass clazz = env->GetObjectClass(activity);
 
 
-	jmethodID vibrate_method = env->GetStaticMethodID(activity, "vibrate", "(D)V");
-	env->CallStaticVoidMethod(activity, vibrate_method, seconds);
+	static jmethodID vibrateMethod = env->GetMethodID(clazz, "vibrate", "(D)V");
+	env->CallVoidMethod(activity, vibrateMethod, seconds);
 
 
+	env->DeleteLocalRef(clazz);
 	env->DeleteLocalRef(activity);
 	env->DeleteLocalRef(activity);
 }
 }
 
 
@@ -192,40 +157,9 @@ void freeGameArchiveMemory(void *ptr)
 	delete[] game_love_data;
 	delete[] game_love_data;
 }
 }
 
 
-bool loadGameArchiveToMemory(const char* filename, char **ptr, size_t *size)
-{
-	SDL_RWops *asset_game_file = SDL_RWFromFile(filename, "rb");
-	if (!asset_game_file) {
-		SDL_Log("Could not find %s", filename);
-		return false;
-	}
-
-	Sint64 file_size = asset_game_file->size(asset_game_file);
-	if (file_size <= 0) {
-		SDL_Log("Could not load game from %s. File has invalid file size: %d.", filename, (int) file_size);
-		return false;
-	}
-
-	*ptr = new char[file_size];
-	if (!*ptr) {
-		SDL_Log("Could not allocate memory for in-memory game archive");
-		return false;
-	}
-
-	size_t bytes_copied = asset_game_file->read(asset_game_file, (void*) *ptr, sizeof(char), (size_t) file_size);
-	if (bytes_copied != file_size) {
-		SDL_Log("Incomplete copy of in-memory game archive!");
-		delete[] *ptr;
-		return false;
-	}
-
-	*size = (size_t) file_size;
-	return true;
-}
-
 bool directoryExists(const char *path)
 bool directoryExists(const char *path)
 {
 {
-	struct stat s;
+	struct stat s {};
 	int err = stat(path, &s);
 	int err = stat(path, &s);
 	if (err == -1)
 	if (err == -1)
 	{
 	{
@@ -284,9 +218,9 @@ bool hasRecordingPermission()
 {
 {
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
 	jobject activity = (jobject) SDL_AndroidGetActivity();
 	jobject activity = (jobject) SDL_AndroidGetActivity();
+	jclass clazz = env->GetObjectClass(activity);
 
 
-	jclass clazz(env->GetObjectClass(activity));
-	jmethodID methodID = env->GetMethodID(clazz, "hasRecordAudioPermission", "()Z");
+	static jmethodID methodID = env->GetMethodID(clazz, "hasRecordAudioPermission", "()Z");
 	jboolean result = false;
 	jboolean result = false;
 
 
 	if (methodID == nullptr)
 	if (methodID == nullptr)
@@ -755,25 +689,23 @@ void deinitializeVirtualArchive()
 
 
 bool checkFusedGame(void **physfsIO_Out)
 bool checkFusedGame(void **physfsIO_Out)
 {
 {
-	// TODO: Reorder the loading in 12.0
 	PHYSFS_Io *&io = *(PHYSFS_Io **) physfsIO_Out;
 	PHYSFS_Io *&io = *(PHYSFS_Io **) physfsIO_Out;
 	AAssetManager *assetManager = getAssetManager();
 	AAssetManager *assetManager = getAssetManager();
 
 
-	// Prefer game.love inside assets/ folder
-	AAsset *asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM);
+	// Prefer main.lua inside assets/ folder
+	AAsset *asset = AAssetManager_open(assetManager, "main.lua", AASSET_MODE_STREAMING);
 	if (asset)
 	if (asset)
 	{
 	{
-		io = aasset::io::fromAAsset(assetManager, "game.love", asset);
+		AAsset_close(asset);
+		io = nullptr;
 		return true;
 		return true;
 	}
 	}
 
 
-	// If there's no game.love inside assets/ try main.lua
-	asset = AAssetManager_open(assetManager, "main.lua", AASSET_MODE_STREAMING);
-
+	// If there's no main.lua inside assets/ try game.love
+	asset = AAssetManager_open(assetManager, "game.love", AASSET_MODE_RANDOM);
 	if (asset)
 	if (asset)
 	{
 	{
-		AAsset_close(asset);
-		io = nullptr;
+		io = aasset::io::fromAAsset(assetManager, "game.love", asset);
 		return true;
 		return true;
 	}
 	}
 
 
@@ -784,43 +716,171 @@ bool checkFusedGame(void **physfsIO_Out)
 const char *getCRequirePath()
 const char *getCRequirePath()
 {
 {
 	static bool initialized = false;
 	static bool initialized = false;
-	static const char *path = nullptr;
+	static std::string path;
 
 
 	if (!initialized)
 	if (!initialized)
 	{
 	{
 		JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
 		JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
 		jobject activity = (jobject) SDL_AndroidGetActivity();
 		jobject activity = (jobject) SDL_AndroidGetActivity();
+		jclass clazz = env->GetObjectClass(activity);
 
 
-		jclass clazz(env->GetObjectClass(activity));
-		jmethodID method_id = env->GetMethodID(clazz, "getCRequirePath", "()Ljava/lang/String;");
-
-		path = "";
-		initialized = true;
+		static jmethodID getCRequireMethod = env->GetMethodID(clazz, "getCRequirePath", "()Ljava/lang/String;");
 
 
-		if (method_id)
+		jstring cpath = (jstring) env->CallObjectMethod(activity, getCRequireMethod);
+		const char *utf = env->GetStringUTFChars(cpath, nullptr);
+		if (utf)
 		{
 		{
-			jstring cpath = (jstring) env->CallObjectMethod(activity, method_id);
-			const char *utf = env->GetStringUTFChars(cpath, nullptr);
-			if (utf)
-			{
-				path = SDL_strdup(utf);
-				env->ReleaseStringUTFChars(cpath, utf);
-			}
-
-			env->DeleteLocalRef(cpath);
-		}
-		else
-		{
-			// NoSuchMethodException is thrown in case methodID is null
-			env->ExceptionClear();
-			return "";
+			path = utf;
+			env->ReleaseStringUTFChars(cpath, utf);
 		}
 		}
 
 
+		env->DeleteLocalRef(cpath);
 		env->DeleteLocalRef(activity);
 		env->DeleteLocalRef(activity);
 		env->DeleteLocalRef(clazz);
 		env->DeleteLocalRef(clazz);
 	}
 	}
 
 
-	return path;
+	return path.c_str();
+}
+
+int getFDFromContentProtocol(const char *path)
+{
+	int fd = -1;
+
+	if (strstr(path, "content://") == path)
+	{
+		JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
+		jobject activity = (jobject) SDL_AndroidGetActivity();
+		jclass clazz = env->GetObjectClass(activity);
+
+		static jmethodID converter = env->GetMethodID(clazz, "convertToFileDescriptor", "(Ljava/lang/String;)I");
+
+		jstring uri = env->NewStringUTF(path);
+		fd = (int) env->CallIntMethod(activity, converter, uri);
+
+		env->DeleteLocalRef(uri);
+		env->DeleteLocalRef(clazz);
+		env->DeleteLocalRef(activity);
+	}
+
+	return fd;
+}
+
+int getFDFromLoveProtocol(const char *path)
+{
+	constexpr const char PROTOCOL[] = "love2d://fd/";
+	constexpr size_t PROTOCOL_LEN = sizeof(PROTOCOL) - 1;
+
+	if (*path == '/')
+		path++;
+
+	if (memcmp(path, PROTOCOL, PROTOCOL_LEN) == 0)
+	{
+		try
+		{
+			return std::stoi(path + PROTOCOL_LEN, nullptr, 10);
+		}
+		catch (std::logic_error &)
+		{ }
+	}
+
+	return -1;
+}
+
+class FileDescriptorTracker: public love::Object
+{
+public:
+	explicit FileDescriptorTracker(int fd): Object(), fd(fd) {}
+	~FileDescriptorTracker() override { close(fd); }
+	int getFd() const { return fd; }
+private:
+	int fd;
+};
+
+struct FileDescriptorIO
+{
+	FileDescriptorTracker *fd;
+	off64_t size;
+	off64_t offset;
+};
+
+void *getIOFromFD(int fd)
+{
+	if (fd == -1)
+		return nullptr;
+
+	// Create file descriptor IO structure
+	FileDescriptorIO *fdio = new FileDescriptorIO();
+	fdio->size = lseek64(fd, 0, SEEK_END);
+	fdio->offset = 0;
+	lseek64(fd, 0, SEEK_SET);
+
+	if (fdio->size == -1)
+	{
+		// Cannot get size
+		delete fdio;
+		return nullptr;
+	}
+
+	fdio->fd = new FileDescriptorTracker(fd);
+
+	PHYSFS_Io *io = new PHYSFS_Io();
+	io->version = 0;
+	io->opaque = fdio;
+	io->read = [](PHYSFS_Io *io, void *buf, PHYSFS_uint64 size)
+	{
+		FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque;
+		ssize_t ret = pread64(fdio->fd->getFd(), buf, (size_t) size, fdio->offset);
+
+		if (ret == -1)
+			PHYSFS_setErrorCode(PHYSFS_ERR_OTHER_ERROR);
+		else
+			fdio->offset = std::min(fdio->offset + (off64_t) ret, fdio->size);
+
+		return (PHYSFS_sint64) ret;
+	};
+	io->write = nullptr;
+	io->seek = [](PHYSFS_Io *io, PHYSFS_uint64 offset)
+	{
+		FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque;
+		fdio->offset = std::min(std::max<off64_t>((off64_t) offset, 0), fdio->size);
+		// Always success
+		return 1;
+	};
+	io->tell = [](PHYSFS_Io *io)
+	{
+		FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque;
+		return (PHYSFS_sint64) fdio->offset;
+	};
+	io->length = [](PHYSFS_Io *io)
+	{
+		FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque;
+		return (PHYSFS_sint64) fdio->size;
+	};
+	io->duplicate = [](PHYSFS_Io *io)
+	{
+		FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque;
+		FileDescriptorIO *fdio2 = new FileDescriptorIO();
+		PHYSFS_Io *io2 = new PHYSFS_Io();
+
+		fdio->fd->retain();
+
+		// Copy data
+		*fdio2 = *fdio;
+		*io2 = *io;
+		io2->opaque = fdio2;
+
+		return io2;
+	};
+	io->flush = nullptr;
+	io->destroy = [](PHYSFS_Io *io)
+	{
+		FileDescriptorIO *fdio = (FileDescriptorIO *) io->opaque;
+		fdio->fd->release();
+		delete fdio;
+		delete io;
+	};
+
+	return io;
 }
 }
 
 
 } // android
 } // android

+ 21 - 7
src/common/android.h

@@ -50,11 +50,6 @@ double getScreenScale();
  **/
  **/
 bool getSafeArea(int &top, int &left, int &bottom, int &right);
 bool getSafeArea(int &top, int &left, int &bottom, int &right);
 
 
-/**
- * Gets the selected love file in the device filesystem.
- **/
-const char *getSelectedGameFile();
-
 bool openURL(const std::string &url);
 bool openURL(const std::string &url);
 
 
 void vibrate(double seconds);
 void vibrate(double seconds);
@@ -64,8 +59,6 @@ void vibrate(double seconds);
  */
  */
 void freeGameArchiveMemory(void *ptr);
 void freeGameArchiveMemory(void *ptr);
 
 
-bool loadGameArchiveToMemory(const char *filename, char **ptr, size_t *size);
-
 bool directoryExists(const char *path);
 bool directoryExists(const char *path);
 
 
 bool mkdir(const char *path);
 bool mkdir(const char *path);
@@ -103,6 +96,27 @@ bool checkFusedGame(void **physfsIO_Out);
 
 
 const char *getCRequirePath();
 const char *getCRequirePath();
 
 
+/**
+ * Convert "content://" to file descriptor.
+ * @param path Path with content:// URI
+ * @return File descriptor if successful, -1 on failure.
+ */
+int getFDFromContentProtocol(const char *path);
+
+/**
+ * Attempt to parse "(/)love2d://fd/<fd>" from path.
+ * @param path Potentially special path.
+ * @return File descriptor passed if successful, -1 if path is not valid.
+ */
+int getFDFromLoveProtocol(const char *path);
+
+/**
+ * Create PHYSFS_Io from file descriptor.
+ * @param fd File descriptor
+ * @return PHYSFS_Io casted to void*.
+ */
+void *getIOFromFD(int fd);
+
 } // android
 } // android
 } // love
 } // love
 
 

+ 171 - 77
src/libraries/dr/dr_flac.h

@@ -1,6 +1,6 @@
 /*
 /*
 FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file.
 FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file.
-dr_flac - v0.12.33 - 2021-12-22
+dr_flac - v0.12.38 - 2022-04-10
 
 
 David Reid - [email protected]
 David Reid - [email protected]
 
 
@@ -232,7 +232,7 @@ extern "C" {
 
 
 #define DRFLAC_VERSION_MAJOR     0
 #define DRFLAC_VERSION_MAJOR     0
 #define DRFLAC_VERSION_MINOR     12
 #define DRFLAC_VERSION_MINOR     12
-#define DRFLAC_VERSION_REVISION  33
+#define DRFLAC_VERSION_REVISION  38
 #define DRFLAC_VERSION_STRING    DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION)
 #define DRFLAC_VERSION_STRING    DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION)
 
 
 #include <stddef.h> /* For size_t. */
 #include <stddef.h> /* For size_t. */
@@ -1363,9 +1363,15 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat
     I am using "__inline__" only when we're compiling in strict ANSI mode.
     I am using "__inline__" only when we're compiling in strict ANSI mode.
     */
     */
     #if defined(__STRICT_ANSI__)
     #if defined(__STRICT_ANSI__)
-        #define DRFLAC_INLINE __inline__ __attribute__((always_inline))
+        #define DRFLAC_GNUC_INLINE_HINT __inline__
     #else
     #else
-        #define DRFLAC_INLINE inline __attribute__((always_inline))
+        #define DRFLAC_GNUC_INLINE_HINT inline
+    #endif
+
+    #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__)
+        #define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT __attribute__((always_inline))
+    #else
+        #define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT
     #endif
     #endif
 #elif defined(__WATCOMC__)
 #elif defined(__WATCOMC__)
     #define DRFLAC_INLINE __inline
     #define DRFLAC_INLINE __inline
@@ -1378,7 +1384,7 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat
     #define DRFLAC_X64
     #define DRFLAC_X64
 #elif defined(__i386) || defined(_M_IX86)
 #elif defined(__i386) || defined(_M_IX86)
     #define DRFLAC_X86
     #define DRFLAC_X86
-#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARM64)
+#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64)
     #define DRFLAC_ARM
     #define DRFLAC_ARM
 #endif
 #endif
 
 
@@ -1431,16 +1437,6 @@ Unfortuantely dr_flac depends on this for a few things so we're just going to di
     #if defined(DRFLAC_ARM)
     #if defined(DRFLAC_ARM)
         #if !defined(DRFLAC_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64))
         #if !defined(DRFLAC_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64))
             #define DRFLAC_SUPPORT_NEON
             #define DRFLAC_SUPPORT_NEON
-        #endif
-
-        /* Fall back to looking for the #include file. */
-        #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include)
-            #if !defined(DRFLAC_SUPPORT_NEON) && !defined(DRFLAC_NO_NEON) && __has_include(<arm_neon.h>)
-                #define DRFLAC_SUPPORT_NEON
-            #endif
-        #endif
-
-        #if defined(DRFLAC_SUPPORT_NEON)
             #include <arm_neon.h>
             #include <arm_neon.h>
         #endif
         #endif
     #endif
     #endif
@@ -1909,6 +1905,12 @@ static DRFLAC_INLINE drflac_uint32 drflac__be2host_32(drflac_uint32 n)
     return n;
     return n;
 }
 }
 
 
+static DRFLAC_INLINE drflac_uint32 drflac__be2host_32_ptr_unaligned(const void* pData)
+{
+    const drflac_uint8* pNum = (drflac_uint8*)pData;
+    return *(pNum) << 24 | *(pNum+1) << 16 | *(pNum+2) << 8 | *(pNum+3);
+}
+
 static DRFLAC_INLINE drflac_uint64 drflac__be2host_64(drflac_uint64 n)
 static DRFLAC_INLINE drflac_uint64 drflac__be2host_64(drflac_uint64 n)
 {
 {
     if (drflac__is_little_endian()) {
     if (drflac__is_little_endian()) {
@@ -1928,6 +1930,12 @@ static DRFLAC_INLINE drflac_uint32 drflac__le2host_32(drflac_uint32 n)
     return n;
     return n;
 }
 }
 
 
+static DRFLAC_INLINE drflac_uint32 drflac__le2host_32_ptr_unaligned(const void* pData)
+{
+    const drflac_uint8* pNum = (drflac_uint8*)pData;
+    return *pNum | *(pNum+1) << 8 |  *(pNum+2) << 16 | *(pNum+3) << 24;
+}
+
 
 
 static DRFLAC_INLINE drflac_uint32 drflac__unsynchsafe_32(drflac_uint32 n)
 static DRFLAC_INLINE drflac_uint32 drflac__unsynchsafe_32(drflac_uint32 n)
 {
 {
@@ -2429,6 +2437,10 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_uint32(drflac_bs* bs, unsigned i
         if (!drflac__reload_cache(bs)) {
         if (!drflac__reload_cache(bs)) {
             return DRFLAC_FALSE;
             return DRFLAC_FALSE;
         }
         }
+        if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) {
+            /* This happens when we get to end of stream */
+            return DRFLAC_FALSE;
+        }
 
 
         *pResultOut = (resultHi << bitCountLo) | (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountLo);
         *pResultOut = (resultHi << bitCountLo) | (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountLo);
         bs->consumedBits += bitCountLo;
         bs->consumedBits += bitCountLo;
@@ -2872,9 +2884,24 @@ static DRFLAC_INLINE drflac_bool32 drflac__seek_past_next_set_bit(drflac_bs* bs,
         }
         }
     }
     }
 
 
+    if (bs->cache == 1) {
+        /* Not catching this would lead to undefined behaviour: a shift of a 32-bit number by 32 or more is undefined */
+        *pOffsetOut = zeroCounter + (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs) - 1;
+        if (!drflac__reload_cache(bs)) {
+            return DRFLAC_FALSE;
+        }
+
+        return DRFLAC_TRUE;
+    }
+
     setBitOffsetPlus1 = drflac__clz(bs->cache);
     setBitOffsetPlus1 = drflac__clz(bs->cache);
     setBitOffsetPlus1 += 1;
     setBitOffsetPlus1 += 1;
 
 
+    if (setBitOffsetPlus1 > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) {
+        /* This happens when we get to end of stream */
+        return DRFLAC_FALSE;
+    }
+
     bs->consumedBits += setBitOffsetPlus1;
     bs->consumedBits += setBitOffsetPlus1;
     bs->cache <<= setBitOffsetPlus1;
     bs->cache <<= setBitOffsetPlus1;
 
 
@@ -2989,6 +3016,25 @@ static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64
 }
 }
 
 
 
 
+static DRFLAC_INLINE drflac_uint32 drflac__ilog2_u32(drflac_uint32 x)
+{
+#if 1   /* Needs optimizing. */
+    drflac_uint32 result = 0;
+    while (x > 0) {
+        result += 1;
+        x >>= 1;
+    }
+
+    return result;
+#endif
+}
+
+static DRFLAC_INLINE drflac_bool32 drflac__use_64_bit_prediction(drflac_uint32 bitsPerSample, drflac_uint32 order, drflac_uint32 precision)
+{
+    /* https://web.archive.org/web/20220205005724/https://github.com/ietf-wg-cellar/flac-specification/blob/37a49aa48ba4ba12e8757badfc59c0df35435fec/rfc_backmatter.md */
+    return bitsPerSample + precision + drflac__ilog2_u32(order) > 32;
+}
+
 
 
 /*
 /*
 The next two functions are responsible for calculating the prediction.
 The next two functions are responsible for calculating the prediction.
@@ -2996,6 +3042,9 @@ The next two functions are responsible for calculating the prediction.
 When the bits per sample is >16 we need to use 64-bit integer arithmetic because otherwise we'll run out of precision. It's
 When the bits per sample is >16 we need to use 64-bit integer arithmetic because otherwise we'll run out of precision. It's
 safe to assume this will be slower on 32-bit platforms so we use a more optimal solution when the bits per sample is <=16.
 safe to assume this will be slower on 32-bit platforms so we use a more optimal solution when the bits per sample is <=16.
 */
 */
+#if defined(__clang__)
+__attribute__((no_sanitize("signed-integer-overflow")))
+#endif
 static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_32(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples)
 static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_32(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples)
 {
 {
     drflac_int32 prediction = 0;
     drflac_int32 prediction = 0;
@@ -3231,7 +3280,7 @@ static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64(drflac_uint32
 Reference implementation for reading and decoding samples with residual. This is intentionally left unoptimized for the
 Reference implementation for reading and decoding samples with residual. This is intentionally left unoptimized for the
 sake of readability and should only be used as a reference.
 sake of readability and should only be used as a reference.
 */
 */
-static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
+static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
 {
 {
     drflac_uint32 i;
     drflac_uint32 i;
 
 
@@ -3270,10 +3319,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drfla
         }
         }
 
 
 
 
-        if (bitsPerSample+shift >= 32) {
-            pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + i);
+        if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) {
+            pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i);
         } else {
         } else {
-            pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i);
+            pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i);
         }
         }
     }
     }
 
 
@@ -3370,6 +3419,10 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts(drflac_bs* bs, drflac
             if (!drflac__reload_cache(bs)) {
             if (!drflac__reload_cache(bs)) {
                 return DRFLAC_FALSE;
                 return DRFLAC_FALSE;
             }
             }
+            if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) {
+                /* This happens when we get to end of stream */
+                return DRFLAC_FALSE;
+            }
         }
         }
 
 
         riceParamPart = (drflac_uint32)(resultHi | DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, bitCountLo));
         riceParamPart = (drflac_uint32)(resultHi | DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, bitCountLo));
@@ -3450,6 +3503,10 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts_x1(drflac_bs* bs, drf
                 if (!drflac__reload_cache(bs)) {
                 if (!drflac__reload_cache(bs)) {
                     return DRFLAC_FALSE;
                     return DRFLAC_FALSE;
                 }
                 }
+                if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) {
+                    /* This happens when we get to end of stream */
+                    return DRFLAC_FALSE;
+                }
 
 
                 bs_cache = bs->cache;
                 bs_cache = bs->cache;
                 bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount;
                 bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount;
@@ -3560,6 +3617,11 @@ static DRFLAC_INLINE drflac_bool32 drflac__seek_rice_parts(drflac_bs* bs, drflac
                     return DRFLAC_FALSE;
                     return DRFLAC_FALSE;
                 }
                 }
 
 
+                if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) {
+                    /* This happens when we get to end of stream */
+                    return DRFLAC_FALSE;
+                }
+
                 bs_cache = bs->cache;
                 bs_cache = bs->cache;
                 bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount;
                 bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount;
             }
             }
@@ -3646,7 +3708,7 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar_zeroorde
     return DRFLAC_TRUE;
     return DRFLAC_TRUE;
 }
 }
 
 
-static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
+static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
 {
 {
     drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF};
     drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF};
     drflac_uint32 zeroCountPart0 = 0;
     drflac_uint32 zeroCountPart0 = 0;
@@ -3664,14 +3726,14 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b
     DRFLAC_ASSERT(bs != NULL);
     DRFLAC_ASSERT(bs != NULL);
     DRFLAC_ASSERT(pSamplesOut != NULL);
     DRFLAC_ASSERT(pSamplesOut != NULL);
 
 
-    if (order == 0) {
-        return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut);
+    if (lpcOrder == 0) {
+        return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut);
     }
     }
 
 
     riceParamMask  = (drflac_uint32)~((~0UL) << riceParam);
     riceParamMask  = (drflac_uint32)~((~0UL) << riceParam);
     pSamplesOutEnd = pSamplesOut + (count & ~3);
     pSamplesOutEnd = pSamplesOut + (count & ~3);
 
 
-    if (bitsPerSample+shift > 32) {
+    if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) {
         while (pSamplesOut < pSamplesOutEnd) {
         while (pSamplesOut < pSamplesOutEnd) {
             /*
             /*
             Rice extraction. It's faster to do this one at a time against local variables than it is to use the x4 version
             Rice extraction. It's faster to do this one at a time against local variables than it is to use the x4 version
@@ -3699,10 +3761,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b
             riceParamPart2  = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01];
             riceParamPart2  = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01];
             riceParamPart3  = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01];
             riceParamPart3  = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01];
 
 
-            pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0);
-            pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 1);
-            pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 2);
-            pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 3);
+            pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0);
+            pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 1);
+            pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 2);
+            pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 3);
 
 
             pSamplesOut += 4;
             pSamplesOut += 4;
         }
         }
@@ -3730,10 +3792,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b
             riceParamPart2  = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01];
             riceParamPart2  = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01];
             riceParamPart3  = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01];
             riceParamPart3  = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01];
 
 
-            pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0);
-            pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 1);
-            pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 2);
-            pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 3);
+            pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0);
+            pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 1);
+            pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 2);
+            pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 3);
 
 
             pSamplesOut += 4;
             pSamplesOut += 4;
         }
         }
@@ -3753,10 +3815,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b
         /*riceParamPart0  = (riceParamPart0 >> 1) ^ (~(riceParamPart0 & 0x01) + 1);*/
         /*riceParamPart0  = (riceParamPart0 >> 1) ^ (~(riceParamPart0 & 0x01) + 1);*/
 
 
         /* Sample reconstruction. */
         /* Sample reconstruction. */
-        if (bitsPerSample+shift > 32) {
-            pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0);
+        if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) {
+            pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0);
         } else {
         } else {
-            pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0);
+            pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0);
         }
         }
 
 
         i += 1;
         i += 1;
@@ -4212,20 +4274,20 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41_64(drflac
     return DRFLAC_TRUE;
     return DRFLAC_TRUE;
 }
 }
 
 
-static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
+static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
 {
 {
     DRFLAC_ASSERT(bs != NULL);
     DRFLAC_ASSERT(bs != NULL);
     DRFLAC_ASSERT(pSamplesOut != NULL);
     DRFLAC_ASSERT(pSamplesOut != NULL);
 
 
     /* In my testing the order is rarely > 12, so in this case I'm going to simplify the SSE implementation by only handling order <= 12. */
     /* In my testing the order is rarely > 12, so in this case I'm going to simplify the SSE implementation by only handling order <= 12. */
-    if (order > 0 && order <= 12) {
-        if (bitsPerSample+shift > 32) {
-            return drflac__decode_samples_with_residual__rice__sse41_64(bs, count, riceParam, order, shift, coefficients, pSamplesOut);
+    if (lpcOrder > 0 && lpcOrder <= 12) {
+        if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) {
+            return drflac__decode_samples_with_residual__rice__sse41_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut);
         } else {
         } else {
-            return drflac__decode_samples_with_residual__rice__sse41_32(bs, count, riceParam, order, shift, coefficients, pSamplesOut);
+            return drflac__decode_samples_with_residual__rice__sse41_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut);
         }
         }
     } else {
     } else {
-        return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut);
+        return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut);
     }
     }
 }
 }
 #endif
 #endif
@@ -4562,7 +4624,7 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_
 
 
     /*
     /*
     Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than
     Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than
-    what's available in the input buffers. It would be conenient to use a fall-through switch to do this, but this results
+    what's available in the input buffers. It would be convenient to use a fall-through switch to do this, but this results
     in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted
     in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted
     so I think there's opportunity for this to be simplified.
     so I think there's opportunity for this to be simplified.
     */
     */
@@ -4710,41 +4772,41 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_
     return DRFLAC_TRUE;
     return DRFLAC_TRUE;
 }
 }
 
 
-static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
+static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
 {
 {
     DRFLAC_ASSERT(bs != NULL);
     DRFLAC_ASSERT(bs != NULL);
     DRFLAC_ASSERT(pSamplesOut != NULL);
     DRFLAC_ASSERT(pSamplesOut != NULL);
 
 
     /* In my testing the order is rarely > 12, so in this case I'm going to simplify the NEON implementation by only handling order <= 12. */
     /* In my testing the order is rarely > 12, so in this case I'm going to simplify the NEON implementation by only handling order <= 12. */
-    if (order > 0 && order <= 12) {
-        if (bitsPerSample+shift > 32) {
-            return drflac__decode_samples_with_residual__rice__neon_64(bs, count, riceParam, order, shift, coefficients, pSamplesOut);
+    if (lpcOrder > 0 && lpcOrder <= 12) {
+        if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) {
+            return drflac__decode_samples_with_residual__rice__neon_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut);
         } else {
         } else {
-            return drflac__decode_samples_with_residual__rice__neon_32(bs, count, riceParam, order, shift, coefficients, pSamplesOut);
+            return drflac__decode_samples_with_residual__rice__neon_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut);
         }
         }
     } else {
     } else {
-        return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut);
+        return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut);
     }
     }
 }
 }
 #endif
 #endif
 
 
-static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
+static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
 {
 {
 #if defined(DRFLAC_SUPPORT_SSE41)
 #if defined(DRFLAC_SUPPORT_SSE41)
     if (drflac__gIsSSE41Supported) {
     if (drflac__gIsSSE41Supported) {
-        return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut);
+        return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut);
     } else
     } else
 #elif defined(DRFLAC_SUPPORT_NEON)
 #elif defined(DRFLAC_SUPPORT_NEON)
     if (drflac__gIsNEONSupported) {
     if (drflac__gIsNEONSupported) {
-        return drflac__decode_samples_with_residual__rice__neon(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut);
+        return drflac__decode_samples_with_residual__rice__neon(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut);
     } else
     } else
 #endif
 #endif
     {
     {
         /* Scalar fallback. */
         /* Scalar fallback. */
     #if 0
     #if 0
-        return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut);
+        return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut);
     #else
     #else
-        return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut);
+        return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut);
     #endif
     #endif
     }
     }
 }
 }
@@ -4765,7 +4827,10 @@ static drflac_bool32 drflac__read_and_seek_residual__rice(drflac_bs* bs, drflac_
     return DRFLAC_TRUE;
     return DRFLAC_TRUE;
 }
 }
 
 
-static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
+#if defined(__clang__)
+__attribute__((no_sanitize("signed-integer-overflow")))
+#endif
+static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut)
 {
 {
     drflac_uint32 i;
     drflac_uint32 i;
 
 
@@ -4782,10 +4847,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs*
             pSamplesOut[i] = 0;
             pSamplesOut[i] = 0;
         }
         }
 
 
-        if (bitsPerSample >= 24) {
-            pSamplesOut[i] += drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + i);
+        if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) {
+            pSamplesOut[i] += drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i);
         } else {
         } else {
-            pSamplesOut[i] += drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i);
+            pSamplesOut[i] += drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i);
         }
         }
     }
     }
 
 
@@ -4798,7 +4863,7 @@ Reads and decodes the residual for the sub-frame the decoder is currently sittin
 when the decoder is sitting at the very start of the RESIDUAL block. The first <order> residuals will be ignored. The
 when the decoder is sitting at the very start of the RESIDUAL block. The first <order> residuals will be ignored. The
 <blockSize> and <order> parameters are used to determine how many residual values need to be decoded.
 <blockSize> and <order> parameters are used to determine how many residual values need to be decoded.
 */
 */
-static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples)
+static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pDecodedSamples)
 {
 {
     drflac_uint8 residualMethod;
     drflac_uint8 residualMethod;
     drflac_uint8 partitionOrder;
     drflac_uint8 partitionOrder;
@@ -4818,7 +4883,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_
     }
     }
 
 
     /* Ignore the first <order> values. */
     /* Ignore the first <order> values. */
-    pDecodedSamples += order;
+    pDecodedSamples += lpcOrder;
 
 
     if (!drflac__read_uint8(bs, 4, &partitionOrder)) {
     if (!drflac__read_uint8(bs, 4, &partitionOrder)) {
         return DRFLAC_FALSE;
         return DRFLAC_FALSE;
@@ -4833,11 +4898,11 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_
     }
     }
 
 
     /* Validation check. */
     /* Validation check. */
-    if ((blockSize / (1 << partitionOrder)) < order) {
+    if ((blockSize / (1 << partitionOrder)) < lpcOrder) {
         return DRFLAC_FALSE;
         return DRFLAC_FALSE;
     }
     }
 
 
-    samplesInPartition = (blockSize / (1 << partitionOrder)) - order;
+    samplesInPartition = (blockSize / (1 << partitionOrder)) - lpcOrder;
     partitionsRemaining = (1 << partitionOrder);
     partitionsRemaining = (1 << partitionOrder);
     for (;;) {
     for (;;) {
         drflac_uint8 riceParam = 0;
         drflac_uint8 riceParam = 0;
@@ -4858,7 +4923,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_
         }
         }
 
 
         if (riceParam != 0xFF) {
         if (riceParam != 0xFF) {
-            if (!drflac__decode_samples_with_residual__rice(bs, bitsPerSample, samplesInPartition, riceParam, order, shift, coefficients, pDecodedSamples)) {
+            if (!drflac__decode_samples_with_residual__rice(bs, bitsPerSample, samplesInPartition, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) {
                 return DRFLAC_FALSE;
                 return DRFLAC_FALSE;
             }
             }
         } else {
         } else {
@@ -4867,7 +4932,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_
                 return DRFLAC_FALSE;
                 return DRFLAC_FALSE;
             }
             }
 
 
-            if (!drflac__decode_samples_with_residual__unencoded(bs, bitsPerSample, samplesInPartition, unencodedBitsPerSample, order, shift, coefficients, pDecodedSamples)) {
+            if (!drflac__decode_samples_with_residual__unencoded(bs, bitsPerSample, samplesInPartition, unencodedBitsPerSample, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) {
                 return DRFLAC_FALSE;
                 return DRFLAC_FALSE;
             }
             }
         }
         }
@@ -5036,7 +5101,7 @@ static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32
         pDecodedSamples[i] = sample;
         pDecodedSamples[i] = sample;
     }
     }
 
 
-    if (!drflac__decode_samples_with_residual(bs, subframeBitsPerSample, blockSize, lpcOrder, 0, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) {
+    if (!drflac__decode_samples_with_residual(bs, subframeBitsPerSample, blockSize, lpcOrder, 0, 4, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) {
         return DRFLAC_FALSE;
         return DRFLAC_FALSE;
     }
     }
 
 
@@ -5091,7 +5156,7 @@ static drflac_bool32 drflac__decode_samples__lpc(drflac_bs* bs, drflac_uint32 bl
         }
         }
     }
     }
 
 
-    if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, lpcShift, coefficients, pDecodedSamples)) {
+    if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) {
         return DRFLAC_FALSE;
         return DRFLAC_FALSE;
     }
     }
 
 
@@ -5219,6 +5284,9 @@ static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_u
                 return DRFLAC_FALSE;
                 return DRFLAC_FALSE;
             }
             }
             crc8 = drflac_crc8(crc8, header->blockSizeInPCMFrames, 16);
             crc8 = drflac_crc8(crc8, header->blockSizeInPCMFrames, 16);
+            if (header->blockSizeInPCMFrames == 0xFFFF) {
+                return DRFLAC_FALSE;    /* Frame is too big. This is the size of the frame minus 1. The STREAMINFO block defines the max block size which is 16-bits. Adding one will make it 17 bits and therefore too big. */
+            }
             header->blockSizeInPCMFrames += 1;
             header->blockSizeInPCMFrames += 1;
         } else {
         } else {
             DRFLAC_ASSERT(blockSize >= 8);
             DRFLAC_ASSERT(blockSize >= 8);
@@ -5257,6 +5325,11 @@ static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_u
             header->bitsPerSample = streaminfoBitsPerSample;
             header->bitsPerSample = streaminfoBitsPerSample;
         }
         }
 
 
+        if (header->bitsPerSample != streaminfoBitsPerSample) {
+            /* If this subframe has a different bitsPerSample then streaminfo or the first frame, reject it */
+            return DRFLAC_FALSE;
+        }
+
         if (!drflac__read_uint8(bs, 8, &header->crc8)) {
         if (!drflac__read_uint8(bs, 8, &header->crc8)) {
             return DRFLAC_FALSE;
             return DRFLAC_FALSE;
         }
         }
@@ -5343,6 +5416,11 @@ static drflac_bool32 drflac__decode_subframe(drflac_bs* bs, drflac_frame* frame,
         subframeBitsPerSample += 1;
         subframeBitsPerSample += 1;
     }
     }
 
 
+    if (subframeBitsPerSample > 32) {
+        /* libFLAC and ffmpeg reject 33-bit subframes as well */
+        return DRFLAC_FALSE;
+    }
+
     /* Need to handle wasted bits per sample. */
     /* Need to handle wasted bits per sample. */
     if (pSubframe->wastedBitsPerSample >= subframeBitsPerSample) {
     if (pSubframe->wastedBitsPerSample >= subframeBitsPerSample) {
         return DRFLAC_FALSE;
         return DRFLAC_FALSE;
@@ -6485,7 +6563,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
                     pRunningData    = (const char*)pRawData;
                     pRunningData    = (const char*)pRawData;
                     pRunningDataEnd = (const char*)pRawData + blockSize;
                     pRunningDataEnd = (const char*)pRawData + blockSize;
 
 
-                    metadata.data.vorbis_comment.vendorLength = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
+                    metadata.data.vorbis_comment.vendorLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
 
 
                     /* Need space for the rest of the block */
                     /* Need space for the rest of the block */
                     if ((pRunningDataEnd - pRunningData) - 4 < (drflac_int64)metadata.data.vorbis_comment.vendorLength) { /* <-- Note the order of operations to avoid overflow to a valid value */
                     if ((pRunningDataEnd - pRunningData) - 4 < (drflac_int64)metadata.data.vorbis_comment.vendorLength) { /* <-- Note the order of operations to avoid overflow to a valid value */
@@ -6493,7 +6571,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
                         return DRFLAC_FALSE;
                         return DRFLAC_FALSE;
                     }
                     }
                     metadata.data.vorbis_comment.vendor       = pRunningData;                                            pRunningData += metadata.data.vorbis_comment.vendorLength;
                     metadata.data.vorbis_comment.vendor       = pRunningData;                                            pRunningData += metadata.data.vorbis_comment.vendorLength;
-                    metadata.data.vorbis_comment.commentCount = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
+                    metadata.data.vorbis_comment.commentCount = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
 
 
                     /* Need space for 'commentCount' comments after the block, which at minimum is a drflac_uint32 per comment */
                     /* Need space for 'commentCount' comments after the block, which at minimum is a drflac_uint32 per comment */
                     if ((pRunningDataEnd - pRunningData) / sizeof(drflac_uint32) < metadata.data.vorbis_comment.commentCount) { /* <-- Note the order of operations to avoid overflow to a valid value */
                     if ((pRunningDataEnd - pRunningData) / sizeof(drflac_uint32) < metadata.data.vorbis_comment.commentCount) { /* <-- Note the order of operations to avoid overflow to a valid value */
@@ -6511,7 +6589,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
                             return DRFLAC_FALSE;
                             return DRFLAC_FALSE;
                         }
                         }
 
 
-                        commentLength = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
+                        commentLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
                         if (pRunningDataEnd - pRunningData < (drflac_int64)commentLength) { /* <-- Note the order of operations to avoid overflow to a valid value */
                         if (pRunningDataEnd - pRunningData < (drflac_int64)commentLength) { /* <-- Note the order of operations to avoid overflow to a valid value */
                             drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
                             drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
                             return DRFLAC_FALSE;
                             return DRFLAC_FALSE;
@@ -6620,8 +6698,8 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
                     pRunningData    = (const char*)pRawData;
                     pRunningData    = (const char*)pRawData;
                     pRunningDataEnd = (const char*)pRawData + blockSize;
                     pRunningDataEnd = (const char*)pRawData + blockSize;
 
 
-                    metadata.data.picture.type       = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
-                    metadata.data.picture.mimeLength = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
+                    metadata.data.picture.type       = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
+                    metadata.data.picture.mimeLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
 
 
                     /* Need space for the rest of the block */
                     /* Need space for the rest of the block */
                     if ((pRunningDataEnd - pRunningData) - 24 < (drflac_int64)metadata.data.picture.mimeLength) { /* <-- Note the order of operations to avoid overflow to a valid value */
                     if ((pRunningDataEnd - pRunningData) - 24 < (drflac_int64)metadata.data.picture.mimeLength) { /* <-- Note the order of operations to avoid overflow to a valid value */
@@ -6629,7 +6707,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
                         return DRFLAC_FALSE;
                         return DRFLAC_FALSE;
                     }
                     }
                     metadata.data.picture.mime              = pRunningData;                                            pRunningData += metadata.data.picture.mimeLength;
                     metadata.data.picture.mime              = pRunningData;                                            pRunningData += metadata.data.picture.mimeLength;
-                    metadata.data.picture.descriptionLength = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
+                    metadata.data.picture.descriptionLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
 
 
                     /* Need space for the rest of the block */
                     /* Need space for the rest of the block */
                     if ((pRunningDataEnd - pRunningData) - 20 < (drflac_int64)metadata.data.picture.descriptionLength) { /* <-- Note the order of operations to avoid overflow to a valid value */
                     if ((pRunningDataEnd - pRunningData) - 20 < (drflac_int64)metadata.data.picture.descriptionLength) { /* <-- Note the order of operations to avoid overflow to a valid value */
@@ -6637,11 +6715,11 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
                         return DRFLAC_FALSE;
                         return DRFLAC_FALSE;
                     }
                     }
                     metadata.data.picture.description     = pRunningData;                                            pRunningData += metadata.data.picture.descriptionLength;
                     metadata.data.picture.description     = pRunningData;                                            pRunningData += metadata.data.picture.descriptionLength;
-                    metadata.data.picture.width           = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
-                    metadata.data.picture.height          = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
-                    metadata.data.picture.colorDepth      = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
-                    metadata.data.picture.indexColorCount = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
-                    metadata.data.picture.pictureDataSize = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4;
+                    metadata.data.picture.width           = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
+                    metadata.data.picture.height          = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
+                    metadata.data.picture.colorDepth      = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
+                    metadata.data.picture.indexColorCount = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
+                    metadata.data.picture.pictureDataSize = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
                     metadata.data.picture.pPictureData    = (const drflac_uint8*)pRunningData;
                     metadata.data.picture.pPictureData    = (const drflac_uint8*)pRunningData;
 
 
                     /* Need space for the picture after the block */
                     /* Need space for the picture after the block */
@@ -7865,7 +7943,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac
 #ifndef DR_FLAC_NO_OGG
 #ifndef DR_FLAC_NO_OGG
     if (init.container == drflac_container_ogg) {
     if (init.container == drflac_container_ogg) {
         drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + seektableSize);
         drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + seektableSize);
-        *pInternalOggbs = oggbs;
+        DRFLAC_COPY_MEMORY(pInternalOggbs, &oggbs, sizeof(oggbs));
 
 
         /* The Ogg bistream needs to be layered on top of the original bitstream. */
         /* The Ogg bistream needs to be layered on top of the original bitstream. */
         pFlac->bs.onRead = drflac__on_read_ogg;
         pFlac->bs.onRead = drflac__on_read_ogg;
@@ -11786,7 +11864,7 @@ DRFLAC_API const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator
         return NULL;
         return NULL;
     }
     }
 
 
-    length = drflac__le2host_32(*(const drflac_uint32*)pIter->pRunningData);
+    length = drflac__le2host_32_ptr_unaligned(pIter->pRunningData);
     pIter->pRunningData += 4;
     pIter->pRunningData += 4;
 
 
     pComment = pIter->pRunningData;
     pComment = pIter->pRunningData;
@@ -11856,6 +11934,22 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat
 /*
 /*
 REVISION HISTORY
 REVISION HISTORY
 ================
 ================
+v0.12.38 - 2022-04-10
+  - Fix compilation error on older versions of GCC.
+
+v0.12.37 - 2022-02-12
+  - Improve ARM detection.
+
+v0.12.36 - 2022-02-07
+  - Fix a compilation error with the ARM build.
+
+v0.12.35 - 2022-02-06
+  - Fix a bug due to underestimating the amount of precision required for the prediction stage.
+  - Fix some bugs found from fuzz testing.
+
+v0.12.34 - 2022-01-07
+  - Fix some misalignment bugs when reading metadata.
+
 v0.12.33 - 2021-12-22
 v0.12.33 - 2021-12-22
   - Fix a bug with seeking when the seek table does not start at PCM frame 0.
   - Fix a bug with seeking when the seek table does not start at PCM frame 0.
 
 

+ 5 - 0
src/modules/audio/Audio.cpp

@@ -78,6 +78,11 @@ bool Audio::setMixWithSystem(bool mix)
 #endif
 #endif
 }
 }
 
 
+void Audio::setPlaybackDevice(const char */*name*/)
+{
+	throw love::Exception("Re-setting output device is not supported.");
+}
+
 StringMap<Audio::DistanceModel, Audio::DISTANCE_MAX_ENUM>::Entry Audio::distanceModelEntries[] =
 StringMap<Audio::DistanceModel, Audio::DISTANCE_MAX_ENUM>::Entry Audio::distanceModelEntries[] =
 {
 {
 	{"none", Audio::DISTANCE_NONE},
 	{"none", Audio::DISTANCE_NONE},

+ 15 - 0
src/modules/audio/Audio.h

@@ -297,6 +297,21 @@ public:
 	virtual void pauseContext() = 0;
 	virtual void pauseContext() = 0;
 	virtual void resumeContext() = 0;
 	virtual void resumeContext() = 0;
 
 
+	/**
+	 * Get current playback device name.
+	 */
+	virtual std::string getPlaybackDevice() = 0;
+
+	/**
+	 * Retrieve list of available playback devices.
+	 */
+	virtual void getPlaybackDevices(std::vector<std::string> &list) = 0;
+
+	/**
+	 * Set the current playback device to specified device name.
+	 */
+	virtual void setPlaybackDevice(const char *name);
+
 private:
 private:
 
 
 	static StringMap<DistanceModel, DISTANCE_MAX_ENUM>::Entry distanceModelEntries[];
 	static StringMap<DistanceModel, DISTANCE_MAX_ENUM>::Entry distanceModelEntries[];

+ 9 - 0
src/modules/audio/null/Audio.cpp

@@ -211,6 +211,15 @@ void Audio::resumeContext()
 {
 {
 }
 }
 
 
+std::string Audio::getPlaybackDevice()
+{
+	return "";
+}
+
+void Audio::getPlaybackDevices(std::vector<std::string> &/*list*/)
+{
+}
+
 
 
 } // null
 } // null
 } // audio
 } // audio

+ 3 - 0
src/modules/audio/null/Audio.h

@@ -89,6 +89,9 @@ public:
 	void pauseContext();
 	void pauseContext();
 	void resumeContext();
 	void resumeContext();
 
 
+	std::string getPlaybackDevice();
+	void getPlaybackDevices(std::vector<std::string> &list);
+
 private:
 private:
 	float volume;
 	float volume;
 	DistanceModel distanceModel;
 	DistanceModel distanceModel;

+ 65 - 5
src/modules/audio/openal/Audio.cpp

@@ -93,6 +93,18 @@ ALenum Audio::getFormat(int bitDepth, int channels)
 	return AL_NONE;
 	return AL_NONE;
 }
 }
 
 
+static const char *getDeviceSpecifier(ALCdevice *device)
+{
+#ifndef ALC_ALL_DEVICES_SPECIFIER
+	constexpr ALCenum ALC_ALL_DEVICES_SPECIFIER = 0x1013;
+#endif
+	static ALCenum deviceEnum = alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") == ALC_TRUE
+		? ALC_ALL_DEVICES_SPECIFIER
+		: ALC_DEVICE_SPECIFIER;
+
+	return alcGetString(device, deviceEnum);
+}
+
 Audio::Audio()
 Audio::Audio()
 	: device(nullptr)
 	: device(nullptr)
 	, context(nullptr)
 	, context(nullptr)
@@ -100,6 +112,9 @@ Audio::Audio()
 	, poolThread(nullptr)
 	, poolThread(nullptr)
 	, distanceModel(DISTANCE_INVERSE_CLAMPED)
 	, distanceModel(DISTANCE_INVERSE_CLAMPED)
 {
 {
+	attribs.push_back(0);
+	attribs.push_back(0);
+
 	// Before opening new device, check if recording
 	// Before opening new device, check if recording
 	// is requested.
 	// is requested.
 	if (getRequestRecordingPermission())
 	if (getRequestRecordingPermission())
@@ -122,12 +137,11 @@ Audio::Audio()
 			throw love::Exception("Could not open device.");
 			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 };
-#else
-		ALint *attribs = nullptr;
+		attribs.insert(attribs.begin(), ALC_MAX_AUXILIARY_SENDS);
+		attribs.insert(attribs.begin() + 1, MAX_SOURCE_EFFECTS);
 #endif
 #endif
 
 
-		context = alcCreateContext(device, attribs);
+		context = alcCreateContext(device, attribs.data());
 
 
 		if (context == nullptr)
 		if (context == nullptr)
 			throw love::Exception("Could not create context.");
 			throw love::Exception("Could not create context.");
@@ -165,7 +179,7 @@ Audio::Audio()
 
 
 	try
 	try
 	{
 	{
-		pool = new Pool();
+		pool = new Pool(device);
 	}
 	}
 	catch (love::Exception &)
 	catch (love::Exception &)
 	{
 	{
@@ -314,6 +328,52 @@ void Audio::resumeContext()
 		alcMakeContextCurrent(context);
 		alcMakeContextCurrent(context);
 }
 }
 
 
+std::string Audio::getPlaybackDevice()
+{
+	const char *dev = getDeviceSpecifier(device);
+
+	if (dev == nullptr)
+		throw Exception("Failed to get current device: %s", alcGetString(device, alcGetError(device)));
+
+	return dev;
+}
+
+void Audio::getPlaybackDevices(std::vector<std::string> &list)
+{
+	const char *devices = getDeviceSpecifier(nullptr);
+
+	if (devices == nullptr)
+		throw Exception("Failed to enumerate devices: %s", alcGetString(nullptr, alcGetError(nullptr)));
+
+	for (const char *device = devices; *device; device++)
+	{
+		list.emplace_back(device);
+		device += list.back().length();
+	}
+}
+
+void Audio::setPlaybackDevice(const char* name)
+{
+#ifndef ALC_SOFT_reopen_device
+	typedef ALCboolean (ALC_APIENTRY*LPALCREOPENDEVICESOFT)(ALCdevice *device,
+		const ALCchar *deviceName, const ALCint *attribs);
+#endif
+	static LPALCREOPENDEVICESOFT alcReopenDeviceSOFT = alcIsExtensionPresent(device, "ALC_SOFT_reopen_device") == ALC_TRUE
+		? (LPALCREOPENDEVICESOFT) alcGetProcAddress(device, "alcReopenDeviceSOFT")
+		: nullptr;
+
+	if (alcReopenDeviceSOFT == nullptr)
+	{
+		// Default implementation throws exception. To make
+		// error message consistent, call the base class.
+		love::audio::Audio::setPlaybackDevice(name);
+		return;
+	}
+
+	if (alcReopenDeviceSOFT(device, (const ALCchar *) name, attribs.data()) == ALC_FALSE)
+		throw love::Exception("Cannot set output device: %s", alcGetString(device, alcGetError(device)));
+}
+
 void Audio::setVolume(float volume)
 void Audio::setVolume(float volume)
 {
 {
 	alListenerf(AL_GAIN, volume);
 	alListenerf(AL_GAIN, volume);

+ 5 - 0
src/modules/audio/openal/Audio.h

@@ -127,6 +127,10 @@ public:
 
 
 	bool getEffectID(const char *name, ALuint &id);
 	bool getEffectID(const char *name, ALuint &id);
 
 
+	std::string getPlaybackDevice();
+	void getPlaybackDevices(std::vector<std::string> &list);
+	void setPlaybackDevice(const char *name);
+
 private:
 private:
 	void initializeEFX();
 	void initializeEFX();
 	// The OpenAL device.
 	// The OpenAL device.
@@ -137,6 +141,7 @@ private:
 
 
 	// The OpenAL context.
 	// The OpenAL context.
 	ALCcontext *context;
 	ALCcontext *context;
+	std::vector<ALCint> attribs;
 
 
 	// The OpenAL effects
 	// The OpenAL effects
 	struct EffectMapStorage
 	struct EffectMapStorage

+ 50 - 2
src/modules/audio/openal/Pool.cpp

@@ -20,6 +20,7 @@
 
 
 #include "Pool.h"
 #include "Pool.h"
 
 
+#include "event/Event.h"
 #include "Source.h"
 #include "Source.h"
 
 
 namespace love
 namespace love
@@ -29,8 +30,20 @@ namespace audio
 namespace openal
 namespace openal
 {
 {
 
 
-Pool::Pool()
-	: sources()
+static Variant::SharedTable *putSourcesAsSharedTable(std::vector<audio::Source *> &sources)
+{
+	Variant::SharedTable *table = new Variant::SharedTable();
+
+	for (int i = 0; i < sources.size(); i++)
+		table->pairs.emplace_back((double) (i + 1), Variant(&Source::type, sources[i]));
+
+	return table;
+}
+
+Pool::Pool(ALCdevice *device)
+	: device(device)
+	, sources()
+	, disconnectNotified(false)
 	, totalSources(0)
 	, totalSources(0)
 {
 {
 	// Clear errors.
 	// Clear errors.
@@ -101,8 +114,43 @@ bool Pool::isPlaying(Source *s)
 
 
 void Pool::update()
 void Pool::update()
 {
 {
+#ifndef ALC_CONNECTED
+	constexpr ALCenum ALC_CONNECTED = 0x313;
+#endif
+
 	thread::Lock lock(mutex);
 	thread::Lock lock(mutex);
 
 
+	static bool disconnectExtSupported = alcIsExtensionPresent(device, "ALC_EXT_Disconnect") == ALC_TRUE;
+
+	// Device disconnection event
+	if (disconnectExtSupported)
+	{
+		auto eventModule = Module::getInstance<event::Event>(Module::M_EVENT);
+		if (eventModule)
+		{
+			ALCint connected;
+			alcGetIntegerv(device, ALC_CONNECTED, 1, &connected);
+
+			if (connected)
+				disconnectNotified = false;
+			else if (!disconnectNotified)
+			{
+				// Get all sources in this Pool then stop it
+				// since they're all internally stopped.
+				std::vector<audio::Source *> sources = getPlayingSources();
+				Source::stop(sources);
+
+				std::vector<Variant> vargs;
+				vargs.emplace_back(putSourcesAsSharedTable(sources));
+
+				StrongRef<event::Message> msg(new event::Message("audiodisconnected", vargs), Acquire::NORETAIN);
+				eventModule->push(msg);
+
+				disconnectNotified = true;
+			}
+		}
+	}
+
 	std::vector<Source *> torelease;
 	std::vector<Source *> torelease;
 
 
 	for (const auto &i : playing)
 	for (const auto &i : playing)

+ 7 - 1
src/modules/audio/openal/Pool.h

@@ -64,7 +64,7 @@ class Pool
 {
 {
 public:
 public:
 
 
-	Pool();
+	Pool(ALCdevice *device);
 	~Pool();
 	~Pool();
 
 
 	/**
 	/**
@@ -101,9 +101,15 @@ private:
 	// Maximum possible number of OpenAL sources the pool attempts to generate.
 	// Maximum possible number of OpenAL sources the pool attempts to generate.
 	static const int MAX_SOURCES = 64;
 	static const int MAX_SOURCES = 64;
 
 
+	// Current OpenAL device
+	ALCdevice *device;
+
 	// OpenAL sources
 	// OpenAL sources
 	ALuint sources[MAX_SOURCES];
 	ALuint sources[MAX_SOURCES];
 
 
+	// Is device disconnection has been notified?
+	bool disconnectNotified;
+
 	// Total number of created sources in the pool.
 	// Total number of created sources in the pool.
 	int totalSources;
 	int totalSources;
 
 

+ 73 - 21
src/modules/audio/wrap_Audio.cpp

@@ -48,32 +48,35 @@ int w_newSource(lua_State *L)
 {
 {
 	Source::Type stype = Source::TYPE_STREAM;
 	Source::Type stype = Source::TYPE_STREAM;
 
 
-	if (!luax_istype(L, 1, love::sound::SoundData::type) && !luax_istype(L, 1, love::sound::Decoder::type))
+	if (!luax_istype(L, 1, love::sound::SoundData::type))
 	{
 	{
-		const char *stypestr = luaL_checkstring(L, 2);
-		if (stypestr && !Source::getConstant(stypestr, stype))
-			return luax_enumerror(L, "source type", Source::getConstants(stype), stypestr);
+		if (!luax_istype(L, 1, love::sound::Decoder::type))
+		{
+			const char *stypestr = luaL_checkstring(L, 2);
+			if (stypestr && !Source::getConstant(stypestr, stype))
+				return luax_enumerror(L, "source type", Source::getConstants(stype), stypestr);
 
 
-		if (stype == Source::TYPE_QUEUE)
-			return luaL_error(L, "Cannot create queueable sources using newSource. Use newQueueableSource instead.");
-	}
+			if (stype == Source::TYPE_QUEUE)
+				return luaL_error(L, "Cannot create queueable sources using newSource. Use newQueueableSource instead.");
+		}
 
 
-	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
+		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);
 			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");
+			// (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))
@@ -543,6 +546,52 @@ int w_setMixWithSystem(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_getPlaybackDevice(lua_State* L)
+{
+	std::string device;
+
+	luax_catchexcept(L, [&]() { device = instance()->getPlaybackDevice(); });
+	luax_pushstring(L, device);
+	return 1;
+}
+
+int w_getPlaybackDevices(lua_State* L)
+{
+	std::vector<std::string> list;
+
+	luax_catchexcept(L, [&]() { instance()->getPlaybackDevices(list); });
+	lua_createtable(L, 0, (int) list.size());
+	for (int i = 0; i < (int) list.size(); i++)
+	{
+		lua_pushnumber(L, i + 1);
+		lua_pushstring(L, list[i].c_str());
+		lua_rawset(L, -3);
+	}
+
+	return 1;
+}
+
+int w_setPlaybackDevice(lua_State* L)
+{
+	const char *device = luaL_optstring(L, 1, nullptr);
+
+	try
+	{
+		instance()->setPlaybackDevice(device);
+		luax_pushboolean(L, true);
+		return 1;
+	}
+	catch (love::Exception& e)
+	{
+		luax_pushboolean(L, false);
+		lua_pushstring(L, e.what());
+		return 2;
+	}
+
+	// To avoid compiler warning
+	return 0;
+}
+
 // List of functions to wrap.
 // List of functions to wrap.
 static const luaL_Reg functions[] =
 static const luaL_Reg functions[] =
 {
 {
@@ -574,6 +623,9 @@ static const luaL_Reg functions[] =
 	{ "getMaxSourceEffects", w_getMaxSourceEffects },
 	{ "getMaxSourceEffects", w_getMaxSourceEffects },
 	{ "isEffectsSupported", w_isEffectsSupported },
 	{ "isEffectsSupported", w_isEffectsSupported },
 	{ "setMixWithSystem", w_setMixWithSystem },
 	{ "setMixWithSystem", w_setMixWithSystem },
+	{ "getPlaybackDevice", w_getPlaybackDevice },
+	{ "getPlaybackDevices", w_getPlaybackDevices },
+	{ "setPlaybackDevice", w_setPlaybackDevice },
 
 
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };

+ 20 - 1
src/modules/filesystem/NativeFile.cpp

@@ -22,6 +22,10 @@
 #include "NativeFile.h"
 #include "NativeFile.h"
 #include "common/utf8.h"
 #include "common/utf8.h"
 
 
+#ifdef LOVE_ANDROID
+#include "common/android.h"
+#endif
+
 // Assume POSIX or Visual Studio.
 // Assume POSIX or Visual Studio.
 #include <sys/types.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/stat.h>
@@ -84,7 +88,22 @@ bool NativeFile::open(Mode newmode)
 	if (file != nullptr)
 	if (file != nullptr)
 		return false;
 		return false;
 
 
-#ifdef LOVE_WINDOWS
+#if defined(LOVE_ANDROID)
+	// Try to handle content:// URI
+	int fd = love::android::getFDFromContentProtocol(filename.c_str());
+	if (fd != -1)
+	{
+		if (newmode != MODE_READ)
+		{
+			::close(fd);
+			throw love::Exception("%s is read-only.", filename.c_str());
+		}
+
+		file = fdopen(fd, "rb");
+	}
+	else
+		file = fopen(filename.c_str(), getModeString(newmode));
+#elif defined(LOVE_WINDOWS)
 	// make sure non-ASCII filenames work.
 	// make sure non-ASCII filenames work.
 	std::wstring modestr = to_widestr(getModeString(newmode));
 	std::wstring modestr = to_widestr(getModeString(newmode));
 	std::wstring wfilename = to_widestr(filename);
 	std::wstring wfilename = to_widestr(filename);

+ 10 - 26
src/modules/filesystem/physfs/Filesystem.cpp

@@ -236,8 +236,6 @@ bool Filesystem::setSource(const char *source)
 	if (!love::android::createStorageDirectories())
 	if (!love::android::createStorageDirectories())
 		SDL_Log("Error creating storage directories!");
 		SDL_Log("Error creating storage directories!");
 
 
-	new_search_path = "";
-
 	PHYSFS_Io *gameLoveIO;
 	PHYSFS_Io *gameLoveIO;
 	bool hasFusedGame = love::android::checkFusedGame((void **) &gameLoveIO);
 	bool hasFusedGame = love::android::checkFusedGame((void **) &gameLoveIO);
 	bool isAAssetMounted = false;
 	bool isAAssetMounted = false;
@@ -263,38 +261,24 @@ bool Filesystem::setSource(const char *source)
 
 
 	if (!isAAssetMounted)
 	if (!isAAssetMounted)
 	{
 	{
-		new_search_path = love::android::getSelectedGameFile();
-
-		// try mounting first, if that fails, load to memory and mount
-		if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
+		// Is this love2d://fd/ URIs?
+		int fd = love::android::getFDFromLoveProtocol(new_search_path.c_str());
+		if (fd != -1)
 		{
 		{
-			// PHYSFS cannot yet mount a zip file inside an .apk
-			SDL_Log("Mounting %s did not work. Loading to memory.",
-					new_search_path.c_str());
-			char* game_archive_ptr = NULL;
-			size_t game_archive_size = 0;
-			if (!love::android::loadGameArchiveToMemory(
-						new_search_path.c_str(), &game_archive_ptr,
-						&game_archive_size))
+			PHYSFS_Io *io = (PHYSFS_Io *) love::android::getIOFromFD(fd);
+
+			if (PHYSFS_mountIo(io, "LOVE.FD", nullptr, 0))
 			{
 			{
-				SDL_Log("Failure memory loading archive %s", new_search_path.c_str());
-				return false;
-			}
-			if (!PHYSFS_mountMemory(
-					game_archive_ptr, game_archive_size,
-					love::android::freeGameArchiveMemory, "archive.zip", "/", 0))
-			{
-				SDL_Log("Failure mounting in-memory archive.");
-				love::android::freeGameArchiveMemory(game_archive_ptr);
-				return false;
+				gameSource = new_search_path;
+				return true;
 			}
 			}
 		}
 		}
 	}
 	}
-#else
+#endif
+
 	// Add the directory.
 	// Add the directory.
 	if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
 	if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
 		return false;
 		return false;
-#endif
 
 
 	// Save the game source.
 	// Save the game source.
 	gameSource = new_search_path;
 	gameSource = new_search_path;

+ 26 - 30
src/modules/filesystem/wrap_Filesystem.cpp

@@ -305,7 +305,7 @@ File *luax_getfile(lua_State *L, int idx)
 	return file;
 	return file;
 }
 }
 
 
-FileData *luax_getfiledata(lua_State *L, int idx)
+FileData *luax_getfiledata(lua_State *L, int idx, bool ioerror)
 {
 {
 	FileData *data = nullptr;
 	FileData *data = nullptr;
 	File *file = nullptr;
 	File *file = nullptr;
@@ -325,18 +325,33 @@ FileData *luax_getfiledata(lua_State *L, int idx)
 		luaL_argerror(L, idx, "filename, File, or FileData expected");
 		luaL_argerror(L, idx, "filename, File, or FileData expected");
 		return nullptr; // Never reached.
 		return nullptr; // Never reached.
 	}
 	}
-
-	if (file)
+	else if (file && !data)
 	{
 	{
-		luax_catchexcept(L,
-			[&]() { data = file->read(); },
-			[&](bool) { file->release(); }
-		);
+		try
+		{
+			data = file->read();
+		}
+		catch (love::Exception &e)
+		{
+			file->release();
+			if (ioerror)
+				luax_ioError(L, "%s", e.what());
+			else
+				luaL_error(L, "%s", e.what());
+			return nullptr; // Never reached.
+		}
+
+		file->release();
 	}
 	}
 
 
 	return data;
 	return data;
 }
 }
 
 
+FileData *luax_getfiledata(lua_State *L, int idx)
+{
+	return luax_getfiledata(L, idx, false);
+}
+
 Data *luax_getdata(lua_State *L, int idx)
 Data *luax_getdata(lua_State *L, int idx)
 {
 {
 	Data *data = nullptr;
 	Data *data = nullptr;
@@ -389,29 +404,10 @@ int w_newFileData(lua_State *L)
 	// Single argument: treat as filepath or File.
 	// Single argument: treat as filepath or File.
 	if (lua_gettop(L) == 1)
 	if (lua_gettop(L) == 1)
 	{
 	{
-		// We don't use luax_getfiledata because we want to use an ioError.
-		if (lua_isstring(L, 1))
-			luax_convobj(L, 1, "filesystem", "newFile");
-
-		// Get FileData from the File.
-		if (luax_istype(L, 1, File::type))
-		{
-			File *file = luax_checkfile(L, 1);
-
-			StrongRef<FileData> data;
-			try
-			{
-				data.set(file->read(), Acquire::NORETAIN);
-			}
-			catch (love::Exception &e)
-			{
-				return luax_ioError(L, "%s", e.what());
-			}
-			luax_pushtype(L, data);
-			return 1;
-		}
-		else
-			return luaL_argerror(L, 1, "filename or File expected");
+		FileData *data = luax_getfiledata(L, 1, true);
+		luax_pushtype(L, data);
+		data->release();
+		return 1;
 	}
 	}
 
 
 	size_t length = 0;
 	size_t length = 0;

+ 3 - 2
src/modules/graphics/Buffer.cpp

@@ -36,6 +36,7 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
 	, usageFlags(settings.usageFlags)
 	, usageFlags(settings.usageFlags)
 	, dataUsage(settings.dataUsage)
 	, dataUsage(settings.dataUsage)
 	, mapped(false)
 	, mapped(false)
+	, mappedType(MAP_WRITE_INVALIDATE)
 	, immutable(false)
 	, immutable(false)
 {
 {
 	if (size == 0 && arraylength == 0)
 	if (size == 0 && arraylength == 0)
@@ -61,8 +62,8 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
 	if (storagebuffer && dataUsage == BUFFERDATAUSAGE_STREAM)
 	if (storagebuffer && dataUsage == BUFFERDATAUSAGE_STREAM)
 		throw love::Exception("Buffers created with 'stream' data usage cannot be used as a shader storage buffer.");
 		throw love::Exception("Buffers created with 'stream' data usage cannot be used as a shader storage buffer.");
 
 
-	if (dataUsage == BUFFERDATAUSAGE_STAGING && (indexbuffer || vertexbuffer || texelbuffer || storagebuffer))
-		throw love::Exception("Buffers created with 'staging' data usage cannot be index, vertex, texel, or shaderstorage buffer types.");
+	if (dataUsage == BUFFERDATAUSAGE_READBACK && (indexbuffer || vertexbuffer || texelbuffer || storagebuffer))
+		throw love::Exception("Buffers created with 'readback' data usage cannot be index, vertex, texel, or shaderstorage buffer types.");
 
 
 	size_t offset = 0;
 	size_t offset = 0;
 	size_t stride = 0;
 	size_t stride = 0;

+ 5 - 4
src/modules/graphics/Buffer.h

@@ -53,6 +53,7 @@ public:
 	enum MapType
 	enum MapType
 	{
 	{
 		MAP_WRITE_INVALIDATE,
 		MAP_WRITE_INVALIDATE,
+		MAP_READ_ONLY,
 	};
 	};
 
 
 	struct DataDeclaration
 	struct DataDeclaration
@@ -128,7 +129,7 @@ public:
 	/**
 	/**
 	 * Fill a portion of the buffer with data.
 	 * Fill a portion of the buffer with data.
 	 */
 	 */
-	virtual void fill(size_t offset, size_t size, const void *data) = 0;
+	virtual bool fill(size_t offset, size_t size, const void *data) = 0;
 
 
 	/**
 	/**
 	 * Copy a portion of this Buffer's data to another buffer, using the GPU.
 	 * Copy a portion of this Buffer's data to another buffer, using the GPU.
@@ -147,10 +148,10 @@ public:
 	{
 	{
 	public:
 	public:
 
 
-		Mapper(Buffer &buffer)
+		Mapper(Buffer &buffer, MapType maptype = MAP_WRITE_INVALIDATE)
 			: buffer(buffer)
 			: buffer(buffer)
 		{
 		{
-			data = buffer.map(MAP_WRITE_INVALIDATE, 0, buffer.getSize());
+			data = buffer.map(maptype, 0, buffer.getSize());
 		}
 		}
 
 
 		~Mapper()
 		~Mapper()
@@ -179,7 +180,7 @@ protected:
 	BufferDataUsage dataUsage;
 	BufferDataUsage dataUsage;
 
 
 	bool mapped;
 	bool mapped;
-
+	MapType mappedType;
 	bool immutable;
 	bool immutable;
 	
 	
 }; // Buffer
 }; // Buffer

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

@@ -240,6 +240,9 @@ Graphics::~Graphics()
 	for (int i = 0; i < (int) SHADERSTAGE_MAX_ENUM; i++)
 	for (int i = 0; i < (int) SHADERSTAGE_MAX_ENUM; i++)
 		cachedShaderStages[i].clear();
 		cachedShaderStages[i].clear();
 
 
+	pendingReadbacks.clear();
+	clearTemporaryResources();
+
 	Shader::deinitialize();
 	Shader::deinitialize();
 }
 }
 
 
@@ -438,6 +441,46 @@ love::graphics::Text *Graphics::newText(graphics::Font *font, const std::vector<
 	return new Text(font, text);
 	return new Text(font, text);
 }
 }
 
 
+love::data::ByteData *Graphics::readbackBuffer(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
+{
+	StrongRef<GraphicsReadback> readback;
+	readback.set(newReadbackInternal(READBACK_IMMEDIATE, buffer, offset, size, dest, destoffset), Acquire::NORETAIN);
+
+	auto data = readback->getBufferData();
+	if (data == nullptr)
+		throw love::Exception("love.graphics.readbackBuffer failed.");
+
+	data->retain();
+	return data;
+}
+
+GraphicsReadback *Graphics::readbackBufferAsync(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
+{
+	auto readback = newReadbackInternal(READBACK_ASYNC, buffer, offset, size, dest, destoffset);
+	pendingReadbacks.push_back(readback);
+	return readback;
+}
+
+image::ImageData *Graphics::readbackTexture(Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
+{
+	StrongRef<GraphicsReadback> readback;
+	readback.set(newReadbackInternal(READBACK_IMMEDIATE, texture, slice, mipmap, rect, dest, destx, desty), Acquire::NORETAIN);
+
+	auto imagedata = readback->getImageData();
+	if (imagedata == nullptr)
+		throw love::Exception("love.graphics.readbackTexture failed.");
+
+	imagedata->retain();
+	return imagedata;
+}
+
+GraphicsReadback *Graphics::readbackTextureAsync(Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
+{
+	auto readback = newReadbackInternal(READBACK_ASYNC, texture, slice, mipmap, rect, dest, destx, desty);
+	pendingReadbacks.push_back(readback);
+	return readback;
+}
+
 void Graphics::cleanupCachedShaderStage(ShaderStageType type, const std::string &hashkey)
 void Graphics::cleanupCachedShaderStage(ShaderStageType type, const std::string &hashkey)
 {
 {
 	cachedShaderStages[type].erase(hashkey);
 	cachedShaderStages[type].erase(hashkey);
@@ -893,6 +936,10 @@ void Graphics::setRenderTargets(const RenderTargets &rts)
 		realRTs.depthStencil.texture = getTemporaryTexture(dsformat, pixelw, pixelh, reqmsaa);
 		realRTs.depthStencil.texture = getTemporaryTexture(dsformat, pixelw, pixelh, reqmsaa);
 		realRTs.depthStencil.slice = 0;
 		realRTs.depthStencil.slice = 0;
 
 
+		// TODO: fix this to call release at the right time.
+		// This only works here because nothing else calls getTemporaryTexture.
+		releaseTemporaryTexture(realRTs.depthStencil.texture);
+
 		setRenderTargetsInternal(realRTs, pixelw, pixelh, hasSRGBtexture);
 		setRenderTargetsInternal(realRTs, pixelw, pixelh, hasSRGBtexture);
 	}
 	}
 	else
 	else
@@ -995,12 +1042,15 @@ Texture *Graphics::getTemporaryTexture(PixelFormat format, int w, int h, int sam
 
 
 	for (TemporaryTexture &temp : temporaryTextures)
 	for (TemporaryTexture &temp : temporaryTextures)
 	{
 	{
+		if (temp.framesSinceUse < 0)
+			continue;
+
 		Texture *c = temp.texture;
 		Texture *c = temp.texture;
 		if (c->getPixelFormat() == format && c->getPixelWidth() == w
 		if (c->getPixelFormat() == format && c->getPixelWidth() == w
 			&& c->getPixelHeight() == h && c->getRequestedMSAA() == samples)
 			&& c->getPixelHeight() == h && c->getRequestedMSAA() == samples)
 		{
 		{
 			texture = c;
 			texture = c;
-			temp.framesSinceUse = 0;
+			temp.framesSinceUse = -1;
 			break;
 			break;
 		}
 		}
 	}
 	}
@@ -1022,6 +1072,115 @@ Texture *Graphics::getTemporaryTexture(PixelFormat format, int w, int h, int sam
 	return texture;
 	return texture;
 }
 }
 
 
+void Graphics::releaseTemporaryTexture(Texture *texture)
+{
+	for (TemporaryTexture &temp : temporaryTextures)
+	{
+		if (temp.texture == texture)
+		{
+			temp.framesSinceUse = 0;
+			break;
+		}
+	}
+}
+
+Buffer *Graphics::getTemporaryBuffer(size_t size, DataFormat format, uint32 usageflags, BufferDataUsage datausage)
+{
+	Buffer *buffer = nullptr;
+
+	for (TemporaryBuffer &temp : temporaryBuffers)
+	{
+		if (temp.framesSinceUse < 0)
+			continue;
+
+		Buffer *b = temp.buffer;
+
+		if (temp.size == size && b->getDataMember(0).decl.format == format
+			&& b->getUsageFlags() == usageflags && b->getDataUsage() == datausage)
+		{
+			buffer = b;
+			temp.framesSinceUse = -1;
+			break;
+		}
+	}
+
+	if (buffer == nullptr)
+	{
+		Buffer::Settings settings(usageflags, datausage);
+		buffer = newBuffer(settings, format, nullptr, size, 0);
+
+		temporaryBuffers.emplace_back(buffer, size);
+	}
+
+	return buffer;
+}
+
+void Graphics::releaseTemporaryBuffer(Buffer *buffer)
+{
+	for (TemporaryBuffer &temp : temporaryBuffers)
+	{
+		if (temp.buffer == buffer)
+		{
+			temp.framesSinceUse = 0;
+			break;
+		}
+	}
+}
+
+void Graphics::updateTemporaryResources()
+{
+	for (int i = (int) temporaryTextures.size() - 1; i >= 0; i--)
+	{
+		auto &t = temporaryTextures[i];
+		if (t.framesSinceUse >= MAX_TEMPORARY_RESOURCE_UNUSED_FRAMES)
+		{
+			t.texture->release();
+			t = temporaryTextures.back();
+			temporaryTextures.pop_back();
+		}
+		else if (t.framesSinceUse >= 0)
+			t.framesSinceUse++;
+	}
+
+	for (int i = (int) temporaryBuffers.size() - 1; i >= 0; i--)
+	{
+		auto &t = temporaryBuffers[i];
+		if (t.framesSinceUse >= MAX_TEMPORARY_RESOURCE_UNUSED_FRAMES)
+		{
+			t.buffer->release();
+			t = temporaryBuffers.back();
+			temporaryBuffers.pop_back();
+		}
+		else if (t.framesSinceUse >= 0)
+			t.framesSinceUse++;
+	}
+}
+
+void Graphics::clearTemporaryResources()
+{
+	for (auto temp :temporaryBuffers)
+		temp.buffer->release();
+
+	for (auto temp : temporaryTextures)
+		temp.texture->release();
+
+	temporaryBuffers.clear();
+	temporaryTextures.clear();
+}
+
+void Graphics::updatePendingReadbacks()
+{
+	for (int i = (int)pendingReadbacks.size() - 1; i >= 0; i--)
+	{
+		pendingReadbacks[i]->update();
+		if (pendingReadbacks[i]->isComplete())
+		{
+			pendingReadbacks[i] = pendingReadbacks.back();
+			pendingReadbacks.pop_back();
+		}
+	}
+}
+
 void Graphics::intersectScissor(const Rect &rect)
 void Graphics::intersectScissor(const Rect &rect)
 {
 {
 	Rect currect = states.back().scissorRect;
 	Rect currect = states.back().scissorRect;
@@ -1187,6 +1346,9 @@ void Graphics::copyBuffer(Buffer *source, Buffer *dest, size_t sourceoffset, siz
 	if (dest->getDataUsage() == BUFFERDATAUSAGE_STREAM)
 	if (dest->getDataUsage() == BUFFERDATAUSAGE_STREAM)
 		throw love::Exception("Buffers created with 'stream' data usage cannot be used as a copy destination.");
 		throw love::Exception("Buffers created with 'stream' data usage cannot be used as a copy destination.");
 
 
+	if (source->getDataUsage() == BUFFERDATAUSAGE_READBACK)
+		throw love::Exception("Buffers created with 'readback' data usage cannot be used as a copy source.");
+
 	if (sourcerange.getMax() >= source->getSize())
 	if (sourcerange.getMax() >= source->getSize())
 		throw love::Exception("Buffer copy source offset and size doesn't fit within the source Buffer's size.");
 		throw love::Exception("Buffer copy source offset and size doesn't fit within the source Buffer's size.");
 
 
@@ -1296,6 +1458,9 @@ void Graphics::copyBufferToTexture(Buffer *source, Texture *dest, size_t sourceo
 	if (!capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE])
 	if (!capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE])
 		throw love::Exception("Copying a Buffer to a Texture is not supported on this system.");
 		throw love::Exception("Copying a Buffer to a Texture is not supported on this system.");
 
 
+	if (source->getDataUsage() == BUFFERDATAUSAGE_READBACK)
+		throw love::Exception("Buffers created with 'readback' data usage cannot be used as a copy source.");
+
 	PixelFormat format = dest->getPixelFormat();
 	PixelFormat format = dest->getPixelFormat();
 
 
 	if (isPixelFormatDepthStencil(format))
 	if (isPixelFormatDepthStencil(format))

+ 37 - 3
src/modules/graphics/Graphics.h

@@ -37,6 +37,7 @@
 #include "Shader.h"
 #include "Shader.h"
 #include "Quad.h"
 #include "Quad.h"
 #include "Mesh.h"
 #include "Mesh.h"
+#include "GraphicsReadback.h"
 #include "Deprecations.h"
 #include "Deprecations.h"
 #include "renderstate.h"
 #include "renderstate.h"
 #include "math/Transform.h"
 #include "math/Transform.h"
@@ -461,6 +462,12 @@ public:
 
 
 	Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
 	Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
 
 
+	data::ByteData *readbackBuffer(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset);
+	GraphicsReadback *readbackBufferAsync(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset);
+
+	image::ImageData *readbackTexture(Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty);
+	GraphicsReadback *readbackTextureAsync(Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty);
+
 	bool validateShader(bool gles, const std::vector<std::string> &stages, const Shader::CompileOptions &options, std::string &err);
 	bool validateShader(bool gles, const std::vector<std::string> &stages, const Shader::CompileOptions &options, std::string &err);
 
 
 	/**
 	/**
@@ -857,6 +864,12 @@ public:
 
 
 	static void flushBatchedDrawsGlobal();
 	static void flushBatchedDrawsGlobal();
 
 
+	Texture *getTemporaryTexture(PixelFormat format, int w, int h, int samples);
+	void releaseTemporaryTexture(Texture *texture);
+
+	Buffer *getTemporaryBuffer(size_t size, DataFormat format, uint32 usageflags, BufferDataUsage datausage);
+	void releaseTemporaryBuffer(Buffer *buffer);
+
 	void cleanupCachedShaderStage(ShaderStageType type, const std::string &cachekey);
 	void cleanupCachedShaderStage(ShaderStageType type, const std::string &cachekey);
 
 
 	template <typename T>
 	template <typename T>
@@ -955,6 +968,19 @@ protected:
 		}
 		}
 	};
 	};
 
 
+	struct TemporaryBuffer
+	{
+		Buffer *buffer;
+		size_t size;
+		int framesSinceUse;
+
+		TemporaryBuffer(Buffer *buf, size_t size)
+			: buffer(buf)
+			, size(size)
+			, framesSinceUse(-1)
+		{}
+	};
+
 	struct TemporaryTexture
 	struct TemporaryTexture
 	{
 	{
 		Texture *texture;
 		Texture *texture;
@@ -962,7 +988,7 @@ protected:
 
 
 		TemporaryTexture(Texture *tex)
 		TemporaryTexture(Texture *tex)
 			: texture(tex)
 			: texture(tex)
-			, framesSinceUse(0)
+			, framesSinceUse(-1)
 		{}
 		{}
 	};
 	};
 
 
@@ -971,6 +997,9 @@ protected:
 	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;
 
 
+	virtual GraphicsReadback *newReadbackInternal(ReadbackMethod method, Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset) = 0;
+	virtual GraphicsReadback *newReadbackInternal(ReadbackMethod method, Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty) = 0;
+
 	virtual bool dispatch(int x, int y, int z) = 0;
 	virtual bool dispatch(int x, int y, int z) = 0;
 
 
 	virtual void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture) = 0;
 	virtual void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture) = 0;
@@ -981,7 +1010,10 @@ protected:
 	void createQuadIndexBuffer();
 	void createQuadIndexBuffer();
 	void createFanIndexBuffer();
 	void createFanIndexBuffer();
 
 
-	Texture *getTemporaryTexture(PixelFormat format, int w, int h, int samples);
+	void updateTemporaryResources();
+	void clearTemporaryResources();
+
+	void updatePendingReadbacks();
 
 
 	void restoreState(const DisplayState &s);
 	void restoreState(const DisplayState &s);
 	void restoreStateChecked(const DisplayState &s);
 	void restoreStateChecked(const DisplayState &s);
@@ -1004,6 +1036,7 @@ protected:
 	StrongRef<love::graphics::Font> defaultFont;
 	StrongRef<love::graphics::Font> defaultFont;
 
 
 	std::vector<ScreenshotInfo> pendingScreenshotCallbacks;
 	std::vector<ScreenshotInfo> pendingScreenshotCallbacks;
+	std::vector<StrongRef<GraphicsReadback>> pendingReadbacks;
 
 
 	BatchedDrawState batchedDrawState;
 	BatchedDrawState batchedDrawState;
 
 
@@ -1015,6 +1048,7 @@ protected:
 	std::vector<DisplayState> states;
 	std::vector<DisplayState> states;
 	std::vector<StackType> stackTypeStack;
 	std::vector<StackType> stackTypeStack;
 
 
+	std::vector<TemporaryBuffer> temporaryBuffers;
 	std::vector<TemporaryTexture> temporaryTextures;
 	std::vector<TemporaryTexture> temporaryTextures;
 
 
 	int renderTargetSwitchCount;
 	int renderTargetSwitchCount;
@@ -1029,7 +1063,7 @@ protected:
 	Deprecations deprecations;
 	Deprecations deprecations;
 
 
 	static const size_t MAX_USER_STACK_DEPTH = 128;
 	static const size_t MAX_USER_STACK_DEPTH = 128;
-	static const int MAX_TEMPORARY_TEXTURE_UNUSED_FRAMES = 16;
+	static const int MAX_TEMPORARY_RESOURCE_UNUSED_FRAMES = 16;
 
 
 private:
 private:
 
 

+ 229 - 0
src/modules/graphics/GraphicsReadback.cpp

@@ -0,0 +1,229 @@
+/**
+ * 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 "GraphicsReadback.h"
+#include "Buffer.h"
+#include "Texture.h"
+#include "Graphics.h"
+#include "data/ByteData.h"
+#include "image/ImageData.h"
+#include "image/Image.h"
+
+namespace love
+{
+namespace graphics
+{
+
+love::Type GraphicsReadback::type("GraphicsReadback", &Object::type);
+
+GraphicsReadback::GraphicsReadback(Graphics *gfx, ReadbackMethod method, Buffer *buffer, size_t offset, size_t size, love::data::ByteData *dest, size_t destoffset)
+	: dataType(DATA_BUFFER)
+	, method(method)
+	, bufferData(dest)
+{
+	const auto &caps = gfx->getCapabilities();
+
+	if (!caps.features[Graphics::FEATURE_COPY_BUFFER])
+		throw love::Exception("readbackBuffer is not supported on this system (buffer copy support is required).");
+
+	if (offset + size > buffer->getSize())
+		throw love::Exception("Invalid offset or size for the given Buffer.");
+
+	if (dest != nullptr && destoffset + size > dest->getSize())
+		throw love::Exception("Invalid destination offset or size for the given ByteData.");
+
+	bufferDataOffset = dest != nullptr ? destoffset : 0;
+}
+
+GraphicsReadback::GraphicsReadback(Graphics *gfx, ReadbackMethod method, Texture *texture, int slice, int mipmap, const Rect &rect, love::image::ImageData *dest, int destx, int desty)
+	: dataType(DATA_TEXTURE)
+	, method(method)
+	, imageData(dest)
+	, rect(rect)
+{
+	const auto &caps = gfx->getCapabilities();
+
+	if (gfx->isRenderTargetActive(texture))
+		throw love::Exception("readbackTexture cannot be called while that Texture is an active render target.");
+
+	if (!texture->isReadable())
+		throw love::Exception("readbackTexture requires a readable Texture.");
+
+	int tw = texture->getPixelWidth(mipmap);
+	int th = texture->getPixelHeight(mipmap);
+	auto texType = texture->getTextureType();
+
+	if (rect.x < 0 || rect.y < 0 || rect.w <= 0 || rect.h <= 0 || (rect.x + rect.w) > tw || (rect.y + rect.h) > th)
+		throw love::Exception("Invalid rectangle dimensions.");
+
+	if (slice < 0 || (texType == TEXTURE_VOLUME && slice >= texture->getDepth(mipmap))
+		|| (texType == TEXTURE_2D_ARRAY && slice >= texture->getLayerCount())
+		|| (texType == TEXTURE_CUBE && slice >= 6))
+	{
+		throw love::Exception("Invalid slice index.");
+	}
+
+	textureFormat = getLinearPixelFormat(texture->getPixelFormat());
+
+	if (!image::ImageData::validPixelFormat(textureFormat))
+	{
+		const char *formatname = "unknown";
+		love::getConstant(textureFormat, formatname);
+		throw love::Exception("ImageData with the '%s' pixel format is not supported.", formatname);
+	}
+
+	bool isRT = texture->isRenderTarget();
+
+	if (method == READBACK_ASYNC)
+	{
+		if (isRT && !caps.features[Graphics::FEATURE_COPY_RENDER_TARGET_TO_BUFFER])
+			throw love::Exception("readbackTextureAsync is not supported on this system.");
+		else if (!isRT && !caps.features[Graphics::FEATURE_COPY_TEXTURE_TO_BUFFER])
+			throw love::Exception("readbackTextureAsync a with non-render-target textures is not supported on this system.");
+	}
+	else
+	{
+		if (!isRT && !caps.features[Graphics::FEATURE_COPY_TEXTURE_TO_BUFFER])
+			throw love::Exception("readbackTexture with a non-render-target texture is not supported on this system.");
+	}
+
+	if (dest != nullptr)
+	{
+		if (dest->getFormat() != textureFormat)
+			throw love::Exception("Destination ImageData pixel format must match the source Texture's format.");
+
+		if (destx < 0 || desty < 0)
+			throw love::Exception("Invalid destination ImageData x/y coordinates.");
+
+		if (destx + rect.w > dest->getWidth() || desty + rect.h > dest->getHeight())
+			throw love::Exception("The specified rectangle does not fit within the destination ImageData's dimensions.");
+	}
+
+	imageDataX = dest != nullptr ? destx : 0;
+	imageDataY = dest != nullptr ? desty : 0;
+}
+
+GraphicsReadback::~GraphicsReadback()
+{
+}
+
+love::data::ByteData *GraphicsReadback::getBufferData() const
+{
+	if (!isComplete())
+		return nullptr;
+	return bufferData;
+}
+
+love::image::ImageData *GraphicsReadback::getImageData() const
+{
+	if (!isComplete())
+		return nullptr;
+	return imageData;
+}
+
+void *GraphicsReadback::prepareReadbackDest(size_t size)
+{
+	if (dataType == DATA_TEXTURE)
+	{
+		if (imageData.get())
+		{
+			// Not the cleanest, but should work since uncompressed formats always
+			// have 1x1 blocks.
+			int pixels = imageDataY * imageData->getWidth() + imageDataX;
+			size_t offset = getPixelFormatUncompressedRowSize(textureFormat, pixels);
+
+			return (uint8 *) imageData->getData() + offset;
+		}
+		else
+		{
+			auto module = Module::getInstance<image::Image>(Module::M_IMAGE);
+			if (module == nullptr)
+				throw love::Exception("The love.image module must be loaded for readbackTexture.");
+
+			imageData.set(module->newImageData(rect.w, rect.h, textureFormat, nullptr), Acquire::NORETAIN);
+			return imageData->getData();
+		}
+	}
+	else
+	{
+		if (!bufferData.get())
+			bufferData.set(new love::data::ByteData(size, false), Acquire::NORETAIN);
+
+		return (uint8 *) bufferData->getData() + bufferDataOffset;
+	}
+}
+
+GraphicsReadback::Status GraphicsReadback::readbackBuffer(Buffer *buffer, size_t offset, size_t size)
+{
+	if (buffer == nullptr)
+		return STATUS_ERROR;
+
+	const void *data = buffer->map(Buffer::MAP_READ_ONLY, offset, size);
+
+	if (data == nullptr)
+		return STATUS_ERROR;
+
+	bool success = true;
+
+	try
+	{
+		void *dest = prepareReadbackDest(size);
+		if (dest == nullptr)
+			return STATUS_ERROR;
+
+		if (imageData.get())
+		{
+			love::thread::Lock lock(imageData->getMutex());
+
+			if (imageData->getWidth() != rect.w)
+			{
+				// Readback of compressed textures into ImageData isn't supported,
+				// so this is fine.
+				size_t stride = getPixelFormatUncompressedRowSize(textureFormat, imageData->getWidth());
+				size_t rowsize = getPixelFormatUncompressedRowSize(textureFormat, rect.w);
+
+				for (int i = 0; i < rect.h; i++)
+				{
+					memcpy(dest, data, rowsize);
+					dest = (uint8 *) dest + stride;
+					data = (uint8 *) data + rowsize;
+				}
+			}
+			else
+			{
+				memcpy(dest, data, std::min(size, imageData->getSize()));
+			}
+		}
+		else
+		{
+			memcpy(dest, data, std::min(size, bufferData->getSize()));
+		}
+	}
+	catch (love::Exception &)
+	{
+		success = false;
+	}
+
+	buffer->unmap(offset, size);
+	return success ? STATUS_COMPLETE : STATUS_ERROR;
+}
+
+} // graphics
+} // love

+ 112 - 0
src/modules/graphics/GraphicsReadback.h

@@ -0,0 +1,112 @@
+/**
+ * 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.
+ **/
+
+#pragma once
+
+// LOVE
+#include "common/config.h"
+#include "common/int.h"
+#include "common/math.h"
+#include "common/Object.h"
+#include "common/StringMap.h"
+#include "common/pixelformat.h"
+
+namespace love::image
+{
+class ImageData;
+class CompressedImageData;
+}
+
+namespace love::data
+{
+class ByteData;
+}
+
+namespace love
+{
+namespace graphics
+{
+
+class Buffer;
+class Texture;
+class Graphics;
+
+enum ReadbackMethod
+{
+	READBACK_IMMEDIATE,
+	READBACK_ASYNC,
+};
+
+class GraphicsReadback : public love::Object
+{
+public:
+
+	enum Status
+	{
+		STATUS_WAITING,
+		STATUS_COMPLETE,
+		STATUS_ERROR,
+		STATUS_MAX_ENUM
+	};
+
+	static love::Type type;
+
+	GraphicsReadback(Graphics *gfx, ReadbackMethod method, Buffer *buffer, size_t offset, size_t size, love::data::ByteData *dest, size_t destoffset);
+	GraphicsReadback(Graphics *gfx, ReadbackMethod method, Texture *texture, int slice, int mipmap, const Rect &rect, love::image::ImageData *dest, int destx, int desty);
+	virtual ~GraphicsReadback();
+
+	virtual void wait() = 0;
+	virtual void update() = 0;
+
+	bool isComplete() const { return status != STATUS_WAITING; }
+	ReadbackMethod getMethod() const { return method; }
+	bool hasError() const { return status == STATUS_ERROR; }
+
+	love::data::ByteData *getBufferData() const;
+	love::image::ImageData *getImageData() const;
+
+protected:
+
+	enum DataType
+	{
+		DATA_BUFFER,
+		DATA_TEXTURE,
+	};
+
+	void *prepareReadbackDest(size_t size);
+	Status readbackBuffer(Buffer *buffer, size_t offset, size_t size);
+
+	DataType dataType;
+	ReadbackMethod method;
+	Status status = STATUS_WAITING;
+
+	StrongRef<love::data::ByteData> bufferData;
+	size_t bufferDataOffset = 0;
+
+	StrongRef<love::image::ImageData> imageData;
+	Rect rect = {};
+	PixelFormat textureFormat = PIXELFORMAT_UNKNOWN;
+	int imageDataX = 0;
+	int imageDataY = 0;
+
+}; // GraphicsReadback
+
+} // graphics
+} // love

+ 0 - 41
src/modules/graphics/Texture.cpp

@@ -563,47 +563,6 @@ void Texture::generateMipmaps()
 	generateMipmapsInternal();
 	generateMipmapsInternal();
 }
 }
 
 
-love::image::ImageData *Texture::newImageData(love::image::Image *module, int slice, int mipmap, const Rect &r)
-{
-	if (!isReadable())
-		throw love::Exception("Texture:newImageData cannot be called on non-readable Textures.");
-
-	if (!isRenderTarget())
-		throw love::Exception("Texture:newImageData can only be called on render target Textures.");
-
-	if (isPixelFormatDepthStencil(getPixelFormat()))
-		throw love::Exception("Texture:newImageData cannot be called on Textures with depth/stencil pixel formats.");
-
-	if (r.x < 0 || r.y < 0 || r.w <= 0 || r.h <= 0 || (r.x + r.w) > getPixelWidth(mipmap) || (r.y + r.h) > getPixelHeight(mipmap))
-		throw love::Exception("Invalid rectangle dimensions.");
-
-	if (slice < 0 || (texType == TEXTURE_VOLUME && slice >= getDepth(mipmap))
-		|| (texType == TEXTURE_2D_ARRAY && slice >= layers)
-		|| (texType == TEXTURE_CUBE && slice >= 6))
-	{
-		throw love::Exception("Invalid slice index.");
-	}
-
-	Graphics *gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
-	if (gfx != nullptr && gfx->isRenderTargetActive(this))
-		throw love::Exception("Texture:newImageData cannot be called while that Texture is an active render target.");
-
-	PixelFormat dataformat = getLinearPixelFormat(getPixelFormat());
-
-	if (!image::ImageData::validPixelFormat(dataformat))
-	{
-		const char *formatname = "unknown";
-		love::getConstant(dataformat, formatname);
-		throw love::Exception("ImageData with the '%s' pixel format is not supported.", formatname);
-	}
-
-	auto imagedata = module->newImageData(r.w, r.h, dataformat);
-
-	readbackImageData(imagedata, slice, mipmap, r);
-
-	return imagedata;
-}
-
 TextureType Texture::getTextureType() const
 TextureType Texture::getTextureType() const
 {
 {
 	return texType;
 	return texType;

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

@@ -245,8 +245,6 @@ public:
 
 
 	void generateMipmaps();
 	void generateMipmaps();
 
 
-	love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect);
-
 	virtual void copyFromBuffer(Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect) = 0;
 	virtual void copyFromBuffer(Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect) = 0;
 	virtual void copyToBuffer(Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth, size_t size) = 0;
 	virtual void copyToBuffer(Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth, size_t size) = 0;
 
 
@@ -313,7 +311,6 @@ protected:
 
 
 	bool supportsGenerateMipmaps(const char *&outReason) const;
 	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;
 
 
 	bool validateDimensions(bool throwException) const;
 	bool validateDimensions(bool throwException) const;
 
 

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

@@ -40,7 +40,7 @@ public:
 
 
 	void *map(MapType map, size_t offset, size_t size) override;
 	void *map(MapType map, size_t offset, size_t size) override;
 	void unmap(size_t usedoffset, size_t usedsize) override;
 	void unmap(size_t usedoffset, size_t usedsize) override;
-	void fill(size_t offset, size_t size, const void *data) override;
+	bool fill(size_t offset, size_t size, const void *data) override;
 	void copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size) override;
 	void copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size) override;
 
 
 	ptrdiff_t getHandle() const override { return (ptrdiff_t) buffer; }
 	ptrdiff_t getHandle() const override { return (ptrdiff_t) buffer; }

+ 38 - 24
src/modules/graphics/metal/Buffer.mm

@@ -18,7 +18,7 @@
 * 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.
 **/
 **/
 
 
-#import "Buffer.h"
+#include "Buffer.h"
 #include "Graphics.h"
 #include "Graphics.h"
 
 
 namespace love
 namespace love
@@ -65,11 +65,16 @@ Buffer::Buffer(love::graphics::Graphics *gfx, id<MTLDevice> device, const Settin
 	size = getSize();
 	size = getSize();
 	arraylength = getArrayLength();
 	arraylength = getArrayLength();
 
 
-	MTLResourceOptions opts = MTLResourceStorageModePrivate;
+	MTLResourceOptions opts = 0;
+	if (settings.dataUsage == BUFFERDATAUSAGE_READBACK)
+		opts |= MTLResourceStorageModeShared;
+	else
+		opts |= MTLResourceStorageModePrivate;
+
 	buffer = [device newBufferWithLength:size options:opts];
 	buffer = [device newBufferWithLength:size options:opts];
 
 
 	if (buffer == nil)
 	if (buffer == nil)
-		throw love::Exception("Could not create buffer (out of VRAM?)");
+		throw love::Exception("Could not create buffer with %d bytes (out of VRAM?)", size);
 
 
 	if (usageFlags & BUFFERUSAGEFLAG_TEXEL)
 	if (usageFlags & BUFFERUSAGEFLAG_TEXEL)
 	{
 	{
@@ -109,16 +114,30 @@ Buffer::~Buffer()
 	texture = nil;
 	texture = nil;
 }}
 }}
 
 
-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 || isImmutable())
+	if (size == 0)
+		return nullptr;
+
+	if (map == MAP_WRITE_INVALIDATE && (isImmutable() || dataUsage == BUFFERDATAUSAGE_READBACK))
 		return nullptr;
 		return nullptr;
 
 
+	if (map == MAP_READ_ONLY && dataUsage != BUFFERDATAUSAGE_READBACK)
+		return  nullptr;
+
 	Range r(offset, size);
 	Range r(offset, size);
 
 
 	if (!Range(0, getSize()).contains(r))
 	if (!Range(0, getSize()).contains(r))
 		return nullptr;
 		return nullptr;
 
 
+	if (map == MAP_READ_ONLY)
+	{
+		mappedRange = r;
+		mapped = true;
+		mappedType = map;
+		return (char *) buffer.contents + offset;
+	}
+
 	auto gfx = Graphics::getInstance();
 	auto gfx = Graphics::getInstance();
 
 
 	// TODO: Don't create a new buffer every time, also do something for stream
 	// TODO: Don't create a new buffer every time, also do something for stream
@@ -129,6 +148,7 @@ void *Buffer::map(MapType /*map*/, size_t offset, size_t size)
 	{
 	{
 		mappedRange = r;
 		mappedRange = r;
 		mapped = true;
 		mapped = true;
+		mappedType = map;
 		return mapBuffer.contents;
 		return mapBuffer.contents;
 	}
 	}
 
 
@@ -137,6 +157,12 @@ void *Buffer::map(MapType /*map*/, size_t offset, size_t size)
 
 
 void Buffer::unmap(size_t usedoffset, size_t usedsize)
 void Buffer::unmap(size_t usedoffset, size_t usedsize)
 { @autoreleasepool {
 { @autoreleasepool {
+	if (mappedType == MAP_READ_ONLY)
+	{
+		mapped = false;
+		return;
+	}
+
 	if (mapBuffer == nil)
 	if (mapBuffer == nil)
 		return;
 		return;
 
 
@@ -158,29 +184,17 @@ void Buffer::unmap(size_t usedoffset, size_t usedsize)
 	mapped = false;
 	mapped = false;
 }}
 }}
 
 
-void Buffer::fill(size_t offset, size_t size, const void *data)
+bool Buffer::fill(size_t offset, size_t size, const void *data)
 { @autoreleasepool {
 { @autoreleasepool {
-	if (size == 0 || isImmutable())
-		return;
+	void *dest = map(MAP_WRITE_INVALIDATE, offset, size);
 
 
-	size_t buffersize = getSize();
+	if (dest == nullptr)
+		return false;
 
 
-	if (!Range(0, buffersize).contains(Range(offset, size)))
-		return;
+	memcpy(dest, data, size);
 
 
-	// TODO: Don't create a new buffer every time, also do something for stream
-	// buffers.
-	auto gfx = Graphics::getInstance();
-	auto encoder = gfx->useBlitEncoder();
-
-	auto tempbuffer = [gfx->device newBufferWithLength:size options:MTLResourceStorageModeShared];
-	memcpy(tempbuffer.contents, data, size);
-
-	[encoder copyFromBuffer:tempbuffer
-			   sourceOffset:0
-				   toBuffer:buffer
-		  destinationOffset:offset
-					   size:size];
+	unmap(offset, size);
+	return true;
 }}
 }}
 
 
 void Buffer::copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size)
 void Buffer::copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size)

+ 4 - 0
src/modules/graphics/metal/Graphics.h

@@ -199,6 +199,10 @@ private:
 	love::graphics::ShaderStage *newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
 	love::graphics::ShaderStage *newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
 	love::graphics::Shader *newShaderInternal(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) override;
 	love::graphics::Shader *newShaderInternal(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) override;
 	love::graphics::StreamBuffer *newStreamBuffer(BufferUsage usage, size_t size) override;
 	love::graphics::StreamBuffer *newStreamBuffer(BufferUsage usage, size_t size) override;
+
+	love::graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset) override;
+	love::graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty) override;
+
 	void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBcanvas) override;
 	void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBcanvas) override;
 	void initCapabilities() override;
 	void initCapabilities() override;
 	void getAPIStats(int &shaderswitches) const override;
 	void getAPIStats(int &shaderswitches) const override;

+ 14 - 16
src/modules/graphics/metal/Graphics.mm

@@ -22,6 +22,7 @@
 #include "StreamBuffer.h"
 #include "StreamBuffer.h"
 #include "Buffer.h"
 #include "Buffer.h"
 #include "Texture.h"
 #include "Texture.h"
+#include "GraphicsReadback.h"
 #include "Shader.h"
 #include "Shader.h"
 #include "ShaderStage.h"
 #include "ShaderStage.h"
 #include "window/Window.h"
 #include "window/Window.h"
@@ -418,6 +419,16 @@ love::graphics::Buffer *Graphics::newBuffer(const Buffer::Settings &settings, co
 	return new Buffer(this, device, settings, format, data, size, arraylength);
 	return new Buffer(this, device, settings, format, data, size, arraylength);
 }
 }
 
 
+love::graphics::GraphicsReadback *Graphics::newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
+{
+	return new GraphicsReadback(this, method, buffer, offset, size, dest, destoffset);
+}
+
+love::graphics::GraphicsReadback *Graphics::newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
+{
+	return new GraphicsReadback(this, method, texture, slice, mipmap, rect, dest, destx, desty);
+}
+
 Matrix4 Graphics::computeDeviceProjection(const Matrix4 &projection, bool /*rendertotexture*/) const
 Matrix4 Graphics::computeDeviceProjection(const Matrix4 &projection, bool /*rendertotexture*/) const
 {
 {
 	uint32 flags = DEVICE_PROJECTION_FLIP_Y;
 	uint32 flags = DEVICE_PROJECTION_FLIP_Y;
@@ -501,10 +512,7 @@ void Graphics::unSetMode()
 
 
 	submitCommandBuffer(SUBMIT_DONE);
 	submitCommandBuffer(SUBMIT_DONE);
 
 
-	for (auto temp : temporaryTextures)
-		temp.texture->release();
-
-	temporaryTextures.clear();
+	clearTemporaryResources();
 
 
 	created = false;
 	created = false;
 	metalLayer = nil;
 	metalLayer = nil;
@@ -1620,18 +1628,8 @@ void Graphics::present(void *screenshotCallbackData)
 	renderTargetSwitchCount = 0;
 	renderTargetSwitchCount = 0;
 	drawCallsBatched = 0;
 	drawCallsBatched = 0;
 
 
-	// This assumes temporary canvases will only be used within a render pass.
-	for (int i = (int) temporaryTextures.size() - 1; i >= 0; i--)
-	{
-		if (temporaryTextures[i].framesSinceUse >= MAX_TEMPORARY_TEXTURE_UNUSED_FRAMES)
-		{
-			temporaryTextures[i].texture->release();
-			temporaryTextures[i] = temporaryTextures.back();
-			temporaryTextures.pop_back();
-		}
-		else
-			temporaryTextures[i].framesSinceUse++;
-	}
+	updatePendingReadbacks();
+	updateTemporaryResources();
 }}
 }}
 
 
 int Graphics::getRequestedBackbufferMSAA() const
 int Graphics::getRequestedBackbufferMSAA() const

+ 54 - 0
src/modules/graphics/metal/GraphicsReadback.h

@@ -0,0 +1,54 @@
+/**
+ * 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.
+ **/
+
+#pragma once
+
+// LOVE
+#include "graphics/GraphicsReadback.h"
+#include "common/math.h"
+
+#include <atomic>
+
+#import <Metal/MTLCommandBuffer.h>
+
+namespace love::graphics::metal
+{
+
+class GraphicsReadback final : public love::graphics::GraphicsReadback
+{
+public:
+
+	GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset);
+	GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty);
+	virtual ~GraphicsReadback();
+
+	void wait() override;
+	void update() override;
+
+private:
+
+	id<MTLCommandBuffer> cmd;
+	std::atomic_bool done;
+
+	StrongRef<love::graphics::Buffer> stagingBuffer;
+
+}; // GraphicsReadback
+
+} // love::graphics::metal

+ 143 - 0
src/modules/graphics/metal/GraphicsReadback.mm

@@ -0,0 +1,143 @@
+/**
+ * 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 "GraphicsReadback.h"
+#include "Buffer.h"
+#include "Texture.h"
+#include "Graphics.h"
+#include "data/ByteData.h"
+
+namespace love::graphics::metal
+{
+
+GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
+	: love::graphics::GraphicsReadback(gfx, method, buffer, offset, size, dest, destoffset)
+	, done(false)
+{ @autoreleasepool {
+	auto mgfx = (Graphics *) gfx;
+
+	// Immediate readback of readback-type buffers doesn't need a staging buffer.
+	if (method != READBACK_IMMEDIATE || buffer->getDataUsage() != BUFFERDATAUSAGE_READBACK)
+	{
+		stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
+		gfx->copyBuffer(buffer, stagingBuffer, offset, 0, size);
+	}
+
+	// use instead of get, in case this was the first command in the frame.
+	cmd = mgfx->useCommandBuffer();
+
+	auto pthis = this;
+	pthis->retain();
+	[cmd addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull)
+	{
+		pthis->done = true;
+		pthis->release();
+	}];
+
+	if (method == READBACK_IMMEDIATE)
+	{
+		wait();
+
+		if (stagingBuffer.get())
+		{
+			status = readbackBuffer(stagingBuffer, 0, size);
+			gfx->releaseTemporaryBuffer(stagingBuffer);
+		}
+		else
+		{
+			status = readbackBuffer(buffer, offset, size);
+		}
+	}
+}}
+
+GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
+	: love::graphics::GraphicsReadback(gfx, method, texture, slice, mipmap, rect, dest, destx, desty)
+	, done(false)
+{ @autoreleasepool {
+	auto mgfx = (Graphics *) gfx;
+	size_t size = getPixelFormatSliceSize(textureFormat, rect.w, rect.h);
+
+	stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
+
+	gfx->copyTextureToBuffer(texture, stagingBuffer, slice, mipmap, rect, 0, 0);
+
+	cmd = mgfx->getCommandBuffer();
+
+	auto pthis = this;
+	pthis->retain();
+	[cmd addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull)
+	{
+		pthis->done = true;
+		pthis->release();
+	}];
+
+	if (method == READBACK_IMMEDIATE)
+	{
+		wait();
+		status = readbackBuffer(stagingBuffer, 0, size);
+		gfx->releaseTemporaryBuffer(stagingBuffer);
+	}
+}}
+
+GraphicsReadback::~GraphicsReadback()
+{ @autoreleasepool {
+	cmd = nil;
+}}
+
+void GraphicsReadback::wait()
+{ @autoreleasepool {
+	if (status != STATUS_WAITING || cmd == nil)
+		return;
+
+	if (cmd.status == MTLCommandBufferStatusNotEnqueued)
+	{
+		auto gfx = Graphics::getInstance();
+		gfx->submitCommandBuffer(Graphics::SUBMIT_STORE);
+	}
+
+	[cmd waitUntilCompleted];
+	cmd = nil;
+
+	update();
+}}
+
+void GraphicsReadback::update()
+{
+	if (status != STATUS_WAITING)
+		return;
+
+	if (done)
+	{
+		if (stagingBuffer.get())
+			status = readbackBuffer(stagingBuffer, 0, stagingBuffer->getSize());
+		else
+			status = STATUS_ERROR;
+
+		if (stagingBuffer.get())
+		{
+			auto gfx = Module::getInstance<love::graphics::Graphics>(Module::M_GRAPHICS);
+			if (gfx != nullptr)
+				gfx->releaseTemporaryBuffer(stagingBuffer);
+			stagingBuffer.set(nullptr);
+		}
+	}
+}
+
+} // love::graphics::metal

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

@@ -57,7 +57,6 @@ private:
 
 
 	void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) override;
 	void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) override;
 	void generateMipmapsInternal() override;
 	void generateMipmapsInternal() override;
-	void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) override;
 
 
 	id<MTLTexture> texture;
 	id<MTLTexture> texture;
 	id<MTLTexture> msaaTexture;
 	id<MTLTexture> msaaTexture;

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

@@ -273,52 +273,6 @@ void Texture::generateMipmapsInternal()
 	[encoder generateMipmapsForTexture:texture];
 	[encoder generateMipmapsForTexture:texture];
 }}
 }}
 
 
-void Texture::readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect)
-{ @autoreleasepool {
-	auto gfx = Graphics::getInstance();
-
-	id<MTLBlitCommandEncoder> encoder = gfx->useBlitEncoder();
-
-	size_t rowSize = 0;
-	if (isCompressed())
-		rowSize = getPixelFormatCompressedBlockRowSize(format, rect.w);
-	else
-		rowSize = getPixelFormatUncompressedRowSize(format, rect.w);
-
-	// TODO: Verify this is correct for compressed formats at small sizes.
-	// TODO: make sure this is consistent with the imagedata byte size?
-	size_t sliceSize = getPixelFormatSliceSize(format, rect.w, rect.h);
-
-	int z = texType == TEXTURE_VOLUME ? slice : 0;
-
-	id<MTLBuffer> buffer = [gfx->device newBufferWithLength:sliceSize
-													options:MTLResourceStorageModeShared];
-
-	MTLBlitOption options = MTLBlitOptionNone;
-	if (isPixelFormatDepthStencil(format))
-		options = MTLBlitOptionDepthFromDepthStencil;
-
-	[encoder copyFromTexture:texture
-				 sourceSlice:texType == TEXTURE_VOLUME ? 0 : slice
-				 sourceLevel:mipmap
-				sourceOrigin:MTLOriginMake(rect.x, rect.y, z)
-				  sourceSize:MTLSizeMake(rect.w, rect.h, 1)
-					toBuffer:buffer
-		   destinationOffset:0
-	  destinationBytesPerRow:rowSize
-	destinationBytesPerImage:sliceSize
-					 options:options];
-
-	id<MTLCommandBuffer> cmd = gfx->getCommandBuffer();
-
-	gfx->submitBlitEncoder();
-	gfx->submitCommandBuffer(Graphics::SUBMIT_STORE);
-
-	[cmd waitUntilCompleted];
-
-	memcpy(imagedata->getData(), buffer.contents, imagedata->getSize());
-}}
-
 void Texture::copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect)
 void Texture::copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect)
 { @autoreleasepool {
 { @autoreleasepool {
 	id<MTLBlitCommandEncoder> encoder = Graphics::getInstance()->useBlitEncoder();
 	id<MTLBlitCommandEncoder> encoder = Graphics::getInstance()->useBlitEncoder();

+ 35 - 8
src/modules/graphics/opengl/Buffer.cpp

@@ -103,7 +103,7 @@ Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const st
 	if (!load(data))
 	if (!load(data))
 	{
 	{
 		unloadVolatile();
 		unloadVolatile();
-		throw love::Exception("Could not create buffer (out of VRAM?)");
+		throw love::Exception("Could not create buffer with %d bytes (out of VRAM?)", size);
 	}
 	}
 }
 }
 
 
@@ -164,11 +164,17 @@ bool Buffer::supportsOrphan() const
 	return dataUsage == BUFFERDATAUSAGE_STREAM || dataUsage == BUFFERDATAUSAGE_DYNAMIC;
 	return dataUsage == BUFFERDATAUSAGE_STREAM || dataUsage == BUFFERDATAUSAGE_DYNAMIC;
 }
 }
 
 
-void *Buffer::map(MapType /*map*/, size_t offset, size_t size)
+void *Buffer::map(MapType map, size_t offset, size_t size)
 {
 {
-	if (size == 0 || isImmutable())
+	if (size == 0)
 		return nullptr;
 		return nullptr;
 
 
+	if (map == MAP_WRITE_INVALIDATE && (isImmutable() || dataUsage == BUFFERDATAUSAGE_READBACK))
+		return nullptr;
+
+	if (map == MAP_READ_ONLY && dataUsage != BUFFERDATAUSAGE_READBACK)
+		return  nullptr;
+
 	Range r(offset, size);
 	Range r(offset, size);
 
 
 	if (!Range(0, getSize()).contains(r))
 	if (!Range(0, getSize()).contains(r))
@@ -176,7 +182,16 @@ void *Buffer::map(MapType /*map*/, size_t offset, size_t size)
 
 
 	char *data = nullptr;
 	char *data = nullptr;
 
 
-	if (ownsMemoryMap)
+	if (map == MAP_READ_ONLY)
+	{
+		gl.bindBuffer(mapUsage, buffer);
+
+		if (GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0)
+			data = (char *) glMapBufferRange(target, offset, size, GL_MAP_READ_BIT);
+		else if (GLAD_VERSION_1_1)
+			data = (char *) glMapBuffer(target, GL_READ_ONLY) + offset;
+	}
+	else if (ownsMemoryMap)
 	{
 	{
 		if (memoryMap == nullptr)
 		if (memoryMap == nullptr)
 			memoryMap = (char *) malloc(getSize());
 			memoryMap = (char *) malloc(getSize());
@@ -191,6 +206,7 @@ void *Buffer::map(MapType /*map*/, size_t offset, size_t size)
 	if (data != nullptr)
 	if (data != nullptr)
 	{
 	{
 		mapped = true;
 		mapped = true;
+		mappedType = map;
 		mappedRange = r;
 		mappedRange = r;
 		if (!ownsMemoryMap)
 		if (!ownsMemoryMap)
 			memoryMap = data;
 			memoryMap = data;
@@ -208,6 +224,15 @@ void Buffer::unmap(size_t usedoffset, size_t usedsize)
 
 
 	mapped = false;
 	mapped = false;
 
 
+	if (mappedType == MAP_READ_ONLY)
+	{
+		gl.bindBuffer(mapUsage, buffer);
+		glUnmapBuffer(target);
+		if (!ownsMemoryMap)
+			memoryMap = nullptr;
+		return;
+	}
+
 	// Orphan optimization - see fill().
 	// Orphan optimization - see fill().
 	if (supportsOrphan() && mappedRange.first == 0 && mappedRange.getSize() == getSize())
 	if (supportsOrphan() && mappedRange.first == 0 && mappedRange.getSize() == getSize())
 	{
 	{
@@ -227,15 +252,15 @@ void Buffer::unmap(size_t usedoffset, size_t usedsize)
 	}
 	}
 }
 }
 
 
-void Buffer::fill(size_t offset, size_t size, const void *data)
+bool Buffer::fill(size_t offset, size_t size, const void *data)
 {
 {
-	if (size == 0 || isImmutable())
-		return;
+	if (size == 0 || isImmutable() || dataUsage == BUFFERDATAUSAGE_READBACK)
+		return false;
 
 
 	size_t buffersize = getSize();
 	size_t buffersize = getSize();
 
 
 	if (!Range(0, buffersize).contains(Range(offset, size)))
 	if (!Range(0, buffersize).contains(Range(offset, size)))
-		return;
+		return false;
 
 
 	GLenum gldatausage = OpenGL::getGLBufferDataUsage(dataUsage);
 	GLenum gldatausage = OpenGL::getGLBufferDataUsage(dataUsage);
 
 
@@ -259,6 +284,8 @@ void Buffer::fill(size_t offset, size_t size, const void *data)
 	{
 	{
 		glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, data);
 		glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, data);
 	}
 	}
+
+	return true;
 }
 }
 
 
 void Buffer::copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size)
 void Buffer::copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size)

+ 3 - 1
src/modules/graphics/opengl/Buffer.h

@@ -52,12 +52,14 @@ public:
 
 
 	void *map(MapType map, size_t offset, size_t size) override;
 	void *map(MapType map, size_t offset, size_t size) override;
 	void unmap(size_t usedoffset, size_t usedsize) override;
 	void unmap(size_t usedoffset, size_t usedsize) override;
-	void fill(size_t offset, size_t size, const void *data) override;
+	bool fill(size_t offset, size_t size, const void *data) override;
 	void copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size) override;
 	void copyTo(love::graphics::Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size) override;
 
 
 	ptrdiff_t getHandle() const override { return buffer; };
 	ptrdiff_t getHandle() const override { return buffer; };
 	ptrdiff_t getTexelBufferHandle() const override { return texture; };
 	ptrdiff_t getTexelBufferHandle() const override { return texture; };
 
 
+	BufferUsage getMapUsage() const { return mapUsage; }
+
 private:
 private:
 
 
 	bool load(const void *initialdata);
 	bool load(const void *initialdata);

+ 16 - 0
src/modules/graphics/opengl/FenceSync.cpp

@@ -45,6 +45,22 @@ bool FenceSync::fence()
 	return !wasActive;
 	return !wasActive;
 }
 }
 
 
+bool FenceSync::isComplete() const
+{
+	if (sync == 0)
+		return true;
+
+	GLenum status = glClientWaitSync(sync, 0, 0);
+
+	if (status == GL_ALREADY_SIGNALED || status == GL_CONDITION_SATISFIED)
+		return true;
+
+	if (status == GL_WAIT_FAILED)
+		return true;
+
+	return false;
+}
+
 bool FenceSync::cpuWait()
 bool FenceSync::cpuWait()
 {
 {
 	if (sync == 0)
 	if (sync == 0)

+ 1 - 0
src/modules/graphics/opengl/FenceSync.h

@@ -42,6 +42,7 @@ public:
 	~FenceSync();
 	~FenceSync();
 
 
 	bool fence();
 	bool fence();
+	bool isComplete() const;
 	bool cpuWait();
 	bool cpuWait();
 	void cleanup();
 	void cleanup();
 
 

+ 15 - 16
src/modules/graphics/opengl/Graphics.cpp

@@ -26,6 +26,7 @@
 #include "Graphics.h"
 #include "Graphics.h"
 #include "font/Font.h"
 #include "font/Font.h"
 #include "StreamBuffer.h"
 #include "StreamBuffer.h"
+#include "GraphicsReadback.h"
 #include "math/MathModule.h"
 #include "math/MathModule.h"
 #include "window/Window.h"
 #include "window/Window.h"
 #include "Buffer.h"
 #include "Buffer.h"
@@ -177,6 +178,16 @@ love::graphics::Buffer *Graphics::newBuffer(const Buffer::Settings &settings, co
 	return new Buffer(this, settings, format, data, size, arraylength);
 	return new Buffer(this, settings, format, data, size, arraylength);
 }
 }
 
 
+love::graphics::GraphicsReadback *Graphics::newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
+{
+	return new GraphicsReadback(this, method, buffer, offset, size, dest, destoffset);
+}
+
+love::graphics::GraphicsReadback *Graphics::newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
+{
+	return new GraphicsReadback(this, method, texture, slice, mipmap, rect, dest, destx, desty);
+}
+
 Matrix4 Graphics::computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const
 Matrix4 Graphics::computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const
 {
 {
 	uint32 flags = DEVICE_PROJECTION_DEFAULT;
 	uint32 flags = DEVICE_PROJECTION_DEFAULT;
@@ -462,14 +473,12 @@ void Graphics::unSetMode()
 	// mode change.
 	// mode change.
 	Volatile::unloadAll();
 	Volatile::unloadAll();
 
 
+	clearTemporaryResources();
+
 	for (const auto &pair : framebufferObjects)
 	for (const auto &pair : framebufferObjects)
 		gl.deleteFramebuffer(pair.second);
 		gl.deleteFramebuffer(pair.second);
 
 
-	for (auto temp : temporaryTextures)
-		temp.texture->release();
-
 	framebufferObjects.clear();
 	framebufferObjects.clear();
-	temporaryTextures.clear();
 
 
 	if (mainVAO != 0)
 	if (mainVAO != 0)
 	{
 	{
@@ -1335,18 +1344,8 @@ void Graphics::present(void *screenshotCallbackData)
 	renderTargetSwitchCount = 0;
 	renderTargetSwitchCount = 0;
 	drawCallsBatched = 0;
 	drawCallsBatched = 0;
 
 
-	// This assumes temporary textures will only be used within a render pass.
-	for (int i = (int) temporaryTextures.size() - 1; i >= 0; i--)
-	{
-		if (temporaryTextures[i].framesSinceUse >= MAX_TEMPORARY_TEXTURE_UNUSED_FRAMES)
-		{
-			temporaryTextures[i].texture->release();
-			temporaryTextures[i] = temporaryTextures.back();
-			temporaryTextures.pop_back();
-		}
-		else
-			temporaryTextures[i].framesSinceUse++;
-	}
+	updatePendingReadbacks();
+	updateTemporaryResources();
 }
 }
 
 
 int Graphics::getRequestedBackbufferMSAA() const
 int Graphics::getRequestedBackbufferMSAA() const

+ 4 - 0
src/modules/graphics/opengl/Graphics.h

@@ -141,6 +141,10 @@ private:
 	love::graphics::ShaderStage *newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
 	love::graphics::ShaderStage *newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
 	love::graphics::Shader *newShaderInternal(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) override;
 	love::graphics::Shader *newShaderInternal(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) override;
 	love::graphics::StreamBuffer *newStreamBuffer(BufferUsage type, size_t size) override;
 	love::graphics::StreamBuffer *newStreamBuffer(BufferUsage type, size_t size) override;
+
+	love::graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset) override;
+	love::graphics::GraphicsReadback *newReadbackInternal(ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty) override;
+
 	void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture) override;
 	void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture) override;
 	void initCapabilities() override;
 	void initCapabilities() override;
 	void getAPIStats(int &shaderswitches) const override;
 	void getAPIStats(int &shaderswitches) const override;

+ 126 - 0
src/modules/graphics/opengl/GraphicsReadback.cpp

@@ -0,0 +1,126 @@
+/**
+ * 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 "GraphicsReadback.h"
+#include "Buffer.h"
+#include "Texture.h"
+#include "graphics/Graphics.h"
+#include "data/ByteData.h"
+
+namespace love
+{
+namespace graphics
+{
+namespace opengl
+{
+
+GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset)
+	: love::graphics::GraphicsReadback(gfx, method, buffer, offset, size, dest, destoffset)
+{
+	// Immediate readback of readback-type buffers doesn't need a staging buffer.
+	if (method != READBACK_IMMEDIATE || buffer->getDataUsage() != BUFFERDATAUSAGE_READBACK)
+	{
+		stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
+		gfx->copyBuffer(buffer, stagingBuffer, offset, 0, size);
+	}
+
+	if (method == READBACK_IMMEDIATE)
+	{
+		if (stagingBuffer.get())
+		{
+			status = readbackBuffer(stagingBuffer, 0, size);
+			gfx->releaseTemporaryBuffer(stagingBuffer);
+		}
+		else
+		{
+			status = readbackBuffer(buffer, offset, size);
+		}
+	}
+	else
+	{
+		sync.fence();
+	}
+}
+
+GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty)
+	: love::graphics::GraphicsReadback(gfx, method, texture, slice, mipmap, rect, dest, destx, desty)
+{
+	size_t size = getPixelFormatSliceSize(textureFormat, rect.w, rect.h);
+
+	if (method == READBACK_IMMEDIATE)
+	{
+		void *dest = prepareReadbackDest(size);
+
+		love::thread::Lock lock(imageData->getMutex());
+
+		// Direct readback without copying avoids the need for a staging buffer,
+		// and lowers the system requirements of immediate RT readback.
+		Texture *t = (Texture *) texture;
+		t->readbackInternal(slice, mipmap, rect, imageData->getWidth(), size, dest);
+
+		status = STATUS_COMPLETE;
+	}
+	else
+	{
+		stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
+
+		gfx->copyTextureToBuffer(texture, stagingBuffer, slice, mipmap, rect, 0, 0);
+		sync.fence();
+	}
+}
+
+GraphicsReadback::~GraphicsReadback()
+{
+}
+
+void GraphicsReadback::wait()
+{
+	if (status != STATUS_WAITING)
+		return;
+
+	sync.cpuWait();
+	update();
+}
+
+void GraphicsReadback::update()
+{
+	if (status != STATUS_WAITING)
+		return;
+
+	if (sync.isComplete())
+	{
+		if (stagingBuffer.get())
+			status = readbackBuffer(stagingBuffer, 0, stagingBuffer->getSize());
+		else
+			status = STATUS_ERROR;
+
+		if (stagingBuffer.get())
+		{
+			auto gfx = Module::getInstance<love::graphics::Graphics>(Module::M_GRAPHICS);
+			if (gfx != nullptr)
+				gfx->releaseTemporaryBuffer(stagingBuffer);
+			stagingBuffer.set(nullptr);
+		}
+	}
+}
+
+} // opengl
+} // graphics
+} // love

+ 56 - 0
src/modules/graphics/opengl/GraphicsReadback.h

@@ -0,0 +1,56 @@
+/**
+ * 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.
+ **/
+
+#pragma once
+
+// LOVE
+#include "graphics/GraphicsReadback.h"
+#include "FenceSync.h"
+#include "common/math.h"
+
+namespace love
+{
+namespace graphics
+{
+namespace opengl
+{
+
+class GraphicsReadback final : public love::graphics::GraphicsReadback
+{
+public:
+
+	GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset);
+	GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod method, love::graphics::Texture *texture, int slice, int mipmap, const Rect &rect, image::ImageData *dest, int destx, int desty);
+	virtual ~GraphicsReadback();
+
+	void wait() override;
+	void update() override;
+
+private:
+
+	FenceSync sync;
+
+	StrongRef<love::graphics::Buffer> stagingBuffer;
+
+}; // GraphicsReadback
+
+} // opengl
+} // graphics
+} // love

+ 1 - 1
src/modules/graphics/opengl/OpenGL.cpp

@@ -843,7 +843,7 @@ GLenum OpenGL::getGLBufferDataUsage(BufferDataUsage usage)
 		case BUFFERDATAUSAGE_STREAM: return GL_STREAM_DRAW;
 		case BUFFERDATAUSAGE_STREAM: return GL_STREAM_DRAW;
 		case BUFFERDATAUSAGE_DYNAMIC: return GL_DYNAMIC_DRAW;
 		case BUFFERDATAUSAGE_DYNAMIC: return GL_DYNAMIC_DRAW;
 		case BUFFERDATAUSAGE_STATIC: return GL_STATIC_DRAW;
 		case BUFFERDATAUSAGE_STATIC: return GL_STATIC_DRAW;
-		case BUFFERDATAUSAGE_STAGING:
+		case BUFFERDATAUSAGE_READBACK:
 			return (GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0) ? GL_STREAM_READ : GL_STREAM_DRAW;
 			return (GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0) ? GL_STREAM_READ : GL_STREAM_DRAW;
 		default: return 0;
 		default: return 0;
 	}
 	}

+ 33 - 51
src/modules/graphics/opengl/Texture.cpp

@@ -506,30 +506,46 @@ void Texture::generateMipmapsInternal()
 	glGenerateMipmap(gltextype);
 	glGenerateMipmap(gltextype);
 }
 }
 
 
-void Texture::readbackImageData(love::image::ImageData *data, int slice, int mipmap, const Rect &r)
+void Texture::readbackInternal(int slice, int mipmap, const Rect &rect, int destwidth, size_t size, void *dest)
 {
 {
-	if (fbo == 0) // Should never be reached.
-		return;
+	// Not supported in GL with compressed textures...
+	if ((GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0) && !isCompressed())
+		glPixelStorei(GL_PACK_ROW_LENGTH, destwidth);
 
 
-	bool isSRGB = false;
-	OpenGL::TextureFormat fmt = gl.convertPixelFormat(data->getFormat(), false, isSRGB);
+	gl.bindTextureToUnit(this, 0, false);
 
 
-	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
-	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO());
+	bool isSRGB = false;
+	OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false, isSRGB);
 
 
-	if (slice > 0 || mipmap > 0)
+	if (gl.isCopyTextureToBufferSupported())
 	{
 	{
-		int layer = texType == TEXTURE_CUBE ? 0 : slice;
-		int face = texType == TEXTURE_CUBE ? slice : 0;
-		gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, mipmap, layer, face);
+		if (isCompressed())
+			glGetCompressedTextureSubImage(texture, mipmap, rect.x, rect.y, slice, rect.w, rect.h, 1, size, dest);
+		else
+			glGetTextureSubImage(texture, mipmap, rect.x, rect.y, slice, rect.w, rect.h, 1, fmt.externalformat, fmt.type, size, dest);
 	}
 	}
+	else if (fbo)
+	{
+		GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
+		gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO());
+
+		if (slice > 0 || mipmap > 0)
+		{
+			int layer = texType == TEXTURE_CUBE ? 0 : slice;
+			int face = texType == TEXTURE_CUBE ? slice : 0;
+			gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, mipmap, layer, face);
+		}
 
 
-	glReadPixels(r.x, r.y, r.w, r.h, fmt.externalformat, fmt.type, data->getData());
+		glReadPixels(rect.x, rect.y, rect.w, rect.h, fmt.externalformat, fmt.type, dest);
 
 
-	if (slice > 0 || mipmap > 0)
-		gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
+		if (slice > 0 || mipmap > 0)
+			gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
 
 
-	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
+		gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
+	}
+
+	if ((GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0) && !isCompressed())
+		glPixelStorei(GL_PACK_ROW_LENGTH, 0);
 }
 }
 
 
 void Texture::copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect)
 void Texture::copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect)
@@ -558,46 +574,12 @@ void Texture::copyToBuffer(love::graphics::Buffer *dest, int slice, int mipmap,
 	GLuint glbuffer = (GLuint) dest->getHandle();
 	GLuint glbuffer = (GLuint) dest->getHandle();
 	glBindBuffer(GL_PIXEL_PACK_BUFFER, glbuffer);
 	glBindBuffer(GL_PIXEL_PACK_BUFFER, glbuffer);
 
 
-	if (!isCompressed()) // Not supported in GL with compressed textures...
-		glPixelStorei(GL_PACK_ROW_LENGTH, destwidth);
-
-	gl.bindTextureToUnit(this, 0, false);
-
-	bool isSRGB = false;
-	OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false, isSRGB);
-
-	// glTexSubImage and friends copy from the active pixel_unpack_buffer by
+	// glTexSubImage and friends copy to the active PIXEL_PACK_BUFFER by
 	// treating the pointer as a byte offset.
 	// treating the pointer as a byte offset.
 	uint8 *byteoffset = (uint8 *)(ptrdiff_t)destoffset;
 	uint8 *byteoffset = (uint8 *)(ptrdiff_t)destoffset;
 
 
-	if (gl.isCopyTextureToBufferSupported())
-	{
-		if (isCompressed())
-			glGetCompressedTextureSubImage(texture, mipmap, rect.x, rect.y, slice, rect.w, rect.h, 1, size, byteoffset);
-		else
-			glGetTextureSubImage(texture, mipmap, rect.x, rect.y, slice, rect.w, rect.h, 1, fmt.externalformat, fmt.type, size, byteoffset);
-	}
-	else if (fbo)
-	{
-		GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
-		gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO());
-
-		if (slice > 0 || mipmap > 0)
-		{
-			int layer = texType == TEXTURE_CUBE ? 0 : slice;
-			int face = texType == TEXTURE_CUBE ? slice : 0;
-			gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, mipmap, layer, face);
-		}
-
-		glReadPixels(rect.x, rect.y, rect.w, rect.h, fmt.externalformat, fmt.type, byteoffset);
-
-		if (slice > 0 || mipmap > 0)
-			gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
-
-		gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
-	}
+	readbackInternal(slice, mipmap, rect, destwidth, size, byteoffset);
 
 
-	glPixelStorei(GL_PACK_ROW_LENGTH, 0);
 	glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
 	glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
 }
 }
 
 

+ 2 - 2
src/modules/graphics/opengl/Texture.h

@@ -58,6 +58,8 @@ public:
 
 
 	inline GLuint getFBO() const { return fbo; }
 	inline GLuint getFBO() const { return fbo; }
 
 
+	void readbackInternal(int slice, int mipmap, const Rect &rect, int destwidth, size_t size, void *dest);
+
 private:
 private:
 	void createTexture();
 	void createTexture();
 
 
@@ -65,8 +67,6 @@ private:
 
 
 	void generateMipmapsInternal() override;
 	void generateMipmapsInternal() override;
 
 
-	void readbackImageData(love::image::ImageData *imagedata, int slice, int mipmap, const Rect &rect) override;
-
 	Slices slices;
 	Slices slices;
 
 
 	GLuint fbo;
 	GLuint fbo;

+ 4 - 4
src/modules/graphics/vertex.cpp

@@ -383,10 +383,10 @@ STRINGMAP_END(IndexDataType, INDEX_MAX_ENUM, indexType)
 
 
 STRINGMAP_BEGIN(BufferDataUsage, BUFFERDATAUSAGE_MAX_ENUM, bufferDataUsage)
 STRINGMAP_BEGIN(BufferDataUsage, BUFFERDATAUSAGE_MAX_ENUM, bufferDataUsage)
 {
 {
-	{ "stream",  BUFFERDATAUSAGE_STREAM  },
-	{ "dynamic", BUFFERDATAUSAGE_DYNAMIC },
-	{ "static",  BUFFERDATAUSAGE_STATIC  },
-	{ "staging", BUFFERDATAUSAGE_STAGING },
+	{ "stream",   BUFFERDATAUSAGE_STREAM   },
+	{ "dynamic",  BUFFERDATAUSAGE_DYNAMIC  },
+	{ "static",   BUFFERDATAUSAGE_STATIC   },
+	{ "readback", BUFFERDATAUSAGE_READBACK },
 }
 }
 STRINGMAP_END(BufferDataUsage, BUFFERDATAUSAGE_MAX_ENUM, bufferDataUsage)
 STRINGMAP_END(BufferDataUsage, BUFFERDATAUSAGE_MAX_ENUM, bufferDataUsage)
 
 

+ 1 - 1
src/modules/graphics/vertex.h

@@ -111,7 +111,7 @@ enum BufferDataUsage
 	BUFFERDATAUSAGE_STREAM,
 	BUFFERDATAUSAGE_STREAM,
 	BUFFERDATAUSAGE_DYNAMIC,
 	BUFFERDATAUSAGE_DYNAMIC,
 	BUFFERDATAUSAGE_STATIC,
 	BUFFERDATAUSAGE_STATIC,
-	BUFFERDATAUSAGE_STAGING,
+	BUFFERDATAUSAGE_READBACK,
 	BUFFERDATAUSAGE_MAX_ENUM
 	BUFFERDATAUSAGE_MAX_ENUM
 };
 };
 
 

+ 126 - 0
src/modules/graphics/wrap_Graphics.cpp

@@ -2141,6 +2141,126 @@ int w_newVideo(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_readbackBuffer(lua_State *L)
+{
+	Buffer *b = luax_checkbuffer(L, 1);
+	lua_Integer offset = luaL_optinteger(L, 2, 0);
+	lua_Integer size = luaL_optinteger(L, 3, b->getSize() - offset);
+
+	data::ByteData *dest = nullptr;
+	size_t destoffset = 0;
+	if (!lua_isnoneornil(L, 4))
+	{
+		dest = luax_checktype<data::ByteData>(L, 4);
+		destoffset = (size_t) luaL_optinteger(L, 5, 0);
+	}
+
+	love::data::ByteData *data = nullptr;
+	luax_catchexcept(L, [&]() { data = instance()->readbackBuffer(b, offset, size, dest, destoffset); });
+
+	luax_pushtype(L, data);
+	data->release();
+	return 1;
+}
+
+int w_readbackBufferAsync(lua_State *L)
+{
+	Buffer *b = luax_checkbuffer(L, 1);
+	lua_Integer offset = luaL_optinteger(L, 2, 0);
+	lua_Integer size = luaL_optinteger(L, 3, b->getSize() - offset);
+
+	data::ByteData *dest = nullptr;
+	size_t destoffset = 0;
+	if (!lua_isnoneornil(L, 4))
+	{
+		dest = luax_checktype<data::ByteData>(L, 4);
+		destoffset = (size_t) luaL_optinteger(L, 5, 0);
+	}
+
+	GraphicsReadback *r = nullptr;
+	luax_catchexcept(L, [&]() { r = instance()->readbackBufferAsync(b, offset, size, dest, destoffset); });
+
+	luax_pushtype(L, r);
+	r->release();
+	return 1;
+}
+
+int w_readbackTexture(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+
+	int slice = 0;
+	if (t->getTextureType() != TEXTURE_2D)
+		slice = (int) luaL_checkinteger(L, 2) - 1;
+
+	int mipmap = (int) luaL_optinteger(L, 3, 1) - 1;
+
+	Rect rect = {0, 0, t->getPixelWidth(mipmap), t->getPixelHeight(mipmap)};
+	if (!lua_isnoneornil(L, 4))
+	{
+		rect.x = (int) luaL_checkinteger(L, 4);
+		rect.y = (int) luaL_checkinteger(L, 5);
+		rect.w = (int) luaL_checkinteger(L, 6);
+		rect.h = (int) luaL_checkinteger(L, 7);
+	}
+
+	image::ImageData *dest = nullptr;
+	int destx = 0;
+	int desty = 0;
+
+	if (!lua_isnoneornil(L, 8))
+	{
+		dest = luax_checktype<image::ImageData>(L, 8);
+		destx = (int) luaL_optinteger(L, 9, 0);
+		desty = (int) luaL_optinteger(L, 10, 0);
+	}
+
+	image::ImageData *imagedata = nullptr;
+	luax_catchexcept(L, [&]() { imagedata = instance()->readbackTexture(t, slice, mipmap, rect, dest, destx, desty); });
+
+	luax_pushtype(L, imagedata);
+	imagedata->release();
+	return 1;
+}
+
+int w_readbackTextureAsync(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+
+	int slice = 0;
+	if (t->getTextureType() != TEXTURE_2D)
+		slice = (int) luaL_checkinteger(L, 2) - 1;
+
+	int mipmap = (int) luaL_optinteger(L, 3, 1) - 1;
+
+	Rect rect = {0, 0, t->getPixelWidth(mipmap), t->getPixelHeight(mipmap)};
+	if (!lua_isnoneornil(L, 4))
+	{
+		rect.x = (int) luaL_checkinteger(L, 4);
+		rect.y = (int) luaL_checkinteger(L, 5);
+		rect.w = (int) luaL_checkinteger(L, 6);
+		rect.h = (int) luaL_checkinteger(L, 7);
+	}
+
+	image::ImageData *dest = nullptr;
+	int destx = 0;
+	int desty = 0;
+
+	if (!lua_isnoneornil(L, 8))
+	{
+		dest = luax_checktype<image::ImageData>(L, 8);
+		destx = (int) luaL_optinteger(L, 9, 0);
+		desty = (int) luaL_optinteger(L, 10, 0);
+	}
+
+	GraphicsReadback *r = nullptr;
+	luax_catchexcept(L, [&]() { r = instance()->readbackTextureAsync(t, slice, mipmap, rect, dest, destx, desty); });
+
+	luax_pushtype(L, r);
+	r->release();
+	return 1;
+}
+
 int w_setColor(lua_State *L)
 int w_setColor(lua_State *L)
 {
 {
 	Colorf c;
 	Colorf c;
@@ -3650,6 +3770,11 @@ static const luaL_Reg functions[] =
 	{ "newText", w_newText },
 	{ "newText", w_newText },
 	{ "_newVideo", w_newVideo },
 	{ "_newVideo", w_newVideo },
 
 
+	{ "readbackBuffer", w_readbackBuffer },
+	{ "readbackBufferAsync", w_readbackBufferAsync },
+	{ "readbackTexture", w_readbackTexture },
+	{ "readbackTextureAsync", w_readbackTextureAsync },
+
 	{ "validateShader", w_validateShader },
 	{ "validateShader", w_validateShader },
 
 
 	{ "setCanvas", w_setCanvas },
 	{ "setCanvas", w_setCanvas },
@@ -3787,6 +3912,7 @@ static const lua_CFunction types[] =
 	luaopen_font,
 	luaopen_font,
 	luaopen_quad,
 	luaopen_quad,
 	luaopen_graphicsbuffer,
 	luaopen_graphicsbuffer,
+	luaopen_graphicsreadback,
 	luaopen_spritebatch,
 	luaopen_spritebatch,
 	luaopen_particlesystem,
 	luaopen_particlesystem,
 	luaopen_shader,
 	luaopen_shader,

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

@@ -32,6 +32,7 @@
 #include "wrap_Text.h"
 #include "wrap_Text.h"
 #include "wrap_Video.h"
 #include "wrap_Video.h"
 #include "wrap_Buffer.h"
 #include "wrap_Buffer.h"
+#include "wrap_GraphicsReadback.h"
 #include "Graphics.h"
 #include "Graphics.h"
 
 
 namespace love
 namespace love

+ 95 - 0
src/modules/graphics/wrap_GraphicsReadback.cpp

@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2006-2021 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+// LOVE
+#include "wrap_GraphicsReadback.h"
+#include "data/ByteData.h"
+#include "image/ImageData.h"
+
+namespace love
+{
+namespace graphics
+{
+
+GraphicsReadback *luax_checkgraphicsreadback(lua_State *L, int idx)
+{
+	return luax_checktype<GraphicsReadback>(L, idx);
+}
+
+int w_GraphicsReadback_isComplete(lua_State *L)
+{
+	GraphicsReadback *t = luax_checkgraphicsreadback(L, 1);
+	luax_pushboolean(L, t->isComplete());
+	return 1;
+}
+
+int w_GraphicsReadback_hasError(lua_State *L)
+{
+	GraphicsReadback *t = luax_checkgraphicsreadback(L, 1);
+	luax_pushboolean(L, t->hasError());
+	return 1;
+}
+
+int w_GraphicsReadback_wait(lua_State *L)
+{
+	GraphicsReadback *t = luax_checkgraphicsreadback(L, 1);
+	t->wait();
+	return 0;
+}
+
+int w_GraphicsReadback_update(lua_State *L)
+{
+	GraphicsReadback *t = luax_checkgraphicsreadback(L, 1);
+	luax_catchexcept(L, [&]() { t->update(); });
+	return 0;
+}
+
+int w_GraphicsReadback_getBufferData(lua_State *L)
+{
+	GraphicsReadback *t = luax_checkgraphicsreadback(L, 1);
+	luax_pushtype(L, t->getBufferData());
+	return 1;
+}
+
+int w_GraphicsReadback_getImageData(lua_State *L)
+{
+	GraphicsReadback *t = luax_checkgraphicsreadback(L, 1);
+	luax_pushtype(L, t->getImageData());
+	return 1;
+}
+
+static const luaL_Reg w_GraphicsReadback_functions[] =
+{
+	{ "isComplete", w_GraphicsReadback_isComplete },
+	{ "hasError", w_GraphicsReadback_hasError },
+	{ "wait", w_GraphicsReadback_wait },
+	{ "update", w_GraphicsReadback_update },
+	{ "getBufferData", w_GraphicsReadback_getBufferData },
+	{ "getImageData", w_GraphicsReadback_getImageData },
+	{ 0, 0 }
+};
+
+extern "C" int luaopen_graphicsreadback(lua_State *L)
+{
+	return luax_register_type(L, &GraphicsReadback::type, w_GraphicsReadback_functions, nullptr);
+}
+
+} // graphics
+} // love

+ 36 - 0
src/modules/graphics/wrap_GraphicsReadback.h

@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2006-2021 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.
+ **/
+
+#pragma once
+
+// LOVE
+#include "common/runtime.h"
+#include "GraphicsReadback.h"
+
+namespace love
+{
+namespace graphics
+{
+
+GraphicsReadback *luax_checkgraphicsreadback(lua_State *L, int idx);
+extern "C" int luaopen_graphicsreadback(lua_State *L);
+
+} // graphics
+} // love

+ 12 - 6
src/modules/graphics/wrap_Texture.cpp

@@ -385,16 +385,15 @@ int w_Texture_replacePixels(lua_State *L)
 
 
 int w_Texture_newImageData(lua_State *L)
 int w_Texture_newImageData(lua_State *L)
 {
 {
+	luax_markdeprecated(L, 1, "Texture:newImageData", API_METHOD, DEPRECATED_RENAMED, "love.graphics.readbackTexture");
+
 	Texture *t = luax_checktexture(L, 1);
 	Texture *t = luax_checktexture(L, 1);
-	love::image::Image *image = luax_getmodule<love::image::Image>(L, love::image::Image::type);
 
 
 	int slice = 0;
 	int slice = 0;
-	int mipmap = 0;
-
 	if (t->getTextureType() != TEXTURE_2D)
 	if (t->getTextureType() != TEXTURE_2D)
 		slice = (int) luaL_checkinteger(L, 2) - 1;
 		slice = (int) luaL_checkinteger(L, 2) - 1;
 
 
-	mipmap = (int) luaL_optinteger(L, 3, 1) - 1;
+	int mipmap = (int) luaL_optinteger(L, 3, 1) - 1;
 
 
 	Rect rect = {0, 0, t->getPixelWidth(mipmap), t->getPixelHeight(mipmap)};
 	Rect rect = {0, 0, t->getPixelWidth(mipmap), t->getPixelHeight(mipmap)};
 	if (!lua_isnoneornil(L, 4))
 	if (!lua_isnoneornil(L, 4))
@@ -405,8 +404,12 @@ int w_Texture_newImageData(lua_State *L)
 		rect.h = (int) luaL_checkinteger(L, 7);
 		rect.h = (int) luaL_checkinteger(L, 7);
 	}
 	}
 
 
+	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
+	if (gfx == nullptr)
+		return luaL_error(L, "Cannot find Graphics module.");
+
 	love::image::ImageData *img = nullptr;
 	love::image::ImageData *img = nullptr;
-	luax_catchexcept(L, [&](){ img = t->newImageData(image, slice, mipmap, rect); });
+	luax_catchexcept(L, [&](){ img = gfx->readbackTexture(t, slice, mipmap, rect, nullptr, 0, 0); });
 
 
 	luax_pushtype(L, img);
 	luax_pushtype(L, img);
 	img->release();
 	img->release();
@@ -501,8 +504,11 @@ const luaL_Reg w_Texture_functions[] =
 	{ "setDepthSampleMode", w_Texture_setDepthSampleMode },
 	{ "setDepthSampleMode", w_Texture_setDepthSampleMode },
 	{ "generateMipmaps", w_Texture_generateMipmaps },
 	{ "generateMipmaps", w_Texture_generateMipmaps },
 	{ "replacePixels", w_Texture_replacePixels },
 	{ "replacePixels", w_Texture_replacePixels },
-	{ "newImageData", w_Texture_newImageData },
 	{ "renderTo", w_Texture_renderTo },
 	{ "renderTo", w_Texture_renderTo },
+
+	// Deprecated
+	{ "newImageData", w_Texture_newImageData },
+
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };
 
 

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

@@ -99,6 +99,8 @@ bool Keyboard::isModifierActive(ModifierKey key) const
 		return (modstate & KMOD_SCROLL) != 0;
 		return (modstate & KMOD_SCROLL) != 0;
 	case MODKEY_MODE:
 	case MODKEY_MODE:
 		return (modstate & KMOD_MODE) != 0;
 		return (modstate & KMOD_MODE) != 0;
+	default:
+		break;
 	}
 	}
 
 
 	return false;
 	return false;

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

@@ -129,6 +129,11 @@ function love.createhandlers()
 		localechanged = function ()
 		localechanged = function ()
 			if love.localechanged then return love.localechanged() end
 			if love.localechanged then return love.localechanged() end
 		end,
 		end,
+		audiodisconnected = function (sources)
+			if not love.audiodisconnected or not love.audiodisconnected(sources) then
+				love.audio.setPlaybackDevice()
+			end
+		end,
 	}, {
 	}, {
 		__index = function(self, name)
 		__index = function(self, name)
 			error("Unknown event: " .. name)
 			error("Unknown event: " .. name)