Browse Source

Merge branch '12.0-development' into metal

Alex Szpakowski 3 years ago
parent
commit
40aa50662a

+ 8 - 4
.github/workflows/main.yml

@@ -58,10 +58,15 @@ jobs:
       shell: cmd
       shell: cmd
       run: cmake --build build --config Release --target install -j2
       run: cmake --build build --config Release --target install -j2
     - name: Artifact
     - name: Artifact
-      uses: actions/upload-artifact@v1
+      uses: actions/upload-artifact@v2
       with:
       with:
         name: love-windows-${{ matrix.platform }}
         name: love-windows-${{ matrix.platform }}
         path: install
         path: install
+    - name: Artifact JIT Modules
+      uses: actions/upload-artifact@v2
+      with:
+        name: love-windows-jitmodules-${{ matrix.platform }}
+        path: build/libs/LuaJIT/src/jit/*.lua
   macOS:
   macOS:
     runs-on: macos-latest
     runs-on: macos-latest
     steps:
     steps:
@@ -71,7 +76,7 @@ jobs:
       uses: actions/checkout@v2
       uses: actions/checkout@v2
       with:
       with:
         path: apple-dependencies
         path: apple-dependencies
-        repository: slime73/love-apple-dependencies
+        repository: love2d/love-apple-dependencies
         ref: main
         ref: main
     - name: Move Dependencies
     - name: Move Dependencies
       run:
       run:
@@ -99,11 +104,10 @@ jobs:
       uses: actions/checkout@v2
       uses: actions/checkout@v2
       with:
       with:
         path: apple-dependencies
         path: apple-dependencies
-        repository: slime73/love-apple-dependencies
+        repository: love2d/love-apple-dependencies
         ref: main
         ref: main
     - name: Move Dependencies
     - name: Move Dependencies
       run: |
       run: |
-        mv apple-dependencies/iOS/include platform/xcode/ios
         mv apple-dependencies/iOS/libraries platform/xcode/ios
         mv apple-dependencies/iOS/libraries platform/xcode/ios
     - name: Build
     - name: Build
       run:
       run:

+ 26 - 1
changes.txt

@@ -3,17 +3,33 @@ LOVE 12.0 [N/A]
 
 
 Released: N/A
 Released: N/A
 
 
+* Added love.parsedGameArguments and love.rawGameArguments tables, in the main thread.
+* Added love.markDeprecated.
 * Added love.event.restart(optionalvalue). A new love.restart field will contain the value after restarting.
 * Added love.event.restart(optionalvalue). A new love.restart field will contain the value after restarting.
+* Added love.filesystem.mountFullPath and love.filesystem.unmountFullPath, including opt-in mount-for-write support.
+* Added love.filesystem.mountCommonPath, unmountCommonPath, and getFullCommonPath.
+* Added 'readonly' field to love.filesystem.getInfo's returned table.
 * Added SoundData:copyFrom.
 * Added SoundData:copyFrom.
 * Added SoundData:slice.
 * Added SoundData:slice.
+* Added Joystick:setPlayerIndex and Joystick:getPlayerIndex.
+* Added Joystick:getGamepadType.
+* Added new Gamepad API buttons: "misc1", "paddle1", "paddle2", "paddle3", "paddle4". and "touchpad".
 * Added t.highdpi startup flag in love.conf, replacing t.window.highdpi and the highdpi flag of love.window.setMode.
 * Added t.highdpi startup flag in love.conf, replacing t.window.highdpi and the highdpi flag of love.window.setMode.
 * Added per-shader opt in support for the GLSL 4.30 (desktop) and GLSL ES 3.10 (mobile) shading languages, via #pragma language glsl4.
 * Added per-shader opt in support for the GLSL 4.30 (desktop) and GLSL ES 3.10 (mobile) shading languages, via #pragma language glsl4.
 * Added love.graphics.newTexture. newImage and newCanvas still exist as convenience constructor functions.
 * Added love.graphics.newTexture. newImage and newCanvas still exist as convenience constructor functions.
 * Added love.graphics.getTextureFormats, which replaces getImageFormats and getCanvasFormats.
 * Added love.graphics.getTextureFormats, which replaces getImageFormats and getCanvasFormats.
-* Added Graphics Buffer objects, including vertex, index, and texel buffers.
+* Added integer texture formats.
+* Added Graphics Buffer objects, including vertex, index, texel, and shader storage buffers.
+* Added love.graphics.copyBuffer, copyBufferToTexture, and copyTextureToBuffer.
 * Added APIs for interacting with the Buffer objects owned by Meshes.
 * Added APIs for interacting with the Buffer objects owned by Meshes.
 * Added Mesh:getAttachedAttributes.
 * Added Mesh:getAttachedAttributes.
 * Added integer buffer data formats.
 * Added integer buffer data formats.
+* Added 'staging' buffer data usage enum, useful for Buffers used in intermediate copy steps.
+* Added new lower level 'vertexmain' and 'pixelmain' shader entry points.
+* Added Compute Shader support via new 'computemain' shader entry point.
+* Added love.graphics.dispatchThreadgroups for running compute shaders.
+* Added Shader:hasStage.
+* Added ability to set point size within a vertex shader by setting the 'love_PointSize' variable.
 * Added love.graphics.setBlendState, which gives lower level control over blend operations than setBlendMode.
 * Added love.graphics.setBlendState, which gives lower level control over blend operations than setBlendMode.
 * Added new 'clampone' wrap mode.
 * Added new 'clampone' wrap mode.
 * Added a variant of Font:getWidth which takes a codepoint number argument.
 * Added a variant of Font:getWidth which takes a codepoint number argument.
@@ -22,6 +38,9 @@ Released: N/A
 * Changed the Texture class and implementation to no longer have separate Canvas and Image subclasses.
 * Changed the Texture class and implementation to no longer have separate Canvas and Image subclasses.
 * Changed Images to no longer hold onto a CPU copy of their pixel data after creation.
 * Changed Images to no longer hold onto a CPU copy of their pixel data after creation.
 * Changed love.window.setMode to no longer clear the contents of Canvases or otherwise recreate OpenGL resources.
 * Changed love.window.setMode to no longer clear the contents of Canvases or otherwise recreate OpenGL resources.
+* Changed love.graphics.points to require 'love_PointSize' to be set in the vertex shader, if a custom shader is used.
+* Changed RevoluteJoint:getMotorTorque to take 'dt' as a parameter instead of 'inverse_dt'.
+* Changed love.math.noise to use higher precision numbers for its internal calculations.
 
 
 * Updated Box2D from 2.3 to 2.4.1.
 * Updated Box2D from 2.3 to 2.4.1.
 
 
@@ -39,21 +58,26 @@ LOVE 11.4 [Mysterious Mysteries]
 Released: N/A
 Released: N/A
 
 
 * Added native arm64 support on macOS.
 * Added native arm64 support on macOS.
+* Added a variant of love.filesystem.newFileData which accepts a Data object.
 * Added Body:getLocalPoints.
 * Added Body:getLocalPoints.
 * Added Font:getKerning.
 * Added Font:getKerning.
 * Added support for r16, rg16, and rgba16 pixel formats in Canvases.
 * Added support for r16, rg16, and rgba16 pixel formats in Canvases.
 * Added Shader:send(name, matrixlayout, data, ...) variant, whose argument order is more consistent than Shader:send(name, data, matrixlayout, ...).
 * Added Shader:send(name, matrixlayout, data, ...) variant, whose argument order is more consistent than Shader:send(name, data, matrixlayout, ...).
 
 
+* Changed all builds and platforms where LOVE provides LuaJIT to use LuaJIT 2.1 instead of 2.0.
 * Changed love.timer.getTime to start at 0 when the module is first loaded.
 * Changed love.timer.getTime to start at 0 when the module is first loaded.
 * Changed certain out-of-Lua-memory situations to show a message box instead of instantly crashing.
 * Changed certain out-of-Lua-memory situations to show a message box instead of instantly crashing.
+* Changed the naming scheme of LOVE's embedded Lua files for improved integration with Lua chunkname APIs.
 
 
 * Fixed build-time compatibility with Lua 5.4.
 * Fixed build-time compatibility with Lua 5.4.
 * Fixed code compatibility with math.mod and string.gfind when LuaJIT 2.1 is used.
 * Fixed code compatibility with math.mod and string.gfind when LuaJIT 2.1 is used.
 * Fixed errors on some systems related to > 53 bit pointer addresses, when recent versions of LuaJIT 2.1 are used.
 * Fixed errors on some systems related to > 53 bit pointer addresses, when recent versions of LuaJIT 2.1 are used.
+* Fixed the default error handler showing a blank screen on some mobile devices.
 * Fixed drag-and-drop to open a love game on macOS causing love.event.quit("restart") to fail.
 * Fixed drag-and-drop to open a love game on macOS causing love.event.quit("restart") to fail.
 * Fixed fused macOS apps opening other love games when drag-and-drop is used (if the fused app hasn't already removed .love files from recognized document types).
 * Fixed fused macOS apps opening other love games when drag-and-drop is used (if the fused app hasn't already removed .love files from recognized document types).
 * Fixed File:isEOF when called on a dropped file.
 * Fixed File:isEOF when called on a dropped file.
 * Fixed support for > 2GB dropped files on desktops.
 * Fixed support for > 2GB dropped files on desktops.
+* Fixed ByteData and DataView missing Data:clone implementations.
 * Fixed love.physics meter scale value persisting after love.event.quit("restart").
 * Fixed love.physics meter scale value persisting after love.event.quit("restart").
 * Fixed audio to resume properly after interruption on iOS.
 * Fixed audio to resume properly after interruption on iOS.
 * Fixed love.graphics.newVideo to error instead of crash when an invalid video file is given.
 * Fixed love.graphics.newVideo to error instead of crash when an invalid video file is given.
@@ -76,6 +100,7 @@ Released: N/A
 * Fixed rare issues where textures were not sent to shaders correctly.
 * Fixed rare issues where textures were not sent to shaders correctly.
 * Fixed Shader:send(name, data, matrixlayout, ...).
 * Fixed Shader:send(name, data, matrixlayout, ...).
 * Fixed quad offsets in ParticleSystems when ParticleSystem:setOffset is not used.
 * Fixed quad offsets in ParticleSystems when ParticleSystem:setOffset is not used.
+* Fixed a performance issue with setting a small subrange of data in non-stream Meshes and SpriteBatches.
 * Fixed rounded rectangles breaking if the rx or ry parameters are negative.
 * Fixed rounded rectangles breaking if the rx or ry parameters are negative.
 * Fixed rounded rectangle automatic points calculation when rx or ry are more than half the rectangle's size.
 * Fixed rounded rectangle automatic points calculation when rx or ry are more than half the rectangle's size.
 * Fixed source code compilation on Xcode 12+.
 * Fixed source code compilation on Xcode 12+.

+ 24 - 19
platform/xcode/ios/luajit-iOS.sh

@@ -1,25 +1,25 @@
 mkdir -p include/luajit
 mkdir -p include/luajit
 mkdir -p libraries/luajit
 mkdir -p libraries/luajit
 
 
-git clone https://github.com/LuaJIT/LuaJIT.git luajit-git
+if [ ! -d luajit-git ]; then
+	git clone https://github.com/LuaJIT/LuaJIT.git luajit-git
+fi
 cd luajit-git
 cd luajit-git
-git pull
+git pull --no-rebase
 git checkout v2.1
 git checkout v2.1
 
 
+export MACOSX_DEPLOYMENT_TARGET=10.7
+
 # iOS device binaries
 # iOS device binaries
+# LuaJIT does not support building for armv7 on modern macOS versions.
 
 
 ISDKP=$(xcrun --sdk iphoneos --show-sdk-path)
 ISDKP=$(xcrun --sdk iphoneos --show-sdk-path)
 ICC=$(xcrun --sdk iphoneos --find clang)
 ICC=$(xcrun --sdk iphoneos --find clang)
 
 
-ISDKF="-arch armv7 -isysroot $ISDKP -mios-version-min=8.0"
-make clean
-make -j8 HOST_CC="clang -m32 -arch i386" CROSS="$(dirname $ICC)/" TARGET_FLAGS="$ISDKF" TARGET_SYS=iOS
-cp src/libluajit.a ../libraries/luajit/libluajit_arm7.a
-
 ISDKF="-arch arm64 -isysroot $ISDKP -mios-version-min=8.0"
 ISDKF="-arch arm64 -isysroot $ISDKP -mios-version-min=8.0"
-make clean
-make -j8 CROSS="$(dirname $ICC)/" TARGET_FLAGS="$ISDKF" TARGET_SYS=iOS
-cp src/libluajit.a ../libraries/luajit/libluajit_arm64.a
+make clean TARGET_SYS=iOS
+make -j8 CC="clang" CROSS="$(dirname $ICC)/" TARGET_FLAGS="$ISDKF" TARGET_SYS=iOS
+cp src/libluajit.a ../libraries/luajit/libluajit_arm64_device.a
 
 
 
 
 # iOS simulator binaries
 # iOS simulator binaries
@@ -27,15 +27,15 @@ cp src/libluajit.a ../libraries/luajit/libluajit_arm64.a
 ISDKP=$(xcrun --sdk iphonesimulator --show-sdk-path)
 ISDKP=$(xcrun --sdk iphonesimulator --show-sdk-path)
 ICC=$(xcrun --sdk iphonesimulator --find clang)
 ICC=$(xcrun --sdk iphonesimulator --find clang)
 
 
-ISDKF="-arch i386 -isysroot $ISDKP -mios-simulator-version-min=8.0"
-make clean
-make -j8 HOST_CC="clang -m32 -arch i386" CROSS="$(dirname $ICC)/" TARGET_FLAGS="$ISDKF" TARGET_SYS=iOS
-cp src/libluajit.a ../libraries/luajit/libluajit_x86.a
-
 ISDKF="-arch x86_64 -isysroot $ISDKP -mios-simulator-version-min=8.0"
 ISDKF="-arch x86_64 -isysroot $ISDKP -mios-simulator-version-min=8.0"
-make clean
-make -j8 CROSS="$(dirname $ICC)/" TARGET_FLAGS="$ISDKF" TARGET_SYS=iOS
-cp src/libluajit.a ../libraries/luajit/libluajit_x86_64.a
+make clean TARGET_SYS=iOS
+make -j8 CC="clang" CROSS="$(dirname $ICC)/" TARGET_FLAGS="$ISDKF" TARGET_SYS=iOS
+cp src/libluajit.a ../libraries/luajit/libluajit_x86_64_sim.a
+
+ISDKF="-arch arm64 -isysroot $ISDKP -mios-simulator-version-min=8.0"
+make clean TARGET_SYS=iOS
+make -j8 CC="clang" CROSS="$(dirname $ICC)/" TARGET_FLAGS="$ISDKF" TARGET_SYS=iOS
+cp src/libluajit.a ../libraries/luajit/libluajit_arm64_sim.a
 
 
 
 
 # copy includes
 # copy includes
@@ -49,4 +49,9 @@ cp src/luajit.h ../include/luajit
 
 
 # combine lib
 # combine lib
 cd ../libraries/luajit
 cd ../libraries/luajit
-lipo -create -output libluajit.a libluajit_arm7.a libluajit_arm64.a libluajit_x86.a libluajit_x86_64.a
+lipo -create -output libluajit_device.a libluajit_arm64_device.a
+lipo -create -output libluajit_sim.a libluajit_x86_64_sim.a libluajit_arm64_sim.a
+
+# create xcframework with all platforms
+rm -rf Lua.xcframework
+xcodebuild -create-xcframework -library libluajit_device.a -headers ../../include/luajit -library libluajit_sim.a -headers ../../include/luajit -output Lua.xcframework

+ 43 - 152
platform/xcode/liblove.xcodeproj/project.pbxproj

@@ -794,7 +794,6 @@
 		FA56AA381FAFF02000A43D5F /* memory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA56AA361FAFF02000A43D5F /* memory.cpp */; };
 		FA56AA381FAFF02000A43D5F /* memory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA56AA361FAFF02000A43D5F /* memory.cpp */; };
 		FA56AA391FAFF02000A43D5F /* memory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA56AA361FAFF02000A43D5F /* memory.cpp */; };
 		FA56AA391FAFF02000A43D5F /* memory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA56AA361FAFF02000A43D5F /* memory.cpp */; };
 		FA56AA3A1FAFF02000A43D5F /* memory.h in Headers */ = {isa = PBXBuildFile; fileRef = FA56AA371FAFF02000A43D5F /* memory.h */; };
 		FA56AA3A1FAFF02000A43D5F /* memory.h in Headers */ = {isa = PBXBuildFile; fileRef = FA56AA371FAFF02000A43D5F /* memory.h */; };
-		FA56D9BC1C208A0200D8D3C7 /* libmodplug.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA56D9BA1C2089EE00D8D3C7 /* libmodplug.a */; };
 		FA577AB016C7507900860150 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA577A7916C71A1700860150 /* Cocoa.framework */; };
 		FA577AB016C7507900860150 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA577A7916C71A1700860150 /* Cocoa.framework */; };
 		FA577AC516C7513400860150 /* libmodplug.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA577A8216C71A5300860150 /* libmodplug.framework */; };
 		FA577AC516C7513400860150 /* libmodplug.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA577A8216C71A5300860150 /* libmodplug.framework */; };
 		FA577AC816C7513C00860150 /* ogg.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA577A7116C719F400860150 /* ogg.framework */; };
 		FA577AC816C7513C00860150 /* ogg.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA577A7116C719F400860150 /* ogg.framework */; };
@@ -804,10 +803,6 @@
 		FA57FB991AE1993600F2AD6D /* noise1234.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA57FB961AE1993600F2AD6D /* noise1234.cpp */; };
 		FA57FB991AE1993600F2AD6D /* noise1234.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA57FB961AE1993600F2AD6D /* noise1234.cpp */; };
 		FA57FB9A1AE1993600F2AD6D /* noise1234.h in Headers */ = {isa = PBXBuildFile; fileRef = FA57FB971AE1993600F2AD6D /* noise1234.h */; };
 		FA57FB9A1AE1993600F2AD6D /* noise1234.h in Headers */ = {isa = PBXBuildFile; fileRef = FA57FB971AE1993600F2AD6D /* noise1234.h */; };
 		FA59A2D31C06481400328DBA /* ParticleSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAE272501C05A15B00A67640 /* ParticleSystem.cpp */; };
 		FA59A2D31C06481400328DBA /* ParticleSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAE272501C05A15B00A67640 /* ParticleSystem.cpp */; };
-		FA59A2D81C0649C200328DBA /* libtheora.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA59A2D61C0649BB00328DBA /* libtheora.a */; };
-		FA5D24B31A96D2EC00C6FC8F /* libogg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA5D24AB1A96D2EC00C6FC8F /* libogg.a */; };
-		FA5D24B51A96D2EC00C6FC8F /* libvorbis.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA5D24AF1A96D2EC00C6FC8F /* libvorbis.a */; };
-		FA5D24CF1A96E68300C6FC8F /* libSDL2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA5D24CD1A96E63D00C6FC8F /* libSDL2.a */; };
 		FA620A321AA2F8DB005DB4C2 /* wrap_Quad.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA620A2E1AA2F8DB005DB4C2 /* wrap_Quad.cpp */; };
 		FA620A321AA2F8DB005DB4C2 /* wrap_Quad.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA620A2E1AA2F8DB005DB4C2 /* wrap_Quad.cpp */; };
 		FA620A331AA2F8DB005DB4C2 /* wrap_Quad.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA620A2E1AA2F8DB005DB4C2 /* wrap_Quad.cpp */; };
 		FA620A331AA2F8DB005DB4C2 /* wrap_Quad.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA620A2E1AA2F8DB005DB4C2 /* wrap_Quad.cpp */; };
 		FA620A341AA2F8DB005DB4C2 /* wrap_Quad.h in Headers */ = {isa = PBXBuildFile; fileRef = FA620A2F1AA2F8DB005DB4C2 /* wrap_Quad.h */; };
 		FA620A341AA2F8DB005DB4C2 /* wrap_Quad.h in Headers */ = {isa = PBXBuildFile; fileRef = FA620A2F1AA2F8DB005DB4C2 /* wrap_Quad.h */; };
@@ -831,12 +826,16 @@
 		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 */; };
-		FA7550A81AEBE276003E311E /* libluajit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA7550A71AEBE276003E311E /* libluajit.a */; };
 		FA76344A1E28722A0066EF9E /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7634481E28722A0066EF9E /* StreamBuffer.cpp */; };
 		FA76344A1E28722A0066EF9E /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7634481E28722A0066EF9E /* StreamBuffer.cpp */; };
 		FA76344B1E28722A0066EF9E /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7634481E28722A0066EF9E /* StreamBuffer.cpp */; };
 		FA76344B1E28722A0066EF9E /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7634481E28722A0066EF9E /* StreamBuffer.cpp */; };
 		FA76344C1E28722A0066EF9E /* StreamBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7634491E28722A0066EF9E /* StreamBuffer.h */; };
 		FA76344C1E28722A0066EF9E /* StreamBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7634491E28722A0066EF9E /* StreamBuffer.h */; };
 		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 */; };
+		FA7E9207277E120900C24CB2 /* theora.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA7E9206277E120900C24CB2 /* theora.xcframework */; };
+		FA84DE76277CB3D5002674C6 /* SDL2.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE75277CB3D4002674C6 /* SDL2.xcframework */; };
+		FA84DE7A277D4C88002674C6 /* modplug.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE79277D4C88002674C6 /* modplug.xcframework */; };
+		FA84DE7C277E045E002674C6 /* ogg.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE7B277E045E002674C6 /* ogg.xcframework */; };
+		FA84DE7E277E0A43002674C6 /* vorbis.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE7D277E0A43002674C6 /* vorbis.xcframework */; };
 		FA8951A21AA2EDF300EC385A /* wrap_Event.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA8951A01AA2EDF300EC385A /* wrap_Event.cpp */; };
 		FA8951A21AA2EDF300EC385A /* wrap_Event.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA8951A01AA2EDF300EC385A /* wrap_Event.cpp */; };
 		FA8951A31AA2EDF300EC385A /* wrap_Event.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA8951A01AA2EDF300EC385A /* wrap_Event.cpp */; };
 		FA8951A31AA2EDF300EC385A /* wrap_Event.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA8951A01AA2EDF300EC385A /* wrap_Event.cpp */; };
 		FA8951A41AA2EDF300EC385A /* wrap_Event.h in Headers */ = {isa = PBXBuildFile; fileRef = FA8951A11AA2EDF300EC385A /* wrap_Event.h */; };
 		FA8951A41AA2EDF300EC385A /* wrap_Event.h in Headers */ = {isa = PBXBuildFile; fileRef = FA8951A11AA2EDF300EC385A /* wrap_Event.h */; };
@@ -1091,6 +1090,8 @@
 		FACA02FB1F5E397E0084B28F /* HashFunction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FACA02E61F5E396B0084B28F /* HashFunction.cpp */; };
 		FACA02FB1F5E397E0084B28F /* HashFunction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FACA02E61F5E396B0084B28F /* HashFunction.cpp */; };
 		FACA02FC1F5E39810084B28F /* wrap_CompressedData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FACA02E81F5E396B0084B28F /* wrap_CompressedData.cpp */; };
 		FACA02FC1F5E39810084B28F /* wrap_CompressedData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FACA02E81F5E396B0084B28F /* wrap_CompressedData.cpp */; };
 		FACA02FD1F5E39840084B28F /* wrap_DataModule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FACA02EA1F5E396B0084B28F /* wrap_DataModule.cpp */; };
 		FACA02FD1F5E39840084B28F /* wrap_DataModule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FACA02EA1F5E396B0084B28F /* wrap_DataModule.cpp */; };
+		FACFB751276D7E3B0089F78D /* freetype.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FACFB750276D7E2B0089F78D /* freetype.xcframework */; };
+		FACFB753276D7F860089F78D /* Lua.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FACFB752276D7F6F0089F78D /* Lua.xcframework */; };
 		FAD19A171DFF8CA200D5398A /* ImageDataBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAD19A151DFF8CA200D5398A /* ImageDataBase.cpp */; };
 		FAD19A171DFF8CA200D5398A /* ImageDataBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAD19A151DFF8CA200D5398A /* ImageDataBase.cpp */; };
 		FAD19A181DFF8CA200D5398A /* ImageDataBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAD19A151DFF8CA200D5398A /* ImageDataBase.cpp */; };
 		FAD19A181DFF8CA200D5398A /* ImageDataBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAD19A151DFF8CA200D5398A /* ImageDataBase.cpp */; };
 		FAD19A191DFF8CA200D5398A /* ImageDataBase.h in Headers */ = {isa = PBXBuildFile; fileRef = FAD19A161DFF8CA200D5398A /* ImageDataBase.h */; };
 		FAD19A191DFF8CA200D5398A /* ImageDataBase.h in Headers */ = {isa = PBXBuildFile; fileRef = FAD19A161DFF8CA200D5398A /* ImageDataBase.h */; };
@@ -1131,7 +1132,6 @@
 		FADF543D1E3DAFF700012CC0 /* wrap_Graphics.h in Headers */ = {isa = PBXBuildFile; fileRef = FADF543A1E3DAFF700012CC0 /* wrap_Graphics.h */; };
 		FADF543D1E3DAFF700012CC0 /* wrap_Graphics.h in Headers */ = {isa = PBXBuildFile; fileRef = FADF543A1E3DAFF700012CC0 /* wrap_Graphics.h */; };
 		FAE272521C05A15B00A67640 /* ParticleSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAE272501C05A15B00A67640 /* ParticleSystem.cpp */; };
 		FAE272521C05A15B00A67640 /* ParticleSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAE272501C05A15B00A67640 /* ParticleSystem.cpp */; };
 		FAE272531C05A15B00A67640 /* ParticleSystem.h in Headers */ = {isa = PBXBuildFile; fileRef = FAE272511C05A15B00A67640 /* ParticleSystem.h */; };
 		FAE272531C05A15B00A67640 /* ParticleSystem.h in Headers */ = {isa = PBXBuildFile; fileRef = FAE272511C05A15B00A67640 /* ParticleSystem.h */; };
-		FAE64A7F207135AD00BC7981 /* libfreetype.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FAE64A7D2071359C00BC7981 /* libfreetype.a */; };
 		FAE64A802071362A00BC7981 /* physfs_archiver_7z.c in Sources */ = {isa = PBXBuildFile; fileRef = FAC7CD5D1FE35E95006A60C7 /* physfs_archiver_7z.c */; };
 		FAE64A802071362A00BC7981 /* physfs_archiver_7z.c in Sources */ = {isa = PBXBuildFile; fileRef = FAC7CD5D1FE35E95006A60C7 /* physfs_archiver_7z.c */; };
 		FAE64A812071363100BC7981 /* physfs_archiver_dir.c in Sources */ = {isa = PBXBuildFile; fileRef = FAC7CD6C1FE35E95006A60C7 /* physfs_archiver_dir.c */; };
 		FAE64A812071363100BC7981 /* physfs_archiver_dir.c in Sources */ = {isa = PBXBuildFile; fileRef = FAC7CD6C1FE35E95006A60C7 /* physfs_archiver_dir.c */; };
 		FAE64A822071363100BC7981 /* physfs_archiver_grp.c in Sources */ = {isa = PBXBuildFile; fileRef = FAC7CD741FE35E95006A60C7 /* physfs_archiver_grp.c */; };
 		FAE64A822071363100BC7981 /* physfs_archiver_grp.c in Sources */ = {isa = PBXBuildFile; fileRef = FAC7CD741FE35E95006A60C7 /* physfs_archiver_grp.c */; };
@@ -1861,7 +1861,6 @@
 		FA522D5923FA5ED40059EE3C /* NotoSans-Regular.ttf.gzip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NotoSans-Regular.ttf.gzip.h"; sourceTree = "<group>"; };
 		FA522D5923FA5ED40059EE3C /* NotoSans-Regular.ttf.gzip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NotoSans-Regular.ttf.gzip.h"; sourceTree = "<group>"; };
 		FA56AA361FAFF02000A43D5F /* memory.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = memory.cpp; sourceTree = "<group>"; };
 		FA56AA361FAFF02000A43D5F /* memory.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = memory.cpp; sourceTree = "<group>"; };
 		FA56AA371FAFF02000A43D5F /* memory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = memory.h; sourceTree = "<group>"; };
 		FA56AA371FAFF02000A43D5F /* memory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = memory.h; sourceTree = "<group>"; };
-		FA56D9BA1C2089EE00D8D3C7 /* libmodplug.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libmodplug.a; sourceTree = "<group>"; };
 		FA577A6D16C719EA00860150 /* Lua.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Lua.framework; path = macosx/Frameworks/Lua.framework; sourceTree = "<group>"; };
 		FA577A6D16C719EA00860150 /* Lua.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Lua.framework; path = macosx/Frameworks/Lua.framework; sourceTree = "<group>"; };
 		FA577A7116C719F400860150 /* ogg.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ogg.framework; path = macosx/Frameworks/ogg.framework; sourceTree = "<group>"; };
 		FA577A7116C719F400860150 /* ogg.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ogg.framework; path = macosx/Frameworks/ogg.framework; sourceTree = "<group>"; };
 		FA577A7716C71A0800860150 /* vorbis.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = vorbis.framework; path = macosx/Frameworks/vorbis.framework; sourceTree = "<group>"; };
 		FA577A7716C71A0800860150 /* vorbis.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = vorbis.framework; path = macosx/Frameworks/vorbis.framework; sourceTree = "<group>"; };
@@ -1873,10 +1872,6 @@
 		FA577AAF16C7507900860150 /* love.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = love.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		FA577AAF16C7507900860150 /* love.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = love.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		FA57FB961AE1993600F2AD6D /* noise1234.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = noise1234.cpp; sourceTree = "<group>"; };
 		FA57FB961AE1993600F2AD6D /* noise1234.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = noise1234.cpp; sourceTree = "<group>"; };
 		FA57FB971AE1993600F2AD6D /* noise1234.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = noise1234.h; sourceTree = "<group>"; };
 		FA57FB971AE1993600F2AD6D /* noise1234.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = noise1234.h; sourceTree = "<group>"; };
-		FA59A2D61C0649BB00328DBA /* libtheora.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libtheora.a; sourceTree = "<group>"; };
-		FA5D24AB1A96D2EC00C6FC8F /* libogg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libogg.a; sourceTree = "<group>"; };
-		FA5D24AF1A96D2EC00C6FC8F /* libvorbis.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libvorbis.a; sourceTree = "<group>"; };
-		FA5D24CD1A96E63D00C6FC8F /* libSDL2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libSDL2.a; sourceTree = "<group>"; };
 		FA620A2E1AA2F8DB005DB4C2 /* wrap_Quad.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Quad.cpp; sourceTree = "<group>"; };
 		FA620A2E1AA2F8DB005DB4C2 /* wrap_Quad.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Quad.cpp; sourceTree = "<group>"; };
 		FA620A2F1AA2F8DB005DB4C2 /* wrap_Quad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Quad.h; sourceTree = "<group>"; };
 		FA620A2F1AA2F8DB005DB4C2 /* wrap_Quad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Quad.h; sourceTree = "<group>"; };
 		FA620A301AA2F8DB005DB4C2 /* wrap_Texture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Texture.cpp; sourceTree = "<group>"; };
 		FA620A301AA2F8DB005DB4C2 /* wrap_Texture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Texture.cpp; sourceTree = "<group>"; };
@@ -1894,7 +1889,6 @@
 		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>"; };
-		FA7550A71AEBE276003E311E /* libluajit.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libluajit.a; sourceTree = "<group>"; };
 		FA7634481E28722A0066EF9E /* StreamBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StreamBuffer.cpp; sourceTree = "<group>"; };
 		FA7634481E28722A0066EF9E /* StreamBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StreamBuffer.cpp; sourceTree = "<group>"; };
 		FA7634491E28722A0066EF9E /* StreamBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamBuffer.h; sourceTree = "<group>"; };
 		FA7634491E28722A0066EF9E /* StreamBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamBuffer.h; sourceTree = "<group>"; };
 		FA7DA04C1C16874A0056B200 /* wrap_Math.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wrap_Math.lua; sourceTree = "<group>"; };
 		FA7DA04C1C16874A0056B200 /* wrap_Math.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wrap_Math.lua; sourceTree = "<group>"; };
@@ -1902,6 +1896,11 @@
 		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>"; };
+		FA7E9206277E120900C24CB2 /* theora.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = theora.xcframework; path = ios/libraries/theora.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>"; };
+		FA84DE7B277E045E002674C6 /* ogg.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ogg.xcframework; path = ios/libraries/ogg.xcframework; sourceTree = "<group>"; };
+		FA84DE7D277E0A43002674C6 /* vorbis.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = vorbis.xcframework; path = ios/libraries/vorbis.xcframework; sourceTree = "<group>"; };
 		FA8951A01AA2EDF300EC385A /* wrap_Event.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Event.cpp; sourceTree = "<group>"; };
 		FA8951A01AA2EDF300EC385A /* wrap_Event.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Event.cpp; sourceTree = "<group>"; };
 		FA8951A11AA2EDF300EC385A /* wrap_Event.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Event.h; sourceTree = "<group>"; };
 		FA8951A11AA2EDF300EC385A /* wrap_Event.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Event.h; sourceTree = "<group>"; };
 		FA91DA891F377C3900C80E33 /* deprecation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = deprecation.cpp; sourceTree = "<group>"; };
 		FA91DA891F377C3900C80E33 /* deprecation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = deprecation.cpp; sourceTree = "<group>"; };
@@ -2091,6 +2090,8 @@
 		FACA02E91F5E396B0084B28F /* wrap_CompressedData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_CompressedData.h; sourceTree = "<group>"; };
 		FACA02E91F5E396B0084B28F /* wrap_CompressedData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_CompressedData.h; sourceTree = "<group>"; };
 		FACA02EA1F5E396B0084B28F /* wrap_DataModule.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_DataModule.cpp; sourceTree = "<group>"; };
 		FACA02EA1F5E396B0084B28F /* wrap_DataModule.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_DataModule.cpp; sourceTree = "<group>"; };
 		FACA02EB1F5E396B0084B28F /* wrap_DataModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_DataModule.h; sourceTree = "<group>"; };
 		FACA02EB1F5E396B0084B28F /* wrap_DataModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_DataModule.h; sourceTree = "<group>"; };
+		FACFB750276D7E2B0089F78D /* freetype.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = freetype.xcframework; path = ios/libraries/freetype.xcframework; sourceTree = "<group>"; };
+		FACFB752276D7F6F0089F78D /* Lua.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Lua.xcframework; path = ios/libraries/Lua.xcframework; sourceTree = "<group>"; };
 		FAD19A151DFF8CA200D5398A /* ImageDataBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImageDataBase.cpp; sourceTree = "<group>"; };
 		FAD19A151DFF8CA200D5398A /* ImageDataBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImageDataBase.cpp; sourceTree = "<group>"; };
 		FAD19A161DFF8CA200D5398A /* ImageDataBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageDataBase.h; sourceTree = "<group>"; };
 		FAD19A161DFF8CA200D5398A /* ImageDataBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageDataBase.h; sourceTree = "<group>"; };
 		FAD43ECB1FF312D800831BB8 /* freetype.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = freetype.framework; path = macosx/Frameworks/freetype.framework; sourceTree = "<group>"; };
 		FAD43ECB1FF312D800831BB8 /* freetype.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = freetype.framework; path = macosx/Frameworks/freetype.framework; sourceTree = "<group>"; };
@@ -2121,8 +2122,6 @@
 		FADF543A1E3DAFF700012CC0 /* wrap_Graphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Graphics.h; sourceTree = "<group>"; };
 		FADF543A1E3DAFF700012CC0 /* wrap_Graphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Graphics.h; sourceTree = "<group>"; };
 		FAE272501C05A15B00A67640 /* ParticleSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ParticleSystem.cpp; sourceTree = "<group>"; };
 		FAE272501C05A15B00A67640 /* ParticleSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ParticleSystem.cpp; sourceTree = "<group>"; };
 		FAE272511C05A15B00A67640 /* ParticleSystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ParticleSystem.h; sourceTree = "<group>"; };
 		FAE272511C05A15B00A67640 /* ParticleSystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ParticleSystem.h; sourceTree = "<group>"; };
-		FAE64A7D2071359C00BC7981 /* libfreetype.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libfreetype.a; sourceTree = "<group>"; };
-		FAEC22942534EE6700EBD925 /* NonSemanticDebugPrintf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NonSemanticDebugPrintf.h; sourceTree = "<group>"; };
 		FAEC229F2534F25100EBD925 /* build_info.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = build_info.h; sourceTree = "<group>"; };
 		FAEC229F2534F25100EBD925 /* build_info.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = build_info.h; sourceTree = "<group>"; };
 		FAECA1B01F3164700095D008 /* CompressedSlice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CompressedSlice.cpp; sourceTree = "<group>"; };
 		FAECA1B01F3164700095D008 /* CompressedSlice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CompressedSlice.cpp; sourceTree = "<group>"; };
 		FAECA1B11F3164700095D008 /* CompressedSlice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CompressedSlice.h; sourceTree = "<group>"; };
 		FAECA1B11F3164700095D008 /* CompressedSlice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CompressedSlice.h; sourceTree = "<group>"; };
@@ -2221,13 +2220,13 @@
 			isa = PBXFrameworksBuildPhase;
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
-				FA5D24B51A96D2EC00C6FC8F /* libvorbis.a in Frameworks */,
-				FAE64A7F207135AD00BC7981 /* libfreetype.a in Frameworks */,
-				FA5D24B31A96D2EC00C6FC8F /* libogg.a in Frameworks */,
-				FA59A2D81C0649C200328DBA /* libtheora.a in Frameworks */,
-				FA56D9BC1C208A0200D8D3C7 /* libmodplug.a in Frameworks */,
-				FA7550A81AEBE276003E311E /* libluajit.a in Frameworks */,
-				FA5D24CF1A96E68300C6FC8F /* libSDL2.a in Frameworks */,
+				FACFB751276D7E3B0089F78D /* freetype.xcframework in Frameworks */,
+				FA84DE7A277D4C88002674C6 /* modplug.xcframework in Frameworks */,
+				FA84DE7C277E045E002674C6 /* ogg.xcframework in Frameworks */,
+				FACFB753276D7F860089F78D /* Lua.xcframework in Frameworks */,
+				FA7E9207277E120900C24CB2 /* theora.xcframework in Frameworks */,
+				FA84DE76277CB3D5002674C6 /* SDL2.xcframework in Frameworks */,
+				FA84DE7E277E0A43002674C6 /* vorbis.xcframework in Frameworks */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};
@@ -3439,15 +3438,6 @@
 			path = dr;
 			path = dr;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
-		FA56D9B91C2089CE00D8D3C7 /* modplug */ = {
-			isa = PBXGroup;
-			children = (
-				FA56D9BA1C2089EE00D8D3C7 /* libmodplug.a */,
-			);
-			name = modplug;
-			path = ios/libraries/modplug;
-			sourceTree = "<group>";
-		};
 		FA577A6616C7199700860150 /* Frameworks */ = {
 		FA577A6616C7199700860150 /* Frameworks */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -3457,15 +3447,6 @@
 			name = Frameworks;
 			name = Frameworks;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
-		FA59A2D51C0649BB00328DBA /* theora */ = {
-			isa = PBXGroup;
-			children = (
-				FA59A2D61C0649BB00328DBA /* libtheora.a */,
-			);
-			name = theora;
-			path = ios/libraries/theora;
-			sourceTree = "<group>";
-		};
 		FA5D24A11A96D24500C6FC8F /* Libraries */ = {
 		FA5D24A11A96D24500C6FC8F /* Libraries */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -3477,62 +3458,17 @@
 		FA5D24A31A96D2C300C6FC8F /* ios */ = {
 		FA5D24A31A96D2C300C6FC8F /* ios */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				FA5D24A41A96D2EC00C6FC8F /* freetype */,
-				FA7550A61AEBE250003E311E /* luajit */,
-				FA56D9B91C2089CE00D8D3C7 /* modplug */,
-				FA5D24AA1A96D2EC00C6FC8F /* ogg */,
-				FA5D24CC1A96E63D00C6FC8F /* SDL2 */,
-				FA59A2D51C0649BB00328DBA /* theora */,
-				FA5D24AE1A96D2EC00C6FC8F /* vorbis */,
+				FACFB750276D7E2B0089F78D /* freetype.xcframework */,
+				FACFB752276D7F6F0089F78D /* Lua.xcframework */,
+				FA84DE79277D4C88002674C6 /* modplug.xcframework */,
+				FA84DE7B277E045E002674C6 /* ogg.xcframework */,
+				FA84DE75277CB3D4002674C6 /* SDL2.xcframework */,
+				FA7E9206277E120900C24CB2 /* theora.xcframework */,
+				FA84DE7D277E0A43002674C6 /* vorbis.xcframework */,
 			);
 			);
 			name = ios;
 			name = ios;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
-		FA5D24A41A96D2EC00C6FC8F /* freetype */ = {
-			isa = PBXGroup;
-			children = (
-				FAE64A7D2071359C00BC7981 /* libfreetype.a */,
-			);
-			name = freetype;
-			path = ios/libraries/freetype;
-			sourceTree = "<group>";
-		};
-		FA5D24AA1A96D2EC00C6FC8F /* ogg */ = {
-			isa = PBXGroup;
-			children = (
-				FA5D24AB1A96D2EC00C6FC8F /* libogg.a */,
-			);
-			name = ogg;
-			path = ios/libraries/ogg;
-			sourceTree = "<group>";
-		};
-		FA5D24AE1A96D2EC00C6FC8F /* vorbis */ = {
-			isa = PBXGroup;
-			children = (
-				FA5D24AF1A96D2EC00C6FC8F /* libvorbis.a */,
-			);
-			name = vorbis;
-			path = ios/libraries/vorbis;
-			sourceTree = "<group>";
-		};
-		FA5D24CC1A96E63D00C6FC8F /* SDL2 */ = {
-			isa = PBXGroup;
-			children = (
-				FA5D24CD1A96E63D00C6FC8F /* libSDL2.a */,
-			);
-			name = SDL2;
-			path = ios/libraries/SDL2;
-			sourceTree = "<group>";
-		};
-		FA7550A61AEBE250003E311E /* luajit */ = {
-			isa = PBXGroup;
-			children = (
-				FA7550A71AEBE276003E311E /* libluajit.a */,
-			);
-			name = luajit;
-			path = ios/libraries/luajit;
-			sourceTree = "<group>";
-		};
 		FAAA3FD21F64B3AD00F89E99 /* lua53 */ = {
 		FAAA3FD21F64B3AD00F89E99 /* lua53 */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -5436,32 +5372,17 @@
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				ALWAYS_SEARCH_USER_PATHS = NO;
+				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
+				"ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD_64_BIT)";
+				FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/ios/libraries";
 				GCC_PREPROCESSOR_DEFINITIONS = (
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					"$(inherited)",
 					LOVE_SUPPORT_COREAUDIO,
 					LOVE_SUPPORT_COREAUDIO,
 				);
 				);
 				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
 				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
 				GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
 				GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
-				HEADER_SEARCH_PATHS = (
-					"$(inherited)",
-					ios/include,
-					ios/include/luajit,
-					ios/include/freetype,
-					ios/include/SDL2,
-					ios/include/modplug,
-				);
-				LIBRARY_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/ios/libraries/freetype",
-					"$(PROJECT_DIR)/ios/libraries/lua",
-					"$(PROJECT_DIR)/ios/libraries/ogg",
-					"$(PROJECT_DIR)/ios/libraries/physfs",
-					"$(PROJECT_DIR)/ios/libraries/vorbis",
-					"$(PROJECT_DIR)/ios/libraries/SDL2",
-					"$(PROJECT_DIR)/ios/libraries/luajit",
-					"$(PROJECT_DIR)/ios/libraries/theora",
-					"$(PROJECT_DIR)/ios/libraries/modplug",
-				);
+				HEADER_SEARCH_PATHS = "$(inherited)";
+				LIBRARY_SEARCH_PATHS = "$(inherited)";
 				MTL_ENABLE_DEBUG_INFO = YES;
 				MTL_ENABLE_DEBUG_INFO = YES;
 				OTHER_LDFLAGS = "-ObjC";
 				OTHER_LDFLAGS = "-ObjC";
 				PRODUCT_NAME = love;
 				PRODUCT_NAME = love;
@@ -5475,33 +5396,18 @@
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				ALWAYS_SEARCH_USER_PATHS = NO;
+				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
+				"ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD_64_BIT)";
 				COPY_PHASE_STRIP = YES;
 				COPY_PHASE_STRIP = YES;
 				ENABLE_NS_ASSERTIONS = NO;
 				ENABLE_NS_ASSERTIONS = NO;
+				FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/ios/libraries";
 				GCC_PREPROCESSOR_DEFINITIONS = (
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					"$(inherited)",
 					LOVE_SUPPORT_COREAUDIO,
 					LOVE_SUPPORT_COREAUDIO,
 				);
 				);
 				GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
 				GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
-				HEADER_SEARCH_PATHS = (
-					"$(inherited)",
-					ios/include,
-					ios/include/luajit,
-					ios/include/freetype,
-					ios/include/SDL2,
-					ios/include/modplug,
-				);
-				LIBRARY_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/ios/libraries/freetype",
-					"$(PROJECT_DIR)/ios/libraries/lua",
-					"$(PROJECT_DIR)/ios/libraries/ogg",
-					"$(PROJECT_DIR)/ios/libraries/physfs",
-					"$(PROJECT_DIR)/ios/libraries/vorbis",
-					"$(PROJECT_DIR)/ios/libraries/SDL2",
-					"$(PROJECT_DIR)/ios/libraries/luajit",
-					"$(PROJECT_DIR)/ios/libraries/theora",
-					"$(PROJECT_DIR)/ios/libraries/modplug",
-				);
+				HEADER_SEARCH_PATHS = "$(inherited)";
+				LIBRARY_SEARCH_PATHS = "$(inherited)";
 				MTL_ENABLE_DEBUG_INFO = NO;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				OTHER_LDFLAGS = "-ObjC";
 				OTHER_LDFLAGS = "-ObjC";
 				PRODUCT_NAME = love;
 				PRODUCT_NAME = love;
@@ -5516,33 +5422,18 @@
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				ALWAYS_SEARCH_USER_PATHS = NO;
+				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
+				"ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD_64_BIT)";
 				COPY_PHASE_STRIP = YES;
 				COPY_PHASE_STRIP = YES;
 				ENABLE_NS_ASSERTIONS = NO;
 				ENABLE_NS_ASSERTIONS = NO;
+				FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/ios/libraries";
 				GCC_PREPROCESSOR_DEFINITIONS = (
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					"$(inherited)",
 					LOVE_SUPPORT_COREAUDIO,
 					LOVE_SUPPORT_COREAUDIO,
 				);
 				);
 				GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
 				GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
-				HEADER_SEARCH_PATHS = (
-					"$(inherited)",
-					ios/include,
-					ios/include/luajit,
-					ios/include/freetype,
-					ios/include/SDL2,
-					ios/include/modplug,
-				);
-				LIBRARY_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/ios/libraries/freetype",
-					"$(PROJECT_DIR)/ios/libraries/lua",
-					"$(PROJECT_DIR)/ios/libraries/ogg",
-					"$(PROJECT_DIR)/ios/libraries/physfs",
-					"$(PROJECT_DIR)/ios/libraries/vorbis",
-					"$(PROJECT_DIR)/ios/libraries/SDL2",
-					"$(PROJECT_DIR)/ios/libraries/luajit",
-					"$(PROJECT_DIR)/ios/libraries/theora",
-					"$(PROJECT_DIR)/ios/libraries/modplug",
-				);
+				HEADER_SEARCH_PATHS = "$(inherited)";
+				LIBRARY_SEARCH_PATHS = "$(inherited)";
 				MTL_ENABLE_DEBUG_INFO = NO;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				OTHER_LDFLAGS = "-ObjC";
 				OTHER_LDFLAGS = "-ObjC";
 				PRODUCT_NAME = love;
 				PRODUCT_NAME = love;

+ 10 - 12
platform/xcode/love.xcodeproj/project.pbxproj

@@ -34,6 +34,7 @@
 		FA5D24C21A96D78000C6FC8F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA5D24C11A96D78000C6FC8F /* Foundation.framework */; };
 		FA5D24C21A96D78000C6FC8F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA5D24C11A96D78000C6FC8F /* Foundation.framework */; };
 		FA5D24D11A96E73300C6FC8F /* liblove.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA0B7EEF1A95924A000E1D17 /* liblove.a */; };
 		FA5D24D11A96E73300C6FC8F /* liblove.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA0B7EEF1A95924A000E1D17 /* liblove.a */; };
 		FA7C636A1A9C49570000FD29 /* Launch Screen.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA7C63691A9C49570000FD29 /* Launch Screen.xib */; };
 		FA7C636A1A9C49570000FD29 /* Launch Screen.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA7C63691A9C49570000FD29 /* Launch Screen.xib */; };
+		FA84DE78277CB55B002674C6 /* CoreHaptics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE77277CB55B002674C6 /* CoreHaptics.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
 		FA9B4A0A16E1579F00074F42 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA9B4A0916E1579F00074F42 /* SDL2.framework */; };
 		FA9B4A0A16E1579F00074F42 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA9B4A0916E1579F00074F42 /* SDL2.framework */; };
 		FA9B4A0B16E157B500074F42 /* SDL2.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = FA9B4A0916E1579F00074F42 /* SDL2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		FA9B4A0B16E157B500074F42 /* SDL2.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = FA9B4A0916E1579F00074F42 /* SDL2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		FAAFF04716CB120000CCDE45 /* OpenAL-Soft.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = FAAFF04616CB120000CCDE45 /* OpenAL-Soft.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		FAAFF04716CB120000CCDE45 /* OpenAL-Soft.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = FAAFF04616CB120000CCDE45 /* OpenAL-Soft.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
@@ -125,6 +126,7 @@
 		FA5D249A1A96CF4300C6FC8F /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
 		FA5D249A1A96CF4300C6FC8F /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
 		FA5D24C11A96D78000C6FC8F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.1.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
 		FA5D24C11A96D78000C6FC8F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.1.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
 		FA7C63691A9C49570000FD29 /* Launch Screen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = "Launch Screen.xib"; path = "ios/Launch Screen.xib"; sourceTree = "<group>"; };
 		FA7C63691A9C49570000FD29 /* Launch Screen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = "Launch Screen.xib"; path = "ios/Launch Screen.xib"; sourceTree = "<group>"; };
+		FA84DE77277CB55B002674C6 /* CoreHaptics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreHaptics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.2.sdk/System/Library/Frameworks/CoreHaptics.framework; sourceTree = DEVELOPER_DIR; };
 		FA9B4A0916E1579F00074F42 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = macosx/Frameworks/SDL2.framework; sourceTree = "<group>"; };
 		FA9B4A0916E1579F00074F42 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = macosx/Frameworks/SDL2.framework; sourceTree = "<group>"; };
 		FAAFF04616CB120000CCDE45 /* OpenAL-Soft.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "OpenAL-Soft.framework"; path = "macosx/Frameworks/OpenAL-Soft.framework"; sourceTree = "<group>"; };
 		FAAFF04616CB120000CCDE45 /* OpenAL-Soft.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "OpenAL-Soft.framework"; path = "macosx/Frameworks/OpenAL-Soft.framework"; sourceTree = "<group>"; };
 		FAC1A448196F5DC600125284 /* license.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = license.txt; path = ../../license.txt; sourceTree = "<group>"; };
 		FAC1A448196F5DC600125284 /* license.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = license.txt; path = ../../license.txt; sourceTree = "<group>"; };
@@ -150,6 +152,7 @@
 			isa = PBXFrameworksBuildPhase;
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
+				FA84DE78277CB55B002674C6 /* CoreHaptics.framework in Frameworks */,
 				FAE64A9D2072738600BC7981 /* Metal.framework in Frameworks */,
 				FAE64A9D2072738600BC7981 /* Metal.framework in Frameworks */,
 				FA15DFB41F9B8D9E0042AB22 /* libbz2.tbd in Frameworks */,
 				FA15DFB41F9B8D9E0042AB22 /* libbz2.tbd in Frameworks */,
 				CE73F8001EEB64150052DAB3 /* AVFoundation.framework in Frameworks */,
 				CE73F8001EEB64150052DAB3 /* AVFoundation.framework in Frameworks */,
@@ -173,6 +176,7 @@
 		1058C7A0FEA54F0111CA2CBB /* Frameworks */ = {
 		1058C7A0FEA54F0111CA2CBB /* Frameworks */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				FA84DE77277CB55B002674C6 /* CoreHaptics.framework */,
 				FAE64A9C2072738600BC7981 /* Metal.framework */,
 				FAE64A9C2072738600BC7981 /* Metal.framework */,
 				FA15DFB31F9B8D9E0042AB22 /* libbz2.tbd */,
 				FA15DFB31F9B8D9E0042AB22 /* libbz2.tbd */,
 				CE73F7FF1EEB64150052DAB3 /* AVFoundation.framework */,
 				CE73F7FF1EEB64150052DAB3 /* AVFoundation.framework */,
@@ -645,6 +649,7 @@
 		FA0B7F261A95AAF4000E1D17 /* Debug */ = {
 		FA0B7F261A95AAF4000E1D17 /* Debug */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
+				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
 				ASSETCATALOG_COMPILER_APPICON_NAME = "iOS AppIcon";
 				ASSETCATALOG_COMPILER_APPICON_NAME = "iOS AppIcon";
 				CLANG_WARN_BOOL_CONVERSION = YES;
 				CLANG_WARN_BOOL_CONVERSION = YES;
 				CLANG_WARN_CONSTANT_CONVERSION = YES;
 				CLANG_WARN_CONSTANT_CONVERSION = YES;
@@ -661,11 +666,10 @@
 				DEVELOPMENT_TEAM = "";
 				DEVELOPMENT_TEAM = "";
 				ENABLE_BITCODE = NO;
 				ENABLE_BITCODE = NO;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
-				EXCLUDED_ARCHS = "";
-				"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
 				FRAMEWORK_SEARCH_PATHS = (
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)",
 					"$(PROJECT_DIR)",
+					"$(PROJECT_DIR)/ios/libraries",
 				);
 				);
 				GCC_DYNAMIC_NO_PIC = NO;
 				GCC_DYNAMIC_NO_PIC = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 				GCC_PREPROCESSOR_DEFINITIONS = (
@@ -681,8 +685,6 @@
 				HEADER_SEARCH_PATHS = (
 				HEADER_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					ios/include,
 					ios/include,
-					ios/include/luajit,
-					ios/include/SDL2,
 				);
 				);
 				INFOPLIST_FILE = "$(SRCROOT)/ios/love-ios.plist";
 				INFOPLIST_FILE = "$(SRCROOT)/ios/love-ios.plist";
 				LD_RUNPATH_SEARCH_PATHS = (
 				LD_RUNPATH_SEARCH_PATHS = (
@@ -702,6 +704,7 @@
 		FA0B7F271A95AAF4000E1D17 /* Release */ = {
 		FA0B7F271A95AAF4000E1D17 /* Release */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
+				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
 				ASSETCATALOG_COMPILER_APPICON_NAME = "iOS AppIcon";
 				ASSETCATALOG_COMPILER_APPICON_NAME = "iOS AppIcon";
 				CLANG_WARN_BOOL_CONVERSION = YES;
 				CLANG_WARN_BOOL_CONVERSION = YES;
 				CLANG_WARN_CONSTANT_CONVERSION = YES;
 				CLANG_WARN_CONSTANT_CONVERSION = YES;
@@ -719,11 +722,10 @@
 				ENABLE_BITCODE = NO;
 				ENABLE_BITCODE = NO;
 				ENABLE_NS_ASSERTIONS = NO;
 				ENABLE_NS_ASSERTIONS = NO;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
-				EXCLUDED_ARCHS = "";
-				"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
 				FRAMEWORK_SEARCH_PATHS = (
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)",
 					"$(PROJECT_DIR)",
+					"$(PROJECT_DIR)/ios/libraries",
 				);
 				);
 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@@ -733,8 +735,6 @@
 				HEADER_SEARCH_PATHS = (
 				HEADER_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					ios/include,
 					ios/include,
-					ios/include/luajit,
-					ios/include/SDL2,
 				);
 				);
 				INFOPLIST_FILE = "$(SRCROOT)/ios/love-ios.plist";
 				INFOPLIST_FILE = "$(SRCROOT)/ios/love-ios.plist";
 				LD_RUNPATH_SEARCH_PATHS = (
 				LD_RUNPATH_SEARCH_PATHS = (
@@ -755,6 +755,7 @@
 		FA0B7F281A95AAF4000E1D17 /* Distribution */ = {
 		FA0B7F281A95AAF4000E1D17 /* Distribution */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
+				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
 				ASSETCATALOG_COMPILER_APPICON_NAME = "iOS AppIcon";
 				ASSETCATALOG_COMPILER_APPICON_NAME = "iOS AppIcon";
 				CLANG_WARN_BOOL_CONVERSION = YES;
 				CLANG_WARN_BOOL_CONVERSION = YES;
 				CLANG_WARN_CONSTANT_CONVERSION = YES;
 				CLANG_WARN_CONSTANT_CONVERSION = YES;
@@ -772,11 +773,10 @@
 				ENABLE_BITCODE = NO;
 				ENABLE_BITCODE = NO;
 				ENABLE_NS_ASSERTIONS = NO;
 				ENABLE_NS_ASSERTIONS = NO;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
-				EXCLUDED_ARCHS = "";
-				"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
 				FRAMEWORK_SEARCH_PATHS = (
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)",
 					"$(PROJECT_DIR)",
+					"$(PROJECT_DIR)/ios/libraries",
 				);
 				);
 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@@ -786,8 +786,6 @@
 				HEADER_SEARCH_PATHS = (
 				HEADER_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					ios/include,
 					ios/include,
-					ios/include/luajit,
-					ios/include/SDL2,
 				);
 				);
 				INFOPLIST_FILE = "$(SRCROOT)/ios/love-ios.plist";
 				INFOPLIST_FILE = "$(SRCROOT)/ios/love-ios.plist";
 				LD_RUNPATH_SEARCH_PATHS = (
 				LD_RUNPATH_SEARCH_PATHS = (

+ 15 - 4
readme.md

@@ -54,15 +54,26 @@ Run `platform/unix/automagic` from the repository root, then run ./configure and
 When using a source release, automagic has already been run, and the first step can be skipped.
 When using a source release, automagic has already been run, and the first step can be skipped.
 
 
 ### macOS
 ### macOS
-Download or clone [this repository][dependencies-macos] and place the Frameworks subfolder in love's `platform/xcode/macosx/` folder.
+Download or clone [this repository][dependencies-apple] and copy, move, or symlink the `macOS/Frameworks` subfolder into love's `platform/xcode/macosx` folder.
 
 
 Then use the Xcode project found at `platform/xcode/love.xcodeproj` to build the `love-macosx` target.
 Then use the Xcode project found at `platform/xcode/love.xcodeproj` to build the `love-macosx` target.
 
 
 ### iOS
 ### iOS
 Building for iOS requires macOS and Xcode.
 Building for iOS requires macOS and Xcode.
 
 
-Download the `ios-libraries` zip file corresponding to the LÖVE version being used from [here][dependencies-ios],
-unzip it, and place the `include` and `libraries` subfolders into LÖVE's `platform/xcode/ios` folder.
+#### LÖVE 11.4 and newer
+Download the `love-apple-dependencies` zip file corresponding to the LÖVE version being used from the [Releases page][dependencies-ios],
+unzip it, and place the `iOS/libraries` subfolder into love's `platform/xcode/ios` folder.
+
+Or, download or clone [this repository][dependencies-apple] and copy, move, or symlink the `iOS/libraries` subfolder into love's `platform/xcode/ios` folder.
+
+Then use the Xcode project found at `platform/xcode/love.xcodeproj` to build the `love-ios` target.
+
+See `readme-iOS.rtf` for more information.
+
+#### LÖVE 11.3 and older
+Download the `ios-libraries` zip file corresponding to the LÖVE version being used from the [Releases page][dependencies-ios],
+unzip it, and place the `include` and `libraries` subfolders into love's `platform/xcode/ios` folder.
 
 
 Then use the Xcode project found at `platform/xcode/love.xcodeproj` to build the `love-ios` target.
 Then use the Xcode project found at `platform/xcode/love.xcodeproj` to build the `love-ios` target.
 
 
@@ -88,7 +99,7 @@ Dependencies
 [forums]: https://love2d.org/forums
 [forums]: https://love2d.org/forums
 [discord]: https://discord.gg/rhUets9
 [discord]: https://discord.gg/rhUets9
 [irc]: irc://irc.oftc.net/love
 [irc]: irc://irc.oftc.net/love
-[dependencies-macos]: https://github.com/slime73/love-apple-dependencies
+[dependencies-apple]: https://github.com/love2d/love-apple-dependencies
 [dependencies-ios]: https://github.com/love2d/love/releases
 [dependencies-ios]: https://github.com/love2d/love/releases
 [megasource]: https://github.com/love2d/megasource
 [megasource]: https://github.com/love2d/megasource
 [unstableppa]: https://launchpad.net/~bartbes/+archive/love-unstable
 [unstableppa]: https://launchpad.net/~bartbes/+archive/love-unstable

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

@@ -42,7 +42,8 @@ ByteData::ByteData(const void *d, size_t size)
 	: size(size)
 	: size(size)
 {
 {
 	create();
 	create();
-	memcpy(data, d, size);
+	if (d != nullptr)
+		memcpy(data, d, size);
 }
 }
 
 
 ByteData::ByteData(void *d, size_t size, bool own)
 ByteData::ByteData(void *d, size_t size, bool own)
@@ -53,7 +54,8 @@ ByteData::ByteData(void *d, size_t size, bool own)
 	else
 	else
 	{
 	{
 		create();
 		create();
-		memcpy(data, d, size);
+		if (d != nullptr)
+			memcpy(data, d, size);
 	}
 	}
 }
 }
 
 

+ 9 - 10
src/modules/graphics/Buffer.cpp

@@ -46,14 +46,10 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
 	const auto &caps = gfx->getCapabilities();
 	const auto &caps = gfx->getCapabilities();
 	bool supportsGLSL3 = caps.features[Graphics::FEATURE_GLSL3];
 	bool supportsGLSL3 = caps.features[Graphics::FEATURE_GLSL3];
 
 
-	bool indexbuffer = settings.usageFlags & BUFFERUSAGEFLAG_INDEX;
-	bool vertexbuffer = settings.usageFlags & BUFFERUSAGEFLAG_VERTEX;
-	bool texelbuffer = settings.usageFlags & BUFFERUSAGEFLAG_TEXEL;
-	bool storagebuffer = settings.usageFlags & BUFFERUSAGEFLAG_SHADER_STORAGE;
-	bool copydest = settings.usageFlags & BUFFERUSAGEFLAG_COPY_DEST;
-
-	if (!indexbuffer && !vertexbuffer && !texelbuffer && !storagebuffer)
-		throw love::Exception("Buffer must be created with at least one buffer type (index, vertex, texel, or shaderstorage).");
+	bool indexbuffer = usageFlags & BUFFERUSAGEFLAG_INDEX;
+	bool vertexbuffer = usageFlags & BUFFERUSAGEFLAG_VERTEX;
+	bool texelbuffer = usageFlags & BUFFERUSAGEFLAG_TEXEL;
+	bool storagebuffer = usageFlags & BUFFERUSAGEFLAG_SHADER_STORAGE;
 
 
 	if (texelbuffer && !caps.features[Graphics::FEATURE_TEXEL_BUFFER])
 	if (texelbuffer && !caps.features[Graphics::FEATURE_TEXEL_BUFFER])
 		throw love::Exception("Texel buffers are not supported on this system.");
 		throw love::Exception("Texel buffers are not supported on this system.");
@@ -61,8 +57,11 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
 	if (storagebuffer && !caps.features[Graphics::FEATURE_GLSL4])
 	if (storagebuffer && !caps.features[Graphics::FEATURE_GLSL4])
 		throw love::Exception("Shader Storage buffers are not supported on this system (GLSL 4 support is necessary.)");
 		throw love::Exception("Shader Storage buffers are not supported on this system (GLSL 4 support is necessary.)");
 
 
-	if (copydest && dataUsage == BUFFERDATAUSAGE_STREAM)
-		throw love::Exception("Buffers created with 'stream' data usage cannot be used as a copy destination.");
+	if (storagebuffer && dataUsage == BUFFERDATAUSAGE_STREAM)
+		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.");
 
 
 	size_t offset = 0;
 	size_t offset = 0;
 	size_t stride = 0;
 	size_t stride = 0;

+ 169 - 6
src/modules/graphics/Graphics.cpp

@@ -1104,15 +1104,12 @@ void Graphics::copyBuffer(Buffer *source, Buffer *dest, size_t sourceoffset, siz
 	if (!capabilities.features[FEATURE_COPY_BUFFER])
 	if (!capabilities.features[FEATURE_COPY_BUFFER])
 		throw love::Exception("Buffer copying is not supported on this system.");
 		throw love::Exception("Buffer copying is not supported on this system.");
 
 
-	if (!(source->getUsageFlags() & BUFFERUSAGEFLAG_COPY_SOURCE))
-		throw love::Exception("Copy source buffer must be created with the copysource flag.");
-
-	if (!(dest->getUsageFlags() & BUFFERUSAGEFLAG_COPY_DEST))
-		throw love::Exception("Copy destination buffer must be created with the copydest flag.");
-
 	Range sourcerange(sourceoffset, size);
 	Range sourcerange(sourceoffset, size);
 	Range destrange(destoffset, size);
 	Range destrange(destoffset, size);
 
 
+	if (dest->getDataUsage() == BUFFERDATAUSAGE_STREAM)
+		throw love::Exception("Buffers created with 'stream' data usage cannot be used as a copy destination.");
+
 	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.");
 
 
@@ -1125,6 +1122,169 @@ void Graphics::copyBuffer(Buffer *source, Buffer *dest, size_t sourceoffset, siz
 	source->copyTo(dest, sourceoffset, destoffset, size);
 	source->copyTo(dest, sourceoffset, destoffset, size);
 }
 }
 
 
+void Graphics::copyTextureToBuffer(Texture *source, Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth)
+{
+	if (!capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER])
+	{
+		if (!source->isRenderTarget())
+			throw love::Exception("Copying a non-render target Texture to a Buffer is not supported on this system.");
+
+		if (!capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER])
+			throw love::Exception("Copying a render target Texture to a Buffer is not supported on this system.");
+	}
+
+	PixelFormat format = source->getPixelFormat();
+
+	if (isPixelFormatDepthStencil(format))
+		throw love::Exception("Copying a depth/stencil Texture to a Buffer is not supported.");
+
+	if (!source->isReadable())
+		throw love::Exception("copyTextureToBuffer can only be called on readable Textures.");
+
+	if (dest->getDataUsage() == BUFFERDATAUSAGE_STREAM)
+		throw love::Exception("Buffers created with 'stream' data usage cannot be used as a copy destination.");
+
+	if (isRenderTargetActive(source))
+		throw love::Exception("copyTextureToBuffer cannot be called while the Texture is an active render target.");
+
+	if (mipmap < 0 || mipmap >= source->getMipmapCount())
+		throw love::Exception("Invalid texture mipmap index %d.", mipmap + 1);
+
+	TextureType textype = source->getTextureType();
+	if (slice < 0 || (textype == TEXTURE_CUBE && slice >= 6)
+		|| (textype == TEXTURE_VOLUME && slice >= source->getDepth(mipmap))
+		|| (textype == TEXTURE_2D_ARRAY && slice >= source->getLayerCount()))
+	{
+		throw love::Exception("Invalid texture slice index %d.", slice + 1);
+	}
+
+	int mipw = source->getPixelWidth(mipmap);
+	int miph = source->getPixelHeight(mipmap);
+
+	if (rect.x < 0 || rect.y < 0 || rect.w <= 0 || rect.h <= 0
+		|| (rect.x + rect.w) > mipw || (rect.y + rect.h) > miph)
+	{
+		throw love::Exception("Invalid rectangle dimensions (x=%d, y=%d, w=%d, h=%d) for %dx%d texture.", rect.x, rect.y, rect.w, rect.h, mipw, miph);
+	}
+
+	if (destwidth <= 0)
+		destwidth = rect.w;
+
+	size_t size = 0;
+
+	if (isPixelFormatCompressed(format))
+	{
+		if (destwidth != rect.w) // OpenGL limitation...
+			throw love::Exception("Copying a compressed texture to a buffer cannot use a custom destination width.");
+
+		const PixelFormatInfo &info = getPixelFormatInfo(format);
+		int bw = (int) info.blockWidth;
+		int bh = (int) info.blockHeight;
+		if (rect.x % bw != 0 || rect.y % bh != 0 ||
+			((rect.w % bw != 0 || rect.h % bh != 0) && rect.x + rect.w != source->getPixelWidth(mipmap)))
+		{
+			const char *name = nullptr;
+			love::getConstant(format, name);
+			throw love::Exception("Compressed texture format %s only supports copying a sub-rectangle with offset and dimensions that are a multiple of %d x %d.", name, bw, bh);
+		}
+
+		// Note: this will need to change if destwidth == rect.w restriction
+		// is removed.
+		size = getPixelFormatSliceSize(format, destwidth, rect.h);
+	}
+	else
+	{
+		// Not the cleanest, but should work since uncompressed formats always
+		// have 1x1 blocks.
+		int pixels = (rect.h - 1) * destwidth + rect.w;
+		size = getPixelFormatUncompressedRowSize(format, pixels);
+	}
+
+	Range destrange(destoffset, size);
+
+	if (destrange.getMax() >= dest->getSize())
+		throw love::Exception("Buffer copy destination offset and width/height doesn't fit within the destination Buffer.");
+
+	source->copyToBuffer(dest, slice, mipmap, rect, destoffset, destwidth, size);
+}
+
+void Graphics::copyBufferToTexture(Buffer *source, Texture *dest, size_t sourceoffset, int sourcewidth, int slice, int mipmap, const Rect &rect)
+{
+	if (!capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE])
+		throw love::Exception("Copying a Buffer to a Texture is not supported on this system.");
+
+	PixelFormat format = dest->getPixelFormat();
+
+	if (isPixelFormatDepthStencil(format))
+		throw love::Exception("Copying a Buffer to a depth/stencil Texture is not supported.");
+
+	if (!dest->isReadable())
+		throw love::Exception("copyBufferToTexture can only be called on readable Textures.");
+
+	if (isRenderTargetActive(dest))
+		throw love::Exception("copyBufferToTexture cannot be called while the Texture is an active render target.");
+
+	if (mipmap < 0 || mipmap >= dest->getMipmapCount())
+		throw love::Exception("Invalid texture mipmap index %d.", mipmap + 1);
+
+	TextureType textype = dest->getTextureType();
+	if (slice < 0 || (textype == TEXTURE_CUBE && slice >= 6)
+		|| (textype == TEXTURE_VOLUME && slice >= dest->getDepth(mipmap))
+		|| (textype == TEXTURE_2D_ARRAY && slice >= dest->getLayerCount()))
+	{
+		throw love::Exception("Invalid texture slice index %d.", slice + 1);
+	}
+
+	int mipw = dest->getPixelWidth(mipmap);
+	int miph = dest->getPixelHeight(mipmap);
+
+	if (rect.x < 0 || rect.y < 0 || rect.w <= 0 || rect.h <= 0
+		|| (rect.x + rect.w) > mipw || (rect.y + rect.h) > miph)
+	{
+		throw love::Exception("Invalid rectangle dimensions (x=%d, y=%d, w=%d, h=%d) for %dx%d texture.", rect.x, rect.y, rect.w, rect.h, mipw, miph);
+	}
+
+	if (sourcewidth <= 0)
+		sourcewidth = rect.w;
+
+	size_t size = 0;
+
+	if (isPixelFormatCompressed(format))
+	{
+		if (sourcewidth != rect.w) // OpenGL limitation...
+			throw love::Exception("Copying a buffer to a compressed texture cannot use a custom source width.");
+
+		const PixelFormatInfo &info = getPixelFormatInfo(format);
+		int bw = (int) info.blockWidth;
+		int bh = (int) info.blockHeight;
+		if (rect.x % bw != 0 || rect.y % bh != 0 ||
+			((rect.w % bw != 0 || rect.h % bh != 0) && rect.x + rect.w != dest->getPixelWidth(mipmap)))
+		{
+			const char *name = nullptr;
+			love::getConstant(format, name);
+			throw love::Exception("Compressed texture format %s only supports copying a sub-rectangle with offset and dimensions that are a multiple of %d x %d.", name, bw, bh);
+		}
+
+		// Note: this will need to change if sourcewidth == rect.w restriction
+		// is removed.
+		size = getPixelFormatSliceSize(format, sourcewidth, rect.h);
+	}
+	else
+	{
+		// Not the cleanest, but should work since uncompressed formats always
+		// have 1x1 blocks.
+		int pixels = (rect.h - 1) * sourcewidth + rect.w;
+		size = getPixelFormatUncompressedRowSize(format, pixels);
+	}
+
+	Range sourcerange(sourceoffset, size);
+
+	if (sourcerange.getMax() >= source->getSize())
+		throw love::Exception("Buffer copy source offset and width/height doesn't fit within the source Buffer.");
+
+	dest->copyFromBuffer(source, sourceoffset, sourcewidth, size, slice, mipmap, rect);
+}
+
 void Graphics::dispatchThreadgroups(Shader* shader, int x, int y, int z)
 void Graphics::dispatchThreadgroups(Shader* shader, int x, int y, int z)
 {
 {
 	if (!shader->hasStage(SHADERSTAGE_COMPUTE))
 	if (!shader->hasStage(SHADERSTAGE_COMPUTE))
@@ -1956,6 +2116,9 @@ STRINGMAP_CLASS_BEGIN(Graphics, Graphics::Feature, Graphics::FEATURE_MAX_ENUM, f
 	{ "instancing",               Graphics::FEATURE_INSTANCING           },
 	{ "instancing",               Graphics::FEATURE_INSTANCING           },
 	{ "texelbuffer",              Graphics::FEATURE_TEXEL_BUFFER         },
 	{ "texelbuffer",              Graphics::FEATURE_TEXEL_BUFFER         },
 	{ "copybuffer",               Graphics::FEATURE_COPY_BUFFER          },
 	{ "copybuffer",               Graphics::FEATURE_COPY_BUFFER          },
+	{ "copybuffertotexture",      Graphics::FEATURE_COPY_BUFFER_TO_TEXTURE },
+	{ "copytexturetobuffer",      Graphics::FEATURE_COPY_TEXTURE_TO_BUFFER },
+	{ "copyrendertargettobuffer", Graphics::FEATURE_COPY_RENDER_TARGET_TO_BUFFER },
 }
 }
 STRINGMAP_CLASS_END(Graphics, Graphics::Feature, Graphics::FEATURE_MAX_ENUM, feature)
 STRINGMAP_CLASS_END(Graphics, Graphics::Feature, Graphics::FEATURE_MAX_ENUM, feature)
 
 

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

@@ -145,6 +145,9 @@ public:
 		FEATURE_INSTANCING,
 		FEATURE_INSTANCING,
 		FEATURE_TEXEL_BUFFER,
 		FEATURE_TEXEL_BUFFER,
 		FEATURE_COPY_BUFFER,
 		FEATURE_COPY_BUFFER,
+		FEATURE_COPY_BUFFER_TO_TEXTURE,
+		FEATURE_COPY_TEXTURE_TO_BUFFER,
+		FEATURE_COPY_RENDER_TARGET_TO_BUFFER,
 		FEATURE_MAX_ENUM
 		FEATURE_MAX_ENUM
 	};
 	};
 
 
@@ -675,6 +678,8 @@ public:
 	void captureScreenshot(const ScreenshotInfo &info);
 	void captureScreenshot(const ScreenshotInfo &info);
 
 
 	void copyBuffer(Buffer *source, Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size);
 	void copyBuffer(Buffer *source, Buffer *dest, size_t sourceoffset, size_t destoffset, size_t size);
+	void copyTextureToBuffer(Texture *source, Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth);
+	void copyBufferToTexture(Buffer *source, Texture *dest, size_t sourceoffset, int sourcewidth, int slice, int mipmap, const Rect &rect);
 
 
 	void dispatchThreadgroups(Shader* shader, int x, int y, int z);
 	void dispatchThreadgroups(Shader* shader, int x, int y, int z);
 
 

+ 11 - 2
src/modules/graphics/Texture.cpp

@@ -473,9 +473,18 @@ void Texture::replacePixels(love::image::ImageDataBase *d, int slice, int mipmap
 		throw love::Exception("Invalid rectangle dimensions (x=%d, y=%d, w=%d, h=%d) for %dx%d Texture.", rect.x, rect.y, rect.w, rect.h, mipw, miph);
 		throw love::Exception("Invalid rectangle dimensions (x=%d, y=%d, w=%d, h=%d) for %dx%d Texture.", rect.x, rect.y, rect.w, rect.h, mipw, miph);
 	}
 	}
 
 
-	// We don't currently support partial updates of compressed textures.
 	if (isPixelFormatCompressed(d->getFormat()) && (rect.x != 0 || rect.y != 0 || rect.w != mipw || rect.h != miph))
 	if (isPixelFormatCompressed(d->getFormat()) && (rect.x != 0 || rect.y != 0 || rect.w != mipw || rect.h != miph))
-		throw love::Exception("Compressed textures only support replacing the entire Texture.");
+	{
+		const PixelFormatInfo &info = getPixelFormatInfo(d->getFormat());
+		int bw = (int) info.blockWidth;
+		int bh = (int) info.blockHeight;
+		if (rect.x % bw != 0 || rect.y % bh != 0 || rect.w % bw != 0 || rect.h % bh != 0)
+		{
+			const char *name = nullptr;
+			love::getConstant(d->getFormat(), name);
+			throw love::Exception("Compressed texture format %s only supports replacing a sub-rectangle with offset and dimensions that are a multiple of %d x %d.", name, bw, bh);
+		}
+	}
 
 
 	Graphics::flushBatchedDrawsGlobal();
 	Graphics::flushBatchedDrawsGlobal();
 
 

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

@@ -46,6 +46,7 @@ namespace graphics
 {
 {
 
 
 class Graphics;
 class Graphics;
+class Buffer;
 
 
 enum TextureType
 enum TextureType
 {
 {
@@ -246,6 +247,9 @@ public:
 
 
 	love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect);
 	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 copyToBuffer(Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth, size_t size) = 0;
+
 	virtual ptrdiff_t getRenderTargetHandle() const = 0;
 	virtual ptrdiff_t getRenderTargetHandle() const = 0;
 	virtual ptrdiff_t getSamplerHandle() const = 0;
 	virtual ptrdiff_t getSamplerHandle() const = 0;
 
 

+ 4 - 1
src/modules/graphics/metal/Graphics.mm

@@ -2100,7 +2100,10 @@ void Graphics::initCapabilities()
 	capabilities.features[FEATURE_INSTANCING] = true;
 	capabilities.features[FEATURE_INSTANCING] = true;
 	capabilities.features[FEATURE_TEXEL_BUFFER] = true;
 	capabilities.features[FEATURE_TEXEL_BUFFER] = true;
 	capabilities.features[FEATURE_COPY_BUFFER] = true;
 	capabilities.features[FEATURE_COPY_BUFFER] = true;
-	static_assert(FEATURE_MAX_ENUM == 12, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
+	capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = true;
+	capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = true;
+	capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = true;
+	static_assert(FEATURE_MAX_ENUM == 15, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
 
 
 	// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
 	// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
 	capabilities.limits[LIMIT_POINT_SIZE] = 511;
 	capabilities.limits[LIMIT_POINT_SIZE] = 511;

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

@@ -40,6 +40,9 @@ public:
 	Texture(love::graphics::Graphics *gfx, id<MTLDevice> device, const Settings &settings, const Slices *data);
 	Texture(love::graphics::Graphics *gfx, id<MTLDevice> device, const Settings &settings, const Slices *data);
 	virtual ~Texture();
 	virtual ~Texture();
 
 
+	void copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect) override;
+	void copyToBuffer(love::graphics::Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth, size_t size) override;
+
 	void setSamplerState(const SamplerState &s) override;
 	void setSamplerState(const SamplerState &s) override;
 
 
 	ptrdiff_t getHandle() const override { return (ptrdiff_t) texture; }
 	ptrdiff_t getHandle() const override { return (ptrdiff_t) texture; }

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

@@ -258,6 +258,16 @@ void Texture::readbackImageData(love::image::ImageData *imagedata, int slice, in
 	memcpy(imagedata->getData(), buffer.contents, imagedata->getSize());
 	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)
+{
+	// TODO
+}
+
+void Texture::copyToBuffer(love::graphics::Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth, size_t size)
+{
+	// TODO
+}
+
 void Texture::setSamplerState(const SamplerState &s)
 void Texture::setSamplerState(const SamplerState &s)
 { @autoreleasepool {
 { @autoreleasepool {
 	// Base class does common validation and assigns samplerState.
 	// Base class does common validation and assigns samplerState.

+ 11 - 10
src/modules/graphics/opengl/Buffer.cpp

@@ -80,10 +80,6 @@ Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const st
 		mapUsage = BUFFERUSAGE_INDEX;
 		mapUsage = BUFFERUSAGE_INDEX;
 	else  if (usageFlags & BUFFERUSAGEFLAG_SHADER_STORAGE)
 	else  if (usageFlags & BUFFERUSAGEFLAG_SHADER_STORAGE)
 		mapUsage = BUFFERUSAGE_SHADER_STORAGE;
 		mapUsage = BUFFERUSAGE_SHADER_STORAGE;
-	else if (usageFlags & BUFFERUSAGEFLAG_COPY_SOURCE)
-		mapUsage = BUFFERUSAGE_COPY_SOURCE;
-	else if (usageFlags & BUFFERUSAGEFLAG_COPY_DEST)
-		mapUsage = BUFFERUSAGE_COPY_DEST;
 
 
 	target = OpenGL::getGLBufferType(mapUsage);
 	target = OpenGL::getGLBufferType(mapUsage);
 
 
@@ -161,6 +157,11 @@ bool Buffer::load(const void *initialdata)
 	return (glGetError() == GL_NO_ERROR);
 	return (glGetError() == GL_NO_ERROR);
 }
 }
 
 
+bool Buffer::supportsOrphan() const
+{
+	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)
 	if (size == 0)
@@ -206,7 +207,7 @@ void Buffer::unmap(size_t usedoffset, size_t usedsize)
 	mapped = false;
 	mapped = false;
 
 
 	// Orphan optimization - see fill().
 	// Orphan optimization - see fill().
-	if (dataUsage != BUFFERDATAUSAGE_STATIC && mappedRange.first == 0 && mappedRange.getSize() == getSize())
+	if (supportsOrphan() && mappedRange.first == 0 && mappedRange.getSize() == getSize())
 	{
 	{
 		usedoffset = 0;
 		usedoffset = 0;
 		usedsize = getSize();
 		usedsize = getSize();
@@ -238,15 +239,14 @@ void Buffer::fill(size_t offset, size_t size, const void *data)
 
 
 	gl.bindBuffer(mapUsage, buffer);
 	gl.bindBuffer(mapUsage, buffer);
 
 
-	if (dataUsage != BUFFERDATAUSAGE_STATIC && size == buffersize)
+	if (supportsOrphan() && size == buffersize)
 	{
 	{
 		// "orphan" current buffer to avoid implicit synchronisation on the GPU:
 		// "orphan" current buffer to avoid implicit synchronisation on the GPU:
 		// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
 		// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
-		gl.bindBuffer(mapUsage, buffer);
 		glBufferData(target, (GLsizeiptr) buffersize, nullptr, gldatausage);
 		glBufferData(target, (GLsizeiptr) buffersize, nullptr, gldatausage);
 
 
 #if LOVE_WINDOWS
 #if LOVE_WINDOWS
-		// TODO: Verify that this codepath is a useful optimization.
+		// TODO: Verify that this intel codepath is a useful optimization.
 		if (gl.getVendor() == OpenGL::VENDOR_INTEL)
 		if (gl.getVendor() == OpenGL::VENDOR_INTEL)
 			glBufferData(target, (GLsizeiptr) buffersize, data, gldatausage);
 			glBufferData(target, (GLsizeiptr) buffersize, data, gldatausage);
 		else
 		else
@@ -261,8 +261,9 @@ void Buffer::fill(size_t offset, size_t size, const void *data)
 
 
 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)
 {
 {
-	gl.bindBuffer(BUFFERUSAGE_COPY_SOURCE, buffer);
-	gl.bindBuffer(BUFFERUSAGE_COPY_DEST, ((Buffer *) dest)->buffer);
+	// TODO: tracked state for these bind types?
+	glBindBuffer(GL_COPY_READ_BUFFER, buffer);
+	glBindBuffer(GL_COPY_WRITE_BUFFER, ((Buffer *) dest)->buffer);
 
 
 	glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, sourceoffset, destoffset, size);
 	glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, sourceoffset, destoffset, size);
 }
 }

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

@@ -61,9 +61,7 @@ public:
 private:
 private:
 
 
 	bool load(const void *initialdata);
 	bool load(const void *initialdata);
-
-	void unmapStatic(size_t offset, size_t size);
-	void unmapStream();
+	bool supportsOrphan() const;
 
 
 	BufferUsage mapUsage = BUFFERUSAGE_VERTEX;
 	BufferUsage mapUsage = BUFFERUSAGE_VERTEX;
 	GLenum target = 0;
 	GLenum target = 0;

+ 7 - 5
src/modules/graphics/opengl/Graphics.cpp

@@ -509,8 +509,7 @@ static bool computeDispatchBarriers(Shader *shader, GLbitfield &preDispatchBarri
 		if (usage & BUFFERUSAGEFLAG_VERTEX)
 		if (usage & BUFFERUSAGEFLAG_VERTEX)
 			postDispatchBarriers |= GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
 			postDispatchBarriers |= GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT;
 
 
-		if (usage & (BUFFERUSAGEFLAG_COPY_SOURCE | BUFFERUSAGEFLAG_COPY_DEST))
-			postDispatchBarriers |= GL_PIXEL_BUFFER_BARRIER_BIT;
+		postDispatchBarriers |= GL_PIXEL_BUFFER_BARRIER_BIT;
 	}
 	}
 
 
 	for (const auto &binding : shader->getStorageTextureBindings())
 	for (const auto &binding : shader->getStorageTextureBindings())
@@ -858,7 +857,7 @@ void Graphics::clear(OptionalColorD c, OptionalInt stencil, OptionalDouble depth
 
 
 	if (c.hasValue)
 	if (c.hasValue)
 	{
 	{
-		Colorf cf((float)c.value.r, (float)c.value.g, (float)c.value.b, (float)c.value.b);
+		Colorf cf((float)c.value.r, (float)c.value.g, (float)c.value.b, (float)c.value.a);
 		gammaCorrectColor(cf);
 		gammaCorrectColor(cf);
 		glClearColor(cf.r, cf.g, cf.b, cf.a);
 		glClearColor(cf.r, cf.g, cf.b, cf.a);
 		flags |= GL_COLOR_BUFFER_BIT;
 		flags |= GL_COLOR_BUFFER_BIT;
@@ -1658,8 +1657,11 @@ void Graphics::initCapabilities()
 	capabilities.features[FEATURE_GLSL4] = GLAD_ES_VERSION_3_1 || (gl.isCoreProfile() && GLAD_VERSION_4_3);
 	capabilities.features[FEATURE_GLSL4] = GLAD_ES_VERSION_3_1 || (gl.isCoreProfile() && GLAD_VERSION_4_3);
 	capabilities.features[FEATURE_INSTANCING] = gl.isInstancingSupported();
 	capabilities.features[FEATURE_INSTANCING] = gl.isInstancingSupported();
 	capabilities.features[FEATURE_TEXEL_BUFFER] = gl.isBufferUsageSupported(BUFFERUSAGE_TEXEL);
 	capabilities.features[FEATURE_TEXEL_BUFFER] = gl.isBufferUsageSupported(BUFFERUSAGE_TEXEL);
-	capabilities.features[FEATURE_COPY_BUFFER] = gl.isBufferUsageSupported(BUFFERUSAGE_COPY_SOURCE);
-	static_assert(FEATURE_MAX_ENUM == 12, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
+	capabilities.features[FEATURE_COPY_BUFFER] = gl.isCopyBufferSupported();
+	capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = gl.isCopyBufferToTextureSupported();
+	capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = gl.isCopyTextureToBufferSupported();
+	capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = gl.isCopyRenderTargetToBufferSupported();
+	static_assert(FEATURE_MAX_ENUM == 15, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
 
 
 	capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize();
 	capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize();
 	capabilities.limits[LIMIT_TEXTURE_SIZE] = gl.getMax2DTextureSize();
 	capabilities.limits[LIMIT_TEXTURE_SIZE] = gl.getMax2DTextureSize();

+ 25 - 5
src/modules/graphics/opengl/OpenGL.cpp

@@ -666,8 +666,6 @@ GLenum OpenGL::getGLBufferType(BufferUsage usage)
 		case BUFFERUSAGE_INDEX: return GL_ELEMENT_ARRAY_BUFFER;
 		case BUFFERUSAGE_INDEX: return GL_ELEMENT_ARRAY_BUFFER;
 		case BUFFERUSAGE_TEXEL: return GL_TEXTURE_BUFFER;
 		case BUFFERUSAGE_TEXEL: return GL_TEXTURE_BUFFER;
 		case BUFFERUSAGE_SHADER_STORAGE: return GL_SHADER_STORAGE_BUFFER;
 		case BUFFERUSAGE_SHADER_STORAGE: return GL_SHADER_STORAGE_BUFFER;
-		case BUFFERUSAGE_COPY_SOURCE: return GL_COPY_READ_BUFFER;
-		case BUFFERUSAGE_COPY_DEST: return GL_COPY_WRITE_BUFFER;
 		case BUFFERUSAGE_MAX_ENUM: return GL_ZERO;
 		case BUFFERUSAGE_MAX_ENUM: return GL_ZERO;
 	}
 	}
 
 
@@ -844,6 +842,8 @@ 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:
+			return (GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0) ? GL_STREAM_READ : GL_STREAM_DRAW;
 		default: return 0;
 		default: return 0;
 	}
 	}
 }
 }
@@ -1483,9 +1483,6 @@ bool OpenGL::isBufferUsageSupported(BufferUsage usage) const
 		return GLAD_VERSION_3_1;
 		return GLAD_VERSION_3_1;
 	case BUFFERUSAGE_SHADER_STORAGE:
 	case BUFFERUSAGE_SHADER_STORAGE:
 		return (GLAD_VERSION_4_3 && isCoreProfile()) || GLAD_ES_VERSION_3_1;
 		return (GLAD_VERSION_4_3 && isCoreProfile()) || GLAD_ES_VERSION_3_1;
-	case BUFFERUSAGE_COPY_SOURCE:
-	case BUFFERUSAGE_COPY_DEST:
-		return GLAD_VERSION_3_1 || GLAD_ES_VERSION_3_0;
 	case BUFFERUSAGE_MAX_ENUM:
 	case BUFFERUSAGE_MAX_ENUM:
 		return false;
 		return false;
 	}
 	}
@@ -1530,6 +1527,29 @@ bool OpenGL::isMultiFormatMRTSupported() const
 	return getMaxRenderTargets() > 1 && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object);
 	return getMaxRenderTargets() > 1 && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object);
 }
 }
 
 
+bool OpenGL::isCopyBufferSupported() const
+{
+	return GLAD_VERSION_3_1 || GLAD_ES_VERSION_3_0;
+}
+
+bool OpenGL::isCopyBufferToTextureSupported() const
+{
+	// Requires pixel unpack buffer binding support.
+	return GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0;
+}
+
+bool OpenGL::isCopyTextureToBufferSupported() const
+{
+	// Requires glGetTextureSubImage support.
+	return GLAD_VERSION_4_5 || GLAD_ARB_get_texture_sub_image;
+}
+
+bool OpenGL::isCopyRenderTargetToBufferSupported() const
+{
+	// Requires pixel pack buffer binding support.
+	return GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0;
+}
+
 int OpenGL::getMax2DTextureSize() const
 int OpenGL::getMax2DTextureSize() const
 {
 {
 	return std::max(max2DTextureSize, 1);
 	return std::max(max2DTextureSize, 1);

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

@@ -371,6 +371,10 @@ public:
 	bool isSamplerLODBiasSupported() const;
 	bool isSamplerLODBiasSupported() const;
 	bool isBaseVertexSupported() const;
 	bool isBaseVertexSupported() const;
 	bool isMultiFormatMRTSupported() const;
 	bool isMultiFormatMRTSupported() const;
+	bool isCopyBufferSupported() const;
+	bool isCopyBufferToTextureSupported() const;
+	bool isCopyTextureToBufferSupported() const;
+	bool isCopyRenderTargetToBufferSupported() const;
 
 
 	/**
 	/**
 	 * Returns the maximum supported width or height of a texture.
 	 * Returns the maximum supported width or height of a texture.

+ 78 - 5
src/modules/graphics/opengl/Texture.cpp

@@ -22,6 +22,7 @@
 
 
 #include "graphics/Graphics.h"
 #include "graphics/Graphics.h"
 #include "Graphics.h"
 #include "Graphics.h"
+#include "Buffer.h"
 #include "common/int.h"
 #include "common/int.h"
 
 
 // STD
 // STD
@@ -466,13 +467,16 @@ void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t s
 
 
 	if (isPixelFormatCompressed(pixelformat))
 	if (isPixelFormatCompressed(pixelformat))
 	{
 	{
-		if (r.x != 0 || r.y != 0)
-			throw love::Exception("x and y parameters must be 0 for compressed textures.");
-
 		if (texType == TEXTURE_2D || texType == TEXTURE_CUBE)
 		if (texType == TEXTURE_2D || texType == TEXTURE_CUBE)
-			glCompressedTexImage2D(gltarget, level, fmt.internalformat, r.w, r.h, 0, size, data);
+		{
+			// Possible issues on some very old drivers if TexSubImage is used.
+			if (r.x != 0 || r.y != 0 || r.w != getPixelWidth(level) || r.h != getPixelHeight(level))
+				glCompressedTexSubImage2D(gltarget, level, r.x, r.y, r.w, r.h, fmt.internalformat, size, data);
+			else
+				glCompressedTexImage2D(gltarget, level, fmt.internalformat, r.w, r.h, 0, size, data);
+		}
 		else if (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME)
 		else if (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME)
-			glCompressedTexSubImage3D(gltarget, level, 0, 0, slice, r.w, r.h, 1, fmt.internalformat, size, data);
+			glCompressedTexSubImage3D(gltarget, level, r.x, r.y, slice, r.w, r.h, 1, fmt.internalformat, size, data);
 	}
 	}
 	else
 	else
 	{
 	{
@@ -521,6 +525,75 @@ void Texture::readbackImageData(love::image::ImageData *data, int slice, int mip
 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
 }
 }
 
 
+void Texture::copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect)
+{
+	// Higher level code does validation.
+
+	GLuint glbuffer = (GLuint) source->getHandle();
+	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, glbuffer);
+
+	if (!isCompressed()) // Not supported in GL with compressed textures...
+		glPixelStorei(GL_UNPACK_ROW_LENGTH, sourcewidth);
+
+	// glTexSubImage and friends copy from the active pixel_unpack_buffer by
+	// treating the pointer as a byte offset.
+	const uint8 *byteoffset = (const uint8 *)(ptrdiff_t)sourceoffset;
+	uploadByteData(format, byteoffset, size, mipmap, slice, rect);
+
+	glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+}
+
+void Texture::copyToBuffer(love::graphics::Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth, size_t size)
+{
+	// Higher level code does validation.
+
+	GLuint glbuffer = (GLuint) dest->getHandle();
+	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
+	// treating the pointer as a byte offset.
+	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);
+	}
+
+	glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+	glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+}
+
 void Texture::setSamplerState(const SamplerState &s)
 void Texture::setSamplerState(const SamplerState &s)
 {
 {
 	if (s.depthSampleMode.hasValue && !gl.isDepthCompareSampleSupported())
 	if (s.depthSampleMode.hasValue && !gl.isDepthCompareSampleSupported())

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

@@ -46,6 +46,9 @@ public:
 	bool loadVolatile() override;
 	bool loadVolatile() override;
 	void unloadVolatile() override;
 	void unloadVolatile() override;
 
 
+	void copyFromBuffer(love::graphics::Buffer *source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect &rect) override;
+	void copyToBuffer(love::graphics::Buffer *dest, int slice, int mipmap, const Rect &rect, size_t destoffset, int destwidth, size_t size) override;
+
 	void setSamplerState(const SamplerState &s) override;
 	void setSamplerState(const SamplerState &s) override;
 
 
 	ptrdiff_t getHandle() const override;
 	ptrdiff_t getHandle() const override;

+ 1 - 2
src/modules/graphics/vertex.cpp

@@ -371,8 +371,6 @@ STRINGMAP_BEGIN(BufferUsage, BUFFERUSAGE_MAX_ENUM, bufferUsageName)
 	{ "index",         BUFFERUSAGE_INDEX          },
 	{ "index",         BUFFERUSAGE_INDEX          },
 	{ "texel",         BUFFERUSAGE_TEXEL          },
 	{ "texel",         BUFFERUSAGE_TEXEL          },
 	{ "shaderstorage", BUFFERUSAGE_SHADER_STORAGE },
 	{ "shaderstorage", BUFFERUSAGE_SHADER_STORAGE },
-	{ "copysource",    BUFFERUSAGE_COPY_SOURCE    },
-	{ "copydest",      BUFFERUSAGE_COPY_DEST      },
 }
 }
 STRINGMAP_END(BufferUsage, BUFFERUSAGE_MAX_ENUM, bufferUsageName)
 STRINGMAP_END(BufferUsage, BUFFERUSAGE_MAX_ENUM, bufferUsageName)
 
 
@@ -388,6 +386,7 @@ STRINGMAP_BEGIN(BufferDataUsage, BUFFERDATAUSAGE_MAX_ENUM, bufferDataUsage)
 	{ "stream",  BUFFERDATAUSAGE_STREAM  },
 	{ "stream",  BUFFERDATAUSAGE_STREAM  },
 	{ "dynamic", BUFFERDATAUSAGE_DYNAMIC },
 	{ "dynamic", BUFFERDATAUSAGE_DYNAMIC },
 	{ "static",  BUFFERDATAUSAGE_STATIC  },
 	{ "static",  BUFFERDATAUSAGE_STATIC  },
+	{ "staging", BUFFERDATAUSAGE_STAGING },
 }
 }
 STRINGMAP_END(BufferDataUsage, BUFFERDATAUSAGE_MAX_ENUM, bufferDataUsage)
 STRINGMAP_END(BufferDataUsage, BUFFERDATAUSAGE_MAX_ENUM, bufferDataUsage)
 
 

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

@@ -60,8 +60,6 @@ enum BufferUsage
 	BUFFERUSAGE_INDEX,
 	BUFFERUSAGE_INDEX,
 	BUFFERUSAGE_TEXEL,
 	BUFFERUSAGE_TEXEL,
 	BUFFERUSAGE_SHADER_STORAGE,
 	BUFFERUSAGE_SHADER_STORAGE,
-	BUFFERUSAGE_COPY_SOURCE,
-	BUFFERUSAGE_COPY_DEST,
 	BUFFERUSAGE_MAX_ENUM
 	BUFFERUSAGE_MAX_ENUM
 };
 };
 
 
@@ -72,8 +70,6 @@ enum BufferUsageFlags
 	BUFFERUSAGEFLAG_INDEX = 1 << BUFFERUSAGE_INDEX,
 	BUFFERUSAGEFLAG_INDEX = 1 << BUFFERUSAGE_INDEX,
 	BUFFERUSAGEFLAG_TEXEL = 1 << BUFFERUSAGE_TEXEL,
 	BUFFERUSAGEFLAG_TEXEL = 1 << BUFFERUSAGE_TEXEL,
 	BUFFERUSAGEFLAG_SHADER_STORAGE = 1 << BUFFERUSAGE_SHADER_STORAGE,
 	BUFFERUSAGEFLAG_SHADER_STORAGE = 1 << BUFFERUSAGE_SHADER_STORAGE,
-	BUFFERUSAGEFLAG_COPY_SOURCE = 1 << BUFFERUSAGE_COPY_SOURCE,
-	BUFFERUSAGEFLAG_COPY_DEST = 1 << BUFFERUSAGE_COPY_DEST,
 };
 };
 
 
 enum IndexDataType
 enum IndexDataType
@@ -114,6 +110,7 @@ enum BufferDataUsage
 	BUFFERDATAUSAGE_STREAM,
 	BUFFERDATAUSAGE_STREAM,
 	BUFFERDATAUSAGE_DYNAMIC,
 	BUFFERDATAUSAGE_DYNAMIC,
 	BUFFERDATAUSAGE_STATIC,
 	BUFFERDATAUSAGE_STATIC,
+	BUFFERDATAUSAGE_STAGING,
 	BUFFERDATAUSAGE_MAX_ENUM
 	BUFFERDATAUSAGE_MAX_ENUM
 };
 };
 
 

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

@@ -3353,6 +3353,70 @@ int w_copyBuffer(lua_State *L)
 	return 0;
 	return 0;
 }
 }
 
 
+int w_copyBufferToTexture(lua_State *L)
+{
+	Buffer *source = luax_checkbuffer(L, 1);
+	Texture *dest = luax_checktexture(L, 2);
+
+	ptrdiff_t sourceoffset = luaL_optinteger(L, 3, 0);
+	if (sourceoffset < 0)
+		return luaL_error(L, "copyBufferToTexture source offset cannot be negative.");
+
+	int sourcewidth = (int) luaL_optinteger(L, 4, 0);
+
+	int slice = 0;
+	int mipmap = 0;
+
+	if (dest->getTextureType() != TEXTURE_2D)
+		slice = (int) luaL_checkinteger(L, 5) - 1;
+
+	mipmap = (int) luaL_optinteger(L, 6, 1) - 1;
+
+	Rect rect = {0, 0, dest->getPixelWidth(mipmap), dest->getPixelHeight(mipmap)};
+	if (!lua_isnoneornil(L, 7))
+	{
+		rect.x = (int) luaL_checkinteger(L, 7);
+		rect.y = (int) luaL_checkinteger(L, 8);
+		rect.w = (int) luaL_checkinteger(L, 9);
+		rect.h = (int) luaL_checkinteger(L, 10);
+	}
+
+	luax_catchexcept(L, [&](){ instance()->copyBufferToTexture(source, dest, sourceoffset, sourcewidth, slice, mipmap, rect); });
+	return 0;
+}
+
+int w_copyTextureToBuffer(lua_State *L)
+{
+	Texture *source = luax_checktexture(L, 1);
+	Buffer *dest = luax_checkbuffer(L, 2);
+
+	int slice = 0;
+	int mipmap = 0;
+
+	if (source->getTextureType() != TEXTURE_2D)
+		slice = (int) luaL_checkinteger(L, 3) - 1;
+
+	mipmap = (int) luaL_optinteger(L, 4, 1) - 1;
+
+	Rect rect = {0, 0, source->getPixelWidth(mipmap), source->getPixelHeight(mipmap)};
+	if (!lua_isnoneornil(L, 5))
+	{
+		rect.x = (int) luaL_checkinteger(L, 5);
+		rect.y = (int) luaL_checkinteger(L, 6);
+		rect.w = (int) luaL_checkinteger(L, 7);
+		rect.h = (int) luaL_checkinteger(L, 8);
+	}
+
+	ptrdiff_t destoffset = luaL_optinteger(L, 9, 0);
+	if (destoffset < 0)
+		return luaL_error(L, "copyTextureToBuffer dest offset cannot be negative.");
+
+	int destwidth = (int) luaL_optinteger(L, 10, 0);
+
+	luax_catchexcept(L, [&](){ instance()->copyTextureToBuffer(source, dest, slice, mipmap, rect, destoffset, destwidth); });
+	return 0;
+}
+
 int w_flushBatch(lua_State *)
 int w_flushBatch(lua_State *)
 {
 {
 	instance()->flushBatchedDraws();
 	instance()->flushBatchedDraws();
@@ -3553,6 +3617,8 @@ static const luaL_Reg functions[] =
 	{ "dispatchThreadgroups", w_dispatchThreadgroups },
 	{ "dispatchThreadgroups", w_dispatchThreadgroups },
 
 
 	{ "copyBuffer", w_copyBuffer },
 	{ "copyBuffer", w_copyBuffer },
+	{ "copyBufferToTexture", w_copyBufferToTexture },
+	{ "copyTextureToBuffer", w_copyTextureToBuffer },
 
 
 	{ "isCreated", w_isCreated },
 	{ "isCreated", w_isCreated },
 	{ "isActive", w_isActive },
 	{ "isActive", w_isActive },

+ 1 - 1
src/modules/graphics/wrap_Texture.cpp

@@ -390,13 +390,13 @@ int w_Texture_newImageData(lua_State *L)
 
 
 	int slice = 0;
 	int slice = 0;
 	int mipmap = 0;
 	int mipmap = 0;
-	Rect rect = {0, 0, t->getPixelWidth(), t->getPixelHeight()};
 
 
 	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;
 	mipmap = (int) luaL_optinteger(L, 3, 1) - 1;
 
 
+	Rect rect = {0, 0, t->getPixelWidth(mipmap), t->getPixelHeight(mipmap)};
 	if (!lua_isnoneornil(L, 4))
 	if (!lua_isnoneornil(L, 4))
 	{
 	{
 		rect.x = (int) luaL_checkinteger(L, 4);
 		rect.x = (int) luaL_checkinteger(L, 4);