Browse Source

Merge branch '12.0-development' into 12.0-testsuite

ellraiser 1 year ago
parent
commit
ef39fb3fb7
100 changed files with 2996 additions and 2334 deletions
  1. 0 4
      CMakeLists.txt
  2. 8 28
      platform/xcode/liblove.xcodeproj/project.pbxproj
  3. 3 3
      platform/xcode/love.xcodeproj/project.pbxproj
  4. 2 2
      readme.md
  5. 1 1
      src/common/Optional.h
  6. 6 0
      src/common/deprecation.cpp
  7. 2 0
      src/common/deprecation.h
  8. 253 156
      src/common/pixelformat.cpp
  9. 45 16
      src/common/pixelformat.h
  10. 1 1
      src/modules/data/wrap_ByteData.cpp
  11. 3 13
      src/modules/font/Font.cpp
  12. 2 5
      src/modules/font/Font.h
  13. 7 0
      src/modules/font/TrueTypeRasterizer.h
  14. 5 10
      src/modules/font/freetype/Font.cpp
  15. 1 2
      src/modules/font/freetype/Font.h
  16. 6 6
      src/modules/font/freetype/HarfbuzzShaper.cpp
  17. 1 1
      src/modules/font/freetype/HarfbuzzShaper.h
  18. 4 4
      src/modules/font/freetype/TrueTypeRasterizer.cpp
  19. 1 1
      src/modules/font/freetype/TrueTypeRasterizer.h
  20. 51 33
      src/modules/font/wrap_Font.cpp
  21. 3 3
      src/modules/graphics/Deprecations.cpp
  22. 23 4
      src/modules/graphics/Graphics.cpp
  23. 3 3
      src/modules/graphics/Graphics.h
  24. 3 1
      src/modules/graphics/GraphicsReadback.cpp
  25. 1 0
      src/modules/graphics/GraphicsReadback.h
  26. 9 1
      src/modules/graphics/Shader.cpp
  27. 10 5
      src/modules/graphics/Texture.cpp
  28. 0 2
      src/modules/graphics/Texture.h
  29. 1 2
      src/modules/graphics/metal/Graphics.h
  30. 46 43
      src/modules/graphics/metal/Graphics.mm
  31. 1 1
      src/modules/graphics/metal/Metal.h
  32. 148 67
      src/modules/graphics/metal/Metal.mm
  33. 2 4
      src/modules/graphics/metal/Shader.mm
  34. 1 1
      src/modules/graphics/metal/Texture.mm
  35. 41 71
      src/modules/graphics/opengl/Graphics.cpp
  36. 1 2
      src/modules/graphics/opengl/Graphics.h
  37. 257 120
      src/modules/graphics/opengl/OpenGL.cpp
  38. 21 2
      src/modules/graphics/opengl/OpenGL.h
  39. 1 2
      src/modules/graphics/opengl/Shader.cpp
  40. 56 39
      src/modules/graphics/opengl/Texture.cpp
  41. 151 155
      src/modules/graphics/vulkan/Graphics.cpp
  42. 12 6
      src/modules/graphics/vulkan/Graphics.h
  43. 3 3
      src/modules/graphics/vulkan/GraphicsReadback.cpp
  44. 5 1
      src/modules/graphics/vulkan/Shader.cpp
  45. 8 4
      src/modules/graphics/vulkan/Texture.cpp
  46. 118 49
      src/modules/graphics/vulkan/Vulkan.cpp
  47. 1 1
      src/modules/graphics/vulkan/Vulkan.h
  48. 10 9
      src/modules/graphics/wrap_Graphics.cpp
  49. 13 5
      src/modules/image/CompressedImageData.cpp
  50. 2 2
      src/modules/image/CompressedImageData.h
  51. 0 2
      src/modules/image/CompressedSlice.cpp
  52. 0 2
      src/modules/image/CompressedSlice.h
  53. 1 1
      src/modules/image/FormatHandler.cpp
  54. 1 2
      src/modules/image/FormatHandler.h
  55. 4 5
      src/modules/image/ImageData.cpp
  56. 1 2
      src/modules/image/ImageData.h
  57. 11 0
      src/modules/image/ImageDataBase.cpp
  58. 4 1
      src/modules/image/ImageDataBase.h
  59. 15 17
      src/modules/image/magpie/ASTCHandler.cpp
  60. 1 1
      src/modules/image/magpie/ASTCHandler.h
  61. 38 46
      src/modules/image/magpie/KTXHandler.cpp
  62. 1 1
      src/modules/image/magpie/KTXHandler.h
  63. 1 3
      src/modules/image/magpie/PKMHandler.cpp
  64. 1 1
      src/modules/image/magpie/PKMHandler.h
  65. 18 17
      src/modules/image/magpie/PVRHandler.cpp
  66. 1 1
      src/modules/image/magpie/PVRHandler.h
  67. 34 27
      src/modules/image/magpie/ddsHandler.cpp
  68. 1 1
      src/modules/image/magpie/ddsHandler.h
  69. 16 0
      src/modules/image/wrap_CompressedImageData.cpp
  70. 16 0
      src/modules/image/wrap_ImageData.cpp
  71. 24 6
      src/modules/physics/box2d/Body.cpp
  72. 11 5
      src/modules/physics/box2d/Body.h
  73. 16 17
      src/modules/physics/box2d/ChainShape.cpp
  74. 1 1
      src/modules/physics/box2d/ChainShape.h
  75. 6 2
      src/modules/physics/box2d/CircleShape.cpp
  76. 1 1
      src/modules/physics/box2d/CircleShape.h
  77. 8 9
      src/modules/physics/box2d/Contact.cpp
  78. 2 2
      src/modules/physics/box2d/Contact.h
  79. 7 4
      src/modules/physics/box2d/EdgeShape.cpp
  80. 1 1
      src/modules/physics/box2d/EdgeShape.h
  81. 0 349
      src/modules/physics/box2d/Fixture.cpp
  82. 0 216
      src/modules/physics/box2d/Fixture.h
  83. 126 137
      src/modules/physics/box2d/Physics.cpp
  84. 23 36
      src/modules/physics/box2d/Physics.h
  85. 4 2
      src/modules/physics/box2d/PolygonShape.cpp
  86. 1 1
      src/modules/physics/box2d/PolygonShape.h
  87. 376 28
      src/modules/physics/box2d/Shape.cpp
  88. 127 5
      src/modules/physics/box2d/Shape.h
  89. 54 56
      src/modules/physics/box2d/World.cpp
  90. 8 8
      src/modules/physics/box2d/World.h
  91. 33 3
      src/modules/physics/box2d/wrap_Body.cpp
  92. 10 5
      src/modules/physics/box2d/wrap_ChainShape.cpp
  93. 5 3
      src/modules/physics/box2d/wrap_CircleShape.cpp
  94. 18 8
      src/modules/physics/box2d/wrap_Contact.cpp
  95. 9 5
      src/modules/physics/box2d/wrap_EdgeShape.cpp
  96. 0 311
      src/modules/physics/box2d/wrap_Fixture.cpp
  97. 0 43
      src/modules/physics/box2d/wrap_Fixture.h
  98. 316 36
      src/modules/physics/box2d/wrap_Physics.cpp
  99. 6 2
      src/modules/physics/box2d/wrap_PolygonShape.cpp
  100. 285 8
      src/modules/physics/box2d/wrap_Shape.cpp

+ 0 - 4
CMakeLists.txt

@@ -837,8 +837,6 @@ set(LOVE_SRC_MODULE_PHYSICS_BOX2D
 	src/modules/physics/box2d/DistanceJoint.h
 	src/modules/physics/box2d/DistanceJoint.h
 	src/modules/physics/box2d/EdgeShape.cpp
 	src/modules/physics/box2d/EdgeShape.cpp
 	src/modules/physics/box2d/EdgeShape.h
 	src/modules/physics/box2d/EdgeShape.h
-	src/modules/physics/box2d/Fixture.cpp
-	src/modules/physics/box2d/Fixture.h
 	src/modules/physics/box2d/FrictionJoint.cpp
 	src/modules/physics/box2d/FrictionJoint.cpp
 	src/modules/physics/box2d/FrictionJoint.h
 	src/modules/physics/box2d/FrictionJoint.h
 	src/modules/physics/box2d/GearJoint.cpp
 	src/modules/physics/box2d/GearJoint.cpp
@@ -881,8 +879,6 @@ set(LOVE_SRC_MODULE_PHYSICS_BOX2D
 	src/modules/physics/box2d/wrap_DistanceJoint.h
 	src/modules/physics/box2d/wrap_DistanceJoint.h
 	src/modules/physics/box2d/wrap_EdgeShape.cpp
 	src/modules/physics/box2d/wrap_EdgeShape.cpp
 	src/modules/physics/box2d/wrap_EdgeShape.h
 	src/modules/physics/box2d/wrap_EdgeShape.h
-	src/modules/physics/box2d/wrap_Fixture.cpp
-	src/modules/physics/box2d/wrap_Fixture.h
 	src/modules/physics/box2d/wrap_FrictionJoint.cpp
 	src/modules/physics/box2d/wrap_FrictionJoint.cpp
 	src/modules/physics/box2d/wrap_FrictionJoint.h
 	src/modules/physics/box2d/wrap_FrictionJoint.h
 	src/modules/physics/box2d/wrap_GearJoint.cpp
 	src/modules/physics/box2d/wrap_GearJoint.cpp

+ 8 - 28
platform/xcode/liblove.xcodeproj/project.pbxproj

@@ -51,6 +51,9 @@
 		217DFC111D9F6D490055D849 /* usocket.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBD51D9F6D490055D849 /* usocket.c */; };
 		217DFC111D9F6D490055D849 /* usocket.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBD51D9F6D490055D849 /* usocket.c */; };
 		217DFC121D9F6D490055D849 /* usocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 217DFBD61D9F6D490055D849 /* usocket.h */; };
 		217DFC121D9F6D490055D849 /* usocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 217DFBD61D9F6D490055D849 /* usocket.h */; };
 		D923E7D3296B85B9002FF1B3 /* harfbuzz.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */; };
 		D923E7D3296B85B9002FF1B3 /* harfbuzz.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */; };
+		D943E58E2A24D56000D80361 /* PhysfsIo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D943E58C2A24D56000D80361 /* PhysfsIo.cpp */; };
+		D943E58F2A24D56000D80361 /* PhysfsIo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D943E58C2A24D56000D80361 /* PhysfsIo.cpp */; };
+		D943E5902A24D56000D80361 /* PhysfsIo.h in Headers */ = {isa = PBXBuildFile; fileRef = D943E58D2A24D56000D80361 /* PhysfsIo.h */; };
 		D9DAB9222961F0EE00C64820 /* HarfbuzzShaper.h in Headers */ = {isa = PBXBuildFile; fileRef = D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */; };
 		D9DAB9222961F0EE00C64820 /* HarfbuzzShaper.h in Headers */ = {isa = PBXBuildFile; fileRef = D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */; };
 		D9DAB9232961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */; };
 		D9DAB9232961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */; };
 		D9DAB9242961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */; };
 		D9DAB9242961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */; };
@@ -61,9 +64,6 @@
 		D9DAB92D2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; };
 		D9DAB92D2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; };
 		D9DAB92E2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; };
 		D9DAB92E2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; };
 		D9DAB9322963CD7500C64820 /* harfbuzz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9DAB9312963CD7500C64820 /* harfbuzz.framework */; };
 		D9DAB9322963CD7500C64820 /* harfbuzz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9DAB9312963CD7500C64820 /* harfbuzz.framework */; };
-		D943E58E2A24D56000D80361 /* PhysfsIo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D943E58C2A24D56000D80361 /* PhysfsIo.cpp */; };
-		D943E58F2A24D56000D80361 /* PhysfsIo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D943E58C2A24D56000D80361 /* PhysfsIo.cpp */; };
-		D943E5902A24D56000D80361 /* PhysfsIo.h in Headers */ = {isa = PBXBuildFile; fileRef = D943E58D2A24D56000D80361 /* PhysfsIo.h */; };
 		FA0A3A5F23366CE9001C269E /* floattypes.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0A3A5D23366CE9001C269E /* floattypes.h */; };
 		FA0A3A5F23366CE9001C269E /* floattypes.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0A3A5D23366CE9001C269E /* floattypes.h */; };
 		FA0A3A6023366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; };
 		FA0A3A6023366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; };
 		FA0A3A6123366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; };
 		FA0A3A6123366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; };
@@ -407,9 +407,6 @@
 		FA0B7E091A95902C000E1D17 /* EdgeShape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C291A95902C000E1D17 /* EdgeShape.cpp */; };
 		FA0B7E091A95902C000E1D17 /* EdgeShape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C291A95902C000E1D17 /* EdgeShape.cpp */; };
 		FA0B7E0A1A95902C000E1D17 /* EdgeShape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C291A95902C000E1D17 /* EdgeShape.cpp */; };
 		FA0B7E0A1A95902C000E1D17 /* EdgeShape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C291A95902C000E1D17 /* EdgeShape.cpp */; };
 		FA0B7E0B1A95902C000E1D17 /* EdgeShape.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7C2A1A95902C000E1D17 /* EdgeShape.h */; };
 		FA0B7E0B1A95902C000E1D17 /* EdgeShape.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7C2A1A95902C000E1D17 /* EdgeShape.h */; };
-		FA0B7E0C1A95902C000E1D17 /* Fixture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C2B1A95902C000E1D17 /* Fixture.cpp */; };
-		FA0B7E0D1A95902C000E1D17 /* Fixture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C2B1A95902C000E1D17 /* Fixture.cpp */; };
-		FA0B7E0E1A95902C000E1D17 /* Fixture.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7C2C1A95902C000E1D17 /* Fixture.h */; };
 		FA0B7E0F1A95902C000E1D17 /* FrictionJoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C2D1A95902C000E1D17 /* FrictionJoint.cpp */; };
 		FA0B7E0F1A95902C000E1D17 /* FrictionJoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C2D1A95902C000E1D17 /* FrictionJoint.cpp */; };
 		FA0B7E101A95902C000E1D17 /* FrictionJoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C2D1A95902C000E1D17 /* FrictionJoint.cpp */; };
 		FA0B7E101A95902C000E1D17 /* FrictionJoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C2D1A95902C000E1D17 /* FrictionJoint.cpp */; };
 		FA0B7E111A95902C000E1D17 /* FrictionJoint.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7C2E1A95902C000E1D17 /* FrictionJoint.h */; };
 		FA0B7E111A95902C000E1D17 /* FrictionJoint.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7C2E1A95902C000E1D17 /* FrictionJoint.h */; };
@@ -473,9 +470,6 @@
 		FA0B7E4B1A95902C000E1D17 /* wrap_EdgeShape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C551A95902C000E1D17 /* wrap_EdgeShape.cpp */; };
 		FA0B7E4B1A95902C000E1D17 /* wrap_EdgeShape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C551A95902C000E1D17 /* wrap_EdgeShape.cpp */; };
 		FA0B7E4C1A95902C000E1D17 /* wrap_EdgeShape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C551A95902C000E1D17 /* wrap_EdgeShape.cpp */; };
 		FA0B7E4C1A95902C000E1D17 /* wrap_EdgeShape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C551A95902C000E1D17 /* wrap_EdgeShape.cpp */; };
 		FA0B7E4D1A95902C000E1D17 /* wrap_EdgeShape.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7C561A95902C000E1D17 /* wrap_EdgeShape.h */; };
 		FA0B7E4D1A95902C000E1D17 /* wrap_EdgeShape.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7C561A95902C000E1D17 /* wrap_EdgeShape.h */; };
-		FA0B7E4E1A95902C000E1D17 /* wrap_Fixture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C571A95902C000E1D17 /* wrap_Fixture.cpp */; };
-		FA0B7E4F1A95902C000E1D17 /* wrap_Fixture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C571A95902C000E1D17 /* wrap_Fixture.cpp */; };
-		FA0B7E501A95902C000E1D17 /* wrap_Fixture.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7C581A95902C000E1D17 /* wrap_Fixture.h */; };
 		FA0B7E511A95902C000E1D17 /* wrap_FrictionJoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C591A95902C000E1D17 /* wrap_FrictionJoint.cpp */; };
 		FA0B7E511A95902C000E1D17 /* wrap_FrictionJoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C591A95902C000E1D17 /* wrap_FrictionJoint.cpp */; };
 		FA0B7E521A95902C000E1D17 /* wrap_FrictionJoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C591A95902C000E1D17 /* wrap_FrictionJoint.cpp */; };
 		FA0B7E521A95902C000E1D17 /* wrap_FrictionJoint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7C591A95902C000E1D17 /* wrap_FrictionJoint.cpp */; };
 		FA0B7E531A95902C000E1D17 /* wrap_FrictionJoint.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7C5A1A95902C000E1D17 /* wrap_FrictionJoint.h */; };
 		FA0B7E531A95902C000E1D17 /* wrap_FrictionJoint.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7C5A1A95902C000E1D17 /* wrap_FrictionJoint.h */; };
@@ -1406,6 +1400,8 @@
 		217DFBD51D9F6D490055D849 /* usocket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = usocket.c; sourceTree = "<group>"; };
 		217DFBD51D9F6D490055D849 /* usocket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = usocket.c; sourceTree = "<group>"; };
 		217DFBD61D9F6D490055D849 /* usocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = usocket.h; sourceTree = "<group>"; };
 		217DFBD61D9F6D490055D849 /* usocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = usocket.h; sourceTree = "<group>"; };
 		D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = harfbuzz.xcframework; path = ios/libraries/harfbuzz.xcframework; sourceTree = "<group>"; };
 		D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = harfbuzz.xcframework; path = ios/libraries/harfbuzz.xcframework; sourceTree = "<group>"; };
+		D943E58C2A24D56000D80361 /* PhysfsIo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PhysfsIo.cpp; sourceTree = "<group>"; };
+		D943E58D2A24D56000D80361 /* PhysfsIo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhysfsIo.h; sourceTree = "<group>"; };
 		D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HarfbuzzShaper.h; sourceTree = "<group>"; };
 		D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HarfbuzzShaper.h; sourceTree = "<group>"; };
 		D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HarfbuzzShaper.cpp; sourceTree = "<group>"; };
 		D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HarfbuzzShaper.cpp; sourceTree = "<group>"; };
 		D9DAB9252961F0FF00C64820 /* GenericShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GenericShaper.h; sourceTree = "<group>"; };
 		D9DAB9252961F0FF00C64820 /* GenericShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GenericShaper.h; sourceTree = "<group>"; };
@@ -1413,8 +1409,6 @@
 		D9DAB9272961F0FF00C64820 /* TextShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextShaper.h; sourceTree = "<group>"; };
 		D9DAB9272961F0FF00C64820 /* TextShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextShaper.h; sourceTree = "<group>"; };
 		D9DAB9282961F10000C64820 /* TextShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextShaper.cpp; sourceTree = "<group>"; };
 		D9DAB9282961F10000C64820 /* TextShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextShaper.cpp; sourceTree = "<group>"; };
 		D9DAB9312963CD7500C64820 /* harfbuzz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = harfbuzz.framework; path = macosx/Frameworks/harfbuzz.framework; sourceTree = "<group>"; };
 		D9DAB9312963CD7500C64820 /* harfbuzz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = harfbuzz.framework; path = macosx/Frameworks/harfbuzz.framework; sourceTree = "<group>"; };
-		D943E58C2A24D56000D80361 /* PhysfsIo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PhysfsIo.cpp; sourceTree = "<group>"; };
-		D943E58D2A24D56000D80361 /* PhysfsIo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhysfsIo.h; sourceTree = "<group>"; };
 		FA08F5AE16C7525600F007B5 /* liblove-macosx.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "liblove-macosx.plist"; path = "macosx/liblove-macosx.plist"; sourceTree = "<group>"; };
 		FA08F5AE16C7525600F007B5 /* liblove-macosx.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "liblove-macosx.plist"; path = "macosx/liblove-macosx.plist"; sourceTree = "<group>"; };
 		FA0A3A5D23366CE9001C269E /* floattypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = floattypes.h; sourceTree = "<group>"; };
 		FA0A3A5D23366CE9001C269E /* floattypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = floattypes.h; sourceTree = "<group>"; };
 		FA0A3A5E23366CE9001C269E /* floattypes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = floattypes.cpp; sourceTree = "<group>"; };
 		FA0A3A5E23366CE9001C269E /* floattypes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = floattypes.cpp; sourceTree = "<group>"; };
@@ -1652,8 +1646,6 @@
 		FA0B7C281A95902C000E1D17 /* DistanceJoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DistanceJoint.h; sourceTree = "<group>"; };
 		FA0B7C281A95902C000E1D17 /* DistanceJoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DistanceJoint.h; sourceTree = "<group>"; };
 		FA0B7C291A95902C000E1D17 /* EdgeShape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EdgeShape.cpp; sourceTree = "<group>"; };
 		FA0B7C291A95902C000E1D17 /* EdgeShape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EdgeShape.cpp; sourceTree = "<group>"; };
 		FA0B7C2A1A95902C000E1D17 /* EdgeShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EdgeShape.h; sourceTree = "<group>"; };
 		FA0B7C2A1A95902C000E1D17 /* EdgeShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EdgeShape.h; sourceTree = "<group>"; };
-		FA0B7C2B1A95902C000E1D17 /* Fixture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Fixture.cpp; sourceTree = "<group>"; };
-		FA0B7C2C1A95902C000E1D17 /* Fixture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Fixture.h; sourceTree = "<group>"; };
 		FA0B7C2D1A95902C000E1D17 /* FrictionJoint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FrictionJoint.cpp; sourceTree = "<group>"; };
 		FA0B7C2D1A95902C000E1D17 /* FrictionJoint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FrictionJoint.cpp; sourceTree = "<group>"; };
 		FA0B7C2E1A95902C000E1D17 /* FrictionJoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FrictionJoint.h; sourceTree = "<group>"; };
 		FA0B7C2E1A95902C000E1D17 /* FrictionJoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FrictionJoint.h; sourceTree = "<group>"; };
 		FA0B7C2F1A95902C000E1D17 /* GearJoint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GearJoint.cpp; sourceTree = "<group>"; };
 		FA0B7C2F1A95902C000E1D17 /* GearJoint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GearJoint.cpp; sourceTree = "<group>"; };
@@ -1696,8 +1688,6 @@
 		FA0B7C541A95902C000E1D17 /* wrap_DistanceJoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_DistanceJoint.h; sourceTree = "<group>"; };
 		FA0B7C541A95902C000E1D17 /* wrap_DistanceJoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_DistanceJoint.h; sourceTree = "<group>"; };
 		FA0B7C551A95902C000E1D17 /* wrap_EdgeShape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_EdgeShape.cpp; sourceTree = "<group>"; };
 		FA0B7C551A95902C000E1D17 /* wrap_EdgeShape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_EdgeShape.cpp; sourceTree = "<group>"; };
 		FA0B7C561A95902C000E1D17 /* wrap_EdgeShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_EdgeShape.h; sourceTree = "<group>"; };
 		FA0B7C561A95902C000E1D17 /* wrap_EdgeShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_EdgeShape.h; sourceTree = "<group>"; };
-		FA0B7C571A95902C000E1D17 /* wrap_Fixture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Fixture.cpp; sourceTree = "<group>"; };
-		FA0B7C581A95902C000E1D17 /* wrap_Fixture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Fixture.h; sourceTree = "<group>"; };
 		FA0B7C591A95902C000E1D17 /* wrap_FrictionJoint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_FrictionJoint.cpp; sourceTree = "<group>"; };
 		FA0B7C591A95902C000E1D17 /* wrap_FrictionJoint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_FrictionJoint.cpp; sourceTree = "<group>"; };
 		FA0B7C5A1A95902C000E1D17 /* wrap_FrictionJoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_FrictionJoint.h; sourceTree = "<group>"; };
 		FA0B7C5A1A95902C000E1D17 /* wrap_FrictionJoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_FrictionJoint.h; sourceTree = "<group>"; };
 		FA0B7C5B1A95902C000E1D17 /* wrap_GearJoint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_GearJoint.cpp; sourceTree = "<group>"; };
 		FA0B7C5B1A95902C000E1D17 /* wrap_GearJoint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_GearJoint.cpp; sourceTree = "<group>"; };
@@ -3165,8 +3155,6 @@
 				FA0B7C281A95902C000E1D17 /* DistanceJoint.h */,
 				FA0B7C281A95902C000E1D17 /* DistanceJoint.h */,
 				FA0B7C291A95902C000E1D17 /* EdgeShape.cpp */,
 				FA0B7C291A95902C000E1D17 /* EdgeShape.cpp */,
 				FA0B7C2A1A95902C000E1D17 /* EdgeShape.h */,
 				FA0B7C2A1A95902C000E1D17 /* EdgeShape.h */,
-				FA0B7C2B1A95902C000E1D17 /* Fixture.cpp */,
-				FA0B7C2C1A95902C000E1D17 /* Fixture.h */,
 				FA0B7C2D1A95902C000E1D17 /* FrictionJoint.cpp */,
 				FA0B7C2D1A95902C000E1D17 /* FrictionJoint.cpp */,
 				FA0B7C2E1A95902C000E1D17 /* FrictionJoint.h */,
 				FA0B7C2E1A95902C000E1D17 /* FrictionJoint.h */,
 				FA0B7C2F1A95902C000E1D17 /* GearJoint.cpp */,
 				FA0B7C2F1A95902C000E1D17 /* GearJoint.cpp */,
@@ -3209,8 +3197,6 @@
 				FA0B7C541A95902C000E1D17 /* wrap_DistanceJoint.h */,
 				FA0B7C541A95902C000E1D17 /* wrap_DistanceJoint.h */,
 				FA0B7C551A95902C000E1D17 /* wrap_EdgeShape.cpp */,
 				FA0B7C551A95902C000E1D17 /* wrap_EdgeShape.cpp */,
 				FA0B7C561A95902C000E1D17 /* wrap_EdgeShape.h */,
 				FA0B7C561A95902C000E1D17 /* wrap_EdgeShape.h */,
-				FA0B7C571A95902C000E1D17 /* wrap_Fixture.cpp */,
-				FA0B7C581A95902C000E1D17 /* wrap_Fixture.h */,
 				FA0B7C591A95902C000E1D17 /* wrap_FrictionJoint.cpp */,
 				FA0B7C591A95902C000E1D17 /* wrap_FrictionJoint.cpp */,
 				FA0B7C5A1A95902C000E1D17 /* wrap_FrictionJoint.h */,
 				FA0B7C5A1A95902C000E1D17 /* wrap_FrictionJoint.h */,
 				FA0B7C5B1A95902C000E1D17 /* wrap_GearJoint.cpp */,
 				FA0B7C5B1A95902C000E1D17 /* wrap_GearJoint.cpp */,
@@ -4272,7 +4258,6 @@
 				FAFEB29B28F210550025D7D0 /* unixdgram.h in Headers */,
 				FAFEB29B28F210550025D7D0 /* unixdgram.h in Headers */,
 				FA0B7E051A95902C000E1D17 /* Contact.h in Headers */,
 				FA0B7E051A95902C000E1D17 /* Contact.h in Headers */,
 				FA4F2BE41DE6650600CA37D7 /* Transform.h in Headers */,
 				FA4F2BE41DE6650600CA37D7 /* Transform.h in Headers */,
-				FA0B7E0E1A95902C000E1D17 /* Fixture.h in Headers */,
 				FA6A2B661F5F7B6B0074C308 /* wrap_Data.h in Headers */,
 				FA6A2B661F5F7B6B0074C308 /* wrap_Data.h in Headers */,
 				FA18CEE423DBC6E000263725 /* Graphics.h in Headers */,
 				FA18CEE423DBC6E000263725 /* Graphics.h in Headers */,
 				217DFBE81D9F6D490055D849 /* inet.h in Headers */,
 				217DFBE81D9F6D490055D849 /* inet.h in Headers */,
@@ -4467,7 +4452,6 @@
 				FA4F2B7A1DE0125B00CA37D7 /* xxhash.h in Headers */,
 				FA4F2B7A1DE0125B00CA37D7 /* xxhash.h in Headers */,
 				FA0B7DDE1A95902C000E1D17 /* wrap_BezierCurve.h in Headers */,
 				FA0B7DDE1A95902C000E1D17 /* wrap_BezierCurve.h in Headers */,
 				FA0B7DED1A95902C000E1D17 /* Cursor.h in Headers */,
 				FA0B7DED1A95902C000E1D17 /* Cursor.h in Headers */,
-				FA0B7E501A95902C000E1D17 /* wrap_Fixture.h in Headers */,
 				FA28EBD71E352DB5003446F4 /* FenceSync.h in Headers */,
 				FA28EBD71E352DB5003446F4 /* FenceSync.h in Headers */,
 				FADF542C1E3DAADA00012CC0 /* wrap_Mesh.h in Headers */,
 				FADF542C1E3DAADA00012CC0 /* wrap_Mesh.h in Headers */,
 				FAA3A9B01B7D465A00CED060 /* android.h in Headers */,
 				FAA3A9B01B7D465A00CED060 /* android.h in Headers */,
@@ -4707,7 +4691,6 @@
 				FA3C5E431F8C368C0003C579 /* ShaderStage.cpp in Sources */,
 				FA3C5E431F8C368C0003C579 /* ShaderStage.cpp in Sources */,
 				FA0B7E191A95902C000E1D17 /* MotorJoint.cpp in Sources */,
 				FA0B7E191A95902C000E1D17 /* MotorJoint.cpp in Sources */,
 				FAF1406F1E20934C00F898D2 /* Initialize.cpp in Sources */,
 				FAF1406F1E20934C00F898D2 /* Initialize.cpp in Sources */,
-				FA0B7E4F1A95902C000E1D17 /* wrap_Fixture.cpp in Sources */,
 				FA0B7EBF1A95902C000E1D17 /* Thread.cpp in Sources */,
 				FA0B7EBF1A95902C000E1D17 /* Thread.cpp in Sources */,
 				FACA02F91F5E39790084B28F /* Compressor.cpp in Sources */,
 				FACA02F91F5E39790084B28F /* Compressor.cpp in Sources */,
 				FABDAA002552448300B5C523 /* b2_time_of_impact.cpp in Sources */,
 				FABDAA002552448300B5C523 /* b2_time_of_impact.cpp in Sources */,
@@ -4725,7 +4708,6 @@
 				FA4F2C111DE936FE00CA37D7 /* unix.c in Sources */,
 				FA4F2C111DE936FE00CA37D7 /* unix.c in Sources */,
 				FA1BA0A31E16D97500AA2803 /* wrap_Font.cpp in Sources */,
 				FA1BA0A31E16D97500AA2803 /* wrap_Font.cpp in Sources */,
 				FABDA9842552448200B5C523 /* b2_chain_polygon_contact.cpp in Sources */,
 				FABDA9842552448200B5C523 /* b2_chain_polygon_contact.cpp in Sources */,
-				FA0B7E0D1A95902C000E1D17 /* Fixture.cpp in Sources */,
 				FADF53FE1E3D74F200012CC0 /* TextBatch.cpp in Sources */,
 				FADF53FE1E3D74F200012CC0 /* TextBatch.cpp in Sources */,
 				FA0B7D191A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */,
 				FA0B7D191A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */,
 				FAC271E723B5B5B400C200D3 /* renderstate.cpp in Sources */,
 				FAC271E723B5B5B400C200D3 /* renderstate.cpp in Sources */,
@@ -5140,7 +5122,6 @@
 				FAF140971E20934C00F898D2 /* PpTokens.cpp in Sources */,
 				FAF140971E20934C00F898D2 /* PpTokens.cpp in Sources */,
 				FAF140A91E20934C00F898D2 /* SymbolTable.cpp in Sources */,
 				FAF140A91E20934C00F898D2 /* SymbolTable.cpp in Sources */,
 				FA0B7E181A95902C000E1D17 /* MotorJoint.cpp in Sources */,
 				FA0B7E181A95902C000E1D17 /* MotorJoint.cpp in Sources */,
-				FA0B7E4E1A95902C000E1D17 /* wrap_Fixture.cpp in Sources */,
 				FA18CEC523D3AE6700263725 /* wrap_Buffer.cpp in Sources */,
 				FA18CEC523D3AE6700263725 /* wrap_Buffer.cpp in Sources */,
 				FAF6C9F423C2DE2900D7B5BC /* Logger.cpp in Sources */,
 				FAF6C9F423C2DE2900D7B5BC /* Logger.cpp in Sources */,
 				FABDA9FF2552448300B5C523 /* b2_time_of_impact.cpp in Sources */,
 				FABDA9FF2552448300B5C523 /* b2_time_of_impact.cpp in Sources */,
@@ -5159,7 +5140,6 @@
 				FABDA9832552448200B5C523 /* b2_chain_polygon_contact.cpp in Sources */,
 				FABDA9832552448200B5C523 /* b2_chain_polygon_contact.cpp in Sources */,
 				FA0B7AD61A958EA3000E1D17 /* win32.c in Sources */,
 				FA0B7AD61A958EA3000E1D17 /* win32.c in Sources */,
 				FADF54341E3DAE6E00012CC0 /* wrap_SpriteBatch.cpp in Sources */,
 				FADF54341E3DAE6E00012CC0 /* wrap_SpriteBatch.cpp in Sources */,
-				FA0B7E0C1A95902C000E1D17 /* Fixture.cpp in Sources */,
 				FA0B7D181A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */,
 				FA0B7D181A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */,
 				FA84DE6627791C36002674C6 /* GraphicsReadback.cpp in Sources */,
 				FA84DE6627791C36002674C6 /* GraphicsReadback.cpp in Sources */,
 				FA0B7CFA1A95902C000E1D17 /* Filesystem.cpp in Sources */,
 				FA0B7CFA1A95902C000E1D17 /* Filesystem.cpp in Sources */,
@@ -5531,7 +5511,7 @@
 				);
 				);
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				LIBRARY_SEARCH_PATHS = "";
 				LIBRARY_SEARCH_PATHS = "";
-				MACOSX_DEPLOYMENT_TARGET = 10.9;
+				MACOSX_DEPLOYMENT_TARGET = 10.13;
 				ONLY_ACTIVE_ARCH = NO;
 				ONLY_ACTIVE_ARCH = NO;
 				SDKROOT = macosx;
 				SDKROOT = macosx;
 				USE_HEADERMAP = NO;
 				USE_HEADERMAP = NO;
@@ -5597,7 +5577,7 @@
 				);
 				);
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				LIBRARY_SEARCH_PATHS = "";
 				LIBRARY_SEARCH_PATHS = "";
-				MACOSX_DEPLOYMENT_TARGET = 10.9;
+				MACOSX_DEPLOYMENT_TARGET = 10.13;
 				ONLY_ACTIVE_ARCH = YES;
 				ONLY_ACTIVE_ARCH = YES;
 				SDKROOT = macosx;
 				SDKROOT = macosx;
 				USE_HEADERMAP = NO;
 				USE_HEADERMAP = NO;
@@ -5736,7 +5716,7 @@
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				LIBRARY_SEARCH_PATHS = "";
 				LIBRARY_SEARCH_PATHS = "";
 				LLVM_LTO = YES;
 				LLVM_LTO = YES;
-				MACOSX_DEPLOYMENT_TARGET = 10.9;
+				MACOSX_DEPLOYMENT_TARGET = 10.13;
 				ONLY_ACTIVE_ARCH = NO;
 				ONLY_ACTIVE_ARCH = NO;
 				SDKROOT = macosx;
 				SDKROOT = macosx;
 				USE_HEADERMAP = NO;
 				USE_HEADERMAP = NO;

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

@@ -564,7 +564,7 @@
 				INFOPLIST_FILE = "love-Info.plist";
 				INFOPLIST_FILE = "love-Info.plist";
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
-				MACOSX_DEPLOYMENT_TARGET = 10.9;
+				MACOSX_DEPLOYMENT_TARGET = 10.13;
 				ONLY_ACTIVE_ARCH = YES;
 				ONLY_ACTIVE_ARCH = YES;
 				PRODUCT_NAME = love;
 				PRODUCT_NAME = love;
 				SDKROOT = macosx;
 				SDKROOT = macosx;
@@ -640,7 +640,7 @@
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
 				LLVM_LTO = YES;
 				LLVM_LTO = YES;
-				MACOSX_DEPLOYMENT_TARGET = 10.9;
+				MACOSX_DEPLOYMENT_TARGET = 10.13;
 				ONLY_ACTIVE_ARCH = NO;
 				ONLY_ACTIVE_ARCH = NO;
 				PRODUCT_NAME = love;
 				PRODUCT_NAME = love;
 				SCAN_ALL_SOURCE_FILES_FOR_INCLUDES = YES;
 				SCAN_ALL_SOURCE_FILES_FOR_INCLUDES = YES;
@@ -874,7 +874,7 @@
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
 				LLVM_LTO = YES;
 				LLVM_LTO = YES;
-				MACOSX_DEPLOYMENT_TARGET = 10.9;
+				MACOSX_DEPLOYMENT_TARGET = 10.13;
 				ONLY_ACTIVE_ARCH = NO;
 				ONLY_ACTIVE_ARCH = NO;
 				PRODUCT_NAME = love;
 				PRODUCT_NAME = love;
 				SCAN_ALL_SOURCE_FILES_FOR_INCLUDES = YES;
 				SCAN_ALL_SOURCE_FILES_FOR_INCLUDES = YES;

+ 2 - 2
readme.md

@@ -6,7 +6,7 @@ Documentation
 -------------
 -------------
 
 
 We use our [wiki][wiki] for documentation.
 We use our [wiki][wiki] for documentation.
-If you need further help, feel free to ask on our [forums][forums], our [Discord server][discord], or our IRC channel [#love on OFTC][irc].
+If you need further help, feel free to ask on our [forums][forums], our [Discord server][discord], or our [subreddit][subreddit].
 
 
 Repository
 Repository
 ----------
 ----------
@@ -97,7 +97,7 @@ Dependencies
 [wiki]: https://love2d.org/wiki
 [wiki]: https://love2d.org/wiki
 [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
+[subreddit]: https://www.reddit.com/r/love2d
 [dependencies-apple]: https://github.com/love2d/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

+ 1 - 1
src/common/Optional.h

@@ -46,7 +46,7 @@ struct Optional
 		hasValue = true;
 		hasValue = true;
 	}
 	}
 
 
-	T get(T defaultVal)
+	T get(T defaultVal) const
 	{
 	{
 		return hasValue ? value : defaultVal;
 		return hasValue ? value : defaultVal;
 	}
 	}

+ 6 - 0
src/common/deprecation.cpp

@@ -107,8 +107,12 @@ std::string getDeprecationNotice(const DeprecationInfo &info, bool usewhere)
 
 
 	if (info.apiType == API_FUNCTION)
 	if (info.apiType == API_FUNCTION)
 		notice << "function ";
 		notice << "function ";
+	else if (info.apiType == API_FUNCTION_VARIANT)
+		notice << "function variant in ";
 	else if (info.apiType == API_METHOD)
 	else if (info.apiType == API_METHOD)
 		notice << "method ";
 		notice << "method ";
+	else if (info.apiType == API_METHOD_VARIANT)
+		notice << "method variant in ";
 	else if (info.apiType == API_CALLBACK)
 	else if (info.apiType == API_CALLBACK)
 		notice << "callback ";
 		notice << "callback ";
 	else if (info.apiType == API_FIELD)
 	else if (info.apiType == API_FIELD)
@@ -188,7 +192,9 @@ MarkDeprecated::~MarkDeprecated()
 STRINGMAP_BEGIN(APIType, API_MAX_ENUM, apiType)
 STRINGMAP_BEGIN(APIType, API_MAX_ENUM, apiType)
 {
 {
 	{ "function", API_FUNCTION },
 	{ "function", API_FUNCTION },
+	{ "functionvariant", API_FUNCTION_VARIANT },
 	{ "method",   API_METHOD   },
 	{ "method",   API_METHOD   },
+	{ "methodvariant", API_METHOD_VARIANT },
 	{ "callback", API_CALLBACK },
 	{ "callback", API_CALLBACK },
 	{ "field",    API_FIELD    },
 	{ "field",    API_FIELD    },
 	{ "constant", API_CONSTANT },
 	{ "constant", API_CONSTANT },

+ 2 - 0
src/common/deprecation.h

@@ -32,7 +32,9 @@ namespace love
 enum APIType
 enum APIType
 {
 {
 	API_FUNCTION,
 	API_FUNCTION,
+	API_FUNCTION_VARIANT,
 	API_METHOD,
 	API_METHOD,
+	API_METHOD_VARIANT,
 	API_CALLBACK,
 	API_CALLBACK,
 	API_FIELD,
 	API_FIELD,
 	API_CONSTANT,
 	API_CONSTANT,

+ 253 - 156
src/common/pixelformat.cpp

@@ -26,101 +26,126 @@ namespace love
 
 
 static PixelFormatInfo formatInfo[] =
 static PixelFormatInfo formatInfo[] =
 {
 {
-	// components, blockW, blockH, blockSize, color, depth, stencil, compressed, dataType
-    { 0, 1, 1, 0, false, false, false, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_UNKNOWN
-
-	{ 0, 1, 1, 0, true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_NORMAL
-	{ 0, 1, 1, 0, true, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_HDR
-
-	{ 1, 1, 1, 1, true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_R8_UNORM
-	{ 1, 1, 1, 1, true, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_R8_INT
-	{ 1, 1, 1, 1, true, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_R8_UINT
-	{ 1, 1, 1, 2, true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_R16_UNORM
-	{ 1, 1, 1, 2, true, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_R16_FLOAT
-	{ 1, 1, 1, 2, true, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_R16_INT
-	{ 1, 1, 1, 2, true, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_R16_UINT
-	{ 1, 1, 1, 4, true, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_R32_FLOAT
-	{ 1, 1, 1, 4, true, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_R32_INT
-	{ 1, 1, 1, 4, true, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_R32_UINT
-
-	{ 2, 1, 1, 2, true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RG8_UNORM
-	{ 2, 1, 1, 2, true, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_RG8_INT
-	{ 2, 1, 1, 2, true, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_RG8_UINT
-	{ 2, 1, 1, 2, true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_LA8_UNORM
-	{ 2, 1, 1, 4, true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RG16_UNORM
-	{ 2, 1, 1, 4, true, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_RG16_FLOAT
-	{ 2, 1, 1, 4, true, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_RG16_INT
-	{ 2, 1, 1, 4, true, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_RG16_UINT
-	{ 2, 1, 1, 8, true, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_RG32_FLOAT
-	{ 2, 1, 1, 8, true, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_RG32_INT
-	{ 2, 1, 1, 8, true, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_RG32_UINT
-
-	{ 4, 1, 1, 4,  true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RGBA8_UNORM
-	{ 4, 1, 1, 4,  true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RGBA8_UNORM_sRGB
-	{ 4, 1, 1, 4,  true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_BGRA8_UNORM
-	{ 4, 1, 1, 4,  true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_BGRA8_UNORM_sRGB
-	{ 4, 1, 1, 4,  true, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_RGBA8_INT
-	{ 4, 1, 1, 4,  true, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_RGBA8_UINT
-	{ 4, 1, 1, 8,  true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RGBA16_UNORM
-	{ 4, 1, 1, 8,  true, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_RGBA16_FLOAT
-	{ 4, 1, 1, 8,  true, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_RGBA16_INT
-	{ 4, 1, 1, 8,  true, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_RGBA16_UINT
-	{ 4, 1, 1, 16, true, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_RGBA32_FLOAT
-	{ 4, 1, 1, 16, true, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_RGBA32_INT
-	{ 4, 1, 1, 16, true, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_RGBA32_UINT
-
-	{ 4, 1, 1, 2, true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RGBA4_UNORM
-	{ 4, 1, 1, 2, true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RGB5A1_UNORM
-	{ 3, 1, 1, 2, true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RGB565_UNORM
-	{ 4, 1, 1, 4, true, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RGB10A2_UNORM
-	{ 3, 1, 1, 4, true, false, false, false, PIXELFORMATTYPE_UFLOAT }, // PIXELFORMAT_RG11B10_FLOAT
-
-	{ 1, 1, 1, 1, false, false, true , false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_STENCIL8
-	{ 1, 1, 1, 2, false, true,  false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DEPTH16_UNORM
-	{ 1, 1, 1, 3, false, true,  false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DEPTH24_UNORM
-	{ 1, 1, 1, 4, false, true,  false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_DEPTH32_FLOAT
-	{ 2, 1, 1, 4, false, true,  true , false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DEPTH24_UNORM_STENCIL8
-	{ 2, 1, 1, 5, false, true,  true , false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_DEPTH32_FLOAT_STENCIL8
-
-	{ 3, 4, 4, 8,  true, false, false, true, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DXT1_UNORM
-	{ 4, 4, 4, 16, true, false, false, true, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DXT3_UNORM
-	{ 4, 4, 4, 16, true, false, false, true, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DXT5_UNORM
-	{ 1, 4, 4, 8,  true, false, false, true, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_BC4_UNORM
-	{ 1, 4, 4, 8,  true, false, false, true, PIXELFORMATTYPE_SNORM  }, // PIXELFORMAT_BC4_SNORM
-	{ 2, 4, 4, 16, true, false, false, true, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_BC5_UNORM
-	{ 2, 4, 4, 16, true, false, false, true, PIXELFORMATTYPE_SNORM  }, // PIXELFORMAT_BC5_SNORM
-	{ 3, 4, 4, 16, true, false, false, true, PIXELFORMATTYPE_UFLOAT }, // PIXELFORMAT_BC6H_UFLOAT
-	{ 3, 4, 4, 16, true, false, false, true, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_BC6H_FLOAT
-	{ 4, 4, 4, 16, true, false, false, true, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_BC7_UNORM
-
-	{ 3, 16, 8, 32, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_PVR1_RGB2_UNORM
-	{ 3, 8,  8, 32, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_PVR1_RGB4_UNORM
-	{ 4, 16, 8, 32, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_PVR1_RGBA2_UNORM
-	{ 4, 8,  8, 32, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_PVR1_RGBA4_UNORM
-
-	{ 3, 4, 4, 8,  true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ETC1_UNORM
-	{ 3, 4, 4, 8,  true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ETC2_RGB_UNORM
-	{ 4, 4, 4, 16, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ETC2_RGBA_UNORM
-	{ 4, 4, 4, 8,  true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ETC2_RGBA1_UNORM
-	{ 1, 4, 4, 8,  true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_EAC_R_UNORM
-	{ 1, 4, 4, 8,  true, false, false, true, PIXELFORMATTYPE_SNORM }, // PIXELFORMAT_EAC_R_SNORM
-	{ 2, 4, 4, 16, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_EAC_RG_UNORM
-	{ 2, 4, 4, 16, true, false, false, true, PIXELFORMATTYPE_SNORM }, // PIXELFORMAT_EAC_RG_SNORM
-
-	{ 4, 4,  4,  1, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_4x4
-	{ 4, 5,  4,  1, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_5x4
-	{ 4, 5,  5,  1, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_5x5
-	{ 4, 6,  5,  1, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_6x5
-	{ 4, 6,  6,  1, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_6x6
-	{ 4, 8,  5,  1, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_8x5
-	{ 4, 8,  6,  1, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_8x6
-	{ 4, 8,  8,  1, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_8x8
-	{ 4, 8,  5,  1, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_10x5
-	{ 4, 10, 6,  1, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_10x6
-	{ 4, 10, 8,  1, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_10x8
-	{ 4, 10, 10, 1, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_10x10
-	{ 4, 12, 10, 1, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_12x10
-	{ 4, 12, 12, 1, true, false, false, true, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_12x12
+	// components, blockW, blockH, blockSize, color, depth, stencil, compressed, sRGB, dataType
+    { 0, 1, 1, 0, false, false, false, false, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_UNKNOWN
+
+	{ 0, 1, 1, 0, true, false, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_NORMAL
+	{ 0, 1, 1, 0, true, false, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_HDR
+
+	{ 1, 1, 1, 1, true, false, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_R8_UNORM
+	{ 1, 1, 1, 1, true, false, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_R8_INT
+	{ 1, 1, 1, 1, true, false, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_R8_UINT
+	{ 1, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_R16_UNORM
+	{ 1, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_R16_FLOAT
+	{ 1, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_R16_INT
+	{ 1, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_R16_UINT
+	{ 1, 1, 1, 4, true, false, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_R32_FLOAT
+	{ 1, 1, 1, 4, true, false, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_R32_INT
+	{ 1, 1, 1, 4, true, false, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_R32_UINT
+
+	{ 2, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RG8_UNORM
+	{ 2, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_RG8_INT
+	{ 2, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_RG8_UINT
+	{ 2, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_LA8_UNORM
+	{ 2, 1, 1, 4, true, false, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RG16_UNORM
+	{ 2, 1, 1, 4, true, false, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_RG16_FLOAT
+	{ 2, 1, 1, 4, true, false, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_RG16_INT
+	{ 2, 1, 1, 4, true, false, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_RG16_UINT
+	{ 2, 1, 1, 8, true, false, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_RG32_FLOAT
+	{ 2, 1, 1, 8, true, false, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_RG32_INT
+	{ 2, 1, 1, 8, true, false, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_RG32_UINT
+
+	{ 4, 1, 1, 4,  true, false, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RGBA8_UNORM
+	{ 4, 1, 1, 4,  true, false, false, false, true,  PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RGBA8_sRGB
+	{ 4, 1, 1, 4,  true, false, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_BGRA8_UNORM
+	{ 4, 1, 1, 4,  true, false, false, false, true,  PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_BGRA8_sRGB
+	{ 4, 1, 1, 4,  true, false, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_RGBA8_INT
+	{ 4, 1, 1, 4,  true, false, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_RGBA8_UINT
+	{ 4, 1, 1, 8,  true, false, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RGBA16_UNORM
+	{ 4, 1, 1, 8,  true, false, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_RGBA16_FLOAT
+	{ 4, 1, 1, 8,  true, false, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_RGBA16_INT
+	{ 4, 1, 1, 8,  true, false, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_RGBA16_UINT
+	{ 4, 1, 1, 16, true, false, false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_RGBA32_FLOAT
+	{ 4, 1, 1, 16, true, false, false, false, false, PIXELFORMATTYPE_SINT   }, // PIXELFORMAT_RGBA32_INT
+	{ 4, 1, 1, 16, true, false, false, false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_RGBA32_UINT
+
+	{ 4, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RGBA4_UNORM
+	{ 4, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RGB5A1_UNORM
+	{ 3, 1, 1, 2, true, false, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RGB565_UNORM
+	{ 4, 1, 1, 4, true, false, false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_RGB10A2_UNORM
+	{ 3, 1, 1, 4, true, false, false, false, false, PIXELFORMATTYPE_UFLOAT }, // PIXELFORMAT_RG11B10_FLOAT
+
+	{ 1, 1, 1, 1, false, false, true , false, false, PIXELFORMATTYPE_UINT   }, // PIXELFORMAT_STENCIL8
+	{ 1, 1, 1, 2, false, true,  false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DEPTH16_UNORM
+	{ 1, 1, 1, 3, false, true,  false, false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DEPTH24_UNORM
+	{ 1, 1, 1, 4, false, true,  false, false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_DEPTH32_FLOAT
+	{ 2, 1, 1, 4, false, true,  true , false, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DEPTH24_UNORM_STENCIL8
+	{ 2, 1, 1, 5, false, true,  true , false, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_DEPTH32_FLOAT_STENCIL8
+
+	{ 3, 4, 4, 8,  true, false, false, true, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DXT1_UNORM
+	{ 3, 4, 4, 8,  true, false, false, true, true,  PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DXT1_sRGB
+	{ 4, 4, 4, 16, true, false, false, true, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DXT3_UNORM
+	{ 4, 4, 4, 16, true, false, false, true, true,  PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DXT3_sRGB
+	{ 4, 4, 4, 16, true, false, false, true, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DXT5_UNORM
+	{ 4, 4, 4, 16, true, false, false, true, true,  PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_DXT5_sRGB
+	{ 1, 4, 4, 8,  true, false, false, true, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_BC4_UNORM
+	{ 1, 4, 4, 8,  true, false, false, true, false, PIXELFORMATTYPE_SNORM  }, // PIXELFORMAT_BC4_SNORM
+	{ 2, 4, 4, 16, true, false, false, true, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_BC5_UNORM
+	{ 2, 4, 4, 16, true, false, false, true, false, PIXELFORMATTYPE_SNORM  }, // PIXELFORMAT_BC5_SNORM
+	{ 3, 4, 4, 16, true, false, false, true, false, PIXELFORMATTYPE_UFLOAT }, // PIXELFORMAT_BC6H_UFLOAT
+	{ 3, 4, 4, 16, true, false, false, true, false, PIXELFORMATTYPE_SFLOAT }, // PIXELFORMAT_BC6H_FLOAT
+	{ 4, 4, 4, 16, true, false, false, true, false, PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_BC7_UNORM
+	{ 4, 4, 4, 16, true, false, false, true, true,  PIXELFORMATTYPE_UNORM  }, // PIXELFORMAT_BC7_sRGB
+
+	{ 3, 16, 8, 32, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_PVR1_RGB2_UNORM
+	{ 3, 16, 8, 32, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_PVR1_RGB2_sRGB
+	{ 3, 8,  8, 32, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_PVR1_RGB4_UNORM
+	{ 3, 8,  8, 32, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_PVR1_RGB4_sRGB
+	{ 4, 16, 8, 32, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_PVR1_RGBA2_UNORM
+	{ 4, 16, 8, 32, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_PVR1_RGBA2_sRGB
+	{ 4, 8,  8, 32, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_PVR1_RGBA4_UNORM
+	{ 4, 8,  8, 32, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_PVR1_RGBA4_sRGB
+
+	{ 3, 4, 4, 8,  true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ETC1_UNORM
+	{ 3, 4, 4, 8,  true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ETC2_RGB_UNORM
+	{ 3, 4, 4, 8,  true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ETC2_RGB_sRGB
+	{ 4, 4, 4, 16, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ETC2_RGBA_UNORM
+	{ 4, 4, 4, 16, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ETC2_RGBA_sRGB
+	{ 4, 4, 4, 8,  true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ETC2_RGBA1_UNORM
+	{ 4, 4, 4, 8,  true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ETC2_RGBA1_sRGB
+	{ 1, 4, 4, 8,  true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_EAC_R_UNORM
+	{ 1, 4, 4, 8,  true, false, false, true, false, PIXELFORMATTYPE_SNORM }, // PIXELFORMAT_EAC_R_SNORM
+	{ 2, 4, 4, 16, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_EAC_RG_UNORM
+	{ 2, 4, 4, 16, true, false, false, true, false, PIXELFORMATTYPE_SNORM }, // PIXELFORMAT_EAC_RG_SNORM
+
+	{ 4, 4,  4,  1, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_4x4_UNORM
+	{ 4, 5,  4,  1, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_5x4_UNORM
+	{ 4, 5,  5,  1, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_5x5_UNORM
+	{ 4, 6,  5,  1, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_6x5_UNORM
+	{ 4, 6,  6,  1, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_6x6_UNORM
+	{ 4, 8,  5,  1, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_8x5_UNORM
+	{ 4, 8,  6,  1, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_8x6_UNORM
+	{ 4, 8,  8,  1, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_8x8_UNORM
+	{ 4, 8,  5,  1, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_10x5_UNORM
+	{ 4, 10, 6,  1, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_10x6_UNORM
+	{ 4, 10, 8,  1, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_10x8_UNORM
+	{ 4, 10, 10, 1, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_10x10_UNORM
+	{ 4, 12, 10, 1, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_12x10_UNORM
+	{ 4, 12, 12, 1, true, false, false, true, false, PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_12x12_UNORM
+	{ 4, 4,  4,  1, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_4x4_sRGB
+	{ 4, 5,  4,  1, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_5x4_sRGB
+	{ 4, 5,  5,  1, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_5x5_sRGB
+	{ 4, 6,  5,  1, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_6x5_sRGB
+	{ 4, 6,  6,  1, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_6x6_sRGB
+	{ 4, 8,  5,  1, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_8x5_sRGB
+	{ 4, 8,  6,  1, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_8x6_sRGB
+	{ 4, 8,  8,  1, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_8x8_sRGB
+	{ 4, 8,  5,  1, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_10x5_sRGB
+	{ 4, 10, 6,  1, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_10x6_sRGB
+	{ 4, 10, 8,  1, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_10x8_sRGB
+	{ 4, 10, 10, 1, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_10x10_sRGB
+	{ 4, 12, 10, 1, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_12x10_sRGB
+	{ 4, 12, 12, 1, true, false, false, true, true,  PIXELFORMATTYPE_UNORM }, // PIXELFORMAT_ASTC_12x12_sRGB
 };
 };
 
 
 static_assert(sizeof(formatInfo) / sizeof(PixelFormatInfo) == PIXELFORMAT_MAX_ENUM, "Update the formatInfo array when adding or removing a PixelFormat");
 static_assert(sizeof(formatInfo) / sizeof(PixelFormatInfo) == PIXELFORMAT_MAX_ENUM, "Update the formatInfo array when adding or removing a PixelFormat");
@@ -155,19 +180,19 @@ static StringMap<PixelFormat, PIXELFORMAT_MAX_ENUM>::Entry formatEntries[] =
 	{ "rg32i",  PIXELFORMAT_RG32_INT   },
 	{ "rg32i",  PIXELFORMAT_RG32_INT   },
 	{ "rg32ui", PIXELFORMAT_RG32_UINT  },
 	{ "rg32ui", PIXELFORMAT_RG32_UINT  },
 
 
-	{ "rgba8",     PIXELFORMAT_RGBA8_UNORM      },
-	{ "srgba8",    PIXELFORMAT_RGBA8_UNORM_sRGB },
-	{ "bgra8",     PIXELFORMAT_BGRA8_UNORM      },
-	{ "bgra8srgb", PIXELFORMAT_BGRA8_UNORM_sRGB },
-	{ "rgba8i",    PIXELFORMAT_RGBA8_INT        },
-	{ "rgba8ui",   PIXELFORMAT_RGBA8_UINT       },
-	{ "rgba16",    PIXELFORMAT_RGBA16_UNORM     },
-	{ "rgba16f",   PIXELFORMAT_RGBA16_FLOAT     },
-	{ "rgba16i",   PIXELFORMAT_RGBA16_INT       },
-	{ "rgba16ui",  PIXELFORMAT_RGBA16_UINT      },
-	{ "rgba32f",   PIXELFORMAT_RGBA32_FLOAT     },
-	{ "rgba32i",   PIXELFORMAT_RGBA32_INT       },
-	{ "rgba32ui",  PIXELFORMAT_RGBA32_UINT      },
+	{ "rgba8",     PIXELFORMAT_RGBA8_UNORM  },
+	{ "srgba8",    PIXELFORMAT_RGBA8_sRGB   },
+	{ "bgra8",     PIXELFORMAT_BGRA8_UNORM  },
+	{ "bgra8srgb", PIXELFORMAT_BGRA8_sRGB   },
+	{ "rgba8i",    PIXELFORMAT_RGBA8_INT    },
+	{ "rgba8ui",   PIXELFORMAT_RGBA8_UINT   },
+	{ "rgba16",    PIXELFORMAT_RGBA16_UNORM },
+	{ "rgba16f",   PIXELFORMAT_RGBA16_FLOAT },
+	{ "rgba16i",   PIXELFORMAT_RGBA16_INT   },
+	{ "rgba16ui",  PIXELFORMAT_RGBA16_UINT  },
+	{ "rgba32f",   PIXELFORMAT_RGBA32_FLOAT },
+	{ "rgba32i",   PIXELFORMAT_RGBA32_INT   },
+	{ "rgba32ui",  PIXELFORMAT_RGBA32_UINT  },
 
 
 	{ "rgba4",    PIXELFORMAT_RGBA4_UNORM    },
 	{ "rgba4",    PIXELFORMAT_RGBA4_UNORM    },
 	{ "rgb5a1",   PIXELFORMAT_RGB5A1_UNORM   },
 	{ "rgb5a1",   PIXELFORMAT_RGB5A1_UNORM   },
@@ -182,43 +207,70 @@ static StringMap<PixelFormat, PIXELFORMAT_MAX_ENUM>::Entry formatEntries[] =
 	{ "depth24stencil8",  PIXELFORMAT_DEPTH24_UNORM_STENCIL8 },
 	{ "depth24stencil8",  PIXELFORMAT_DEPTH24_UNORM_STENCIL8 },
 	{ "depth32fstencil8", PIXELFORMAT_DEPTH32_FLOAT_STENCIL8 },
 	{ "depth32fstencil8", PIXELFORMAT_DEPTH32_FLOAT_STENCIL8 },
 	
 	
-	{ "DXT1",      PIXELFORMAT_DXT1_UNORM       },
-	{ "DXT3",      PIXELFORMAT_DXT3_UNORM       },
-	{ "DXT5",      PIXELFORMAT_DXT5_UNORM       },
-	{ "BC4",       PIXELFORMAT_BC4_UNORM        },
-	{ "BC4s",      PIXELFORMAT_BC4_SNORM        },
-	{ "BC5",       PIXELFORMAT_BC5_UNORM        },
-	{ "BC5s",      PIXELFORMAT_BC5_SNORM        },
-	{ "BC6h",      PIXELFORMAT_BC6H_UFLOAT      },
-	{ "BC6hs",     PIXELFORMAT_BC6H_FLOAT       },
-	{ "BC7",       PIXELFORMAT_BC7_UNORM        },
-	{ "PVR1rgb2",  PIXELFORMAT_PVR1_RGB2_UNORM  },
-	{ "PVR1rgb4",  PIXELFORMAT_PVR1_RGB4_UNORM  },
-	{ "PVR1rgba2", PIXELFORMAT_PVR1_RGBA2_UNORM },
-	{ "PVR1rgba4", PIXELFORMAT_PVR1_RGBA4_UNORM },
-	{ "ETC1",      PIXELFORMAT_ETC1_UNORM       },
-	{ "ETC2rgb",   PIXELFORMAT_ETC2_RGB_UNORM   },
-	{ "ETC2rgba",  PIXELFORMAT_ETC2_RGBA_UNORM  },
-	{ "ETC2rgba1", PIXELFORMAT_ETC2_RGBA1_UNORM },
-	{ "EACr",      PIXELFORMAT_EAC_R_UNORM      },
-	{ "EACrs",     PIXELFORMAT_EAC_R_SNORM      },
-	{ "EACrg",     PIXELFORMAT_EAC_RG_UNORM     },
-	{ "EACrgs",    PIXELFORMAT_EAC_RG_SNORM     },
-
-	{ "ASTC4x4",   PIXELFORMAT_ASTC_4x4   },
-	{ "ASTC5x4",   PIXELFORMAT_ASTC_5x4   },
-	{ "ASTC5x5",   PIXELFORMAT_ASTC_5x5   },
-	{ "ASTC6x5",   PIXELFORMAT_ASTC_6x5   },
-	{ "ASTC6x6",   PIXELFORMAT_ASTC_6x6   },
-	{ "ASTC8x5",   PIXELFORMAT_ASTC_8x5   },
-	{ "ASTC8x6",   PIXELFORMAT_ASTC_8x6   },
-	{ "ASTC8x8",   PIXELFORMAT_ASTC_8x8   },
-	{ "ASTC10x5",  PIXELFORMAT_ASTC_10x5  },
-	{ "ASTC10x6",  PIXELFORMAT_ASTC_10x6  },
-	{ "ASTC10x8",  PIXELFORMAT_ASTC_10x8  },
-	{ "ASTC10x10", PIXELFORMAT_ASTC_10x10 },
-	{ "ASTC12x10", PIXELFORMAT_ASTC_12x10 },
-	{ "ASTC12x12", PIXELFORMAT_ASTC_12x12 },
+	{ "DXT1",     PIXELFORMAT_DXT1_UNORM  },
+	{ "DXT1srgb", PIXELFORMAT_DXT1_sRGB   },
+	{ "DXT3",     PIXELFORMAT_DXT3_UNORM  },
+	{ "DXT3srgb", PIXELFORMAT_DXT3_sRGB   },
+	{ "DXT5",     PIXELFORMAT_DXT5_UNORM  },
+	{ "DXT5srgb", PIXELFORMAT_DXT5_sRGB   },
+	{ "BC4",      PIXELFORMAT_BC4_UNORM   },
+	{ "BC4s",     PIXELFORMAT_BC4_SNORM   },
+	{ "BC5",      PIXELFORMAT_BC5_UNORM   },
+	{ "BC5s",     PIXELFORMAT_BC5_SNORM   },
+	{ "BC6h",     PIXELFORMAT_BC6H_UFLOAT },
+	{ "BC6hs",    PIXELFORMAT_BC6H_FLOAT  },
+	{ "BC7",      PIXELFORMAT_BC7_UNORM   },
+	{ "BC7srgb",  PIXELFORMAT_BC7_sRGB    },
+
+	{ "PVR1rgb2",      PIXELFORMAT_PVR1_RGB2_UNORM  },
+	{ "PVR1rgb2srgb",  PIXELFORMAT_PVR1_RGB2_sRGB   },
+	{ "PVR1rgb4",      PIXELFORMAT_PVR1_RGB4_UNORM  },
+	{ "PVR1rgb4srgb",  PIXELFORMAT_PVR1_RGB4_sRGB   },
+	{ "PVR1rgba2",     PIXELFORMAT_PVR1_RGBA2_UNORM },
+	{ "PVR1rgba2srgb", PIXELFORMAT_PVR1_RGBA2_sRGB  },
+	{ "PVR1rgba4",     PIXELFORMAT_PVR1_RGBA4_UNORM },
+	{ "PVR1rgba4srgb", PIXELFORMAT_PVR1_RGBA4_sRGB  },
+
+	{ "ETC1",       PIXELFORMAT_ETC1_UNORM       },
+	{ "ETC2rgb",    PIXELFORMAT_ETC2_RGB_UNORM   },
+	{ "ETC2srgb",   PIXELFORMAT_ETC2_RGB_sRGB    },
+	{ "ETC2rgba",   PIXELFORMAT_ETC2_RGBA_UNORM  },
+	{ "ETC2srgba",  PIXELFORMAT_ETC2_RGBA_sRGB   },
+	{ "ETC2rgba1",  PIXELFORMAT_ETC2_RGBA1_UNORM },
+	{ "ETC2srgba1", PIXELFORMAT_ETC2_RGBA1_sRGB  },
+	{ "EACr",       PIXELFORMAT_EAC_R_UNORM      },
+	{ "EACrs",      PIXELFORMAT_EAC_R_SNORM      },
+	{ "EACrg",      PIXELFORMAT_EAC_RG_UNORM     },
+	{ "EACrgs",     PIXELFORMAT_EAC_RG_SNORM     },
+
+	{ "ASTC4x4",       PIXELFORMAT_ASTC_4x4_UNORM   },
+	{ "ASTC5x4",       PIXELFORMAT_ASTC_5x4_UNORM   },
+	{ "ASTC5x5",       PIXELFORMAT_ASTC_5x5_UNORM   },
+	{ "ASTC6x5",       PIXELFORMAT_ASTC_6x5_UNORM   },
+	{ "ASTC6x6",       PIXELFORMAT_ASTC_6x6_UNORM   },
+	{ "ASTC8x5",       PIXELFORMAT_ASTC_8x5_UNORM   },
+	{ "ASTC8x6",       PIXELFORMAT_ASTC_8x6_UNORM   },
+	{ "ASTC8x8",       PIXELFORMAT_ASTC_8x8_UNORM   },
+	{ "ASTC10x5",      PIXELFORMAT_ASTC_10x5_UNORM  },
+	{ "ASTC10x6",      PIXELFORMAT_ASTC_10x6_UNORM  },
+	{ "ASTC10x8",      PIXELFORMAT_ASTC_10x8_UNORM  },
+	{ "ASTC10x10",     PIXELFORMAT_ASTC_10x10_UNORM },
+	{ "ASTC12x10",     PIXELFORMAT_ASTC_12x10_UNORM },
+	{ "ASTC12x12",     PIXELFORMAT_ASTC_12x12_UNORM },
+	{ "ASTC4x4srgb",   PIXELFORMAT_ASTC_4x4_sRGB    },
+	{ "ASTC5x4srgb",   PIXELFORMAT_ASTC_5x4_sRGB    },
+	{ "ASTC5x5srgb",   PIXELFORMAT_ASTC_5x5_sRGB    },
+	{ "ASTC6x5srgb",   PIXELFORMAT_ASTC_6x5_sRGB    },
+	{ "ASTC6x6srgb",   PIXELFORMAT_ASTC_6x6_sRGB    },
+	{ "ASTC8x5srgb",   PIXELFORMAT_ASTC_8x5_sRGB    },
+	{ "ASTC8x6srgb",   PIXELFORMAT_ASTC_8x6_sRGB    },
+	{ "ASTC8x8srgb",   PIXELFORMAT_ASTC_8x8_sRGB    },
+	{ "ASTC10x5srgb",  PIXELFORMAT_ASTC_10x5_sRGB   },
+	{ "ASTC10x6srgb",  PIXELFORMAT_ASTC_10x6_sRGB   },
+	{ "ASTC10x8srgb",  PIXELFORMAT_ASTC_10x8_sRGB   },
+	{ "ASTC10x10srgb", PIXELFORMAT_ASTC_10x10_sRGB  },
+	{ "ASTC12x10srgb", PIXELFORMAT_ASTC_12x10_sRGB  },
+	{ "ASTC12x12srgb", PIXELFORMAT_ASTC_12x12_sRGB  },
 };
 };
 
 
 static_assert(sizeof(formatEntries) / sizeof(formatEntries[0]) == (size_t) PIXELFORMAT_MAX_ENUM, "pixel format string map is missing entries!");
 static_assert(sizeof(formatEntries) / sizeof(formatEntries[0]) == (size_t) PIXELFORMAT_MAX_ENUM, "pixel format string map is missing entries!");
@@ -275,7 +327,7 @@ bool isPixelFormatStencil(PixelFormat format)
 
 
 bool isPixelFormatSRGB(PixelFormat format)
 bool isPixelFormatSRGB(PixelFormat format)
 {
 {
-	return format == PIXELFORMAT_RGBA8_UNORM_sRGB || format == PIXELFORMAT_BGRA8_UNORM_sRGB;
+	return formatInfo[format].sRGB;
 }
 }
 
 
 bool isPixelFormatInteger(PixelFormat format)
 bool isPixelFormatInteger(PixelFormat format)
@@ -286,20 +338,65 @@ bool isPixelFormatInteger(PixelFormat format)
 
 
 PixelFormat getSRGBPixelFormat(PixelFormat format)
 PixelFormat getSRGBPixelFormat(PixelFormat format)
 {
 {
-	if (format == PIXELFORMAT_RGBA8_UNORM)
-		return PIXELFORMAT_RGBA8_UNORM_sRGB;
-	else if (format == PIXELFORMAT_BGRA8_UNORM)
-		return PIXELFORMAT_BGRA8_UNORM_sRGB;
-	return format;
+	switch (format)
+	{
+	case PIXELFORMAT_RGBA8_UNORM: return PIXELFORMAT_RGBA8_sRGB;
+	case PIXELFORMAT_BGRA8_UNORM: return PIXELFORMAT_BGRA8_sRGB;
+	case PIXELFORMAT_PVR1_RGB2_UNORM: return PIXELFORMAT_PVR1_RGB2_sRGB;
+	case PIXELFORMAT_PVR1_RGB4_UNORM: return PIXELFORMAT_PVR1_RGB4_sRGB;
+	case PIXELFORMAT_PVR1_RGBA2_UNORM: return PIXELFORMAT_PVR1_RGBA2_sRGB;
+	case PIXELFORMAT_PVR1_RGBA4_UNORM: return PIXELFORMAT_PVR1_RGBA4_sRGB;
+	case PIXELFORMAT_ETC1_UNORM: return PIXELFORMAT_ETC2_RGB_sRGB; // ETC2 can load ETC1 data.
+	case PIXELFORMAT_ETC2_RGB_UNORM: return PIXELFORMAT_ETC2_RGB_sRGB;
+	case PIXELFORMAT_ETC2_RGBA_UNORM: return PIXELFORMAT_ETC2_RGBA_sRGB;
+	case PIXELFORMAT_ETC2_RGBA1_UNORM: return PIXELFORMAT_ETC2_RGBA1_sRGB;
+	case PIXELFORMAT_ASTC_4x4_UNORM: return PIXELFORMAT_ASTC_4x4_sRGB;
+	case PIXELFORMAT_ASTC_5x4_UNORM: return PIXELFORMAT_ASTC_5x4_sRGB;
+	case PIXELFORMAT_ASTC_5x5_UNORM: return PIXELFORMAT_ASTC_5x5_sRGB;
+	case PIXELFORMAT_ASTC_6x5_UNORM: return PIXELFORMAT_ASTC_6x5_sRGB;
+	case PIXELFORMAT_ASTC_6x6_UNORM: return PIXELFORMAT_ASTC_6x6_sRGB;
+	case PIXELFORMAT_ASTC_8x5_UNORM: return PIXELFORMAT_ASTC_8x5_sRGB;
+	case PIXELFORMAT_ASTC_8x6_UNORM: return PIXELFORMAT_ASTC_8x6_sRGB;
+	case PIXELFORMAT_ASTC_8x8_UNORM: return PIXELFORMAT_ASTC_8x8_sRGB;
+	case PIXELFORMAT_ASTC_10x5_UNORM: return PIXELFORMAT_ASTC_10x5_sRGB;
+	case PIXELFORMAT_ASTC_10x6_UNORM: return PIXELFORMAT_ASTC_10x6_sRGB;
+	case PIXELFORMAT_ASTC_10x8_UNORM: return PIXELFORMAT_ASTC_10x8_sRGB;
+	case PIXELFORMAT_ASTC_10x10_UNORM: return PIXELFORMAT_ASTC_10x10_sRGB;
+	case PIXELFORMAT_ASTC_12x10_UNORM: return PIXELFORMAT_ASTC_12x10_sRGB;
+	case PIXELFORMAT_ASTC_12x12_UNORM: return PIXELFORMAT_ASTC_12x12_sRGB;
+	default: return format;
+	}
 }
 }
 
 
 PixelFormat getLinearPixelFormat(PixelFormat format)
 PixelFormat getLinearPixelFormat(PixelFormat format)
 {
 {
-	if (format == PIXELFORMAT_RGBA8_UNORM_sRGB)
-		return PIXELFORMAT_RGBA8_UNORM;
-	else if (format == PIXELFORMAT_BGRA8_UNORM_sRGB)
-		return PIXELFORMAT_BGRA8_UNORM;
-	return format;
+	switch (format)
+	{
+	case PIXELFORMAT_RGBA8_sRGB: return PIXELFORMAT_RGBA8_UNORM;
+	case PIXELFORMAT_BGRA8_sRGB: return PIXELFORMAT_BGRA8_UNORM;
+	case PIXELFORMAT_PVR1_RGB2_sRGB: return PIXELFORMAT_PVR1_RGB2_UNORM;
+	case PIXELFORMAT_PVR1_RGB4_sRGB: return PIXELFORMAT_PVR1_RGB4_UNORM;
+	case PIXELFORMAT_PVR1_RGBA2_sRGB: return PIXELFORMAT_PVR1_RGBA2_UNORM;
+	case PIXELFORMAT_PVR1_RGBA4_sRGB: return PIXELFORMAT_PVR1_RGBA4_UNORM;
+	case PIXELFORMAT_ETC2_RGB_sRGB: return PIXELFORMAT_ETC2_RGB_UNORM;
+	case PIXELFORMAT_ETC2_RGBA_sRGB: return PIXELFORMAT_ETC2_RGBA_UNORM;
+	case PIXELFORMAT_ETC2_RGBA1_sRGB: return PIXELFORMAT_ETC2_RGBA1_UNORM;
+	case PIXELFORMAT_ASTC_4x4_sRGB: return PIXELFORMAT_ASTC_4x4_UNORM;
+	case PIXELFORMAT_ASTC_5x4_sRGB: return PIXELFORMAT_ASTC_5x4_UNORM;
+	case PIXELFORMAT_ASTC_5x5_sRGB: return PIXELFORMAT_ASTC_5x5_UNORM;
+	case PIXELFORMAT_ASTC_6x5_sRGB: return PIXELFORMAT_ASTC_6x5_UNORM;
+	case PIXELFORMAT_ASTC_6x6_sRGB: return PIXELFORMAT_ASTC_6x6_UNORM;
+	case PIXELFORMAT_ASTC_8x5_sRGB: return PIXELFORMAT_ASTC_8x5_UNORM;
+	case PIXELFORMAT_ASTC_8x6_sRGB: return PIXELFORMAT_ASTC_8x6_UNORM;
+	case PIXELFORMAT_ASTC_8x8_sRGB: return PIXELFORMAT_ASTC_8x8_UNORM;
+	case PIXELFORMAT_ASTC_10x5_sRGB: return PIXELFORMAT_ASTC_10x5_UNORM;
+	case PIXELFORMAT_ASTC_10x6_sRGB: return PIXELFORMAT_ASTC_10x6_UNORM;
+	case PIXELFORMAT_ASTC_10x8_sRGB: return PIXELFORMAT_ASTC_10x8_UNORM;
+	case PIXELFORMAT_ASTC_10x10_sRGB: return PIXELFORMAT_ASTC_10x10_UNORM;
+	case PIXELFORMAT_ASTC_12x10_sRGB: return PIXELFORMAT_ASTC_12x10_UNORM;
+	case PIXELFORMAT_ASTC_12x12_sRGB: return PIXELFORMAT_ASTC_12x12_UNORM;
+	default: return format;
+	}
 }
 }
 
 
 size_t getPixelFormatBlockSize(PixelFormat format)
 size_t getPixelFormatBlockSize(PixelFormat format)

+ 45 - 16
src/common/pixelformat.h

@@ -60,9 +60,9 @@ enum PixelFormat
 
 
 	// 4-channel normal formats
 	// 4-channel normal formats
 	PIXELFORMAT_RGBA8_UNORM,
 	PIXELFORMAT_RGBA8_UNORM,
-	PIXELFORMAT_RGBA8_UNORM_sRGB,
+	PIXELFORMAT_RGBA8_sRGB,
 	PIXELFORMAT_BGRA8_UNORM,
 	PIXELFORMAT_BGRA8_UNORM,
-	PIXELFORMAT_BGRA8_UNORM_sRGB,
+	PIXELFORMAT_BGRA8_sRGB,
 	PIXELFORMAT_RGBA8_INT,
 	PIXELFORMAT_RGBA8_INT,
 	PIXELFORMAT_RGBA8_UINT,
 	PIXELFORMAT_RGBA8_UINT,
 	PIXELFORMAT_RGBA16_UNORM,
 	PIXELFORMAT_RGBA16_UNORM,
@@ -90,8 +90,11 @@ enum PixelFormat
 
 
 	// compressed formats
 	// compressed formats
 	PIXELFORMAT_DXT1_UNORM,
 	PIXELFORMAT_DXT1_UNORM,
+	PIXELFORMAT_DXT1_sRGB,
 	PIXELFORMAT_DXT3_UNORM,
 	PIXELFORMAT_DXT3_UNORM,
+	PIXELFORMAT_DXT3_sRGB,
 	PIXELFORMAT_DXT5_UNORM,
 	PIXELFORMAT_DXT5_UNORM,
+	PIXELFORMAT_DXT5_sRGB,
 	PIXELFORMAT_BC4_UNORM,
 	PIXELFORMAT_BC4_UNORM,
 	PIXELFORMAT_BC4_SNORM,
 	PIXELFORMAT_BC4_SNORM,
 	PIXELFORMAT_BC5_UNORM,
 	PIXELFORMAT_BC5_UNORM,
@@ -99,32 +102,57 @@ enum PixelFormat
 	PIXELFORMAT_BC6H_UFLOAT,
 	PIXELFORMAT_BC6H_UFLOAT,
 	PIXELFORMAT_BC6H_FLOAT,
 	PIXELFORMAT_BC6H_FLOAT,
 	PIXELFORMAT_BC7_UNORM,
 	PIXELFORMAT_BC7_UNORM,
+	PIXELFORMAT_BC7_sRGB,
+
 	PIXELFORMAT_PVR1_RGB2_UNORM,
 	PIXELFORMAT_PVR1_RGB2_UNORM,
+	PIXELFORMAT_PVR1_RGB2_sRGB,
 	PIXELFORMAT_PVR1_RGB4_UNORM,
 	PIXELFORMAT_PVR1_RGB4_UNORM,
+	PIXELFORMAT_PVR1_RGB4_sRGB,
 	PIXELFORMAT_PVR1_RGBA2_UNORM,
 	PIXELFORMAT_PVR1_RGBA2_UNORM,
+	PIXELFORMAT_PVR1_RGBA2_sRGB,
 	PIXELFORMAT_PVR1_RGBA4_UNORM,
 	PIXELFORMAT_PVR1_RGBA4_UNORM,
+	PIXELFORMAT_PVR1_RGBA4_sRGB,
+
 	PIXELFORMAT_ETC1_UNORM,
 	PIXELFORMAT_ETC1_UNORM,
 	PIXELFORMAT_ETC2_RGB_UNORM,
 	PIXELFORMAT_ETC2_RGB_UNORM,
+	PIXELFORMAT_ETC2_RGB_sRGB,
 	PIXELFORMAT_ETC2_RGBA_UNORM,
 	PIXELFORMAT_ETC2_RGBA_UNORM,
+	PIXELFORMAT_ETC2_RGBA_sRGB,
 	PIXELFORMAT_ETC2_RGBA1_UNORM,
 	PIXELFORMAT_ETC2_RGBA1_UNORM,
+	PIXELFORMAT_ETC2_RGBA1_sRGB,
 	PIXELFORMAT_EAC_R_UNORM,
 	PIXELFORMAT_EAC_R_UNORM,
 	PIXELFORMAT_EAC_R_SNORM,
 	PIXELFORMAT_EAC_R_SNORM,
 	PIXELFORMAT_EAC_RG_UNORM,
 	PIXELFORMAT_EAC_RG_UNORM,
 	PIXELFORMAT_EAC_RG_SNORM,
 	PIXELFORMAT_EAC_RG_SNORM,
-	PIXELFORMAT_ASTC_4x4,
-	PIXELFORMAT_ASTC_5x4,
-	PIXELFORMAT_ASTC_5x5,
-	PIXELFORMAT_ASTC_6x5,
-	PIXELFORMAT_ASTC_6x6,
-	PIXELFORMAT_ASTC_8x5,
-	PIXELFORMAT_ASTC_8x6,
-	PIXELFORMAT_ASTC_8x8,
-	PIXELFORMAT_ASTC_10x5,
-	PIXELFORMAT_ASTC_10x6,
-	PIXELFORMAT_ASTC_10x8,
-	PIXELFORMAT_ASTC_10x10,
-	PIXELFORMAT_ASTC_12x10,
-	PIXELFORMAT_ASTC_12x12,
+
+	PIXELFORMAT_ASTC_4x4_UNORM,
+	PIXELFORMAT_ASTC_5x4_UNORM,
+	PIXELFORMAT_ASTC_5x5_UNORM,
+	PIXELFORMAT_ASTC_6x5_UNORM,
+	PIXELFORMAT_ASTC_6x6_UNORM,
+	PIXELFORMAT_ASTC_8x5_UNORM,
+	PIXELFORMAT_ASTC_8x6_UNORM,
+	PIXELFORMAT_ASTC_8x8_UNORM,
+	PIXELFORMAT_ASTC_10x5_UNORM,
+	PIXELFORMAT_ASTC_10x6_UNORM,
+	PIXELFORMAT_ASTC_10x8_UNORM,
+	PIXELFORMAT_ASTC_10x10_UNORM,
+	PIXELFORMAT_ASTC_12x10_UNORM,
+	PIXELFORMAT_ASTC_12x12_UNORM,
+	PIXELFORMAT_ASTC_4x4_sRGB,
+	PIXELFORMAT_ASTC_5x4_sRGB,
+	PIXELFORMAT_ASTC_5x5_sRGB,
+	PIXELFORMAT_ASTC_6x5_sRGB,
+	PIXELFORMAT_ASTC_6x6_sRGB,
+	PIXELFORMAT_ASTC_8x5_sRGB,
+	PIXELFORMAT_ASTC_8x6_sRGB,
+	PIXELFORMAT_ASTC_8x8_sRGB,
+	PIXELFORMAT_ASTC_10x5_sRGB,
+	PIXELFORMAT_ASTC_10x6_sRGB,
+	PIXELFORMAT_ASTC_10x8_sRGB,
+	PIXELFORMAT_ASTC_10x10_sRGB,
+	PIXELFORMAT_ASTC_12x10_sRGB,
+	PIXELFORMAT_ASTC_12x12_sRGB,
 
 
 	PIXELFORMAT_MAX_ENUM
 	PIXELFORMAT_MAX_ENUM
 };
 };
@@ -149,6 +177,7 @@ struct PixelFormatInfo
 	bool depth;
 	bool depth;
 	bool stencil;
 	bool stencil;
 	bool compressed;
 	bool compressed;
+	bool sRGB;
 	PixelFormatType dataType;
 	PixelFormatType dataType;
 };
 };
 
 

+ 1 - 1
src/modules/data/wrap_ByteData.cpp

@@ -56,7 +56,7 @@ int w_ByteData_setString(lua_State *L)
 	if (size == 0)
 	if (size == 0)
 		return 0;
 		return 0;
 
 
-	if (offset < 0 || offset + size > (int64) t->getSize())
+	if (offset < 0 || offset + (int64) size > (int64) t->getSize())
 		return luaL_error(L, "The given string offset and size don't fit within the Data's size.");
 		return luaL_error(L, "The given string offset and size don't fit within the Data's size.");
 
 
 	memcpy((char *) t->getData() + (size_t) offset, str, size);
 	memcpy((char *) t->getData() + (size_t) offset, str, size);

+ 3 - 13
src/modules/font/Font.cpp

@@ -45,14 +45,9 @@ Font::Font()
 	defaultFontData.set(new data::ByteData(fontdata, rawsize, true), Acquire::NORETAIN);
 	defaultFontData.set(new data::ByteData(fontdata, rawsize, true), Acquire::NORETAIN);
 }
 }
 
 
-Rasterizer *Font::newTrueTypeRasterizer(int size, TrueTypeRasterizer::Hinting hinting)
+Rasterizer *Font::newTrueTypeRasterizer(int size, const TrueTypeRasterizer::Settings &settings)
 {
 {
-	return newTrueTypeRasterizer(defaultFontData.get(), size, hinting);
-}
-
-Rasterizer *Font::newTrueTypeRasterizer(int size, float dpiscale, TrueTypeRasterizer::Hinting hinting)
-{
-	return newTrueTypeRasterizer(defaultFontData.get(), size, dpiscale, hinting);
+	return newTrueTypeRasterizer(defaultFontData.get(), size, settings);
 }
 }
 
 
 Rasterizer *Font::newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &images, float dpiscale)
 Rasterizer *Font::newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &images, float dpiscale)
@@ -78,12 +73,7 @@ Rasterizer *Font::newImageRasterizer(love::image::ImageData *data, const std::st
 		throw love::Exception("UTF-8 decoding error: %s", e.what());
 		throw love::Exception("UTF-8 decoding error: %s", e.what());
 	}
 	}
 
 
-	return newImageRasterizer(data, &glyphs[0], (int) glyphs.size(), extraspacing, dpiscale);
-}
-
-Rasterizer *Font::newImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale)
-{
-	return new ImageRasterizer(data, glyphs, numglyphs, extraspacing, dpiscale);
+	return new ImageRasterizer(data, glyphs.data(), (int) glyphs.size(), extraspacing, dpiscale);
 }
 }
 
 
 GlyphData *Font::newGlyphData(Rasterizer *r, const std::string &text)
 GlyphData *Font::newGlyphData(Rasterizer *r, const std::string &text)

+ 2 - 5
src/modules/font/Font.h

@@ -48,15 +48,12 @@ public:
 
 
 	virtual Rasterizer *newRasterizer(love::filesystem::FileData *data) = 0;
 	virtual Rasterizer *newRasterizer(love::filesystem::FileData *data) = 0;
 
 
-	virtual Rasterizer *newTrueTypeRasterizer(int size, TrueTypeRasterizer::Hinting hinting);
-	virtual Rasterizer *newTrueTypeRasterizer(int size, float dpiscale, TrueTypeRasterizer::Hinting hinting);
-	virtual Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, TrueTypeRasterizer::Hinting hinting) = 0;
-	virtual Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, float dpiscale, TrueTypeRasterizer::Hinting hinting) = 0;
+	Rasterizer *newTrueTypeRasterizer(int size, const TrueTypeRasterizer::Settings &settings);
+	virtual Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, const TrueTypeRasterizer::Settings &settings) = 0;
 
 
 	virtual Rasterizer *newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &images, float dpiscale);
 	virtual Rasterizer *newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &images, float dpiscale);
 
 
 	virtual Rasterizer *newImageRasterizer(love::image::ImageData *data, const std::string &glyphs, int extraspacing, float dpiscale);
 	virtual Rasterizer *newImageRasterizer(love::image::ImageData *data, const std::string &glyphs, int extraspacing, float dpiscale);
-	virtual Rasterizer *newImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int length, int extraspacing, float dpiscale);
 
 
 	virtual GlyphData *newGlyphData(Rasterizer *r, const std::string &glyph);
 	virtual GlyphData *newGlyphData(Rasterizer *r, const std::string &glyph);
 	virtual GlyphData *newGlyphData(Rasterizer *r, uint32 glyph);
 	virtual GlyphData *newGlyphData(Rasterizer *r, uint32 glyph);

+ 7 - 0
src/modules/font/TrueTypeRasterizer.h

@@ -24,6 +24,7 @@
 // LOVE
 // LOVE
 #include "Rasterizer.h"
 #include "Rasterizer.h"
 #include "common/StringMap.h"
 #include "common/StringMap.h"
+#include "common/Optional.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -44,6 +45,12 @@ public:
 		HINTING_MAX_ENUM
 		HINTING_MAX_ENUM
 	};
 	};
 
 
+	struct Settings
+	{
+		Hinting hinting = HINTING_NORMAL;
+		OptionalFloat dpiScale;
+	};
+
 	virtual ~TrueTypeRasterizer() {}
 	virtual ~TrueTypeRasterizer() {}
 
 
 	static bool getConstant(const char *in, Hinting &out);
 	static bool getConstant(const char *in, Hinting &out);

+ 5 - 10
src/modules/font/freetype/Font.cpp

@@ -49,26 +49,21 @@ Font::~Font()
 Rasterizer *Font::newRasterizer(love::filesystem::FileData *data)
 Rasterizer *Font::newRasterizer(love::filesystem::FileData *data)
 {
 {
 	if (TrueTypeRasterizer::accepts(library, data))
 	if (TrueTypeRasterizer::accepts(library, data))
-		return newTrueTypeRasterizer(data, 12, TrueTypeRasterizer::HINTING_NORMAL);
+		return newTrueTypeRasterizer(data, 12, font::TrueTypeRasterizer::Settings());
 	else if (BMFontRasterizer::accepts(data))
 	else if (BMFontRasterizer::accepts(data))
 		return newBMFontRasterizer(data, {}, 1.0f);
 		return newBMFontRasterizer(data, {}, 1.0f);
 
 
 	throw love::Exception("Invalid font file: %s", data->getFilename().c_str());
 	throw love::Exception("Invalid font file: %s", data->getFilename().c_str());
 }
 }
 
 
-Rasterizer *Font::newTrueTypeRasterizer(love::Data *data, int size, TrueTypeRasterizer::Hinting hinting)
+Rasterizer *Font::newTrueTypeRasterizer(love::Data *data, int size, const font::TrueTypeRasterizer::Settings &settings)
 {
 {
-	float dpiscale = 1.0f;
+	float defaultdpiscale = 1.0f;
 	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	if (window != nullptr)
 	if (window != nullptr)
-		dpiscale = window->getDPIScale();
+		defaultdpiscale = window->getDPIScale();
 
 
-	return newTrueTypeRasterizer(data, size, dpiscale, hinting);
-}
-
-Rasterizer *Font::newTrueTypeRasterizer(love::Data *data, int size, float dpiscale, TrueTypeRasterizer::Hinting hinting)
-{
-	return new TrueTypeRasterizer(library, data, size, dpiscale, hinting);
+	return new TrueTypeRasterizer(library, data, size, settings, defaultdpiscale);
 }
 }
 
 
 const char *Font::getName() const
 const char *Font::getName() const

+ 1 - 2
src/modules/font/freetype/Font.h

@@ -45,8 +45,7 @@ public:
 
 
 	// Implements Font
 	// Implements Font
 	Rasterizer *newRasterizer(love::filesystem::FileData *data) override;
 	Rasterizer *newRasterizer(love::filesystem::FileData *data) override;
-	Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, TrueTypeRasterizer::Hinting hinting) override;
-	Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, float dpiscale, TrueTypeRasterizer::Hinting hinting) override;
+	Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, const font::TrueTypeRasterizer::Settings &settings) override;
 
 
 	// Implement Module
 	// Implement Module
 	const char *getName() const override;
 	const char *getName() const override;

+ 6 - 6
src/modules/font/freetype/HarfbuzzShaper.cpp

@@ -153,18 +153,18 @@ void HarfbuzzShaper::computeBufferRanges(const ColoredCodepoints &codepoints, Ra
 
 
 		hb_shape(hbFonts[rasti], hbb, nullptr, 0);
 		hb_shape(hbFonts[rasti], hbb, nullptr, 0);
 
 
-		int glyphcount = (int)hb_buffer_get_length(hbb);
+		size_t glyphcount = (size_t)hb_buffer_get_length(hbb);
 		const hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbb, nullptr);
 		const hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbb, nullptr);
 		hb_direction_t direction = hb_buffer_get_direction(hbb);
 		hb_direction_t direction = hb_buffer_get_direction(hbb);
 
 
 		fallbackranges.clear();
 		fallbackranges.clear();
 
 
-		for (int i = 0; i < glyphcount; i++)
+		for (size_t i = 0; i < glyphcount; i++)
 		{
 		{
 			if (isValidGlyph(glyphinfos[i].codepoint, codepoints.cps, glyphinfos[i].cluster))
 			if (isValidGlyph(glyphinfos[i].codepoint, codepoints.cps, glyphinfos[i].cluster))
 			{
 			{
 				if (bufferranges.empty() || bufferranges.back().index != rasti || bufferranges.back().range.getMax() + 1 != i)
 				if (bufferranges.empty() || bufferranges.back().index != rasti || bufferranges.back().range.getMax() + 1 != i)
-					bufferranges.push_back({(int)rasti, (int)glyphinfos[i].cluster, Range(i, 1)});
+					bufferranges.push_back({rasti, (int)glyphinfos[i].cluster, Range(i, 1)});
 				else
 				else
 					bufferranges.back().range.last++;
 					bufferranges.back().range.last++;
 			}
 			}
@@ -247,7 +247,7 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints,
 			// TODO: this doesn't handle situations where the user inserted a color
 			// TODO: this doesn't handle situations where the user inserted a color
 			// change in the middle of some characters that get combined into a single
 			// change in the middle of some characters that get combined into a single
 			// cluster.
 			// cluster.
-			if (colors && colorindex < ncolors && codepoints.colors[colorindex].index == info.cluster)
+			if (colors && colorindex < ncolors && codepoints.colors[colorindex].index == (int)info.cluster)
 			{
 			{
 				colorToAdd.set(codepoints.colors[colorindex].color);
 				colorToAdd.set(codepoints.colors[colorindex].color);
 				colorindex++;
 				colorindex++;
@@ -273,7 +273,7 @@ void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints,
 				continue;
 				continue;
 
 
 			// This is a glyph index at this point, despite the name.
 			// This is a glyph index at this point, despite the name.
-			GlyphIndex gindex = { (int) info.codepoint, bufferrange.index };
+			GlyphIndex gindex = { (int) info.codepoint, (int)bufferrange.index };
 
 
 			if (clustercodepoint == '\t' && isUsingSpacesForTab())
 			if (clustercodepoint == '\t' && isUsingSpacesForTab())
 			{
 			{
@@ -390,7 +390,7 @@ int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints &codepoints, Ra
 				if (newwidth > wraplimit)
 				if (newwidth > wraplimit)
 				{
 				{
 					// If this is the first character, wrap from the next one instead of this one.
 					// If this is the first character, wrap from the next one instead of this one.
-					int wrapindex = info.cluster > (int) range.first ? info.cluster : (int) range.first + 1;
+					int wrapindex = (int)info.cluster > (int)range.first ? (int)info.cluster : (int)range.first + 1;
 
 
 					// Rewind to after the last seen space when wrapping.
 					// Rewind to after the last seen space when wrapping.
 					if (firstindexafterspace != -1)
 					if (firstindexafterspace != -1)

+ 1 - 1
src/modules/font/freetype/HarfbuzzShaper.h

@@ -53,7 +53,7 @@ private:
 
 
 	struct BufferRange
 	struct BufferRange
 	{
 	{
-		int index;
+		size_t index;
 		int codepointStart;
 		int codepointStart;
 		Range range;
 		Range range;
 	};
 	};

+ 4 - 4
src/modules/font/freetype/TrueTypeRasterizer.cpp

@@ -33,12 +33,12 @@ namespace font
 namespace freetype
 namespace freetype
 {
 {
 
 
-TrueTypeRasterizer::TrueTypeRasterizer(FT_Library library, love::Data *data, int size, float dpiscale, Hinting hinting)
+TrueTypeRasterizer::TrueTypeRasterizer(FT_Library library, love::Data *data, int size, const Settings &settings, float defaultdpiscale)
 	: data(data)
 	: data(data)
-	, hinting(hinting)
+	, hinting(settings.hinting)
 {
 {
-	this->dpiScale = dpiscale;
-	size = floorf(size * dpiscale + 0.5f);
+	dpiScale = settings.dpiScale.get(defaultdpiscale);
+	size = floorf(size * dpiScale + 0.5f);
 
 
 	if (size <= 0)
 	if (size <= 0)
 		throw love::Exception("Invalid TrueType font size: %d", size);
 		throw love::Exception("Invalid TrueType font size: %d", size);

+ 1 - 1
src/modules/font/freetype/TrueTypeRasterizer.h

@@ -44,7 +44,7 @@ class TrueTypeRasterizer : public love::font::TrueTypeRasterizer
 {
 {
 public:
 public:
 
 
-	TrueTypeRasterizer(FT_Library library, love::Data *data, int size, float dpiscale, Hinting hinting);
+	TrueTypeRasterizer(FT_Library library, love::Data *data, int size, const Settings &settings, float defaultdpiscale);
 	virtual ~TrueTypeRasterizer();
 	virtual ~TrueTypeRasterizer();
 
 
 	// Implement Rasterizer
 	// Implement Rasterizer

+ 51 - 33
src/modules/font/wrap_Font.cpp

@@ -64,30 +64,65 @@ int w_newRasterizer(lua_State *L)
 	}
 	}
 }
 }
 
 
+static TrueTypeRasterizer::Settings luax_checktruetypesettings(lua_State* L, int startidx)
+{
+	TrueTypeRasterizer::Settings s;
+
+	if (lua_type(L, startidx) == LUA_TSTRING)
+	{
+		// Legacy parameters.
+		const char *hintstr = lua_isnoneornil(L, startidx) ? nullptr : luaL_checkstring(L, startidx);
+		if (hintstr && !TrueTypeRasterizer::getConstant(hintstr, s.hinting))
+			luax_enumerror(L, "TrueType font hinting mode", TrueTypeRasterizer::getConstants(s.hinting), hintstr);
+
+		if (!lua_isnoneornil(L, startidx + 1))
+			s.dpiScale.set((float)luaL_checknumber(L, startidx + 1));
+	}
+	else
+	{
+		luaL_checktype(L, startidx, LUA_TTABLE);
+
+		lua_getfield(L, startidx, "hinting");
+		if (!lua_isnoneornil(L, -1))
+		{
+			const char *hintstr = luaL_checkstring(L, -1);
+			if (!TrueTypeRasterizer::getConstant(hintstr, s.hinting))
+				luax_enumerror(L, "TrueType font hinting mode", TrueTypeRasterizer::getConstants(s.hinting), hintstr);
+		}
+		lua_pop(L, 1);
+
+		lua_getfield(L, startidx, "dpiscale");
+		if (!lua_isnoneornil(L, -1))
+			s.dpiScale.set((float)luaL_checknumber(L, -1));
+		lua_pop(L, 1);
+	}
+
+	return s;
+}
+
 int w_newTrueTypeRasterizer(lua_State *L)
 int w_newTrueTypeRasterizer(lua_State *L)
 {
 {
 	Rasterizer *t = nullptr;
 	Rasterizer *t = nullptr;
-	TrueTypeRasterizer::Hinting hinting = TrueTypeRasterizer::HINTING_NORMAL;
 
 
 	if (lua_type(L, 1) == LUA_TNUMBER || lua_isnone(L, 1))
 	if (lua_type(L, 1) == LUA_TNUMBER || lua_isnone(L, 1))
 	{
 	{
 		// First argument is a number: use the default TrueType font.
 		// First argument is a number: use the default TrueType font.
 		int size = (int) luaL_optinteger(L, 1, 13);
 		int size = (int) luaL_optinteger(L, 1, 13);
 
 
-		const char *hintstr = lua_isnoneornil(L, 2) ? nullptr : luaL_checkstring(L, 2);
-		if (hintstr && !TrueTypeRasterizer::getConstant(hintstr, hinting))
-			return luax_enumerror(L, "TrueType font hinting mode", TrueTypeRasterizer::getConstants(hinting), hintstr);
+		TrueTypeRasterizer::Settings settings;
+		if (!lua_isnoneornil(L, 2))
+			settings = luax_checktruetypesettings(L, 2);
 
 
-		if (lua_isnoneornil(L, 3))
-			luax_catchexcept(L, [&](){ t = instance()->newTrueTypeRasterizer(size, hinting); });
-		else
-		{
-			float dpiscale = (float) luaL_checknumber(L, 3);
-			luax_catchexcept(L, [&](){ t = instance()->newTrueTypeRasterizer(size, dpiscale, hinting); });
-		}
+		luax_catchexcept(L, [&](){ t = instance()->newTrueTypeRasterizer(size, settings); });
 	}
 	}
 	else
 	else
 	{
 	{
+		int size = (int) luaL_optinteger(L, 2, 12);
+
+		TrueTypeRasterizer::Settings settings;
+		if (!lua_isnoneornil(L, 3))
+			settings = luax_checktruetypesettings(L, 3);
+
 		love::Data *d = nullptr;
 		love::Data *d = nullptr;
 
 
 		if (luax_istype(L, 1, love::Data::type))
 		if (luax_istype(L, 1, love::Data::type))
@@ -98,27 +133,10 @@ int w_newTrueTypeRasterizer(lua_State *L)
 		else
 		else
 			d = filesystem::luax_getfiledata(L, 1);
 			d = filesystem::luax_getfiledata(L, 1);
 
 
-		int size = (int) luaL_optinteger(L, 2, 12);
-
-		const char *hintstr = lua_isnoneornil(L, 3) ? nullptr : luaL_checkstring(L, 3);
-		if (hintstr && !TrueTypeRasterizer::getConstant(hintstr, hinting))
-			return luax_enumerror(L, "TrueType font hinting mode", TrueTypeRasterizer::getConstants(hinting), hintstr);
-
-		if (lua_isnoneornil(L, 4))
-		{
-			luax_catchexcept(L,
-				[&]() { t = instance()->newTrueTypeRasterizer(d, size, hinting); },
-				[&](bool) { d->release(); }
-			);
-		}
-		else
-		{
-			float dpiscale = (float) luaL_checknumber(L, 4);
-			luax_catchexcept(L,
-				[&]() { t = instance()->newTrueTypeRasterizer(d, size, dpiscale, hinting); },
-				[&](bool) { d->release(); }
-			);
-		}
+		luax_catchexcept(L,
+			[&]() { t = instance()->newTrueTypeRasterizer(d, size, settings); },
+			[&](bool) { d->release(); }
+		);
 	}
 	}
 
 
 	luax_pushtype(L, t);
 	luax_pushtype(L, t);
@@ -154,7 +172,7 @@ int w_newBMFontRasterizer(lua_State *L)
 			lua_pop(L, 1);
 			lua_pop(L, 1);
 		}
 		}
 	}
 	}
-	else
+	else if (!lua_isnoneornil(L, 2))
 	{
 	{
 		convimagedata(L, 2);
 		convimagedata(L, 2);
 		image::ImageData *id = luax_checktype<image::ImageData>(L, 2);
 		image::ImageData *id = luax_checktype<image::ImageData>(L, 2);

+ 3 - 3
src/modules/graphics/Deprecations.cpp

@@ -74,12 +74,12 @@ void Deprecations::draw(Graphics *gfx)
 
 
 	if (font.get() == nullptr)
 	if (font.get() == nullptr)
 	{
 	{
-		auto hinting = font::TrueTypeRasterizer::HINTING_NORMAL;
+		font::TrueTypeRasterizer::Settings settings;
 
 
 		if (!isGammaCorrect() && gfx->getScreenDPIScale() <= 1.0)
 		if (!isGammaCorrect() && gfx->getScreenDPIScale() <= 1.0)
-			hinting = font::TrueTypeRasterizer::HINTING_LIGHT;
+			settings.hinting = font::TrueTypeRasterizer::HINTING_LIGHT;
 
 
-		font.set(gfx->newDefaultFont(9, hinting), Acquire::NORETAIN);
+		font.set(gfx->newDefaultFont(9, settings), Acquire::NORETAIN);
 	}
 	}
 
 
 	gfx->flushBatchedDraws();
 	gfx->flushBatchedDraws();

+ 23 - 4
src/modules/graphics/Graphics.cpp

@@ -294,13 +294,13 @@ Font *Graphics::newFont(love::font::Rasterizer *data)
 	return new Font(data, states.back().defaultSamplerState);
 	return new Font(data, states.back().defaultSamplerState);
 }
 }
 
 
-Font *Graphics::newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting)
+Font *Graphics::newDefaultFont(int size, const font::TrueTypeRasterizer::Settings &settings)
 {
 {
 	auto fontmodule = Module::getInstance<font::Font>(M_FONT);
 	auto fontmodule = Module::getInstance<font::Font>(M_FONT);
 	if (!fontmodule)
 	if (!fontmodule)
 		throw love::Exception("Font module has not been loaded.");
 		throw love::Exception("Font module has not been loaded.");
 
 
-	StrongRef<font::Rasterizer> r(fontmodule->newTrueTypeRasterizer(size, hinting), Acquire::NORETAIN);
+	StrongRef<font::Rasterizer> r(fontmodule->newTrueTypeRasterizer(size, settings), Acquire::NORETAIN);
 	return newFont(r.get());
 	return newFont(r.get());
 }
 }
 
 
@@ -731,7 +731,10 @@ void Graphics::checkSetDefaultFont()
 
 
 	// Create a new default font if we don't have one yet.
 	// Create a new default font if we don't have one yet.
 	if (!defaultFont.get())
 	if (!defaultFont.get())
-		defaultFont.set(newDefaultFont(13, font::TrueTypeRasterizer::HINTING_NORMAL), Acquire::NORETAIN);
+	{
+		font::TrueTypeRasterizer::Settings settings;
+		defaultFont.set(newDefaultFont(13, settings), Acquire::NORETAIN);
+	}
 
 
 	states.back().font.set(defaultFont.get());
 	states.back().font.set(defaultFont.get());
 }
 }
@@ -925,7 +928,7 @@ void Graphics::setRenderTargets(const RenderTargets &rts)
 		PixelFormat dsformat = PIXELFORMAT_STENCIL8;
 		PixelFormat dsformat = PIXELFORMAT_STENCIL8;
 		if (wantsdepth && wantsstencil)
 		if (wantsdepth && wantsstencil)
 			dsformat = PIXELFORMAT_DEPTH24_UNORM_STENCIL8;
 			dsformat = PIXELFORMAT_DEPTH24_UNORM_STENCIL8;
-		else if (wantsdepth && isPixelFormatSupported(PIXELFORMAT_DEPTH24_UNORM, PIXELFORMATUSAGEFLAGS_RENDERTARGET, false))
+		else if (wantsdepth && isPixelFormatSupported(PIXELFORMAT_DEPTH24_UNORM, PIXELFORMATUSAGEFLAGS_RENDERTARGET))
 			dsformat = PIXELFORMAT_DEPTH24_UNORM;
 			dsformat = PIXELFORMAT_DEPTH24_UNORM;
 		else if (wantsdepth)
 		else if (wantsdepth)
 			dsformat = PIXELFORMAT_DEPTH16_UNORM;
 			dsformat = PIXELFORMAT_DEPTH16_UNORM;
@@ -2389,6 +2392,22 @@ const Graphics::Capabilities &Graphics::getCapabilities() const
 	return capabilities;
 	return capabilities;
 }
 }
 
 
+PixelFormat Graphics::getSizedFormat(PixelFormat format) const
+{
+	switch (format)
+	{
+	case PIXELFORMAT_NORMAL:
+		if (isGammaCorrect())
+			return PIXELFORMAT_RGBA8_sRGB;
+		else
+			return PIXELFORMAT_RGBA8_UNORM;
+	case PIXELFORMAT_HDR:
+		return PIXELFORMAT_RGBA16_FLOAT;
+	default:
+		return format;
+	}
+}
+
 Graphics::Stats Graphics::getStats() const
 Graphics::Stats Graphics::getStats() const
 {
 {
 	Stats stats;
 	Stats stats;

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

@@ -459,7 +459,7 @@ public:
 
 
 	Quad *newQuad(Quad::Viewport v, double sw, double sh);
 	Quad *newQuad(Quad::Viewport v, double sw, double sh);
 	Font *newFont(love::font::Rasterizer *data);
 	Font *newFont(love::font::Rasterizer *data);
-	Font *newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting);
+	Font *newDefaultFont(int size, const font::TrueTypeRasterizer::Settings &settings);
 	Video *newVideo(love::video::VideoStream *stream, float dpiscale);
 	Video *newVideo(love::video::VideoStream *stream, float dpiscale);
 
 
 	SpriteBatch *newSpriteBatch(Texture *texture, int size, BufferDataUsage usage);
 	SpriteBatch *newSpriteBatch(Texture *texture, int size, BufferDataUsage usage);
@@ -819,12 +819,12 @@ public:
 	/**
 	/**
 	 * Converts PIXELFORMAT_NORMAL and PIXELFORMAT_HDR into a real format.
 	 * Converts PIXELFORMAT_NORMAL and PIXELFORMAT_HDR into a real format.
 	 **/
 	 **/
-	virtual PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const = 0;
+	PixelFormat getSizedFormat(PixelFormat format) const;
 
 
 	/**
 	/**
 	 * Gets whether the specified pixel format usage is supported.
 	 * Gets whether the specified pixel format usage is supported.
 	 **/
 	 **/
-	virtual bool isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB = false) = 0;
+	virtual bool isPixelFormatSupported(PixelFormat format, uint32 usage) = 0;
 
 
 	/**
 	/**
 	 * Gets the renderer used by love.graphics.
 	 * Gets the renderer used by love.graphics.

+ 3 - 1
src/modules/graphics/GraphicsReadback.cpp

@@ -81,6 +81,7 @@ GraphicsReadback::GraphicsReadback(Graphics *gfx, ReadbackMethod method, Texture
 	}
 	}
 
 
 	textureFormat = getLinearPixelFormat(texture->getPixelFormat());
 	textureFormat = getLinearPixelFormat(texture->getPixelFormat());
+	isFormatLinear = isGammaCorrect() && !isPixelFormatSRGB(texture->getPixelFormat());
 
 
 	if (!image::ImageData::validPixelFormat(textureFormat))
 	if (!image::ImageData::validPixelFormat(textureFormat))
 	{
 	{
@@ -96,7 +97,7 @@ GraphicsReadback::GraphicsReadback(Graphics *gfx, ReadbackMethod method, Texture
 		if (isRT && !caps.features[Graphics::FEATURE_COPY_RENDER_TARGET_TO_BUFFER])
 		if (isRT && !caps.features[Graphics::FEATURE_COPY_RENDER_TARGET_TO_BUFFER])
 			throw love::Exception("readbackTextureAsync is not supported on this system.");
 			throw love::Exception("readbackTextureAsync is not supported on this system.");
 		else if (!isRT && !caps.features[Graphics::FEATURE_COPY_TEXTURE_TO_BUFFER])
 		else if (!isRT && !caps.features[Graphics::FEATURE_COPY_TEXTURE_TO_BUFFER])
-			throw love::Exception("readbackTextureAsync a with non-render-target textures is not supported on this system.");
+			throw love::Exception("readbackTextureAsync with a non-render-target texture is not supported on this system.");
 	}
 	}
 	else
 	else
 	{
 	{
@@ -158,6 +159,7 @@ void *GraphicsReadback::prepareReadbackDest(size_t size)
 				throw love::Exception("The love.image module must be loaded for readbackTexture.");
 				throw love::Exception("The love.image module must be loaded for readbackTexture.");
 
 
 			imageData.set(module->newImageData(rect.w, rect.h, textureFormat, nullptr), Acquire::NORETAIN);
 			imageData.set(module->newImageData(rect.w, rect.h, textureFormat, nullptr), Acquire::NORETAIN);
+			imageData->setLinear(isFormatLinear);
 			return imageData->getData();
 			return imageData->getData();
 		}
 		}
 	}
 	}

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

@@ -103,6 +103,7 @@ protected:
 	StrongRef<love::image::ImageData> imageData;
 	StrongRef<love::image::ImageData> imageData;
 	Rect rect = {};
 	Rect rect = {};
 	PixelFormat textureFormat = PIXELFORMAT_UNKNOWN;
 	PixelFormat textureFormat = PIXELFORMAT_UNKNOWN;
+	bool isFormatLinear = false;
 	int imageDataX = 0;
 	int imageDataX = 0;
 	int imageDataY = 0;
 	int imageDataY = 0;
 
 

+ 9 - 1
src/modules/graphics/Shader.cpp

@@ -1010,8 +1010,16 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
 						values[i].u = convertData<uint32>((*constarray)[i]);
 						values[i].u = convertData<uint32>((*constarray)[i]);
 				}
 				}
 				break;
 				break;
-			case glslang::EbtInt:
 			case glslang::EbtBool:
 			case glslang::EbtBool:
+				u.dataType = DATA_BASETYPE_BOOL;
+				if (constarray != nullptr)
+				{
+					values.resize(constarray->size());
+					for (int i = 0; i < constarray->size(); i++)
+						values[i].u = convertData<uint32>((*constarray)[i]);
+				}
+				break;
+			case glslang::EbtInt:
 			default:
 			default:
 				u.dataType = DATA_BASETYPE_INT;
 				u.dataType = DATA_BASETYPE_INT;
 				if (constarray != nullptr)
 				if (constarray != nullptr)

+ 10 - 5
src/modules/graphics/Texture.cpp

@@ -168,7 +168,6 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices)
 	, computeWrite(settings.computeWrite)
 	, computeWrite(settings.computeWrite)
 	, readable(true)
 	, readable(true)
 	, mipmapsMode(settings.mipmaps)
 	, mipmapsMode(settings.mipmaps)
-	, sRGB(isGammaCorrect() && !settings.linear)
 	, width(settings.width)
 	, width(settings.width)
 	, height(settings.height)
 	, height(settings.height)
 	, depth(settings.type == TEXTURE_VOLUME ? settings.layers : 1)
 	, depth(settings.type == TEXTURE_VOLUME ? settings.layers : 1)
@@ -204,7 +203,7 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices)
 		love::image::ImageDataBase *slice = slices->get(0, 0);
 		love::image::ImageDataBase *slice = slices->get(0, 0);
 
 
 		format = slice->getFormat();
 		format = slice->getFormat();
-		if (sRGB)
+		if (isGammaCorrect() && !slice->isLinear())
 			format = getSRGBPixelFormat(format);
 			format = getSRGBPixelFormat(format);
 
 
 		pixelWidth = slice->getWidth();
 		pixelWidth = slice->getWidth();
@@ -235,7 +234,9 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices)
 	else
 	else
 		readable = !renderTarget || !isPixelFormatDepthStencil(format);
 		readable = !renderTarget || !isPixelFormatDepthStencil(format);
 
 
-	format = gfx->getSizedFormat(format, renderTarget, readable);
+	format = gfx->getSizedFormat(format);
+	if (!isGammaCorrect() || settings.linear)
+		format = getLinearPixelFormat(format);
 
 
 	if (mipmapsMode == MIPMAPS_AUTO && isCompressed())
 	if (mipmapsMode == MIPMAPS_AUTO && isCompressed())
 		mipmapsMode = MIPMAPS_MANUAL;
 		mipmapsMode = MIPMAPS_MANUAL;
@@ -290,7 +291,7 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices)
 	if (computeWrite)
 	if (computeWrite)
 		usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE;
 		usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE;
 
 
-	if (!gfx->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage, sRGB))
+	if (!gfx->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage))
 	{
 	{
 		const char *fstr = "unknown";
 		const char *fstr = "unknown";
 		love::getConstant(format, fstr);
 		love::getConstant(format, fstr);
@@ -621,7 +622,7 @@ bool Texture::isCompressed() const
 
 
 bool Texture::isFormatLinear() const
 bool Texture::isFormatLinear() const
 {
 {
-	return isGammaCorrect() && !sRGB && !isPixelFormatSRGB(format);
+	return isGammaCorrect() && !isPixelFormatSRGB(format);
 }
 }
 
 
 bool Texture::isValidSlice(int slice, int mip) const
 bool Texture::isValidSlice(int slice, int mip) const
@@ -885,6 +886,7 @@ bool Texture::Slices::validate() const
 	int w = firstdata->getWidth();
 	int w = firstdata->getWidth();
 	int h = firstdata->getHeight();
 	int h = firstdata->getHeight();
 	PixelFormat format = firstdata->getFormat();
 	PixelFormat format = firstdata->getFormat();
+	bool linear = firstdata->isLinear();
 
 
 	if (textureType == TEXTURE_CUBE && w != h)
 	if (textureType == TEXTURE_CUBE && w != h)
 		throw love::Exception("Cube textures must have equal widths and heights for each cube face.");
 		throw love::Exception("Cube textures must have equal widths and heights for each cube face.");
@@ -924,6 +926,9 @@ bool Texture::Slices::validate() const
 
 
 			if (format != slicedata->getFormat())
 			if (format != slicedata->getFormat())
 				throw love::Exception("All texture slices and mipmaps must have the same pixel format.");
 				throw love::Exception("All texture slices and mipmaps must have the same pixel format.");
+
+			if (linear != slicedata->isLinear())
+				throw love::Exception("All texture slices and mipmaps must have the same linear setting.");
 		}
 		}
 
 
 		mipw = std::max(mipw / 2, 1);
 		mipw = std::max(mipw / 2, 1);

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

@@ -325,8 +325,6 @@ protected:
 
 
 	MipmapsMode mipmapsMode;
 	MipmapsMode mipmapsMode;
 
 
-	bool sRGB;
-
 	int width;
 	int width;
 	int height;
 	int height;
 
 

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

@@ -110,8 +110,7 @@ public:
 
 
 	void setWireframe(bool enable) override;
 	void setWireframe(bool enable) override;
 	
 	
-	PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const override;
-	bool isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB = false) override;
+	bool isPixelFormatSupported(PixelFormat format, uint32 usage) override;
 	Renderer getRenderer() const override;
 	Renderer getRenderer() const override;
 	bool usesGLSLES() const override;
 	bool usesGLSLES() const override;
 	RendererInfo getRendererInfo() const override;
 	RendererInfo getRendererInfo() const override;

+ 46 - 43
src/modules/graphics/metal/Graphics.mm

@@ -503,7 +503,7 @@ void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelh
 	backbufferMSAA.set(nullptr);
 	backbufferMSAA.set(nullptr);
 	if (settings.msaa > 1)
 	if (settings.msaa > 1)
 	{
 	{
-		settings.format = isGammaCorrect() ? PIXELFORMAT_BGRA8_UNORM_sRGB : PIXELFORMAT_BGRA8_UNORM;
+		settings.format = isGammaCorrect() ? PIXELFORMAT_BGRA8_sRGB : PIXELFORMAT_BGRA8_UNORM;
 		backbufferMSAA.set(newTexture(settings), Acquire::NORETAIN);
 		backbufferMSAA.set(newTexture(settings), Acquire::NORETAIN);
 	}
 	}
 
 
@@ -681,7 +681,7 @@ id<MTLRenderCommandEncoder> Graphics::useRenderEncoder()
 			attachmentStoreActions.stencil = MTLStoreActionDontCare;
 			attachmentStoreActions.stencil = MTLStoreActionDontCare;
 
 
 			auto &key = lastRenderPipelineKey;
 			auto &key = lastRenderPipelineKey;
-			key.colorRenderTargetFormats = isGammaCorrect() ? PIXELFORMAT_BGRA8_UNORM_sRGB : PIXELFORMAT_BGRA8_UNORM;
+			key.colorRenderTargetFormats = isGammaCorrect() ? PIXELFORMAT_BGRA8_sRGB : PIXELFORMAT_BGRA8_UNORM;
 			key.depthStencilFormat = backbufferDepthStencil->getPixelFormat();
 			key.depthStencilFormat = backbufferDepthStencil->getPixelFormat();
 			key.msaa = backbufferMSAA ? (uint8) backbufferMSAA->getMSAA() : 1;
 			key.msaa = backbufferMSAA ? (uint8) backbufferMSAA->getMSAA() : 1;
 		}
 		}
@@ -1864,31 +1864,9 @@ void Graphics::setWireframe(bool enable)
 	}
 	}
 }
 }
 
 
-PixelFormat Graphics::getSizedFormat(PixelFormat format, bool /*rendertarget*/, bool /*readable*/) const
+bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage)
 {
 {
-	switch (format)
-	{
-	case PIXELFORMAT_NORMAL:
-		if (isGammaCorrect())
-			return PIXELFORMAT_RGBA8_UNORM_sRGB;
-		else
-			return PIXELFORMAT_RGBA8_UNORM;
-	case PIXELFORMAT_HDR:
-		return PIXELFORMAT_RGBA16_FLOAT;
-	default:
-		return format;
-	}
-}
-
-bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB)
-{
-	bool rendertarget = (usage & PIXELFORMATUSAGEFLAGS_RENDERTARGET) != 0;
-	bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0;
-
-	format = getSizedFormat(format, rendertarget, readable);
-
-	if (sRGB)
-		format = getSRGBPixelFormat(format);
+	format = getSizedFormat(format);
 
 
 	const uint32 sample = PIXELFORMATUSAGEFLAGS_SAMPLE;
 	const uint32 sample = PIXELFORMATUSAGEFLAGS_SAMPLE;
 	const uint32 filter = PIXELFORMATUSAGEFLAGS_LINEAR;
 	const uint32 filter = PIXELFORMATUSAGEFLAGS_LINEAR;
@@ -1902,7 +1880,7 @@ bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRG
 
 
 	uint32 flags = PIXELFORMATUSAGEFLAGS_NONE;
 	uint32 flags = PIXELFORMATUSAGEFLAGS_NONE;
 
 
-	if (isPixelFormatCompressed(format) && rendertarget)
+	if (isPixelFormatCompressed(format) && (usage & rt) != 0)
 		return false;
 		return false;
 
 
 	// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
 	// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
@@ -1977,8 +1955,8 @@ bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRG
 		case PIXELFORMAT_BGRA8_UNORM:
 		case PIXELFORMAT_BGRA8_UNORM:
 			flags |= all;
 			flags |= all;
 			break;
 			break;
-		case PIXELFORMAT_RGBA8_UNORM_sRGB:
-		case PIXELFORMAT_BGRA8_UNORM_sRGB:
+		case PIXELFORMAT_RGBA8_sRGB:
+		case PIXELFORMAT_BGRA8_sRGB:
 			if (families.apple[1] || families.mac[1] || families.macCatalyst[1])
 			if (families.apple[1] || families.mac[1] || families.macCatalyst[1])
 				flags |= commonsample | commonrender;
 				flags |= commonsample | commonrender;
 			if (families.apple[2])
 			if (families.apple[2])
@@ -2077,8 +2055,11 @@ bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRG
 			break;
 			break;
 
 
 		case PIXELFORMAT_DXT1_UNORM:
 		case PIXELFORMAT_DXT1_UNORM:
+		case PIXELFORMAT_DXT1_sRGB:
 		case PIXELFORMAT_DXT3_UNORM:
 		case PIXELFORMAT_DXT3_UNORM:
+		case PIXELFORMAT_DXT3_sRGB:
 		case PIXELFORMAT_DXT5_UNORM:
 		case PIXELFORMAT_DXT5_UNORM:
+		case PIXELFORMAT_DXT5_sRGB:
 		case PIXELFORMAT_BC4_UNORM:
 		case PIXELFORMAT_BC4_UNORM:
 		case PIXELFORMAT_BC4_SNORM:
 		case PIXELFORMAT_BC4_SNORM:
 		case PIXELFORMAT_BC5_UNORM:
 		case PIXELFORMAT_BC5_UNORM:
@@ -2086,22 +2067,30 @@ bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRG
 		case PIXELFORMAT_BC6H_UFLOAT:
 		case PIXELFORMAT_BC6H_UFLOAT:
 		case PIXELFORMAT_BC6H_FLOAT:
 		case PIXELFORMAT_BC6H_FLOAT:
 		case PIXELFORMAT_BC7_UNORM:
 		case PIXELFORMAT_BC7_UNORM:
+		case PIXELFORMAT_BC7_sRGB:
 			if (families.mac[1] || families.macCatalyst[1])
 			if (families.mac[1] || families.macCatalyst[1])
 				flags |= commonsample;
 				flags |= commonsample;
 			break;
 			break;
 
 
 		case PIXELFORMAT_PVR1_RGB2_UNORM:
 		case PIXELFORMAT_PVR1_RGB2_UNORM:
+		case PIXELFORMAT_PVR1_RGB2_sRGB:
 		case PIXELFORMAT_PVR1_RGB4_UNORM:
 		case PIXELFORMAT_PVR1_RGB4_UNORM:
+		case PIXELFORMAT_PVR1_RGB4_sRGB:
 		case PIXELFORMAT_PVR1_RGBA2_UNORM:
 		case PIXELFORMAT_PVR1_RGBA2_UNORM:
+		case PIXELFORMAT_PVR1_RGBA2_sRGB:
 		case PIXELFORMAT_PVR1_RGBA4_UNORM:
 		case PIXELFORMAT_PVR1_RGBA4_UNORM:
+		case PIXELFORMAT_PVR1_RGBA4_sRGB:
 			if (families.apple[1])
 			if (families.apple[1])
 				flags |= commonsample;
 				flags |= commonsample;
 			break;
 			break;
 
 
 		case PIXELFORMAT_ETC1_UNORM:
 		case PIXELFORMAT_ETC1_UNORM:
 		case PIXELFORMAT_ETC2_RGB_UNORM:
 		case PIXELFORMAT_ETC2_RGB_UNORM:
+		case PIXELFORMAT_ETC2_RGB_sRGB:
 		case PIXELFORMAT_ETC2_RGBA_UNORM:
 		case PIXELFORMAT_ETC2_RGBA_UNORM:
+		case PIXELFORMAT_ETC2_RGBA_sRGB:
 		case PIXELFORMAT_ETC2_RGBA1_UNORM:
 		case PIXELFORMAT_ETC2_RGBA1_UNORM:
+		case PIXELFORMAT_ETC2_RGBA1_sRGB:
 		case PIXELFORMAT_EAC_R_UNORM:
 		case PIXELFORMAT_EAC_R_UNORM:
 		case PIXELFORMAT_EAC_R_SNORM:
 		case PIXELFORMAT_EAC_R_SNORM:
 		case PIXELFORMAT_EAC_RG_UNORM:
 		case PIXELFORMAT_EAC_RG_UNORM:
@@ -2110,20 +2099,34 @@ bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRG
 				flags |= commonsample;
 				flags |= commonsample;
 			break;
 			break;
 
 
-		case PIXELFORMAT_ASTC_4x4:
-		case PIXELFORMAT_ASTC_5x4:
-		case PIXELFORMAT_ASTC_5x5:
-		case PIXELFORMAT_ASTC_6x5:
-		case PIXELFORMAT_ASTC_6x6:
-		case PIXELFORMAT_ASTC_8x5:
-		case PIXELFORMAT_ASTC_8x6:
-		case PIXELFORMAT_ASTC_8x8:
-		case PIXELFORMAT_ASTC_10x5:
-		case PIXELFORMAT_ASTC_10x6:
-		case PIXELFORMAT_ASTC_10x8:
-		case PIXELFORMAT_ASTC_10x10:
-		case PIXELFORMAT_ASTC_12x10:
-		case PIXELFORMAT_ASTC_12x12:
+		case PIXELFORMAT_ASTC_4x4_UNORM:
+		case PIXELFORMAT_ASTC_5x4_UNORM:
+		case PIXELFORMAT_ASTC_5x5_UNORM:
+		case PIXELFORMAT_ASTC_6x5_UNORM:
+		case PIXELFORMAT_ASTC_6x6_UNORM:
+		case PIXELFORMAT_ASTC_8x5_UNORM:
+		case PIXELFORMAT_ASTC_8x6_UNORM:
+		case PIXELFORMAT_ASTC_8x8_UNORM:
+		case PIXELFORMAT_ASTC_10x5_UNORM:
+		case PIXELFORMAT_ASTC_10x6_UNORM:
+		case PIXELFORMAT_ASTC_10x8_UNORM:
+		case PIXELFORMAT_ASTC_10x10_UNORM:
+		case PIXELFORMAT_ASTC_12x10_UNORM:
+		case PIXELFORMAT_ASTC_12x12_UNORM:
+		case PIXELFORMAT_ASTC_4x4_sRGB:
+		case PIXELFORMAT_ASTC_5x4_sRGB:
+		case PIXELFORMAT_ASTC_5x5_sRGB:
+		case PIXELFORMAT_ASTC_6x5_sRGB:
+		case PIXELFORMAT_ASTC_6x6_sRGB:
+		case PIXELFORMAT_ASTC_8x5_sRGB:
+		case PIXELFORMAT_ASTC_8x6_sRGB:
+		case PIXELFORMAT_ASTC_8x8_sRGB:
+		case PIXELFORMAT_ASTC_10x5_sRGB:
+		case PIXELFORMAT_ASTC_10x6_sRGB:
+		case PIXELFORMAT_ASTC_10x8_sRGB:
+		case PIXELFORMAT_ASTC_10x10_sRGB:
+		case PIXELFORMAT_ASTC_12x10_sRGB:
+		case PIXELFORMAT_ASTC_12x12_sRGB:
 			if (families.apple[2])
 			if (families.apple[2])
 				flags |= commonsample;
 				flags |= commonsample;
 			break;
 			break;

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

@@ -43,7 +43,7 @@ public:
 		API_AVAILABLE(macos(10.15), ios(13.0)) MTLTextureSwizzleChannels swizzle;
 		API_AVAILABLE(macos(10.15), ios(13.0)) MTLTextureSwizzleChannels swizzle;
 	};
 	};
 
 
-	static PixelFormatDesc convertPixelFormat(id<MTLDevice> device, PixelFormat format, bool &isSRGB);
+	static PixelFormatDesc convertPixelFormat(id<MTLDevice> device, PixelFormat format);
 
 
 }; // Metal
 }; // Metal
 
 

+ 148 - 67
src/modules/graphics/metal/Metal.mm

@@ -28,17 +28,11 @@ namespace graphics
 namespace metal
 namespace metal
 {
 {
 
 
-Metal::PixelFormatDesc Metal::convertPixelFormat(id<MTLDevice> device, PixelFormat format, bool &isSRGB)
+Metal::PixelFormatDesc Metal::convertPixelFormat(id<MTLDevice> device, PixelFormat format)
 {
 {
 	MTLPixelFormat mtlformat = MTLPixelFormatInvalid;
 	MTLPixelFormat mtlformat = MTLPixelFormatInvalid;
 	PixelFormatDesc desc = {};
 	PixelFormatDesc desc = {};
 
 
-	if (isSRGB)
-		format = getSRGBPixelFormat(format);
-
-	if (!isPixelFormatCompressed(format) && !isPixelFormatSRGB(format))
-		isSRGB = false;
-
 	switch (format)
 	switch (format)
 	{
 	{
 	case PIXELFORMAT_R8_UNORM:
 	case PIXELFORMAT_R8_UNORM:
@@ -50,13 +44,13 @@ Metal::PixelFormatDesc Metal::convertPixelFormat(id<MTLDevice> device, PixelForm
 	case PIXELFORMAT_RGBA8_UNORM:
 	case PIXELFORMAT_RGBA8_UNORM:
 		mtlformat = MTLPixelFormatRGBA8Unorm;
 		mtlformat = MTLPixelFormatRGBA8Unorm;
 		break;
 		break;
-	case PIXELFORMAT_RGBA8_UNORM_sRGB:
+	case PIXELFORMAT_RGBA8_sRGB:
 		mtlformat = MTLPixelFormatRGBA8Unorm_sRGB;
 		mtlformat = MTLPixelFormatRGBA8Unorm_sRGB;
 		break;
 		break;
 	case PIXELFORMAT_BGRA8_UNORM:
 	case PIXELFORMAT_BGRA8_UNORM:
 		mtlformat = MTLPixelFormatBGRA8Unorm;
 		mtlformat = MTLPixelFormatBGRA8Unorm;
 		break;
 		break;
-	case PIXELFORMAT_BGRA8_UNORM_sRGB:
+	case PIXELFORMAT_BGRA8_sRGB:
 		mtlformat = MTLPixelFormatBGRA8Unorm_sRGB;
 		mtlformat = MTLPixelFormatBGRA8Unorm_sRGB;
 		break;
 		break;
 	case PIXELFORMAT_R16_UNORM:
 	case PIXELFORMAT_R16_UNORM:
@@ -205,177 +199,264 @@ Metal::PixelFormatDesc Metal::convertPixelFormat(id<MTLDevice> device, PixelForm
 
 
 	case PIXELFORMAT_DXT1_UNORM:
 	case PIXELFORMAT_DXT1_UNORM:
 #ifndef LOVE_IOS
 #ifndef LOVE_IOS
-		mtlformat = isSRGB ? MTLPixelFormatBC1_RGBA_sRGB : MTLPixelFormatBC1_RGBA;
+		mtlformat = MTLPixelFormatBC1_RGBA;
+#endif
+		break;
+	case PIXELFORMAT_DXT1_sRGB:
+#ifndef LOVE_IOS
+		mtlformat = MTLPixelFormatBC1_RGBA_sRGB;
 #endif
 #endif
 		break;
 		break;
 	case PIXELFORMAT_DXT3_UNORM:
 	case PIXELFORMAT_DXT3_UNORM:
 #ifndef LOVE_IOS
 #ifndef LOVE_IOS
-		mtlformat = isSRGB ? MTLPixelFormatBC2_RGBA_sRGB : MTLPixelFormatBC2_RGBA;
+		mtlformat = MTLPixelFormatBC2_RGBA;
+#endif
+		break;
+	case PIXELFORMAT_DXT3_sRGB:
+#ifndef LOVE_IOS
+		mtlformat = MTLPixelFormatBC2_RGBA_sRGB;
 #endif
 #endif
 		break;
 		break;
 	case PIXELFORMAT_DXT5_UNORM:
 	case PIXELFORMAT_DXT5_UNORM:
 #ifndef LOVE_IOS
 #ifndef LOVE_IOS
-		mtlformat = isSRGB ? MTLPixelFormatBC3_RGBA_sRGB : MTLPixelFormatBC3_RGBA;
+		mtlformat = MTLPixelFormatBC3_RGBA;
+#endif
+		break;
+	case PIXELFORMAT_DXT5_sRGB:
+#ifndef LOVE_IOS
+		mtlformat = MTLPixelFormatBC3_RGBA_sRGB;
 #endif
 #endif
 		break;
 		break;
 	case PIXELFORMAT_BC4_UNORM:
 	case PIXELFORMAT_BC4_UNORM:
 #ifndef LOVE_IOS
 #ifndef LOVE_IOS
-		isSRGB = false;
 		mtlformat = MTLPixelFormatBC4_RUnorm;
 		mtlformat = MTLPixelFormatBC4_RUnorm;
 #endif
 #endif
 		break;
 		break;
 	case PIXELFORMAT_BC4_SNORM:
 	case PIXELFORMAT_BC4_SNORM:
 #ifndef LOVE_IOS
 #ifndef LOVE_IOS
-		isSRGB = false;
 		mtlformat = MTLPixelFormatBC4_RSnorm;
 		mtlformat = MTLPixelFormatBC4_RSnorm;
 #endif
 #endif
 		break;
 		break;
 	case PIXELFORMAT_BC5_UNORM:
 	case PIXELFORMAT_BC5_UNORM:
 #ifndef LOVE_IOS
 #ifndef LOVE_IOS
-		isSRGB = false;
 		mtlformat = MTLPixelFormatBC5_RGUnorm;
 		mtlformat = MTLPixelFormatBC5_RGUnorm;
 #endif
 #endif
 		break;
 		break;
 	case PIXELFORMAT_BC5_SNORM:
 	case PIXELFORMAT_BC5_SNORM:
 #ifndef LOVE_IOS
 #ifndef LOVE_IOS
-		isSRGB = false;
 		mtlformat = MTLPixelFormatBC5_RGSnorm;
 		mtlformat = MTLPixelFormatBC5_RGSnorm;
 #endif
 #endif
 		break;
 		break;
 	case PIXELFORMAT_BC6H_UFLOAT:
 	case PIXELFORMAT_BC6H_UFLOAT:
 #ifndef LOVE_IOS
 #ifndef LOVE_IOS
-		isSRGB = false;
 		mtlformat = MTLPixelFormatBC6H_RGBUfloat;
 		mtlformat = MTLPixelFormatBC6H_RGBUfloat;
 #endif
 #endif
 		break;
 		break;
 	case PIXELFORMAT_BC6H_FLOAT:
 	case PIXELFORMAT_BC6H_FLOAT:
 #ifndef LOVE_IOS
 #ifndef LOVE_IOS
-		isSRGB = false;
 		mtlformat = MTLPixelFormatBC6H_RGBFloat;
 		mtlformat = MTLPixelFormatBC6H_RGBFloat;
 #endif
 #endif
 		break;
 		break;
 	case PIXELFORMAT_BC7_UNORM:
 	case PIXELFORMAT_BC7_UNORM:
 #ifndef LOVE_IOS
 #ifndef LOVE_IOS
-		mtlformat = isSRGB ? MTLPixelFormatBC7_RGBAUnorm_sRGB : MTLPixelFormatBC7_RGBAUnorm;
+		mtlformat = MTLPixelFormatBC7_RGBAUnorm;
+#endif
+		break;
+	case PIXELFORMAT_BC7_sRGB:
+#ifndef LOVE_IOS
+		mtlformat = MTLPixelFormatBC7_RGBAUnorm_sRGB;
 #endif
 #endif
 		break;
 		break;
 
 
 	case PIXELFORMAT_PVR1_RGB2_UNORM:
 	case PIXELFORMAT_PVR1_RGB2_UNORM:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatPVRTC_RGB_2BPP_sRGB : MTLPixelFormatPVRTC_RGB_2BPP;
+			mtlformat = MTLPixelFormatPVRTC_RGB_2BPP;
+		break;
+	case PIXELFORMAT_PVR1_RGB2_sRGB:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatPVRTC_RGB_2BPP_sRGB;
 		break;
 		break;
 	case PIXELFORMAT_PVR1_RGB4_UNORM:
 	case PIXELFORMAT_PVR1_RGB4_UNORM:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatPVRTC_RGB_4BPP_sRGB : MTLPixelFormatPVRTC_RGB_4BPP;
+			mtlformat = MTLPixelFormatPVRTC_RGB_4BPP;
+		break;
+	case PIXELFORMAT_PVR1_RGB4_sRGB:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatPVRTC_RGB_4BPP_sRGB;
 		break;
 		break;
 	case PIXELFORMAT_PVR1_RGBA2_UNORM:
 	case PIXELFORMAT_PVR1_RGBA2_UNORM:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatPVRTC_RGB_2BPP_sRGB : MTLPixelFormatPVRTC_RGBA_2BPP;
+			mtlformat = MTLPixelFormatPVRTC_RGBA_2BPP;
+		break;
+	case PIXELFORMAT_PVR1_RGBA2_sRGB:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatPVRTC_RGB_2BPP_sRGB;
 		break;
 		break;
 	case PIXELFORMAT_PVR1_RGBA4_UNORM:
 	case PIXELFORMAT_PVR1_RGBA4_UNORM:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatPVRTC_RGB_4BPP_sRGB : MTLPixelFormatPVRTC_RGBA_4BPP;
+			mtlformat = MTLPixelFormatPVRTC_RGBA_4BPP;
+		break;
+	case PIXELFORMAT_PVR1_RGBA4_sRGB:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatPVRTC_RGB_4BPP_sRGB;
 		break;
 		break;
+
 	case PIXELFORMAT_ETC1_UNORM:
 	case PIXELFORMAT_ETC1_UNORM:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8;
+			mtlformat = MTLPixelFormatETC2_RGB8;
 		break;
 		break;
 	case PIXELFORMAT_ETC2_RGB_UNORM:
 	case PIXELFORMAT_ETC2_RGB_UNORM:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8;
+			mtlformat = MTLPixelFormatETC2_RGB8;
+		break;
+	case PIXELFORMAT_ETC2_RGB_sRGB:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat =  MTLPixelFormatETC2_RGB8_sRGB;
 		break;
 		break;
 	case PIXELFORMAT_ETC2_RGBA_UNORM:
 	case PIXELFORMAT_ETC2_RGBA_UNORM:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatEAC_RGBA8_sRGB : MTLPixelFormatEAC_RGBA8;
+			mtlformat = MTLPixelFormatEAC_RGBA8;
+		break;
+	case PIXELFORMAT_ETC2_RGBA_sRGB:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatEAC_RGBA8_sRGB;
 		break;
 		break;
 	case PIXELFORMAT_ETC2_RGBA1_UNORM:
 	case PIXELFORMAT_ETC2_RGBA1_UNORM:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatETC2_RGB8A1_sRGB : MTLPixelFormatETC2_RGB8A1;
+			mtlformat = MTLPixelFormatETC2_RGB8A1;
+		break;
+	case PIXELFORMAT_ETC2_RGBA1_sRGB:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatETC2_RGB8A1_sRGB;
 		break;
 		break;
 	case PIXELFORMAT_EAC_R_UNORM:
 	case PIXELFORMAT_EAC_R_UNORM:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-		{
-			isSRGB = false;
 			mtlformat = MTLPixelFormatEAC_R11Unorm;
 			mtlformat = MTLPixelFormatEAC_R11Unorm;
-		}
 		break;
 		break;
 	case PIXELFORMAT_EAC_R_SNORM:
 	case PIXELFORMAT_EAC_R_SNORM:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-		{
-			isSRGB = false;
 			mtlformat = MTLPixelFormatEAC_R11Snorm;
 			mtlformat = MTLPixelFormatEAC_R11Snorm;
-		}
 		break;
 		break;
 	case PIXELFORMAT_EAC_RG_UNORM:
 	case PIXELFORMAT_EAC_RG_UNORM:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-		{
-			isSRGB = false;
 			mtlformat = MTLPixelFormatEAC_RG11Unorm;
 			mtlformat = MTLPixelFormatEAC_RG11Unorm;
-		}
 		break;
 		break;
 	case PIXELFORMAT_EAC_RG_SNORM:
 	case PIXELFORMAT_EAC_RG_SNORM:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-		{
-			isSRGB = false;
 			mtlformat = MTLPixelFormatEAC_RG11Snorm;
 			mtlformat = MTLPixelFormatEAC_RG11Snorm;
-		}
 		break;
 		break;
 
 
-	case PIXELFORMAT_ASTC_4x4:
+	case PIXELFORMAT_ASTC_4x4_UNORM:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatASTC_4x4_LDR;
+		break;
+	case PIXELFORMAT_ASTC_5x4_UNORM:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatASTC_5x4_LDR;
+		break;
+	case PIXELFORMAT_ASTC_5x5_UNORM:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatASTC_5x5_LDR;
+		break;
+	case PIXELFORMAT_ASTC_6x5_UNORM:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatASTC_6x5_LDR;
+		break;
+	case PIXELFORMAT_ASTC_6x6_UNORM:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatASTC_6x6_LDR;
+		break;
+	case PIXELFORMAT_ASTC_8x5_UNORM:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatASTC_8x5_LDR;
+		break;
+	case PIXELFORMAT_ASTC_8x6_UNORM:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatASTC_8x6_LDR;
+		break;
+	case PIXELFORMAT_ASTC_8x8_UNORM:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatASTC_8x8_LDR;
+		break;
+	case PIXELFORMAT_ASTC_10x5_UNORM:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatASTC_10x5_LDR;
+		break;
+	case PIXELFORMAT_ASTC_10x6_UNORM:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatASTC_10x6_LDR;
+		break;
+	case PIXELFORMAT_ASTC_10x8_UNORM:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatASTC_10x8_LDR;
+		break;
+	case PIXELFORMAT_ASTC_10x10_UNORM:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatASTC_10x10_LDR;
+		break;
+	case PIXELFORMAT_ASTC_12x10_UNORM:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatASTC_12x10_LDR;
+		break;
+	case PIXELFORMAT_ASTC_12x12_UNORM:
+		if (@available(macOS 11.0, iOS 8.0, *))
+			mtlformat = MTLPixelFormatASTC_12x12_LDR;
+		break;
+	case PIXELFORMAT_ASTC_4x4_sRGB:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatASTC_4x4_sRGB : MTLPixelFormatASTC_4x4_LDR;
+			mtlformat = MTLPixelFormatASTC_4x4_sRGB;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_5x4:
+	case PIXELFORMAT_ASTC_5x4_sRGB:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatASTC_5x4_sRGB : MTLPixelFormatASTC_5x4_LDR;
+			mtlformat = MTLPixelFormatASTC_5x4_sRGB;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_5x5:
+	case PIXELFORMAT_ASTC_5x5_sRGB:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatASTC_5x5_sRGB : MTLPixelFormatASTC_5x5_LDR;
+			mtlformat = MTLPixelFormatASTC_5x5_sRGB;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_6x5:
+	case PIXELFORMAT_ASTC_6x5_sRGB:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatASTC_6x5_sRGB : MTLPixelFormatASTC_6x5_LDR;
+			mtlformat = MTLPixelFormatASTC_6x5_sRGB;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_6x6:
+	case PIXELFORMAT_ASTC_6x6_sRGB:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatASTC_6x6_sRGB : MTLPixelFormatASTC_6x6_LDR;
+			mtlformat = MTLPixelFormatASTC_6x6_sRGB;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_8x5:
+	case PIXELFORMAT_ASTC_8x5_sRGB:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatASTC_8x5_sRGB : MTLPixelFormatASTC_8x5_LDR;
+			mtlformat = MTLPixelFormatASTC_8x5_sRGB;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_8x6:
+	case PIXELFORMAT_ASTC_8x6_sRGB:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatASTC_8x6_sRGB : MTLPixelFormatASTC_8x6_LDR;
+			mtlformat = MTLPixelFormatASTC_8x6_sRGB;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_8x8:
+	case PIXELFORMAT_ASTC_8x8_sRGB:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatASTC_8x8_sRGB : MTLPixelFormatASTC_8x8_LDR;
+			mtlformat = MTLPixelFormatASTC_8x8_sRGB;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_10x5:
+	case PIXELFORMAT_ASTC_10x5_sRGB:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatASTC_10x5_sRGB : MTLPixelFormatASTC_10x5_LDR;
+			mtlformat = MTLPixelFormatASTC_10x5_sRGB;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_10x6:
+	case PIXELFORMAT_ASTC_10x6_sRGB:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatASTC_10x6_sRGB : MTLPixelFormatASTC_10x6_LDR;
+			mtlformat = MTLPixelFormatASTC_10x6_sRGB;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_10x8:
+	case PIXELFORMAT_ASTC_10x8_sRGB:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatASTC_10x8_sRGB : MTLPixelFormatASTC_10x8_LDR;
+			mtlformat = MTLPixelFormatASTC_10x8_sRGB;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_10x10:
+	case PIXELFORMAT_ASTC_10x10_sRGB:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatASTC_10x10_sRGB : MTLPixelFormatASTC_10x10_LDR;
+			mtlformat = MTLPixelFormatASTC_10x10_sRGB;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_12x10:
+	case PIXELFORMAT_ASTC_12x10_sRGB:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatASTC_12x10_sRGB : MTLPixelFormatASTC_12x10_LDR;
+			mtlformat = MTLPixelFormatASTC_12x10_sRGB;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_12x12:
+	case PIXELFORMAT_ASTC_12x12_sRGB:
 		if (@available(macOS 11.0, iOS 8.0, *))
 		if (@available(macOS 11.0, iOS 8.0, *))
-			mtlformat = isSRGB ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR;
+			mtlformat = MTLPixelFormatASTC_12x12_sRGB;
 		break;
 		break;
 
 
 	case PIXELFORMAT_UNKNOWN:
 	case PIXELFORMAT_UNKNOWN:

+ 2 - 4
src/modules/graphics/metal/Shader.mm

@@ -1087,8 +1087,7 @@ id<MTLRenderPipelineState> Shader::getCachedRenderPipeline(const RenderPipelineK
 
 
 		MTLRenderPipelineColorAttachmentDescriptor *attachment = desc.colorAttachments[i];
 		MTLRenderPipelineColorAttachmentDescriptor *attachment = desc.colorAttachments[i];
 
 
-		bool isSRGB = false;
-		auto formatdesc = Metal::convertPixelFormat(device, format, isSRGB);
+		auto formatdesc = Metal::convertPixelFormat(device, format);
 		attachment.pixelFormat = formatdesc.format;
 		attachment.pixelFormat = formatdesc.format;
 
 
 		if (key.blend.enable)
 		if (key.blend.enable)
@@ -1124,8 +1123,7 @@ id<MTLRenderPipelineState> Shader::getCachedRenderPipeline(const RenderPipelineK
 		{
 		{
 			// We already don't really support metal on older systems, this just
 			// We already don't really support metal on older systems, this just
 			// silences a compiler warning about it.
 			// silences a compiler warning about it.
-			bool isSRGB = false;
-			auto formatdesc = Metal::convertPixelFormat(device, dsformat, isSRGB);
+			auto formatdesc = Metal::convertPixelFormat(device, dsformat);
 			if (isPixelFormatDepth(dsformat))
 			if (isPixelFormatDepth(dsformat))
 				desc.depthAttachmentPixelFormat = formatdesc.format;
 				desc.depthAttachmentPixelFormat = formatdesc.format;
 			if (isPixelFormatStencil(dsformat))
 			if (isPixelFormatStencil(dsformat))

+ 1 - 1
src/modules/graphics/metal/Texture.mm

@@ -62,7 +62,7 @@ Texture::Texture(love::graphics::Graphics *gfxbase, id<MTLDevice> device, const
 	desc.mipmapLevelCount = mipmapCount;
 	desc.mipmapLevelCount = mipmapCount;
 	desc.textureType = getMTLTextureType(texType, 1);
 	desc.textureType = getMTLTextureType(texType, 1);
 
 
-	auto formatdesc = Metal::convertPixelFormat(device, format, sRGB);
+	auto formatdesc = Metal::convertPixelFormat(device, format);
 	desc.pixelFormat = formatdesc.format;
 	desc.pixelFormat = formatdesc.format;
 	if (formatdesc.swizzled)
 	if (formatdesc.swizzled)
 	{
 	{

+ 41 - 71
src/modules/graphics/opengl/Graphics.cpp

@@ -250,7 +250,7 @@ void Graphics::updateBackbuffer(int width, int height, int /*pixelwidth*/, int p
 		settings.renderTarget = true;
 		settings.renderTarget = true;
 		settings.readable.set(false);
 		settings.readable.set(false);
 
 
-		settings.format = isGammaCorrect() ? PIXELFORMAT_RGBA8_UNORM_sRGB : PIXELFORMAT_RGBA8_UNORM;
+		settings.format = isGammaCorrect() ? PIXELFORMAT_RGBA8_sRGB : PIXELFORMAT_RGBA8_UNORM;
 		internalBackbuffer.set(newTexture(settings), Acquire::NORETAIN);
 		internalBackbuffer.set(newTexture(settings), Acquire::NORETAIN);
 
 
 		settings.format = PIXELFORMAT_DEPTH24_UNORM_STENCIL8;
 		settings.format = PIXELFORMAT_DEPTH24_UNORM_STENCIL8;
@@ -909,6 +909,30 @@ void Graphics::endPass(bool presenting)
 
 
 void Graphics::clear(OptionalColorD c, OptionalInt stencil, OptionalDouble depth)
 void Graphics::clear(OptionalColorD c, OptionalInt stencil, OptionalDouble depth)
 {
 {
+	if (c.hasValue)
+	{
+		bool hasintegerformat = false;
+
+		const auto &rts = states.back().renderTargets;
+		for (const auto &rt : rts.colors)
+		{
+			if (rt.texture.get() && isPixelFormatInteger(rt.texture->getPixelFormat()))
+				hasintegerformat = true;
+		}
+
+		// This variant of clear() uses glClear() which can't clear integer formats,
+		// so we switch to the MRT variant if needed.
+		if (hasintegerformat)
+		{
+			std::vector<OptionalColorD> colors(rts.colors.size());
+			for (size_t i = 0; i < colors.size(); i++)
+				colors[i] = c;
+
+			clear(colors, stencil, depth);
+			return;
+		}
+	}
+
 	if (c.hasValue || stencil.hasValue || depth.hasValue)
 	if (c.hasValue || stencil.hasValue || depth.hasValue)
 		flushBatchedDraws();
 		flushBatchedDraws();
 
 
@@ -922,36 +946,23 @@ void Graphics::clear(OptionalColorD c, OptionalInt stencil, OptionalDouble depth
 		flags |= GL_COLOR_BUFFER_BIT;
 		flags |= GL_COLOR_BUFFER_BIT;
 	}
 	}
 
 
-	uint32 stencilwrites = gl.getStencilWriteMask();
-
 	if (stencil.hasValue)
 	if (stencil.hasValue)
 	{
 	{
-		if (stencilwrites != LOVE_UINT32_MAX)
-			gl.setStencilWriteMask(LOVE_UINT32_MAX);
-
 		glClearStencil(stencil.value);
 		glClearStencil(stencil.value);
 		flags |= GL_STENCIL_BUFFER_BIT;
 		flags |= GL_STENCIL_BUFFER_BIT;
 	}
 	}
 
 
-	bool hadDepthWrites = gl.hasDepthWrites();
-
 	if (depth.hasValue)
 	if (depth.hasValue)
 	{
 	{
-		if (!hadDepthWrites) // glDepthMask also affects glClear.
-			gl.setDepthWrites(true);
-
 		gl.clearDepth(depth.value);
 		gl.clearDepth(depth.value);
 		flags |= GL_DEPTH_BUFFER_BIT;
 		flags |= GL_DEPTH_BUFFER_BIT;
 	}
 	}
 
 
 	if (flags != 0)
 	if (flags != 0)
+	{
+		OpenGL::CleanClearState cs(flags);
 		glClear(flags);
 		glClear(flags);
-
-	if (stencil.hasValue && stencilwrites != LOVE_UINT32_MAX)
-		gl.setStencilWriteMask(stencilwrites);
-
-	if (depth.hasValue && !hadDepthWrites)
-		gl.setDepthWrites(hadDepthWrites);
+	}
 
 
 	if (c.hasValue && gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current)
 	if (c.hasValue && gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current)
 	{
 	{
@@ -1041,36 +1052,23 @@ void Graphics::clear(const std::vector<OptionalColorD> &colors, OptionalInt sten
 
 
 	GLbitfield flags = 0;
 	GLbitfield flags = 0;
 
 
-	uint32 stencilwrites = gl.getStencilWriteMask();
-
 	if (stencil.hasValue)
 	if (stencil.hasValue)
 	{
 	{
-		if (stencilwrites != LOVE_UINT32_MAX)
-			gl.setStencilWriteMask(LOVE_UINT32_MAX);
-
 		glClearStencil(stencil.value);
 		glClearStencil(stencil.value);
 		flags |= GL_STENCIL_BUFFER_BIT;
 		flags |= GL_STENCIL_BUFFER_BIT;
 	}
 	}
 
 
-	bool hadDepthWrites = gl.hasDepthWrites();
-
 	if (depth.hasValue)
 	if (depth.hasValue)
 	{
 	{
-		if (!hadDepthWrites) // glDepthMask also affects glClear.
-			gl.setDepthWrites(true);
-
 		gl.clearDepth(depth.value);
 		gl.clearDepth(depth.value);
 		flags |= GL_DEPTH_BUFFER_BIT;
 		flags |= GL_DEPTH_BUFFER_BIT;
 	}
 	}
 
 
 	if (flags != 0)
 	if (flags != 0)
+	{
+		OpenGL::CleanClearState cs(flags);
 		glClear(flags);
 		glClear(flags);
-
-	if (stencil.hasValue && stencilwrites != LOVE_UINT32_MAX)
-		gl.setStencilWriteMask(stencilwrites);
-
-	if (depth.hasValue && !hadDepthWrites)
-		gl.setDepthWrites(hadDepthWrites);
+	}
 
 
 	if (gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current)
 	if (gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current)
 	{
 	{
@@ -1191,8 +1189,7 @@ GLuint Graphics::bindCachedFBO(const RenderTargets &targets)
 		auto attachRT = [&](const RenderTarget &rt)
 		auto attachRT = [&](const RenderTarget &rt)
 		{
 		{
 			bool renderbuffer = msaa > 1 || !rt.texture->isReadable();
 			bool renderbuffer = msaa > 1 || !rt.texture->isReadable();
-			bool srgb = false;
-			OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(rt.texture->getPixelFormat(), renderbuffer, srgb);
+			OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(rt.texture->getPixelFormat(), renderbuffer);
 
 
 			if (fmt.framebufferAttachments[0] == GL_COLOR_ATTACHMENT0)
 			if (fmt.framebufferAttachments[0] == GL_COLOR_ATTACHMENT0)
 			{
 			{
@@ -1568,7 +1565,11 @@ void Graphics::setColorMask(ColorChannelMask mask)
 {
 {
 	flushBatchedDraws();
 	flushBatchedDraws();
 
 
-	glColorMask(mask.r, mask.g, mask.b, mask.a);
+	uint32 maskbits =
+		((mask.r ? 1 : 0) << 0) | ((mask.g ? 1 : 0) << 1) |
+		((mask.b ? 1 : 0) << 2) | ((mask.a ? 1 : 0) << 3);
+
+	gl.setColorWriteMask(maskbits);
 	states.back().colorMask = mask;
 	states.back().colorMask = mask;
 }
 }
 
 
@@ -1732,31 +1733,6 @@ void Graphics::initCapabilities()
 	}
 	}
 }
 }
 
 
-PixelFormat Graphics::getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const
-{
-	uint32 requiredflags = 0;
-	if (rendertarget)
-		requiredflags |= PIXELFORMATUSAGEFLAGS_RENDERTARGET;
-	if (readable)
-		requiredflags |= PIXELFORMATUSAGEFLAGS_SAMPLE;
-
-	switch (format)
-	{
-	case PIXELFORMAT_NORMAL:
-		if (isGammaCorrect())
-			return PIXELFORMAT_RGBA8_UNORM_sRGB;
-		else if ((OpenGL::getPixelFormatUsageFlags(PIXELFORMAT_RGBA8_UNORM) & requiredflags) != requiredflags)
-			// 32-bit render targets don't have guaranteed support on GLES2.
-			return PIXELFORMAT_RGBA4_UNORM;
-		else
-			return PIXELFORMAT_RGBA8_UNORM;
-	case PIXELFORMAT_HDR:
-		return PIXELFORMAT_RGBA16_FLOAT;
-	default:
-		return format;
-	}
-}
-
 uint32 Graphics::computePixelFormatUsage(PixelFormat format, bool readable)
 uint32 Graphics::computePixelFormatUsage(PixelFormat format, bool readable)
 {
 {
 	uint32 usage = OpenGL::getPixelFormatUsageFlags(format);
 	uint32 usage = OpenGL::getPixelFormatUsageFlags(format);
@@ -1777,9 +1753,8 @@ uint32 Graphics::computePixelFormatUsage(PixelFormat format, bool readable)
 	{
 	{
 		GLuint texture = 0;
 		GLuint texture = 0;
 		GLuint renderbuffer = 0;
 		GLuint renderbuffer = 0;
-		bool sRGB = isPixelFormatSRGB(format);
 
 
-		OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, !readable, sRGB);
+		OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, !readable);
 
 
 		GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
 		GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
 
 
@@ -1801,7 +1776,7 @@ uint32 Graphics::computePixelFormatUsage(PixelFormat format, bool readable)
 			s.minFilter = s.magFilter = SamplerState::FILTER_NEAREST;
 			s.minFilter = s.magFilter = SamplerState::FILTER_NEAREST;
 			gl.setSamplerState(TEXTURE_2D, s);
 			gl.setSamplerState(TEXTURE_2D, s);
 
 
-			gl.rawTexStorage(TEXTURE_2D, 1, format, sRGB, 1, 1);
+			gl.rawTexStorage(TEXTURE_2D, 1, format, 1, 1);
 		}
 		}
 		else
 		else
 		{
 		{
@@ -1837,16 +1812,11 @@ uint32 Graphics::computePixelFormatUsage(PixelFormat format, bool readable)
 	return usage;
 	return usage;
 }
 }
 
 
-bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB)
+bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage)
 {
 {
-	if (sRGB)
-		format = getSRGBPixelFormat(format);
+	format = getSizedFormat(format);
 
 
-	bool rendertarget = (usage & PIXELFORMATUSAGEFLAGS_RENDERTARGET) != 0;
 	bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0;
 	bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0;
-
-	format = getSizedFormat(format, rendertarget, readable);
-
 	return (usage & pixelFormatUsage[format][readable ? 1 : 0]) == usage;
 	return (usage & pixelFormatUsage[format][readable ? 1 : 0]) == usage;
 }
 }
 
 

+ 1 - 2
src/modules/graphics/opengl/Graphics.h

@@ -106,8 +106,7 @@ public:
 
 
 	void setWireframe(bool enable) override;
 	void setWireframe(bool enable) override;
 
 
-	PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const override;
-	bool isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB = false) override;
+	bool isPixelFormatSupported(PixelFormat format, uint32 usage) override;
 	Renderer getRenderer() const override;
 	Renderer getRenderer() const override;
 	bool usesGLSLES() const override;
 	bool usesGLSLES() const override;
 	RendererInfo getRendererInfo() const override;
 	RendererInfo getRendererInfo() const override;

+ 257 - 120
src/modules/graphics/opengl/OpenGL.cpp

@@ -91,12 +91,49 @@ OpenGL::TempDebugGroup::~TempDebugGroup()
 	}
 	}
 }
 }
 
 
+OpenGL::CleanClearState::CleanClearState(GLbitfield clearFlags)
+	: clearFlags(clearFlags)
+	, colorWriteMask(gl.getColorWriteMask())
+	, stencilWriteMask(gl.getStencilWriteMask())
+	, depthWrites(gl.hasDepthWrites())
+	, scissor(gl.isStateEnabled(ENABLE_SCISSOR_TEST))
+{
+	if ((clearFlags & GL_COLOR_BUFFER_BIT) != 0 && colorWriteMask != LOVE_UINT32_MAX)
+		gl.setColorWriteMask(LOVE_UINT32_MAX);
+
+	if ((clearFlags & GL_DEPTH_BUFFER_BIT) != 0 && !depthWrites)
+		gl.setDepthWrites(true);
+
+	if ((clearFlags & GL_STENCIL_BUFFER_BIT) != 0 && (stencilWriteMask & 0xFF) != 0xFF)
+		gl.setStencilWriteMask(LOVE_UINT32_MAX);
+
+	if (clearFlags != 0 && scissor)
+		gl.setEnableState(ENABLE_SCISSOR_TEST, false);
+}
+
+OpenGL::CleanClearState::~CleanClearState()
+{
+	if ((clearFlags & GL_COLOR_BUFFER_BIT) != 0 && colorWriteMask != LOVE_UINT32_MAX)
+		gl.setColorWriteMask(colorWriteMask);
+
+	if ((clearFlags & GL_DEPTH_BUFFER_BIT) != 0 && !depthWrites)
+		gl.setDepthWrites(depthWrites);
+
+	if ((clearFlags & GL_STENCIL_BUFFER_BIT) != 0 && (stencilWriteMask & 0xFF) != 0xFF)
+		gl.setStencilWriteMask(stencilWriteMask);
+
+	if (clearFlags != 0 && scissor)
+		gl.setEnableState(ENABLE_SCISSOR_TEST, scissor);
+}
+
 OpenGL::OpenGL()
 OpenGL::OpenGL()
 	: stats()
 	: stats()
+	, bugs()
 	, contextInitialized(false)
 	, contextInitialized(false)
 	, pixelShaderHighpSupported(false)
 	, pixelShaderHighpSupported(false)
 	, baseVertexSupported(false)
 	, baseVertexSupported(false)
 	, maxAnisotropy(1.0f)
 	, maxAnisotropy(1.0f)
+	, maxLODBias(0.0f)
 	, max2DTextureSize(0)
 	, max2DTextureSize(0)
 	, max3DTextureSize(0)
 	, max3DTextureSize(0)
 	, maxCubeTextureSize(0)
 	, maxCubeTextureSize(0)
@@ -284,6 +321,7 @@ void OpenGL::setupContext()
 
 
 	setDepthWrites(state.depthWritesEnabled);
 	setDepthWrites(state.depthWritesEnabled);
 	setStencilWriteMask(state.stencilWriteMask);
 	setStencilWriteMask(state.stencilWriteMask);
+	setColorWriteMask(state.colorWriteMask);
 
 
 	createDefaultTexture();
 	createDefaultTexture();
 
 
@@ -608,11 +646,9 @@ void OpenGL::createDefaultTexture()
 
 
 			const GLubyte *p = datatype == DATA_BASETYPE_FLOAT ? pix : intpix;
 			const GLubyte *p = datatype == DATA_BASETYPE_FLOAT ? pix : intpix;
 
 
-			bool isSRGB = false;
-			rawTexStorage(type, 1, format, isSRGB, 1, 1);
-
-			TextureFormat fmt = convertPixelFormat(format, false, isSRGB);
+			rawTexStorage(type, 1, format, 1, 1);
 
 
+			TextureFormat fmt = convertPixelFormat(format, false);
 			int slices = type == TEXTURE_CUBE ? 6 : 1;
 			int slices = type == TEXTURE_CUBE ? 6 : 1;
 
 
 			for (int slice = 0; slice < slices; slice++)
 			for (int slice = 0; slice < slices; slice++)
@@ -1125,6 +1161,17 @@ uint32 OpenGL::getStencilWriteMask() const
 	return state.stencilWriteMask;
 	return state.stencilWriteMask;
 }
 }
 
 
+void OpenGL::setColorWriteMask(uint32 mask)
+{
+	glColorMask(mask & (1 << 0), mask & (1 << 1), mask & (1 << 2), mask & (1 << 3));
+	state.colorWriteMask = mask;
+}
+
+uint32 OpenGL::getColorWriteMask() const
+{
+	return state.colorWriteMask;
+}
+
 void OpenGL::useProgram(GLuint program)
 void OpenGL::useProgram(GLuint program)
 {
 {
 	glUseProgram(program);
 	glUseProgram(program);
@@ -1384,10 +1431,10 @@ void OpenGL::setSamplerState(TextureType target, SamplerState &s)
 	}
 	}
 }
 }
 
 
-bool OpenGL::rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth)
+bool OpenGL::rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, int width, int height, int depth)
 {
 {
 	GLenum gltarget = getGLTextureType(target);
 	GLenum gltarget = getGLTextureType(target);
-	TextureFormat fmt = convertPixelFormat(pixelformat, false, isSRGB);
+	TextureFormat fmt = convertPixelFormat(pixelformat, false);
 
 
 	// This shouldn't be needed for glTexStorage, but some drivers don't follow
 	// This shouldn't be needed for glTexStorage, but some drivers don't follow
 	// the spec apparently.
 	// the spec apparently.
@@ -1651,16 +1698,14 @@ OpenGL::Vendor OpenGL::getVendor() const
 	return vendor;
 	return vendor;
 }
 }
 
 
-OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool renderbuffer, bool &isSRGB)
+OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool renderbuffer)
 {
 {
 	TextureFormat f;
 	TextureFormat f;
 
 
 	f.framebufferAttachments[0] = GL_COLOR_ATTACHMENT0;
 	f.framebufferAttachments[0] = GL_COLOR_ATTACHMENT0;
 	f.framebufferAttachments[1] = GL_NONE;
 	f.framebufferAttachments[1] = GL_NONE;
 
 
-	if (isSRGB)
-		pixelformat = getSRGBPixelFormat(pixelformat);
-	else if (pixelformat == PIXELFORMAT_ETC1_UNORM)
+	if (pixelformat == PIXELFORMAT_ETC1_UNORM)
 	{
 	{
 		// The ETC2 format can load ETC1 textures.
 		// The ETC2 format can load ETC1 textures.
 		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility)
 		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility)
@@ -1693,7 +1738,7 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r
 		f.externalformat = GL_RGBA;
 		f.externalformat = GL_RGBA;
 		f.type = GL_UNSIGNED_BYTE;
 		f.type = GL_UNSIGNED_BYTE;
 		break;
 		break;
-	case PIXELFORMAT_RGBA8_UNORM_sRGB:
+	case PIXELFORMAT_RGBA8_sRGB:
 		f.internalformat = GL_SRGB8_ALPHA8;
 		f.internalformat = GL_SRGB8_ALPHA8;
 		f.type = GL_UNSIGNED_BYTE;
 		f.type = GL_UNSIGNED_BYTE;
 		if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0)
 		if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0)
@@ -1702,7 +1747,7 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r
 			f.externalformat = GL_RGBA;
 			f.externalformat = GL_RGBA;
 		break;
 		break;
 	case PIXELFORMAT_BGRA8_UNORM:
 	case PIXELFORMAT_BGRA8_UNORM:
-	case PIXELFORMAT_BGRA8_UNORM_sRGB:
+	case PIXELFORMAT_BGRA8_sRGB:
 		// Not supported right now.
 		// Not supported right now.
 		break;
 		break;
 	case PIXELFORMAT_R16_UNORM:
 	case PIXELFORMAT_R16_UNORM:
@@ -1977,123 +2022,190 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r
 		break;
 		break;
 
 
 	case PIXELFORMAT_DXT1_UNORM:
 	case PIXELFORMAT_DXT1_UNORM:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT : GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+		f.internalformat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+		break;
+	case PIXELFORMAT_DXT1_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
 		break;
 		break;
 	case PIXELFORMAT_DXT3_UNORM:
 	case PIXELFORMAT_DXT3_UNORM:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT : GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+		f.internalformat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+		break;
+	case PIXELFORMAT_DXT3_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
 		break;
 		break;
 	case PIXELFORMAT_DXT5_UNORM:
 	case PIXELFORMAT_DXT5_UNORM:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT : GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+		f.internalformat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+		break;
+	case PIXELFORMAT_DXT5_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
 		break;
 		break;
 	case PIXELFORMAT_BC4_UNORM:
 	case PIXELFORMAT_BC4_UNORM:
-		isSRGB = false;
 		f.internalformat = GL_COMPRESSED_RED_RGTC1;
 		f.internalformat = GL_COMPRESSED_RED_RGTC1;
 		break;
 		break;
 	case PIXELFORMAT_BC4_SNORM:
 	case PIXELFORMAT_BC4_SNORM:
-		isSRGB = false;
 		f.internalformat = GL_COMPRESSED_SIGNED_RED_RGTC1;
 		f.internalformat = GL_COMPRESSED_SIGNED_RED_RGTC1;
 		break;
 		break;
 	case PIXELFORMAT_BC5_UNORM:
 	case PIXELFORMAT_BC5_UNORM:
-		isSRGB = false;
 		f.internalformat = GL_COMPRESSED_RG_RGTC2;
 		f.internalformat = GL_COMPRESSED_RG_RGTC2;
 		break;
 		break;
 	case PIXELFORMAT_BC5_SNORM:
 	case PIXELFORMAT_BC5_SNORM:
-		isSRGB = false;
 		f.internalformat = GL_COMPRESSED_SIGNED_RG_RGTC2;
 		f.internalformat = GL_COMPRESSED_SIGNED_RG_RGTC2;
 		break;
 		break;
 	case PIXELFORMAT_BC6H_UFLOAT:
 	case PIXELFORMAT_BC6H_UFLOAT:
-		isSRGB = false;
 		f.internalformat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT;
 		f.internalformat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT;
 		break;
 		break;
 	case PIXELFORMAT_BC6H_FLOAT:
 	case PIXELFORMAT_BC6H_FLOAT:
-		isSRGB = false;
 		f.internalformat = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT;
 		f.internalformat = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT;
 		break;
 		break;
 	case PIXELFORMAT_BC7_UNORM:
 	case PIXELFORMAT_BC7_UNORM:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM : GL_COMPRESSED_RGBA_BPTC_UNORM;
+		f.internalformat = GL_COMPRESSED_RGBA_BPTC_UNORM;
+		break;
+	case PIXELFORMAT_BC7_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
 		break;
 		break;
+
 	case PIXELFORMAT_PVR1_RGB2_UNORM:
 	case PIXELFORMAT_PVR1_RGB2_UNORM:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT : GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
+		f.internalformat = GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
+		break;
+	case PIXELFORMAT_PVR1_RGB2_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT;
 		break;
 		break;
 	case PIXELFORMAT_PVR1_RGB4_UNORM:
 	case PIXELFORMAT_PVR1_RGB4_UNORM:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT : GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
+		f.internalformat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
+		break;
+	case PIXELFORMAT_PVR1_RGB4_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT;
 		break;
 		break;
 	case PIXELFORMAT_PVR1_RGBA2_UNORM:
 	case PIXELFORMAT_PVR1_RGBA2_UNORM:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT : GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
+		f.internalformat = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
+		break;
+	case PIXELFORMAT_PVR1_RGBA2_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT;
 		break;
 		break;
 	case PIXELFORMAT_PVR1_RGBA4_UNORM:
 	case PIXELFORMAT_PVR1_RGBA4_UNORM:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT : GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
+		f.internalformat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
+		break;
+	case PIXELFORMAT_PVR1_RGBA4_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT;
 		break;
 		break;
+
 	case PIXELFORMAT_ETC1_UNORM:
 	case PIXELFORMAT_ETC1_UNORM:
-		isSRGB = false;
 		f.internalformat = GL_ETC1_RGB8_OES;
 		f.internalformat = GL_ETC1_RGB8_OES;
 		break;
 		break;
 	case PIXELFORMAT_ETC2_RGB_UNORM:
 	case PIXELFORMAT_ETC2_RGB_UNORM:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ETC2 : GL_COMPRESSED_RGB8_ETC2;
+		f.internalformat = GL_COMPRESSED_RGB8_ETC2;
+		break;
+	case PIXELFORMAT_ETC2_RGB_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ETC2;
 		break;
 		break;
 	case PIXELFORMAT_ETC2_RGBA_UNORM:
 	case PIXELFORMAT_ETC2_RGBA_UNORM:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : GL_COMPRESSED_RGBA8_ETC2_EAC;
+		f.internalformat = GL_COMPRESSED_RGBA8_ETC2_EAC;
+		break;
+	case PIXELFORMAT_ETC2_RGBA_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
 		break;
 		break;
 	case PIXELFORMAT_ETC2_RGBA1_UNORM:
 	case PIXELFORMAT_ETC2_RGBA1_UNORM:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 : GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
+		f.internalformat = GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
+		break;
+	case PIXELFORMAT_ETC2_RGBA1_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2;
 		break;
 		break;
 	case PIXELFORMAT_EAC_R_UNORM:
 	case PIXELFORMAT_EAC_R_UNORM:
-		isSRGB = false;
 		f.internalformat = GL_COMPRESSED_R11_EAC;
 		f.internalformat = GL_COMPRESSED_R11_EAC;
 		break;
 		break;
 	case PIXELFORMAT_EAC_R_SNORM:
 	case PIXELFORMAT_EAC_R_SNORM:
-		isSRGB = false;
 		f.internalformat = GL_COMPRESSED_SIGNED_R11_EAC;
 		f.internalformat = GL_COMPRESSED_SIGNED_R11_EAC;
 		break;
 		break;
 	case PIXELFORMAT_EAC_RG_UNORM:
 	case PIXELFORMAT_EAC_RG_UNORM:
-		isSRGB = false;
 		f.internalformat = GL_COMPRESSED_RG11_EAC;
 		f.internalformat = GL_COMPRESSED_RG11_EAC;
 		break;
 		break;
 	case PIXELFORMAT_EAC_RG_SNORM:
 	case PIXELFORMAT_EAC_RG_SNORM:
-		isSRGB = false;
 		f.internalformat = GL_COMPRESSED_SIGNED_RG11_EAC;
 		f.internalformat = GL_COMPRESSED_SIGNED_RG11_EAC;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_4x4:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : GL_COMPRESSED_RGBA_ASTC_4x4_KHR;
+
+	case PIXELFORMAT_ASTC_4x4_UNORM:
+		f.internalformat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR;
+		break;
+	case PIXELFORMAT_ASTC_4x4_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR;
+		break;
+	case PIXELFORMAT_ASTC_5x4_UNORM:
+		f.internalformat = GL_COMPRESSED_RGBA_ASTC_5x4_KHR;
+		break;
+	case PIXELFORMAT_ASTC_5x4_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR;
+		break;
+	case PIXELFORMAT_ASTC_5x5_UNORM:
+		f.internalformat = GL_COMPRESSED_RGBA_ASTC_5x5_KHR;
+		break;
+	case PIXELFORMAT_ASTC_5x5_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR;
+		break;
+	case PIXELFORMAT_ASTC_6x5_UNORM:
+		f.internalformat = GL_COMPRESSED_RGBA_ASTC_6x5_KHR;
+		break;
+	case PIXELFORMAT_ASTC_6x5_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_5x4:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : GL_COMPRESSED_RGBA_ASTC_5x4_KHR;
+	case PIXELFORMAT_ASTC_6x6_UNORM:
+		f.internalformat = GL_COMPRESSED_RGBA_ASTC_6x6_KHR;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_5x5:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : GL_COMPRESSED_RGBA_ASTC_5x5_KHR;
+	case PIXELFORMAT_ASTC_6x6_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_6x5:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : GL_COMPRESSED_RGBA_ASTC_6x5_KHR;
+	case PIXELFORMAT_ASTC_8x5_UNORM:
+		f.internalformat = GL_COMPRESSED_RGBA_ASTC_8x5_KHR;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_6x6:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : GL_COMPRESSED_RGBA_ASTC_6x6_KHR;
+	case PIXELFORMAT_ASTC_8x5_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_8x5:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : GL_COMPRESSED_RGBA_ASTC_8x5_KHR;
+	case PIXELFORMAT_ASTC_8x6_UNORM:
+		f.internalformat = GL_COMPRESSED_RGBA_ASTC_8x6_KHR;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_8x6:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : GL_COMPRESSED_RGBA_ASTC_8x6_KHR;
+	case PIXELFORMAT_ASTC_8x6_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_8x8:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : GL_COMPRESSED_RGBA_ASTC_8x8_KHR;
+	case PIXELFORMAT_ASTC_8x8_UNORM:
+		f.internalformat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_10x5:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : GL_COMPRESSED_RGBA_ASTC_10x5_KHR;
+	case PIXELFORMAT_ASTC_8x8_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_10x6:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : GL_COMPRESSED_RGBA_ASTC_10x6_KHR;
+	case PIXELFORMAT_ASTC_10x5_UNORM:
+		f.internalformat = GL_COMPRESSED_RGBA_ASTC_10x5_KHR;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_10x8:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : GL_COMPRESSED_RGBA_ASTC_10x8_KHR;
+	case PIXELFORMAT_ASTC_10x5_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_10x10:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : GL_COMPRESSED_RGBA_ASTC_10x10_KHR;
+	case PIXELFORMAT_ASTC_10x6_UNORM:
+		f.internalformat = GL_COMPRESSED_RGBA_ASTC_10x6_KHR;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_12x10:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : GL_COMPRESSED_RGBA_ASTC_12x10_KHR;
+	case PIXELFORMAT_ASTC_10x6_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_12x12:
-		f.internalformat = isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : GL_COMPRESSED_RGBA_ASTC_12x12_KHR;
+	case PIXELFORMAT_ASTC_10x8_UNORM:
+		f.internalformat = GL_COMPRESSED_RGBA_ASTC_10x8_KHR;
+		break;
+	case PIXELFORMAT_ASTC_10x8_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR;
+		break;
+	case PIXELFORMAT_ASTC_10x10_UNORM:
+		f.internalformat = GL_COMPRESSED_RGBA_ASTC_10x10_KHR;
+		break;
+	case PIXELFORMAT_ASTC_10x10_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR;
+		break;
+	case PIXELFORMAT_ASTC_12x10_UNORM:
+		f.internalformat = GL_COMPRESSED_RGBA_ASTC_12x10_KHR;
+		break;
+	case PIXELFORMAT_ASTC_12x10_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR;
+		break;
+	case PIXELFORMAT_ASTC_12x12_UNORM:
+		f.internalformat = GL_COMPRESSED_RGBA_ASTC_12x12_KHR;
+		break;
+	case PIXELFORMAT_ASTC_12x12_sRGB:
+		f.internalformat = GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR;
 		break;
 		break;
 
 
 	default:
 	default:
@@ -2115,9 +2227,6 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r
 		{
 		{
 			f.internalformat = f.externalformat;
 			f.internalformat = f.externalformat;
 		}
 		}
-
-		if (!isPixelFormatSRGB(pixelformat))
-			isSRGB = false;
 	}
 	}
 
 
 	return f;
 	return f;
@@ -2149,7 +2258,7 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat)
 		if (GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_1)
 		if (GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_1)
 			flags |= computewrite;
 			flags |= computewrite;
 		break;
 		break;
-	case PIXELFORMAT_RGBA8_UNORM_sRGB:
+	case PIXELFORMAT_RGBA8_sRGB:
 		if (gl.bugs.brokenSRGB)
 		if (gl.bugs.brokenSRGB)
 			break;
 			break;
 		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB)
 		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB)
@@ -2161,7 +2270,7 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat)
 			flags |= computewrite;
 			flags |= computewrite;
 		break;
 		break;
 	case PIXELFORMAT_BGRA8_UNORM:
 	case PIXELFORMAT_BGRA8_UNORM:
-	case PIXELFORMAT_BGRA8_UNORM_sRGB:
+	case PIXELFORMAT_BGRA8_sRGB:
 		// Not supported right now.
 		// Not supported right now.
 		break;
 		break;
 	case PIXELFORMAT_R16_UNORM:
 	case PIXELFORMAT_R16_UNORM:
@@ -2229,47 +2338,47 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat)
 			flags |= computewrite;
 			flags |= computewrite;
 		break;
 		break;
 
 
-		case PIXELFORMAT_R8_INT:
-		case PIXELFORMAT_R8_UINT:
-		case PIXELFORMAT_RG8_INT:
-		case PIXELFORMAT_RG8_UINT:
-		case PIXELFORMAT_RGBA8_INT:
-		case PIXELFORMAT_RGBA8_UINT:
-		case PIXELFORMAT_R16_INT:
-		case PIXELFORMAT_R16_UINT:
-		case PIXELFORMAT_RG16_INT:
-		case PIXELFORMAT_RG16_UINT:
-		case PIXELFORMAT_RGBA16_INT:
-		case PIXELFORMAT_RGBA16_UINT:
-		case PIXELFORMAT_R32_INT:
-		case PIXELFORMAT_R32_UINT:
-		case PIXELFORMAT_RG32_INT:
-		case PIXELFORMAT_RG32_UINT:
-		case PIXELFORMAT_RGBA32_INT:
-		case PIXELFORMAT_RGBA32_UINT:
-			if (GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0)
-				flags |= PIXELFORMATUSAGEFLAGS_SAMPLE | PIXELFORMATUSAGEFLAGS_RENDERTARGET;
-			if (GLAD_VERSION_4_3)
-				flags |= computewrite;
-			if (GLAD_ES_VERSION_3_1)
+	case PIXELFORMAT_R8_INT:
+	case PIXELFORMAT_R8_UINT:
+	case PIXELFORMAT_RG8_INT:
+	case PIXELFORMAT_RG8_UINT:
+	case PIXELFORMAT_RGBA8_INT:
+	case PIXELFORMAT_RGBA8_UINT:
+	case PIXELFORMAT_R16_INT:
+	case PIXELFORMAT_R16_UINT:
+	case PIXELFORMAT_RG16_INT:
+	case PIXELFORMAT_RG16_UINT:
+	case PIXELFORMAT_RGBA16_INT:
+	case PIXELFORMAT_RGBA16_UINT:
+	case PIXELFORMAT_R32_INT:
+	case PIXELFORMAT_R32_UINT:
+	case PIXELFORMAT_RG32_INT:
+	case PIXELFORMAT_RG32_UINT:
+	case PIXELFORMAT_RGBA32_INT:
+	case PIXELFORMAT_RGBA32_UINT:
+		if (GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0)
+			flags |= PIXELFORMATUSAGEFLAGS_SAMPLE | PIXELFORMATUSAGEFLAGS_RENDERTARGET;
+		if (GLAD_VERSION_4_3)
+			flags |= computewrite;
+		if (GLAD_ES_VERSION_3_1)
+		{
+			switch (pixelformat)
 			{
 			{
-				switch (pixelformat)
-				{
-				case PIXELFORMAT_RGBA8_INT:
-				case PIXELFORMAT_RGBA8_UINT:
-				case PIXELFORMAT_RGBA16_INT:
-				case PIXELFORMAT_RGBA16_UINT:
-				case PIXELFORMAT_R32_INT:
-				case PIXELFORMAT_R32_UINT:
-				case PIXELFORMAT_RGBA32_INT:
-				case PIXELFORMAT_RGBA32_UINT:
-					flags |= computewrite;
-					break;
-				default:
-					break;
-				}
+			case PIXELFORMAT_RGBA8_INT:
+			case PIXELFORMAT_RGBA8_UINT:
+			case PIXELFORMAT_RGBA16_INT:
+			case PIXELFORMAT_RGBA16_UINT:
+			case PIXELFORMAT_R32_INT:
+			case PIXELFORMAT_R32_UINT:
+			case PIXELFORMAT_RGBA32_INT:
+			case PIXELFORMAT_RGBA32_UINT:
+				flags |= computewrite;
+				break;
+			default:
+				break;
 			}
 			}
-			break;
+		}
+		break;
 
 
 	case PIXELFORMAT_LA8_UNORM:
 	case PIXELFORMAT_LA8_UNORM:
 		flags |= commonsample;
 		flags |= commonsample;
@@ -2331,14 +2440,17 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat)
 		break;
 		break;
 
 
 	case PIXELFORMAT_DXT1_UNORM:
 	case PIXELFORMAT_DXT1_UNORM:
+	case PIXELFORMAT_DXT1_sRGB:
 		if (GLAD_EXT_texture_compression_s3tc || GLAD_EXT_texture_compression_dxt1)
 		if (GLAD_EXT_texture_compression_s3tc || GLAD_EXT_texture_compression_dxt1)
 			flags |= commonsample;
 			flags |= commonsample;
 		break;
 		break;
 	case PIXELFORMAT_DXT3_UNORM:
 	case PIXELFORMAT_DXT3_UNORM:
+	case PIXELFORMAT_DXT3_sRGB:
 		if (GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt3)
 		if (GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt3)
 			flags |= commonsample;
 			flags |= commonsample;
 		break;
 		break;
 	case PIXELFORMAT_DXT5_UNORM:
 	case PIXELFORMAT_DXT5_UNORM:
+	case PIXELFORMAT_DXT5_sRGB:
 		if (GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt5)
 		if (GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt5)
 			flags |= commonsample;
 			flags |= commonsample;
 		break;
 		break;
@@ -2352,6 +2464,7 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat)
 	case PIXELFORMAT_BC6H_UFLOAT:
 	case PIXELFORMAT_BC6H_UFLOAT:
 	case PIXELFORMAT_BC6H_FLOAT:
 	case PIXELFORMAT_BC6H_FLOAT:
 	case PIXELFORMAT_BC7_UNORM:
 	case PIXELFORMAT_BC7_UNORM:
+	case PIXELFORMAT_BC7_sRGB:
 		if (GLAD_VERSION_4_2 || GLAD_ARB_texture_compression_bptc)
 		if (GLAD_VERSION_4_2 || GLAD_ARB_texture_compression_bptc)
 			flags |= commonsample;
 			flags |= commonsample;
 		break;
 		break;
@@ -2362,14 +2475,24 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat)
 		if (GLAD_IMG_texture_compression_pvrtc)
 		if (GLAD_IMG_texture_compression_pvrtc)
 			flags |= commonsample;
 			flags |= commonsample;
 		break;
 		break;
+	case PIXELFORMAT_PVR1_RGB2_sRGB:
+	case PIXELFORMAT_PVR1_RGB4_sRGB:
+	case PIXELFORMAT_PVR1_RGBA2_sRGB:
+	case PIXELFORMAT_PVR1_RGBA4_sRGB:
+		if (GLAD_EXT_pvrtc_sRGB)
+			flags |= commonsample;
+		break;
 	case PIXELFORMAT_ETC1_UNORM:
 	case PIXELFORMAT_ETC1_UNORM:
 		// ETC2 support guarantees ETC1 support as well.
 		// ETC2 support guarantees ETC1 support as well.
 		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility || GLAD_OES_compressed_ETC1_RGB8_texture)
 		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility || GLAD_OES_compressed_ETC1_RGB8_texture)
 			flags |= commonsample;
 			flags |= commonsample;
 		break;
 		break;
 	case PIXELFORMAT_ETC2_RGB_UNORM:
 	case PIXELFORMAT_ETC2_RGB_UNORM:
+	case PIXELFORMAT_ETC2_RGB_sRGB:
 	case PIXELFORMAT_ETC2_RGBA_UNORM:
 	case PIXELFORMAT_ETC2_RGBA_UNORM:
+	case PIXELFORMAT_ETC2_RGBA_sRGB:
 	case PIXELFORMAT_ETC2_RGBA1_UNORM:
 	case PIXELFORMAT_ETC2_RGBA1_UNORM:
+	case PIXELFORMAT_ETC2_RGBA1_sRGB:
 	case PIXELFORMAT_EAC_R_UNORM:
 	case PIXELFORMAT_EAC_R_UNORM:
 	case PIXELFORMAT_EAC_R_SNORM:
 	case PIXELFORMAT_EAC_R_SNORM:
 	case PIXELFORMAT_EAC_RG_UNORM:
 	case PIXELFORMAT_EAC_RG_UNORM:
@@ -2377,20 +2500,34 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat)
 		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility)
 		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility)
 			flags |= commonsample;
 			flags |= commonsample;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_4x4:
-	case PIXELFORMAT_ASTC_5x4:
-	case PIXELFORMAT_ASTC_5x5:
-	case PIXELFORMAT_ASTC_6x5:
-	case PIXELFORMAT_ASTC_6x6:
-	case PIXELFORMAT_ASTC_8x5:
-	case PIXELFORMAT_ASTC_8x6:
-	case PIXELFORMAT_ASTC_8x8:
-	case PIXELFORMAT_ASTC_10x5:
-	case PIXELFORMAT_ASTC_10x6:
-	case PIXELFORMAT_ASTC_10x8:
-	case PIXELFORMAT_ASTC_10x10:
-	case PIXELFORMAT_ASTC_12x10:
-	case PIXELFORMAT_ASTC_12x12:
+	case PIXELFORMAT_ASTC_4x4_UNORM:
+	case PIXELFORMAT_ASTC_5x4_UNORM:
+	case PIXELFORMAT_ASTC_5x5_UNORM:
+	case PIXELFORMAT_ASTC_6x5_UNORM:
+	case PIXELFORMAT_ASTC_6x6_UNORM:
+	case PIXELFORMAT_ASTC_8x5_UNORM:
+	case PIXELFORMAT_ASTC_8x6_UNORM:
+	case PIXELFORMAT_ASTC_8x8_UNORM:
+	case PIXELFORMAT_ASTC_10x5_UNORM:
+	case PIXELFORMAT_ASTC_10x6_UNORM:
+	case PIXELFORMAT_ASTC_10x8_UNORM:
+	case PIXELFORMAT_ASTC_10x10_UNORM:
+	case PIXELFORMAT_ASTC_12x10_UNORM:
+	case PIXELFORMAT_ASTC_12x12_UNORM:
+	case PIXELFORMAT_ASTC_4x4_sRGB:
+	case PIXELFORMAT_ASTC_5x4_sRGB:
+	case PIXELFORMAT_ASTC_5x5_sRGB:
+	case PIXELFORMAT_ASTC_6x5_sRGB:
+	case PIXELFORMAT_ASTC_6x6_sRGB:
+	case PIXELFORMAT_ASTC_8x5_sRGB:
+	case PIXELFORMAT_ASTC_8x6_sRGB:
+	case PIXELFORMAT_ASTC_8x8_sRGB:
+	case PIXELFORMAT_ASTC_10x5_sRGB:
+	case PIXELFORMAT_ASTC_10x6_sRGB:
+	case PIXELFORMAT_ASTC_10x8_sRGB:
+	case PIXELFORMAT_ASTC_10x10_sRGB:
+	case PIXELFORMAT_ASTC_12x10_sRGB:
+	case PIXELFORMAT_ASTC_12x12_sRGB:
 		if (GLAD_ES_VERSION_3_2 || GLAD_KHR_texture_compression_astc_ldr)
 		if (GLAD_ES_VERSION_3_2 || GLAD_KHR_texture_compression_astc_ldr)
 			flags |= commonsample;
 			flags |= commonsample;
 		break;
 		break;

+ 21 - 2
src/modules/graphics/opengl/OpenGL.h

@@ -124,6 +124,21 @@ public:
 		~TempDebugGroup();
 		~TempDebugGroup();
 	};
 	};
 
 
+	// glClear() is affected by various OpenGL state...
+	class CleanClearState
+	{
+	public:
+		CleanClearState(GLbitfield clearFlags);
+		~CleanClearState();
+
+	private:
+		GLenum clearFlags;
+		uint32 colorWriteMask;
+		uint32 stencilWriteMask;
+		bool depthWrites;
+		bool scissor;
+	};
+
 	struct Stats
 	struct Stats
 	{
 	{
 		int shaderSwitches;
 		int shaderSwitches;
@@ -301,6 +316,9 @@ public:
 	void setStencilWriteMask(uint32 mask);
 	void setStencilWriteMask(uint32 mask);
 	uint32 getStencilWriteMask() const;
 	uint32 getStencilWriteMask() const;
 
 
+	void setColorWriteMask(uint32 mask);
+	uint32 getColorWriteMask() const;
+
 	/**
 	/**
 	 * Calls glUseProgram.
 	 * Calls glUseProgram.
 	 **/
 	 **/
@@ -363,7 +381,7 @@ public:
 	 * to glTexImage2D/3D for all levels and slices of a texture otherwise.
 	 * to glTexImage2D/3D for all levels and slices of a texture otherwise.
 	 * NOTE: this does not handle compressed texture formats.
 	 * NOTE: this does not handle compressed texture formats.
 	 **/
 	 **/
-	bool rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth = 1);
+	bool rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, int width, int height, int depth = 1);
 
 
 	bool isTextureTypeSupported(TextureType type) const;
 	bool isTextureTypeSupported(TextureType type) const;
 	bool isBufferUsageSupported(BufferUsage usage) const;
 	bool isBufferUsageSupported(BufferUsage usage) const;
@@ -457,7 +475,7 @@ public:
 	static GLint getGLWrapMode(SamplerState::WrapMode wmode);
 	static GLint getGLWrapMode(SamplerState::WrapMode wmode);
 	static GLint getGLCompareMode(CompareMode mode);
 	static GLint getGLCompareMode(CompareMode mode);
 
 
-	static TextureFormat convertPixelFormat(PixelFormat pixelformat, bool renderbuffer, bool &isSRGB);
+	static TextureFormat convertPixelFormat(PixelFormat pixelformat, bool renderbuffer);
 	static bool isTexStorageSupported();
 	static bool isTexStorageSupported();
 	static uint32 getPixelFormatUsageFlags(PixelFormat pixelformat);
 	static uint32 getPixelFormatUsageFlags(PixelFormat pixelformat);
 
 
@@ -528,6 +546,7 @@ private:
 
 
 		bool depthWritesEnabled = true;
 		bool depthWritesEnabled = true;
 		uint32 stencilWriteMask = LOVE_UINT32_MAX;
 		uint32 stencilWriteMask = LOVE_UINT32_MAX;
+		uint32 colorWriteMask = LOVE_UINT32_MAX;
 
 
 		GLuint boundFramebuffers[2];
 		GLuint boundFramebuffers[2];
 
 

+ 1 - 2
src/modules/graphics/opengl/Shader.cpp

@@ -175,8 +175,7 @@ void Shader::mapActiveUniforms()
 			else if ((u.access & ACCESS_READ) != 0)
 			else if ((u.access & ACCESS_READ) != 0)
 				binding.access = GL_READ_ONLY;
 				binding.access = GL_READ_ONLY;
 
 
-			bool sRGB = false;
-			auto fmt = OpenGL::convertPixelFormat(u.storageTextureFormat, false, sRGB);
+			auto fmt = OpenGL::convertPixelFormat(u.storageTextureFormat, false);
 			binding.internalFormat = fmt.internalformat;
 			binding.internalFormat = fmt.internalformat;
 
 
 			for (int i = 0; i < u.count; i++)
 			for (int i = 0; i < u.count; i++)

+ 56 - 39
src/modules/graphics/opengl/Texture.cpp

@@ -56,8 +56,7 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo
 			glReadBuffer(GL_NONE);
 			glReadBuffer(GL_NONE);
 		}
 		}
 
 
-		bool unusedSRGB = false;
-		OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, unusedSRGB);
+		OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false);
 
 
 		int faces = texType == TEXTURE_CUBE ? 6 : 1;
 		int faces = texType == TEXTURE_CUBE ? 6 : 1;
 
 
@@ -78,33 +77,38 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo
 						gl.framebufferTexture(attachment, texType, texture, mip, layer, face);
 						gl.framebufferTexture(attachment, texType, texture, mip, layer, face);
 					}
 					}
 
 
-					if (clear)
+					if (clear && isPixelFormatInteger(format))
 					{
 					{
-						if (isPixelFormatDepthStencil(format))
+						PixelFormatType datatype = getPixelFormatInfo(format).dataType;
+						if (datatype == PIXELFORMATTYPE_SINT)
 						{
 						{
-							bool hadDepthWrites = gl.hasDepthWrites();
-							if (!hadDepthWrites) // glDepthMask also affects glClear.
-								gl.setDepthWrites(true);
+							const GLint carray[] = { 0, 0, 0, 0 };
+							glClearBufferiv(GL_COLOR, 0, carray);
+						}
+						else
+						{
+							const GLuint carray[] = { 0, 0, 0, 0 };
+							glClearBufferuiv(GL_COLOR, 0, carray);
+						}
+					}
+					else if (clear)
+					{
+						bool ds = isPixelFormatDepthStencil(format);
 
 
-							uint32 stencilwrite = gl.getStencilWriteMask();
-							if (stencilwrite != LOVE_UINT32_MAX)
-								gl.setStencilWriteMask(LOVE_UINT32_MAX);
+						GLbitfield clearflags = ds ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT;
+						OpenGL::CleanClearState cleanClearState(clearflags);
 
 
+						if (ds)
+						{
 							gl.clearDepth(1.0);
 							gl.clearDepth(1.0);
 							glClearStencil(0);
 							glClearStencil(0);
-							glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
-
-							if (!hadDepthWrites)
-								gl.setDepthWrites(hadDepthWrites);
-
-							if (stencilwrite != LOVE_UINT32_MAX)
-								gl.setStencilWriteMask(stencilwrite);
 						}
 						}
 						else
 						else
 						{
 						{
 							glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
 							glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-							glClear(GL_COLOR_BUFFER_BIT);
 						}
 						}
+
+						glClear(clearflags);
 					}
 					}
 				}
 				}
 			}
 			}
@@ -120,8 +124,7 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo
 
 
 static GLenum newRenderbuffer(int width, int height, int &samples, PixelFormat pixelformat, GLuint &buffer)
 static GLenum newRenderbuffer(int width, int height, int &samples, PixelFormat pixelformat, GLuint &buffer)
 {
 {
-	bool unusedSRGB = false;
-	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(pixelformat, true, unusedSRGB);
+	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(pixelformat, true);
 
 
 	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
 	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
 
 
@@ -167,24 +170,39 @@ static GLenum newRenderbuffer(int width, int height, int &samples, PixelFormat p
 
 
 	if (status == GL_FRAMEBUFFER_COMPLETE)
 	if (status == GL_FRAMEBUFFER_COMPLETE)
 	{
 	{
-		if (isPixelFormatDepthStencil(pixelformat))
+		if (isPixelFormatInteger(pixelformat))
 		{
 		{
-			bool hadDepthWrites = gl.hasDepthWrites();
-			if (!hadDepthWrites) // glDepthMask also affects glClear.
-				gl.setDepthWrites(true);
-
-			gl.clearDepth(1.0);
-			glClearStencil(0);
-			glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
-
-			if (!hadDepthWrites)
-				gl.setDepthWrites(hadDepthWrites);
+			PixelFormatType datatype = getPixelFormatInfo(pixelformat).dataType;
+			if (datatype == PIXELFORMATTYPE_SINT)
+			{
+				const GLint carray[] = { 0, 0, 0, 0 };
+				glClearBufferiv(GL_COLOR, 0, carray);
+			}
+			else
+			{
+				const GLuint carray[] = { 0, 0, 0, 0 };
+				glClearBufferuiv(GL_COLOR, 0, carray);
+			}
 		}
 		}
 		else
 		else
 		{
 		{
-			// Initialize the buffer to transparent black.
-			glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-			glClear(GL_COLOR_BUFFER_BIT);
+			bool ds = isPixelFormatDepthStencil(pixelformat);
+
+			GLbitfield clearflags = ds ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT;
+			OpenGL::CleanClearState cleanClearState(clearflags);
+
+			if (ds)
+			{
+				gl.clearDepth(1.0);
+				glClearStencil(0);
+			}
+			else
+			{
+				// Initialize the buffer to transparent black.
+				glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+			}
+
+			glClear(clearflags);
 		}
 		}
 	}
 	}
 	else
 	else
@@ -262,7 +280,7 @@ void Texture::createTexture()
 	// remember some driver issues on some old Android systems, maybe...
 	// remember some driver issues on some old Android systems, maybe...
 	// For now, the base class enforces data on init for compressed textures.
 	// For now, the base class enforces data on init for compressed textures.
 	if (!isCompressed())
 	if (!isCompressed())
-		gl.rawTexStorage(texType, mipcount, format, sRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers);
+		gl.rawTexStorage(texType, mipcount, format, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers);
 
 
 	// rawTexStorage handles this for uncompressed textures.
 	// rawTexStorage handles this for uncompressed textures.
 	if (isCompressed() && (GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0))
 	if (isCompressed() && (GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0))
@@ -272,7 +290,7 @@ void Texture::createTexture()
 	int h = pixelHeight;
 	int h = pixelHeight;
 	int d = depth;
 	int d = depth;
 
 
-	OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false, sRGB);
+	OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false);
 
 
 	for (int mip = 0; mip < mipcount; mip++)
 	for (int mip = 0; mip < mipcount; mip++)
 	{
 	{
@@ -440,7 +458,7 @@ void Texture::uploadByteData(PixelFormat pixelformat, const void *data, size_t s
 
 
 	gl.bindTextureToUnit(this, 0, false);
 	gl.bindTextureToUnit(this, 0, false);
 
 
-	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(pixelformat, false, sRGB);
+	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(pixelformat, false);
 	GLenum gltarget = OpenGL::getGLTextureType(texType);
 	GLenum gltarget = OpenGL::getGLTextureType(texType);
 
 
 	if (texType == TEXTURE_CUBE)
 	if (texType == TEXTURE_CUBE)
@@ -488,8 +506,7 @@ void Texture::readbackInternal(int slice, int mipmap, const Rect &rect, int dest
 
 
 	gl.bindTextureToUnit(this, 0, false);
 	gl.bindTextureToUnit(this, 0, false);
 
 
-	bool isSRGB = false;
-	OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false, isSRGB);
+	OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false);
 
 
 	if (gl.isCopyTextureToBufferSupported())
 	if (gl.isCopyTextureToBufferSupported())
 	{
 	{

+ 151 - 155
src/modules/graphics/vulkan/Graphics.cpp

@@ -65,29 +65,100 @@ const char *Graphics::getName() const
 	return "love.graphics.vulkan";
 	return "love.graphics.vulkan";
 }
 }
 
 
-const VkDevice Graphics::getDevice() const
+VkDevice Graphics::getDevice() const
 {
 {
 	return device;
 	return device;
 }
 }
 
 
-const VmaAllocator Graphics::getVmaAllocator() const
+VmaAllocator Graphics::getVmaAllocator() const
 {
 {
 	return vmaAllocator;
 	return vmaAllocator;
 }
 }
 
 
+static void checkOptionalInstanceExtensions(OptionalInstanceExtensions& ext)
+{
+	uint32_t count;
+
+	vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr);
+
+	std::vector<VkExtensionProperties> extensions(count);
+
+	vkEnumerateInstanceExtensionProperties(nullptr, &count, extensions.data());
+
+	for (const auto& extension : extensions)
+	{
+		if (strcmp(extension.extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0)
+			ext.physicalDeviceProperties2 = true;
+	}
+}
+
 Graphics::Graphics()
 Graphics::Graphics()
 {
 {
 	if (SDL_Vulkan_LoadLibrary(nullptr))
 	if (SDL_Vulkan_LoadLibrary(nullptr))
 		throw love::Exception("could not find vulkan");
 		throw love::Exception("could not find vulkan");
 
 
 	volkInitializeCustom((PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr());
 	volkInitializeCustom((PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr());
+
+	if (isDebugEnabled() && !checkValidationSupport())
+		throw love::Exception("validation layers requested, but not available");
+
+	VkApplicationInfo appInfo{};
+	appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+	appInfo.pApplicationName = "LOVE";
+	appInfo.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);	// get this version from somewhere else?
+	appInfo.pEngineName = "LOVE Game Framework";
+	appInfo.engineVersion = VK_MAKE_API_VERSION(0, VERSION_MAJOR, VERSION_MINOR, VERSION_REV);
+	appInfo.apiVersion = VK_API_VERSION_1_3;
+
+	VkInstanceCreateInfo createInfo{};
+	createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+	createInfo.pApplicationInfo = &appInfo;
+	createInfo.pNext = nullptr;
+
+	// GetInstanceExtensions works with a null window parameter as long as
+	// SDL_Vulkan_LoadLibrary has been called (which we do earlier).
+	unsigned int count;
+	if (SDL_Vulkan_GetInstanceExtensions(nullptr, &count, nullptr) != SDL_TRUE)
+		throw love::Exception("couldn't retrieve sdl vulkan extensions");
+
+	std::vector<const char*> extensions = {};
+
+	checkOptionalInstanceExtensions(optionalInstanceExtensions);
+
+	if (optionalInstanceExtensions.physicalDeviceProperties2)
+		extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+
+	size_t additional_extension_count = extensions.size();
+	extensions.resize(additional_extension_count + count);
+
+	if (SDL_Vulkan_GetInstanceExtensions(nullptr, &count, extensions.data() + additional_extension_count) != SDL_TRUE)
+		throw love::Exception("couldn't retrieve sdl vulkan extensions");
+
+	createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
+	createInfo.ppEnabledExtensionNames = extensions.data();
+
+	if (isDebugEnabled())
+	{
+		createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
+		createInfo.ppEnabledLayerNames = validationLayers.data();
+	}
+
+	if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS)
+		throw love::Exception("couldn't create vulkan instance");
+
+	volkLoadInstance(instance);
 }
 }
 
 
 Graphics::~Graphics()
 Graphics::~Graphics()
 {
 {
+	defaultConstantTexCoord.set(nullptr);
 	defaultConstantColor.set(nullptr);
 	defaultConstantColor.set(nullptr);
 	defaultTexture.set(nullptr);
 	defaultTexture.set(nullptr);
 
 
+	Volatile::unloadAll();
+	cleanup();
+	vkDestroyInstance(instance, nullptr);
+
 	SDL_Vulkan_UnloadLibrary();
 	SDL_Vulkan_UnloadLibrary();
 }
 }
 
 
@@ -302,14 +373,14 @@ void Graphics::discard(const std::vector<bool> &colorbuffers, bool depthstencil)
 	startRenderPass();
 	startRenderPass();
 }
 }
 
 
-void Graphics::submitGpuCommands(bool present, void *screenshotCallbackData)
+void Graphics::submitGpuCommands(SubmitMode submitMode, void *screenshotCallbackData)
 {
 {
 	flushBatchedDraws();
 	flushBatchedDraws();
 
 
 	if (renderPassState.active)
 	if (renderPassState.active)
 		endRenderPass();
 		endRenderPass();
 
 
-	if (present)
+	if (submitMode == SUBMIT_PRESENT)
 	{
 	{
 		if (pendingScreenshotCallbacks.empty())
 		if (pendingScreenshotCallbacks.empty())
 			Vulkan::cmdTransitionImageLayout(
 			Vulkan::cmdTransitionImageLayout(
@@ -435,7 +506,7 @@ void Graphics::submitGpuCommands(bool present, void *screenshotCallbackData)
 
 
 	VkFence fence = VK_NULL_HANDLE;
 	VkFence fence = VK_NULL_HANDLE;
 
 
-	if (present)
+	if (submitMode == SUBMIT_PRESENT)
 	{
 	{
 		submitInfo.signalSemaphoreCount = 1;
 		submitInfo.signalSemaphoreCount = 1;
 		submitInfo.pSignalSemaphores = signalSemaphores;
 		submitInfo.pSignalSemaphores = signalSemaphores;
@@ -447,7 +518,7 @@ void Graphics::submitGpuCommands(bool present, void *screenshotCallbackData)
 	if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence) != VK_SUCCESS)
 	if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence) != VK_SUCCESS)
 		throw love::Exception("failed to submit draw command buffer");
 		throw love::Exception("failed to submit draw command buffer");
 	
 	
-	if (!present)
+	if (submitMode == SUBMIT_NOPRESENT || submitMode == SUBMIT_RESTART)
 	{
 	{
 		vkQueueWaitIdle(graphicsQueue);
 		vkQueueWaitIdle(graphicsQueue);
 
 
@@ -458,7 +529,8 @@ void Graphics::submitGpuCommands(bool present, void *screenshotCallbackData)
 			callbacks.clear();
 			callbacks.clear();
 		}
 		}
 
 
-		startRecordingGraphicsCommands();
+		if (submitMode == SUBMIT_RESTART)
+			startRecordingGraphicsCommands();
 	}
 	}
 }
 }
 
 
@@ -475,7 +547,7 @@ void Graphics::present(void *screenshotCallbackdata)
 
 
 	deprecations.draw(this);
 	deprecations.draw(this);
 
 
-	submitGpuCommands(true, screenshotCallbackdata);
+	submitGpuCommands(SUBMIT_PRESENT, screenshotCallbackdata);
 
 
 	VkPresentInfoKHR presentInfo{};
 	VkPresentInfoKHR presentInfo{};
 	presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
 	presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
@@ -540,64 +612,80 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
 	readbackCallbacks.clear();
 	readbackCallbacks.clear();
 	readbackCallbacks.resize(MAX_FRAMES_IN_FLIGHT);
 	readbackCallbacks.resize(MAX_FRAMES_IN_FLIGHT);
 
 
-	createVulkanInstance();
+	bool createBaseObjects = physicalDevice == VK_NULL_HANDLE;
+
 	createSurface();
 	createSurface();
-	pickPhysicalDevice();
-	createLogicalDevice();
-	createPipelineCache();
-	initVMA();
-	initCapabilities();
+
+	if (createBaseObjects)
+	{
+		pickPhysicalDevice();
+		createLogicalDevice();
+		createPipelineCache();
+		initVMA();
+		initCapabilities();
+	}
+
+	msaaSamples = getMsaaCount(requestedMsaa);
+
 	createSwapChain();
 	createSwapChain();
 	createImageViews();
 	createImageViews();
 	createScreenshotCallbackBuffers();
 	createScreenshotCallbackBuffers();
-	createSyncObjects();
 	createColorResources();
 	createColorResources();
 	createDepthResources();
 	createDepthResources();
 	transitionColorDepthLayouts = true;
 	transitionColorDepthLayouts = true;
-	createCommandPool();
-	createCommandBuffers();
 
 
-	beginFrame();
-
-	if (batchedDrawState.vb[0] == nullptr)
+	if (createBaseObjects)
 	{
 	{
-		// Initial sizes that should be good enough for most cases. It will
-		// resize to fit if needed, later.
-		batchedDrawState.vb[0] = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 1024 * 1024 * 1);
-		batchedDrawState.vb[1] = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 256 * 1024 * 1);
-		batchedDrawState.indexBuffer = new StreamBuffer(this, BUFFERUSAGE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
+		createCommandPool();
+		createCommandBuffers();
+		createSyncObjects();
 	}
 	}
 
 
-	// sometimes the VertexTexCoord is not set, so we manually adjust it to (0, 0)
-	if (defaultConstantTexCoord == nullptr)
-	{
-		float zeroTexCoord[2] = { 0.0f, 0.0f };
-		Buffer::DataDeclaration format("ConstantTexCoord", DATAFORMAT_FLOAT_VEC2);
-		Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STATIC);
-		defaultConstantTexCoord = newBuffer(settings, { format }, zeroTexCoord, sizeof(zeroTexCoord), 1);
-	}
+	beginFrame();
 
 
-	// sometimes the VertexColor is not set, so we manually adjust it to white color
-	if (defaultConstantColor == nullptr)
+	if (createBaseObjects)
 	{
 	{
-		uint8 whiteColor[] = { 255, 255, 255, 255 };
-		Buffer::DataDeclaration format("ConstantColor", DATAFORMAT_UNORM8_VEC4);
-		Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STATIC);
-		defaultConstantColor = newBuffer(settings, { format }, whiteColor, sizeof(whiteColor), 1);
-	}
+		if (batchedDrawState.vb[0] == nullptr)
+		{
+			// Initial sizes that should be good enough for most cases. It will
+			// resize to fit if needed, later.
+			batchedDrawState.vb[0] = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 1024 * 1024 * 1);
+			batchedDrawState.vb[1] = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 256 * 1024 * 1);
+			batchedDrawState.indexBuffer = new StreamBuffer(this, BUFFERUSAGE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
+		}
 
 
-	createDefaultTexture();
-	createDefaultShaders();
-	Shader::current = Shader::standardShaders[Shader::StandardShader::STANDARD_DEFAULT];
-	createQuadIndexBuffer();
-	createFanIndexBuffer();
+		// sometimes the VertexTexCoord is not set, so we manually adjust it to (0, 0)
+		if (defaultConstantTexCoord == nullptr)
+		{
+			float zeroTexCoord[2] = { 0.0f, 0.0f };
+			Buffer::DataDeclaration format("ConstantTexCoord", DATAFORMAT_FLOAT_VEC2);
+			Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STATIC);
+			defaultConstantTexCoord = newBuffer(settings, { format }, zeroTexCoord, sizeof(zeroTexCoord), 1);
+		}
+
+		// sometimes the VertexColor is not set, so we manually adjust it to white color
+		if (defaultConstantColor == nullptr)
+		{
+			uint8 whiteColor[] = { 255, 255, 255, 255 };
+			Buffer::DataDeclaration format("ConstantColor", DATAFORMAT_UNORM8_VEC4);
+			Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STATIC);
+			defaultConstantColor = newBuffer(settings, { format }, whiteColor, sizeof(whiteColor), 1);
+		}
+
+		createDefaultTexture();
+		createDefaultShaders();
+		Shader::current = Shader::standardShaders[Shader::StandardShader::STANDARD_DEFAULT];
+		createQuadIndexBuffer();
+		createFanIndexBuffer();
+
+		frameCounter = 0;
+		currentFrame = 0;
+	}
 
 
 	restoreState(states.back());
 	restoreState(states.back());
 
 
 	Vulkan::resetShaderSwitches();
 	Vulkan::resetShaderSwitches();
 
 
-	frameCounter = 0;
-	currentFrame = 0;
 	created = true;
 	created = true;
 	drawCalls = 0;
 	drawCalls = 0;
 	drawCallsBatched = 0;
 	drawCallsBatched = 0;
@@ -659,14 +747,12 @@ void Graphics::getAPIStats(int &shaderswitches) const
 
 
 void Graphics::unSetMode()
 void Graphics::unSetMode()
 {
 {
-	renderPassUsages.clear();
-	framebufferUsages.clear();
-	pipelineUsages.clear();
-	
+	submitGpuCommands(SUBMIT_NOPRESENT);
+
 	created = false;
 	created = false;
-	vkDeviceWaitIdle(device);
-	Volatile::unloadAll();
-	cleanup();
+
+	cleanupSwapChain();
+	vkDestroySurfaceKHR(instance, surface, nullptr);
 }
 }
 
 
 void Graphics::setActive(bool enable)
 void Graphics::setActive(bool enable)
@@ -971,30 +1057,11 @@ void Graphics::setWireframe(bool enable)
 	states.back().wireframe = enable;
 	states.back().wireframe = enable;
 }
 }
 
 
-PixelFormat Graphics::getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const
+bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage)
 {
 {
-	switch (format)
-	{
-	case PIXELFORMAT_NORMAL:
-		if (isGammaCorrect())
-			return PIXELFORMAT_RGBA8_UNORM_sRGB;
-		else
-			return PIXELFORMAT_RGBA8_UNORM;
-	case PIXELFORMAT_HDR:
-		return PIXELFORMAT_RGBA16_FLOAT;
-	default:
-		return format;
-	}
-}
-
-bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB)
-{
-	bool rendertarget = (usage & PIXELFORMATUSAGEFLAGS_RENDERTARGET) != 0;
-	bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0;
-
-	format = getSizedFormat(format, rendertarget, readable);
+	format = getSizedFormat(format);
 
 
-	auto vulkanFormat = Vulkan::getTextureFormat(format, sRGB);
+	auto vulkanFormat = Vulkan::getTextureFormat(format);
 
 
 	VkFormatProperties formatProperties;
 	VkFormatProperties formatProperties;
 	vkGetPhysicalDeviceFormatProperties(physicalDevice, vulkanFormat.internalFormat, &formatProperties);
 	vkGetPhysicalDeviceFormatProperties(physicalDevice, vulkanFormat.internalFormat, &formatProperties);
@@ -1332,75 +1399,6 @@ const OptionalDeviceExtensions &Graphics::getEnabledOptionalDeviceExtensions() c
 	return optionalDeviceExtensions;
 	return optionalDeviceExtensions;
 }
 }
 
 
-static void checkOptionalInstanceExtensions(OptionalInstanceExtensions &ext)
-{
-	uint32_t count;
-
-	vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr);
-
-	std::vector<VkExtensionProperties> extensions(count);
-
-	vkEnumerateInstanceExtensionProperties(nullptr, &count, extensions.data());
-
-	for (const auto &extension : extensions)
-	{
-		if (strcmp(extension.extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0)
-			ext.physicalDeviceProperties2 = true;
-	}
-}
-
-void Graphics::createVulkanInstance()
-{
-	if (isDebugEnabled() && !checkValidationSupport())
-		throw love::Exception("validation layers requested, but not available");
-
-	VkApplicationInfo appInfo{};
-	appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
-	appInfo.pApplicationName = "LOVE";
-	appInfo.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);	// get this version from somewhere else?
-	appInfo.pEngineName = "LOVE Game Framework";
-	appInfo.engineVersion = VK_MAKE_API_VERSION(0, VERSION_MAJOR, VERSION_MINOR, VERSION_REV);
-	appInfo.apiVersion = VK_API_VERSION_1_3;
-
-	VkInstanceCreateInfo createInfo{};
-	createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
-	createInfo.pApplicationInfo = &appInfo;
-	createInfo.pNext = nullptr;
-
-	// GetInstanceExtensions works with a null window parameter as long as
-	// SDL_Vulkan_LoadLibrary has been called (which we do earlier).
-	unsigned int count;
-	if (SDL_Vulkan_GetInstanceExtensions(nullptr, &count, nullptr) != SDL_TRUE)
-		throw love::Exception("couldn't retrieve sdl vulkan extensions");
-
-	std::vector<const char*> extensions = {};
-
-	checkOptionalInstanceExtensions(optionalInstanceExtensions);
-
-	if (optionalInstanceExtensions.physicalDeviceProperties2)
-		extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
-
-	size_t additional_extension_count = extensions.size();
-	extensions.resize(additional_extension_count + count);
-
-	if (SDL_Vulkan_GetInstanceExtensions(nullptr, &count, extensions.data() + additional_extension_count) != SDL_TRUE)
-		throw love::Exception("couldn't retrieve sdl vulkan extensions");
-
-	createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
-	createInfo.ppEnabledExtensionNames = extensions.data();
-
-	if (isDebugEnabled())
-	{
-		createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
-		createInfo.ppEnabledLayerNames = validationLayers.data();
-	}
-
-	if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS)
-		throw love::Exception("couldn't create vulkan instance");
-
-	volkLoadInstance(instance);
-}
-
 bool Graphics::checkValidationSupport()
 bool Graphics::checkValidationSupport()
 {
 {
 	uint32_t layerCount;
 	uint32_t layerCount;
@@ -1458,7 +1456,6 @@ void Graphics::pickPhysicalDevice()
 	minUniformBufferOffsetAlignment = properties.limits.minUniformBufferOffsetAlignment;
 	minUniformBufferOffsetAlignment = properties.limits.minUniformBufferOffsetAlignment;
 	deviceApiVersion = properties.apiVersion;
 	deviceApiVersion = properties.apiVersion;
 
 
-	msaaSamples = getMsaaCount(requestedMsaa);
 	depthStencilFormat = findDepthFormat();
 	depthStencilFormat = findDepthFormat();
 }
 }
 
 
@@ -1573,7 +1570,7 @@ static void findOptionalDeviceExtensions(VkPhysicalDevice physicalDevice, Option
 			optionalDeviceExtensions.memoryRequirements2 = true;
 			optionalDeviceExtensions.memoryRequirements2 = true;
 		if (strcmp(extension.extensionName, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME) == 0)
 		if (strcmp(extension.extensionName, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME) == 0)
 			optionalDeviceExtensions.dedicatedAllocation = true;
 			optionalDeviceExtensions.dedicatedAllocation = true;
-		if (strcmp(extension.extensionName, VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) == 0)
+		if (strcmp(extension.extensionName, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME) == 0)
 			optionalDeviceExtensions.memoryBudget = true;
 			optionalDeviceExtensions.memoryBudget = true;
 		if (strcmp(extension.extensionName, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME) == 0)
 		if (strcmp(extension.extensionName, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME) == 0)
 			optionalDeviceExtensions.shaderFloatControls = true;
 			optionalDeviceExtensions.shaderFloatControls = true;
@@ -2447,12 +2444,12 @@ void Graphics::setRenderPass(const RenderTargets &rts, int pixelw, int pixelh, b
 	RenderPassConfiguration renderPassConfiguration{};
 	RenderPassConfiguration renderPassConfiguration{};
 	for (const auto &color : rts.colors)
 	for (const auto &color : rts.colors)
 		renderPassConfiguration.colorAttachments.push_back({ 
 		renderPassConfiguration.colorAttachments.push_back({ 
-			Vulkan::getTextureFormat(color.texture->getPixelFormat(), isPixelFormatSRGB(color.texture->getPixelFormat())).internalFormat,
+			Vulkan::getTextureFormat(color.texture->getPixelFormat()).internalFormat,
 			VK_ATTACHMENT_LOAD_OP_LOAD,
 			VK_ATTACHMENT_LOAD_OP_LOAD,
 			dynamic_cast<Texture*>(color.texture)->getMsaaSamples() });
 			dynamic_cast<Texture*>(color.texture)->getMsaaSamples() });
 	if (rts.depthStencil.texture != nullptr)
 	if (rts.depthStencil.texture != nullptr)
 		renderPassConfiguration.staticData.depthStencilAttachment = {
 		renderPassConfiguration.staticData.depthStencilAttachment = {
-			Vulkan::getTextureFormat(rts.depthStencil.texture->getPixelFormat(), false).internalFormat,
+			Vulkan::getTextureFormat(rts.depthStencil.texture->getPixelFormat()).internalFormat,
 			VK_ATTACHMENT_LOAD_OP_LOAD,
 			VK_ATTACHMENT_LOAD_OP_LOAD,
 			VK_ATTACHMENT_LOAD_OP_LOAD,
 			VK_ATTACHMENT_LOAD_OP_LOAD,
 			dynamic_cast<Texture*>(rts.depthStencil.texture)->getMsaaSamples() };
 			dynamic_cast<Texture*>(rts.depthStencil.texture)->getMsaaSamples() };
@@ -3040,8 +3037,6 @@ void Graphics::createDefaultTexture()
 
 
 void Graphics::cleanup()
 void Graphics::cleanup()
 {
 {
-	cleanupSwapChain();
-
 	for (auto &cleanUpFns : cleanUpFunctions)
 	for (auto &cleanUpFns : cleanUpFunctions)
 		for (auto &cleanUpFn : cleanUpFns)
 		for (auto &cleanUpFn : cleanUpFns)
 			cleanUpFn();
 			cleanUpFn();
@@ -3076,8 +3071,6 @@ void Graphics::cleanup()
 	vkDestroyCommandPool(device, commandPool, nullptr);
 	vkDestroyCommandPool(device, commandPool, nullptr);
 	vkDestroyPipelineCache(device, pipelineCache, nullptr);
 	vkDestroyPipelineCache(device, pipelineCache, nullptr);
 	vkDestroyDevice(device, nullptr);
 	vkDestroyDevice(device, nullptr);
-	vkDestroySurfaceKHR(instance, surface, nullptr);
-	vkDestroyInstance(instance, nullptr);
 }
 }
 
 
 void Graphics::cleanupSwapChain()
 void Graphics::cleanupSwapChain()
@@ -3087,8 +3080,11 @@ void Graphics::cleanupSwapChain()
 		vmaDestroyBuffer(vmaAllocator, readbackBuffer.buffer, readbackBuffer.allocation);
 		vmaDestroyBuffer(vmaAllocator, readbackBuffer.buffer, readbackBuffer.allocation);
 		vmaDestroyImage(vmaAllocator, readbackBuffer.image, readbackBuffer.imageAllocation);
 		vmaDestroyImage(vmaAllocator, readbackBuffer.image, readbackBuffer.imageAllocation);
 	}
 	}
-	vkDestroyImageView(device, colorImageView, nullptr);
-	vmaDestroyImage(vmaAllocator, colorImage, colorImageAllocation);
+	if (colorImage)
+	{
+		vkDestroyImageView(device, colorImageView, nullptr);
+		vmaDestroyImage(vmaAllocator, colorImage, colorImageAllocation);
+	}
 	vkDestroyImageView(device, depthImageView, nullptr);
 	vkDestroyImageView(device, depthImageView, nullptr);
 	vmaDestroyImage(vmaAllocator, depthImage, depthImageAllocation);
 	vmaDestroyImage(vmaAllocator, depthImage, depthImageAllocation);
 	for (const auto &swapChainImageView : swapChainImageViews)
 	for (const auto &swapChainImageView : swapChainImageViews)

+ 12 - 6
src/modules/graphics/vulkan/Graphics.h

@@ -256,6 +256,14 @@ struct ScreenshotReadbackBuffer
 	VmaAllocation imageAllocation;
 	VmaAllocation imageAllocation;
 };
 };
 
 
+enum SubmitMode
+{
+	SUBMIT_PRESENT,
+	SUBMIT_NOPRESENT,
+	SUBMIT_RESTART,
+	SUBMIT_MAXENUM,
+};
+
 class Graphics final : public love::graphics::Graphics
 class Graphics final : public love::graphics::Graphics
 {
 {
 public:
 public:
@@ -289,8 +297,7 @@ public:
 	void setBlendState(const BlendState &blend) override;
 	void setBlendState(const BlendState &blend) override;
 	void setPointSize(float size) override;
 	void setPointSize(float size) override;
 	void setWireframe(bool enable) override;
 	void setWireframe(bool enable) override;
-	PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const override;
-	bool isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB) override;
+	bool isPixelFormatSupported(PixelFormat format, uint32 usage) override;
 	Renderer getRenderer() const override;
 	Renderer getRenderer() const override;
 	bool usesGLSLES() const override;
 	bool usesGLSLES() const override;
 	RendererInfo getRendererInfo() const override;
 	RendererInfo getRendererInfo() const override;
@@ -300,12 +307,12 @@ public:
 
 
 	// internal functions.
 	// internal functions.
 
 
-	const VkDevice getDevice() const;
-	const VmaAllocator getVmaAllocator() const;
+	VkDevice getDevice() const;
+	VmaAllocator getVmaAllocator() const;
 	VkCommandBuffer getCommandBufferForDataTransfer();
 	VkCommandBuffer getCommandBufferForDataTransfer();
 	void queueCleanUp(std::function<void()> cleanUp);
 	void queueCleanUp(std::function<void()> cleanUp);
 	void addReadbackCallback(std::function<void()> callback);
 	void addReadbackCallback(std::function<void()> callback);
-	void submitGpuCommands(bool present, void *screenshotCallbackData = nullptr);
+	void submitGpuCommands(SubmitMode, void *screenshotCallbackData = nullptr);
 	const VkDeviceSize getMinUniformBufferOffsetAlignment() const;
 	const VkDeviceSize getMinUniformBufferOffsetAlignment() const;
 	VkSampler getCachedSampler(const SamplerState &sampler);
 	VkSampler getCachedSampler(const SamplerState &sampler);
 	void setComputeShader(Shader *computeShader);
 	void setComputeShader(Shader *computeShader);
@@ -326,7 +333,6 @@ protected:
 	void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture) override;
 	void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture) override;
 
 
 private:
 private:
-	void createVulkanInstance();
 	bool checkValidationSupport();
 	bool checkValidationSupport();
 	void pickPhysicalDevice();
 	void pickPhysicalDevice();
 	int rateDeviceSuitability(VkPhysicalDevice device);
 	int rateDeviceSuitability(VkPhysicalDevice device);

+ 3 - 3
src/modules/graphics/vulkan/GraphicsReadback.cpp

@@ -44,7 +44,7 @@ GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod
 
 
 	if (method == READBACK_IMMEDIATE)
 	if (method == READBACK_IMMEDIATE)
 	{
 	{
-		vgfx->submitGpuCommands(false);
+		vgfx->submitGpuCommands(SUBMIT_RESTART);
 		if (stagingBuffer.get()) {
 		if (stagingBuffer.get()) {
 			status = readbackBuffer(stagingBuffer, 0, size);
 			status = readbackBuffer(stagingBuffer, 0, size);
 			gfx->releaseTemporaryBuffer(stagingBuffer);
 			gfx->releaseTemporaryBuffer(stagingBuffer);
@@ -79,7 +79,7 @@ GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod
 	});
 	});
 
 
 	if (method == READBACK_IMMEDIATE)
 	if (method == READBACK_IMMEDIATE)
-		vgfx->submitGpuCommands(false);
+		vgfx->submitGpuCommands(SUBMIT_RESTART);
 }
 }
 
 
 GraphicsReadback::~GraphicsReadback()
 GraphicsReadback::~GraphicsReadback()
@@ -89,7 +89,7 @@ GraphicsReadback::~GraphicsReadback()
 void GraphicsReadback::wait()
 void GraphicsReadback::wait()
 {
 {
 	if (status == STATUS_WAITING)
 	if (status == STATUS_WAITING)
-		vgfx->submitGpuCommands(false);
+		vgfx->submitGpuCommands(SUBMIT_RESTART);
 }
 }
 
 
 void GraphicsReadback::update()
 void GraphicsReadback::update()

+ 5 - 1
src/modules/graphics/vulkan/Shader.cpp

@@ -551,7 +551,8 @@ int Shader::getVertexAttributeIndex(const std::string &name)
 
 
 const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
 const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
 {
 {
-	return &uniformInfos.at(name);
+	const auto it = uniformInfos.find(name);
+	return it != uniformInfos.end() ? &(it->second) : nullptr;
 }
 }
 
 
 const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
 const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
@@ -656,6 +657,9 @@ void Shader::buildLocalUniforms(spirv_cross::Compiler &comp, const spirv_cross::
 		if (reflectionIt != validationReflection.localUniforms.end())
 		if (reflectionIt != validationReflection.localUniforms.end())
 		{
 		{
 			const auto &localUniform = reflectionIt->second;
 			const auto &localUniform = reflectionIt->second;
+			if (localUniform.dataType == DATA_BASETYPE_BOOL)
+				u.baseType = UNIFORM_BOOL;
+
 			const auto &values = localUniform.initializerValues;
 			const auto &values = localUniform.initializerValues;
 			if (!values.empty())
 			if (!values.empty())
 				memcpy(
 				memcpy(

+ 8 - 4
src/modules/graphics/vulkan/Texture.cpp

@@ -41,6 +41,10 @@ Texture::Texture(love::graphics::Graphics *gfx, const Settings &settings, const
 		slices = *data;
 		slices = *data;
 
 
 	loadVolatile();
 	loadVolatile();
+
+	// ImageData is referenced by the first loadVolatile call, but we don't
+	// hang on to it after that so we can save memory.
+	slices.clear();
 }
 }
 
 
 bool Texture::loadVolatile()
 bool Texture::loadVolatile()
@@ -55,7 +59,7 @@ bool Texture::loadVolatile()
 	if (isPixelFormatColor(format))
 	if (isPixelFormatColor(format))
 		imageAspect |= VK_IMAGE_ASPECT_COLOR_BIT;
 		imageAspect |= VK_IMAGE_ASPECT_COLOR_BIT;
 
 
-	auto vulkanFormat = Vulkan::getTextureFormat(format, sRGB);
+	auto vulkanFormat = Vulkan::getTextureFormat(format);
 
 
 	VkImageUsageFlags usageFlags =
 	VkImageUsageFlags usageFlags =
 		VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
 		VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
@@ -150,7 +154,7 @@ bool Texture::loadVolatile()
 	createTextureImageView();
 	createTextureImageView();
 	textureSampler = vgfx->getCachedSampler(samplerState);
 	textureSampler = vgfx->getCachedSampler(samplerState);
 
 
-	if (!isPixelFormatDepthStencil(format) && mipmapCount > 1 && getMipmapsMode() != MIPMAPS_NONE)
+	if (!isPixelFormatDepthStencil(format) && slices.getMipmapCount() <= 1 && getMipmapsMode() != MIPMAPS_NONE)
 		generateMipmaps();
 		generateMipmaps();
 
 
 	if (renderTarget)
 	if (renderTarget)
@@ -273,7 +277,7 @@ VkImageLayout Texture::getImageLayout() const
 
 
 void Texture::createTextureImageView()
 void Texture::createTextureImageView()
 {
 {
-	auto vulkanFormat = Vulkan::getTextureFormat(format, sRGB);
+	auto vulkanFormat = Vulkan::getTextureFormat(format);
 
 
 	VkImageViewCreateInfo viewInfo{};
 	VkImageViewCreateInfo viewInfo{};
 	viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
 	viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
@@ -345,7 +349,7 @@ void Texture::clear()
 
 
 VkClearColorValue Texture::getClearValue()
 VkClearColorValue Texture::getClearValue()
 {
 {
-	auto vulkanFormat = Vulkan::getTextureFormat(format, sRGB);
+	auto vulkanFormat = Vulkan::getTextureFormat(format);
 
 
 	VkClearColorValue clearColor{};
 	VkClearColorValue clearColor{};
 	switch (vulkanFormat.internalFormatRepresentation)
 	switch (vulkanFormat.internalFormatRepresentation)

+ 118 - 49
src/modules/graphics/vulkan/Vulkan.cpp

@@ -130,16 +130,10 @@ VkFormat Vulkan::getVulkanVertexFormat(DataFormat format)
 	}
 	}
 }
 }
 
 
-TextureFormat Vulkan::getTextureFormat(PixelFormat format, bool sRGB)
+TextureFormat Vulkan::getTextureFormat(PixelFormat format)
 {
 {
 	TextureFormat textureFormat{};
 	TextureFormat textureFormat{};
 
 
-	if (sRGB)
-		format = getSRGBPixelFormat(format);
-
-	if (!isPixelFormatCompressed(format) && !isPixelFormatSRGB(format))
-		sRGB = false;
-
 	switch (format)
 	switch (format)
 	{
 	{
 	case PIXELFORMAT_UNKNOWN:
 	case PIXELFORMAT_UNKNOWN:
@@ -231,13 +225,13 @@ TextureFormat Vulkan::getTextureFormat(PixelFormat format, bool sRGB)
 	case PIXELFORMAT_RGBA8_UNORM:
 	case PIXELFORMAT_RGBA8_UNORM:
 		textureFormat.internalFormat = VK_FORMAT_R8G8B8A8_UNORM;
 		textureFormat.internalFormat = VK_FORMAT_R8G8B8A8_UNORM;
 		break;
 		break;
-	case PIXELFORMAT_RGBA8_UNORM_sRGB:
+	case PIXELFORMAT_RGBA8_sRGB:
 		textureFormat.internalFormat = VK_FORMAT_R8G8B8A8_SRGB;
 		textureFormat.internalFormat = VK_FORMAT_R8G8B8A8_SRGB;
 		break;
 		break;
 	case PIXELFORMAT_BGRA8_UNORM:
 	case PIXELFORMAT_BGRA8_UNORM:
 		textureFormat.internalFormat = VK_FORMAT_B8G8R8A8_UNORM;
 		textureFormat.internalFormat = VK_FORMAT_B8G8R8A8_UNORM;
 		break;
 		break;
-	case PIXELFORMAT_BGRA8_UNORM_sRGB:
+	case PIXELFORMAT_BGRA8_sRGB:
 		textureFormat.internalFormat = VK_FORMAT_B8G8R8A8_SRGB;
 		textureFormat.internalFormat = VK_FORMAT_B8G8R8A8_SRGB;
 		break;
 		break;
 	case PIXELFORMAT_RGBA8_INT:
 	case PIXELFORMAT_RGBA8_INT:
@@ -308,13 +302,22 @@ TextureFormat Vulkan::getTextureFormat(PixelFormat format, bool sRGB)
 		textureFormat.internalFormat = VK_FORMAT_D32_SFLOAT_S8_UINT;
 		textureFormat.internalFormat = VK_FORMAT_D32_SFLOAT_S8_UINT;
 		break;
 		break;
 	case PIXELFORMAT_DXT1_UNORM:
 	case PIXELFORMAT_DXT1_UNORM:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_BC1_RGBA_SRGB_BLOCK : VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
+		textureFormat.internalFormat = VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_DXT1_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_BC1_RGBA_SRGB_BLOCK;
 		break;
 		break;
 	case PIXELFORMAT_DXT3_UNORM:
 	case PIXELFORMAT_DXT3_UNORM:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_BC2_SRGB_BLOCK : VK_FORMAT_BC2_UNORM_BLOCK;
+		textureFormat.internalFormat = VK_FORMAT_BC2_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_DXT3_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_BC2_SRGB_BLOCK;
 		break;
 		break;
 	case PIXELFORMAT_DXT5_UNORM:
 	case PIXELFORMAT_DXT5_UNORM:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_BC3_SRGB_BLOCK : VK_FORMAT_BC3_UNORM_BLOCK;
+		textureFormat.internalFormat = VK_FORMAT_BC3_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_DXT5_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_BC3_SRGB_BLOCK;
 		break;
 		break;
 	case PIXELFORMAT_BC4_UNORM:
 	case PIXELFORMAT_BC4_UNORM:
 		textureFormat.internalFormat = VK_FORMAT_BC4_UNORM_BLOCK;
 		textureFormat.internalFormat = VK_FORMAT_BC4_UNORM_BLOCK;
@@ -335,31 +338,55 @@ TextureFormat Vulkan::getTextureFormat(PixelFormat format, bool sRGB)
 		textureFormat.internalFormat = VK_FORMAT_BC6H_SFLOAT_BLOCK;
 		textureFormat.internalFormat = VK_FORMAT_BC6H_SFLOAT_BLOCK;
 		break;
 		break;
 	case PIXELFORMAT_BC7_UNORM:
 	case PIXELFORMAT_BC7_UNORM:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_BC7_SRGB_BLOCK : VK_FORMAT_BC7_UNORM_BLOCK;
+		textureFormat.internalFormat = VK_FORMAT_BC7_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_BC7_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_BC7_SRGB_BLOCK;
 		break;
 		break;
 	case PIXELFORMAT_PVR1_RGB2_UNORM:
 	case PIXELFORMAT_PVR1_RGB2_UNORM:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG : VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG;
+		textureFormat.internalFormat = VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG;
+		break;
+	case PIXELFORMAT_PVR1_RGB2_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG;
 		break;
 		break;
 	case PIXELFORMAT_PVR1_RGB4_UNORM:
 	case PIXELFORMAT_PVR1_RGB4_UNORM:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG : VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG;
+		textureFormat.internalFormat = VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG;
+		break;
+	case PIXELFORMAT_PVR1_RGB4_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG;
 		break;
 		break;
 	case PIXELFORMAT_PVR1_RGBA2_UNORM:
 	case PIXELFORMAT_PVR1_RGBA2_UNORM:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG : VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG;
+		textureFormat.internalFormat = VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG;
+		break;
+	case PIXELFORMAT_PVR1_RGBA2_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG;
 		break;
 		break;
 	case PIXELFORMAT_PVR1_RGBA4_UNORM:
 	case PIXELFORMAT_PVR1_RGBA4_UNORM:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG : VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG;
+		textureFormat.internalFormat = VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG;
+		break;
+	case PIXELFORMAT_PVR1_RGBA4_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG;
 		break;
 		break;
 	case PIXELFORMAT_ETC1_UNORM:
 	case PIXELFORMAT_ETC1_UNORM:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
+		textureFormat.internalFormat = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
 		break;
 		break;
 	case PIXELFORMAT_ETC2_RGB_UNORM:
 	case PIXELFORMAT_ETC2_RGB_UNORM:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
+		textureFormat.internalFormat = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ETC2_RGB_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK;
 		break;
 		break;
 	case PIXELFORMAT_ETC2_RGBA_UNORM:
 	case PIXELFORMAT_ETC2_RGBA_UNORM:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK;
+		textureFormat.internalFormat = VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ETC2_RGBA_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK;
 		break;
 		break;
 	case PIXELFORMAT_ETC2_RGBA1_UNORM:
 	case PIXELFORMAT_ETC2_RGBA1_UNORM:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK;
+		textureFormat.internalFormat = VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ETC2_RGBA1_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK;
 		break;
 		break;
 	case PIXELFORMAT_EAC_R_UNORM:
 	case PIXELFORMAT_EAC_R_UNORM:
 		textureFormat.internalFormat = VK_FORMAT_EAC_R11_UNORM_BLOCK;
 		textureFormat.internalFormat = VK_FORMAT_EAC_R11_UNORM_BLOCK;
@@ -373,47 +400,89 @@ TextureFormat Vulkan::getTextureFormat(PixelFormat format, bool sRGB)
 	case PIXELFORMAT_EAC_RG_SNORM:
 	case PIXELFORMAT_EAC_RG_SNORM:
 		textureFormat.internalFormat = VK_FORMAT_EAC_R11G11_SNORM_BLOCK;
 		textureFormat.internalFormat = VK_FORMAT_EAC_R11G11_SNORM_BLOCK;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_4x4:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ASTC_4x4_SRGB_BLOCK : VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
+	case PIXELFORMAT_ASTC_4x4_UNORM:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ASTC_5x4_UNORM:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_5x4_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ASTC_5x5_UNORM:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_5x5_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ASTC_6x5_UNORM:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_6x5_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ASTC_6x6_UNORM:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_6x6_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ASTC_8x5_UNORM:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_8x5_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ASTC_8x6_UNORM:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_8x6_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ASTC_8x8_UNORM:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_8x8_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ASTC_10x5_UNORM:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_10x5_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ASTC_10x6_UNORM:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_10x6_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ASTC_10x8_UNORM:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_10x8_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ASTC_10x10_UNORM:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_10x10_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ASTC_12x10_UNORM:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_12x10_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ASTC_12x12_UNORM:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_12x12_UNORM_BLOCK;
+		break;
+	case PIXELFORMAT_ASTC_4x4_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_4x4_SRGB_BLOCK;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_5x4:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ASTC_5x4_SRGB_BLOCK : VK_FORMAT_ASTC_5x4_UNORM_BLOCK;
+	case PIXELFORMAT_ASTC_5x4_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_5x4_SRGB_BLOCK;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_5x5:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ASTC_5x5_SRGB_BLOCK : VK_FORMAT_ASTC_5x5_UNORM_BLOCK;
+	case PIXELFORMAT_ASTC_5x5_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_5x5_SRGB_BLOCK;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_6x5:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ASTC_6x5_SRGB_BLOCK : VK_FORMAT_ASTC_6x5_UNORM_BLOCK;
+	case PIXELFORMAT_ASTC_6x5_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_6x5_SRGB_BLOCK;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_6x6:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ASTC_6x6_SRGB_BLOCK : VK_FORMAT_ASTC_6x6_UNORM_BLOCK;
+	case PIXELFORMAT_ASTC_6x6_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_6x6_SRGB_BLOCK;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_8x5:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ASTC_8x5_SRGB_BLOCK : VK_FORMAT_ASTC_8x5_UNORM_BLOCK;
+	case PIXELFORMAT_ASTC_8x5_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_8x5_SRGB_BLOCK;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_8x6:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ASTC_8x6_SRGB_BLOCK : VK_FORMAT_ASTC_8x6_UNORM_BLOCK;
+	case PIXELFORMAT_ASTC_8x6_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_8x6_SRGB_BLOCK;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_8x8:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ASTC_8x8_SRGB_BLOCK : VK_FORMAT_ASTC_8x8_UNORM_BLOCK;
+	case PIXELFORMAT_ASTC_8x8_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_8x8_SRGB_BLOCK;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_10x5:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ASTC_10x5_SRGB_BLOCK : VK_FORMAT_ASTC_10x5_UNORM_BLOCK;
+	case PIXELFORMAT_ASTC_10x5_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_10x5_SRGB_BLOCK;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_10x6:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ASTC_10x6_SRGB_BLOCK : VK_FORMAT_ASTC_10x6_UNORM_BLOCK;
+	case PIXELFORMAT_ASTC_10x6_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_10x6_SRGB_BLOCK;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_10x8:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ASTC_10x8_SRGB_BLOCK : VK_FORMAT_ASTC_10x8_UNORM_BLOCK;
+	case PIXELFORMAT_ASTC_10x8_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_10x8_SRGB_BLOCK;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_10x10:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ASTC_10x10_SRGB_BLOCK : VK_FORMAT_ASTC_10x10_UNORM_BLOCK;
+	case PIXELFORMAT_ASTC_10x10_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_10x10_SRGB_BLOCK;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_12x10:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ASTC_12x10_SRGB_BLOCK : VK_FORMAT_ASTC_12x10_UNORM_BLOCK;
+	case PIXELFORMAT_ASTC_12x10_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_12x10_SRGB_BLOCK;
 		break;
 		break;
-	case PIXELFORMAT_ASTC_12x12:
-		textureFormat.internalFormat = sRGB ? VK_FORMAT_ASTC_12x12_SRGB_BLOCK : VK_FORMAT_ASTC_12x12_UNORM_BLOCK;
+	case PIXELFORMAT_ASTC_12x12_sRGB:
+		textureFormat.internalFormat = VK_FORMAT_ASTC_12x12_SRGB_BLOCK;
 		break;
 		break;
 	default:
 	default:
 		throw love::Exception("unknown pixel format");
 		throw love::Exception("unknown pixel format");

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

@@ -59,7 +59,7 @@ public:
 	static void resetShaderSwitches();
 	static void resetShaderSwitches();
 
 
 	static VkFormat getVulkanVertexFormat(DataFormat format);
 	static VkFormat getVulkanVertexFormat(DataFormat format);
-	static TextureFormat getTextureFormat(PixelFormat, bool sRGB);
+	static TextureFormat getTextureFormat(PixelFormat format);
 	static std::string getVendorName(uint32_t vendorId);
 	static std::string getVendorName(uint32_t vendorId);
 	static std::string getVulkanApiVersion(uint32_t apiVersion);
 	static std::string getVulkanApiVersion(uint32_t apiVersion);
 	static VkPrimitiveTopology getPrimitiveTypeTopology(graphics::PrimitiveType);
 	static VkPrimitiveTopology getPrimitiveTypeTopology(graphics::PrimitiveType);

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

@@ -2355,7 +2355,11 @@ int w_setColorMask(lua_State *L)
 {
 {
 	ColorChannelMask mask;
 	ColorChannelMask mask;
 
 
-	if (lua_gettop(L) <= 1)
+	if (lua_isnoneornil(L, 1))
+	{
+		mask.r = mask.g = mask.b = mask.a = true;
+	}
+	else if (lua_gettop(L) <= 1)
 	{
 	{
 		// Set all color components if a single argument is given.
 		// Set all color components if a single argument is given.
 		mask.r = mask.g = mask.b = mask.a = luax_checkboolean(L, 1);
 		mask.r = mask.g = mask.b = mask.a = luax_checkboolean(L, 1);
@@ -2783,7 +2787,6 @@ int w_getTextureFormats(lua_State *L)
 	luaL_checktype(L, 1, LUA_TTABLE);
 	luaL_checktype(L, 1, LUA_TTABLE);
 
 
 	bool rt = luax_checkboolflag(L, 1, Texture::getConstant(Texture::SETTING_RENDER_TARGET));
 	bool rt = luax_checkboolflag(L, 1, Texture::getConstant(Texture::SETTING_RENDER_TARGET));
-	bool linear = luax_boolflag(L, 1, Texture::getConstant(Texture::SETTING_LINEAR), false);
 	bool computewrite = luax_boolflag(L, 1, Texture::getConstant(Texture::SETTING_COMPUTE_WRITE), false);
 	bool computewrite = luax_boolflag(L, 1, Texture::getConstant(Texture::SETTING_COMPUTE_WRITE), false);
 
 
 	OptionalBool readable;
 	OptionalBool readable;
@@ -2808,8 +2811,6 @@ int w_getTextureFormats(lua_State *L)
 		if (rt && isPixelFormatDepth(format))
 		if (rt && isPixelFormatDepth(format))
 			continue;
 			continue;
 
 
-		bool sRGB = isGammaCorrect() && !linear;
-
 		uint32 usage = PIXELFORMATUSAGEFLAGS_NONE;
 		uint32 usage = PIXELFORMATUSAGEFLAGS_NONE;
 		if (rt)
 		if (rt)
 			usage |= PIXELFORMATUSAGEFLAGS_RENDERTARGET;
 			usage |= PIXELFORMATUSAGEFLAGS_RENDERTARGET;
@@ -2818,7 +2819,7 @@ int w_getTextureFormats(lua_State *L)
 		if (computewrite)
 		if (computewrite)
 			usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE;
 			usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE;
 
 
-		luax_pushboolean(L, instance()->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage, sRGB));
+		luax_pushboolean(L, instance()->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage));
 		lua_setfield(L, -2, name);
 		lua_setfield(L, -2, name);
 	}
 	}
 
 
@@ -2862,14 +2863,14 @@ int w_getCanvasFormats(lua_State *L)
 			supported = [](PixelFormat format) -> bool
 			supported = [](PixelFormat format) -> bool
 			{
 			{
 				const uint32 usage = PIXELFORMATUSAGEFLAGS_SAMPLE | PIXELFORMATUSAGEFLAGS_RENDERTARGET;
 				const uint32 usage = PIXELFORMATUSAGEFLAGS_SAMPLE | PIXELFORMATUSAGEFLAGS_RENDERTARGET;
-				return instance()->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage, false);
+				return instance()->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage);
 			};
 			};
 		}
 		}
 		else
 		else
 		{
 		{
 			supported = [](PixelFormat format) -> bool
 			supported = [](PixelFormat format) -> bool
 			{
 			{
-				return instance()->isPixelFormatSupported(format, PIXELFORMATUSAGEFLAGS_RENDERTARGET, false);
+				return instance()->isPixelFormatSupported(format, PIXELFORMATUSAGEFLAGS_RENDERTARGET);
 			};
 			};
 		}
 		}
 	}
 	}
@@ -2881,7 +2882,7 @@ int w_getCanvasFormats(lua_State *L)
 			uint32 usage = PIXELFORMATUSAGEFLAGS_RENDERTARGET;
 			uint32 usage = PIXELFORMATUSAGEFLAGS_RENDERTARGET;
 			if (readable)
 			if (readable)
 				usage |= PIXELFORMATUSAGEFLAGS_SAMPLE;
 				usage |= PIXELFORMATUSAGEFLAGS_SAMPLE;
-			return instance()->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage, false);
+			return instance()->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage);
 		};
 		};
 	}
 	}
 
 
@@ -2894,7 +2895,7 @@ int w_getImageFormats(lua_State *L)
 
 
 	const auto supported = [](PixelFormat format) -> bool
 	const auto supported = [](PixelFormat format) -> bool
 	{
 	{
-		return instance()->isPixelFormatSupported(format, PIXELFORMATUSAGEFLAGS_SAMPLE, false);
+		return instance()->isPixelFormatSupported(format, PIXELFORMATUSAGEFLAGS_SAMPLE);
 	};
 	};
 
 
 	const auto ignore = [](PixelFormat format) -> bool
 	const auto ignore = [](PixelFormat format) -> bool

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

@@ -30,7 +30,6 @@ love::Type CompressedImageData::type("CompressedImageData", &Data::type);
 
 
 CompressedImageData::CompressedImageData(const std::list<FormatHandler *> &formats, Data *filedata)
 CompressedImageData::CompressedImageData(const std::list<FormatHandler *> &formats, Data *filedata)
 	: format(PIXELFORMAT_UNKNOWN)
 	: format(PIXELFORMAT_UNKNOWN)
-	, sRGB(false)
 {
 {
 	FormatHandler *parser = nullptr;
 	FormatHandler *parser = nullptr;
 
 
@@ -46,7 +45,7 @@ CompressedImageData::CompressedImageData(const std::list<FormatHandler *> &forma
 	if (parser == nullptr)
 	if (parser == nullptr)
 		throw love::Exception("Could not parse compressed data: Unknown format.");
 		throw love::Exception("Could not parse compressed data: Unknown format.");
 
 
-	memory = parser->parseCompressed(filedata, dataImages, format, sRGB);
+	memory = parser->parseCompressed(filedata, dataImages, format);
 
 
 	if (memory == nullptr)
 	if (memory == nullptr)
 		throw love::Exception("Could not parse compressed data.");
 		throw love::Exception("Could not parse compressed data.");
@@ -56,11 +55,14 @@ CompressedImageData::CompressedImageData(const std::list<FormatHandler *> &forma
 
 
 	if (dataImages.size() == 0 || memory->getSize() == 0)
 	if (dataImages.size() == 0 || memory->getSize() == 0)
 		throw love::Exception("Could not parse compressed data: No valid data?");
 		throw love::Exception("Could not parse compressed data: No valid data?");
+
+	// This throws away some information the decoder could give us, but we
+	// can't really rely on it I think...
+	format = getLinearPixelFormat(format);
 }
 }
 
 
 CompressedImageData::CompressedImageData(const CompressedImageData &c)
 CompressedImageData::CompressedImageData(const CompressedImageData &c)
 	: format(c.format)
 	: format(c.format)
-	, sRGB(c.sRGB)
 {
 {
 	memory.set(c.memory->clone(), Acquire::NORETAIN);
 	memory.set(c.memory->clone(), Acquire::NORETAIN);
 
 
@@ -134,9 +136,15 @@ PixelFormat CompressedImageData::getFormat() const
 	return format;
 	return format;
 }
 }
 
 
-bool CompressedImageData::isSRGB() const
+void CompressedImageData::setLinear(bool linear)
+{
+	for (auto &slice : dataImages)
+		slice->setLinear(linear);
+}
+
+bool CompressedImageData::isLinear() const
 {
 {
-	return sRGB;
+	return dataImages.empty() ? false : dataImages[0]->isLinear();
 }
 }
 
 
 CompressedSlice *CompressedImageData::getSlice(int slice, int miplevel) const
 CompressedSlice *CompressedImageData::getSlice(int slice, int miplevel) const

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

@@ -93,14 +93,14 @@ public:
 	 **/
 	 **/
 	PixelFormat getFormat() const;
 	PixelFormat getFormat() const;
 
 
-	bool isSRGB() const;
+	void setLinear(bool linear);
+	bool isLinear() const;
 
 
 	CompressedSlice *getSlice(int slice, int miplevel) const;
 	CompressedSlice *getSlice(int slice, int miplevel) const;
 
 
 protected:
 protected:
 
 
 	PixelFormat format;
 	PixelFormat format;
-	bool sRGB;
 
 
 	// Single block of memory containing all of the sub-images.
 	// Single block of memory containing all of the sub-images.
 	StrongRef<ByteData> memory;
 	StrongRef<ByteData> memory;

+ 0 - 2
src/modules/image/CompressedSlice.cpp

@@ -31,7 +31,6 @@ CompressedSlice::CompressedSlice(PixelFormat format, int width, int height, Byte
 	, memory(memory)
 	, memory(memory)
 	, offset(offset)
 	, offset(offset)
 	, dataSize(size)
 	, dataSize(size)
-	, sRGB(false)
 {
 {
 }
 }
 
 
@@ -40,7 +39,6 @@ CompressedSlice::CompressedSlice(const CompressedSlice &s)
 	, memory(s.memory)
 	, memory(s.memory)
 	, offset(s.offset)
 	, offset(s.offset)
 	, dataSize(s.dataSize)
 	, dataSize(s.dataSize)
-	, sRGB(s.sRGB)
 {
 {
 }
 }
 
 

+ 0 - 2
src/modules/image/CompressedSlice.h

@@ -46,7 +46,6 @@ public:
 	CompressedSlice *clone() const override;
 	CompressedSlice *clone() const override;
 	void *getData() const override { return (uint8 *) memory->getData() + offset; }
 	void *getData() const override { return (uint8 *) memory->getData() + offset; }
 	size_t getSize() const override { return dataSize; }
 	size_t getSize() const override { return dataSize; }
-	bool isSRGB() const override { return sRGB; }
 	size_t getOffset() const { return offset; }
 	size_t getOffset() const { return offset; }
 
 
 private:
 private:
@@ -54,7 +53,6 @@ private:
 	StrongRef<ByteData> memory;
 	StrongRef<ByteData> memory;
 	size_t offset;
 	size_t offset;
 	size_t dataSize;
 	size_t dataSize;
-	bool sRGB;
 
 
 }; // CompressedSlice
 }; // CompressedSlice
 
 

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

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

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

@@ -106,13 +106,12 @@ public:
 	 * @param[out] images The list of sub-images generated. Byte data is a
 	 * @param[out] images The list of sub-images generated. Byte data is a
 	 *             pointer to the returned data.
 	 *             pointer to the returned data.
 	 * @param[out] format The format of the Compressed Data.
 	 * @param[out] format The format of the Compressed Data.
-	 * @param[out] sRGB Whether the texture is sRGB-encoded.
 	 *
 	 *
 	 * @return The single block of memory containing the parsed images.
 	 * @return The single block of memory containing the parsed images.
 	 **/
 	 **/
 	virtual StrongRef<ByteData> parseCompressed(Data *filedata,
 	virtual StrongRef<ByteData> parseCompressed(Data *filedata,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        std::vector<StrongRef<CompressedSlice>> &images,
-	        PixelFormat &format, bool &sRGB);
+	        PixelFormat &format);
 
 
 	/**
 	/**
 	 * Frees raw pixel memory allocated by the format handler.
 	 * Frees raw pixel memory allocated by the format handler.

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

@@ -152,6 +152,10 @@ void ImageData::decode(Data *data)
 	else
 	else
 		delete[] this->data;
 		delete[] this->data;
 
 
+	// This throws away some information the decoder could give us, but we
+	// can't really rely on it I think...
+	decodedimage.format = getLinearPixelFormat(decodedimage.format);
+
 	this->width  = decodedimage.width;
 	this->width  = decodedimage.width;
 	this->height = decodedimage.height;
 	this->height = decodedimage.height;
 	this->data   = decodedimage.data;
 	this->data   = decodedimage.data;
@@ -247,11 +251,6 @@ void *ImageData::getData() const
 	return data;
 	return data;
 }
 }
 
 
-bool ImageData::isSRGB() const
-{
-	return false;
-}
-
 bool ImageData::inside(int x, int y) const
 bool ImageData::inside(int x, int y) const
 {
 {
 	return x >= 0 && x < getWidth() && y >= 0 && y < getHeight();
 	return x >= 0 && x < getWidth() && y >= 0 && y < getHeight();

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

@@ -62,7 +62,7 @@ public:
 	static love::Type type;
 	static love::Type type;
 
 
 	ImageData(Data *data);
 	ImageData(Data *data);
-	ImageData(int width, int height, PixelFormat format = PIXELFORMAT_RGBA8_UNORM);
+	ImageData(int width, int height, PixelFormat format);
 	ImageData(int width, int height, PixelFormat format, void *data, bool own);
 	ImageData(int width, int height, PixelFormat format, void *data, bool own);
 	ImageData(const ImageData &c);
 	ImageData(const ImageData &c);
 	virtual ~ImageData();
 	virtual ~ImageData();
@@ -116,7 +116,6 @@ public:
 	ImageData *clone() const override;
 	ImageData *clone() const override;
 	void *getData() const override;
 	void *getData() const override;
 	size_t getSize() const override;
 	size_t getSize() const override;
-	bool isSRGB() const override;
 
 
 	size_t getPixelSize() const;
 	size_t getPixelSize() const;
 
 

+ 11 - 0
src/modules/image/ImageDataBase.cpp

@@ -29,6 +29,7 @@ ImageDataBase::ImageDataBase(PixelFormat format, int width, int height)
 	: format(format)
 	: format(format)
 	, width(width)
 	, width(width)
 	, height(height)
 	, height(height)
+	, linear(false)
 {
 {
 }
 }
 
 
@@ -47,5 +48,15 @@ int ImageDataBase::getHeight() const
 	return height;
 	return height;
 }
 }
 
 
+void ImageDataBase::setLinear(bool linear)
+{
+	this->linear = linear;
+}
+
+bool ImageDataBase::isLinear() const
+{
+	return linear;
+}
+
 } // image
 } // image
 } // love
 } // love

+ 4 - 1
src/modules/image/ImageDataBase.h

@@ -40,7 +40,8 @@ public:
 	int getWidth() const;
 	int getWidth() const;
 	int getHeight() const;
 	int getHeight() const;
 
 
-	virtual bool isSRGB() const = 0;
+	void setLinear(bool linear);
+	bool isLinear() const;
 
 
 protected:
 protected:
 
 
@@ -50,6 +51,8 @@ protected:
 	int width;
 	int width;
 	int height;
 	int height;
 
 
+	bool linear;
+
 }; // ImageDataBase
 }; // ImageDataBase
 
 
 } // image
 } // image

+ 15 - 17
src/modules/image/magpie/ASTCHandler.cpp

@@ -54,33 +54,33 @@ static PixelFormat convertFormat(uint32 blockX, uint32 blockY, uint32 blockZ)
 		return PIXELFORMAT_UNKNOWN;
 		return PIXELFORMAT_UNKNOWN;
 
 
 	if (blockX == 4 && blockY == 4)
 	if (blockX == 4 && blockY == 4)
-		return PIXELFORMAT_ASTC_4x4;
+		return PIXELFORMAT_ASTC_4x4_UNORM;
 	else if (blockX == 5 && blockY == 4)
 	else if (blockX == 5 && blockY == 4)
-		return PIXELFORMAT_ASTC_5x4;
+		return PIXELFORMAT_ASTC_5x4_UNORM;
 	else if (blockX == 5 && blockY == 5)
 	else if (blockX == 5 && blockY == 5)
-		return PIXELFORMAT_ASTC_5x5;
+		return PIXELFORMAT_ASTC_5x5_UNORM;
 	else if (blockX == 6 && blockY == 5)
 	else if (blockX == 6 && blockY == 5)
-		return PIXELFORMAT_ASTC_6x5;
+		return PIXELFORMAT_ASTC_6x5_UNORM;
 	else if (blockX == 6 && blockY == 6)
 	else if (blockX == 6 && blockY == 6)
-		return PIXELFORMAT_ASTC_6x6;
+		return PIXELFORMAT_ASTC_6x6_UNORM;
 	else if (blockX == 8 && blockY == 5)
 	else if (blockX == 8 && blockY == 5)
-		return PIXELFORMAT_ASTC_8x5;
+		return PIXELFORMAT_ASTC_8x5_UNORM;
 	else if (blockX == 8 && blockY == 6)
 	else if (blockX == 8 && blockY == 6)
-		return PIXELFORMAT_ASTC_8x6;
+		return PIXELFORMAT_ASTC_8x6_UNORM;
 	else if (blockX == 8 && blockY == 8)
 	else if (blockX == 8 && blockY == 8)
-		return PIXELFORMAT_ASTC_8x8;
+		return PIXELFORMAT_ASTC_8x8_UNORM;
 	else if (blockX == 10 && blockY == 5)
 	else if (blockX == 10 && blockY == 5)
-		return PIXELFORMAT_ASTC_10x5;
+		return PIXELFORMAT_ASTC_10x5_UNORM;
 	else if (blockX == 10 && blockY == 6)
 	else if (blockX == 10 && blockY == 6)
-		return PIXELFORMAT_ASTC_10x6;
+		return PIXELFORMAT_ASTC_10x6_UNORM;
 	else if (blockX == 10 && blockY == 8)
 	else if (blockX == 10 && blockY == 8)
-		return PIXELFORMAT_ASTC_10x8;
+		return PIXELFORMAT_ASTC_10x8_UNORM;
 	else if (blockX == 10 && blockY == 10)
 	else if (blockX == 10 && blockY == 10)
-		return PIXELFORMAT_ASTC_10x10;
+		return PIXELFORMAT_ASTC_10x10_UNORM;
 	else if (blockX == 12 && blockY == 10)
 	else if (blockX == 12 && blockY == 10)
-		return PIXELFORMAT_ASTC_12x10;
+		return PIXELFORMAT_ASTC_12x10_UNORM;
 	else if (blockX == 12 && blockY == 12)
 	else if (blockX == 12 && blockY == 12)
-		return PIXELFORMAT_ASTC_12x12;
+		return PIXELFORMAT_ASTC_12x12_UNORM;
 
 
 	return PIXELFORMAT_UNKNOWN;
 	return PIXELFORMAT_UNKNOWN;
 }
 }
@@ -105,7 +105,7 @@ bool ASTCHandler::canParseCompressed(Data *data)
 	return true;
 	return true;
 }
 }
 
 
-StrongRef<ByteData> ASTCHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
+StrongRef<ByteData> ASTCHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format)
 {
 {
 	if (!canParseCompressed(filedata))
 	if (!canParseCompressed(filedata))
 		throw love::Exception("Could not decode compressed data (not an .astc file?)");
 		throw love::Exception("Could not decode compressed data (not an .astc file?)");
@@ -138,8 +138,6 @@ StrongRef<ByteData> ASTCHandler::parseCompressed(Data *filedata, std::vector<Str
 	images.emplace_back(new CompressedSlice(cformat, sizeX, sizeY, memory, 0, totalsize), Acquire::NORETAIN);
 	images.emplace_back(new CompressedSlice(cformat, sizeX, sizeY, memory, 0, totalsize), Acquire::NORETAIN);
 
 
 	format = cformat;
 	format = cformat;
-	sRGB = false;
-
 	return memory;
 	return memory;
 }
 }
 
 

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

@@ -45,7 +45,7 @@ public:
 
 
 	StrongRef<ByteData> parseCompressed(Data *filedata,
 	StrongRef<ByteData> parseCompressed(Data *filedata,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        std::vector<StrongRef<CompressedSlice>> &images,
-	        PixelFormat &format, bool &sRGB) override;
+	        PixelFormat &format) override;
 
 
 }; // ASTCHandler
 }; // ASTCHandler
 
 

+ 38 - 46
src/modules/image/magpie/KTXHandler.cpp

@@ -137,10 +137,8 @@ enum KTXGLInternalFormat
 	KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR = 0x93DD
 	KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR = 0x93DD
 };
 };
 
 
-PixelFormat convertFormat(uint32 glformat, bool &sRGB)
+PixelFormat convertFormat(uint32 glformat)
 {
 {
-	sRGB = false;
-
 	// hnnngg ASTC...
 	// hnnngg ASTC...
 
 
 	switch (glformat)
 	switch (glformat)
@@ -160,18 +158,15 @@ PixelFormat convertFormat(uint32 glformat, bool &sRGB)
 	case KTX_GL_COMPRESSED_RGB8_ETC2:
 	case KTX_GL_COMPRESSED_RGB8_ETC2:
 		return PIXELFORMAT_ETC2_RGB_UNORM;
 		return PIXELFORMAT_ETC2_RGB_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ETC2:
 	case KTX_GL_COMPRESSED_SRGB8_ETC2:
-		sRGB = true;
-		return PIXELFORMAT_ETC2_RGB_UNORM;
+		return PIXELFORMAT_ETC2_RGB_sRGB;
 	case KTX_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
 	case KTX_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
 		return PIXELFORMAT_ETC2_RGBA1_UNORM;
 		return PIXELFORMAT_ETC2_RGBA1_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
 	case KTX_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
-		sRGB = true;
-		return PIXELFORMAT_ETC2_RGBA1_UNORM;
+		return PIXELFORMAT_ETC2_RGBA1_sRGB;
 	case KTX_GL_COMPRESSED_RGBA8_ETC2_EAC:
 	case KTX_GL_COMPRESSED_RGBA8_ETC2_EAC:
 		return PIXELFORMAT_ETC2_RGBA_UNORM;
 		return PIXELFORMAT_ETC2_RGBA_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
-		sRGB = true;
-		return PIXELFORMAT_ETC2_RGBA_UNORM;
+		return PIXELFORMAT_ETC2_RGBA_sRGB;
 
 
 	// PVRTC.
 	// PVRTC.
 	case KTX_GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
 	case KTX_GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
@@ -185,15 +180,15 @@ PixelFormat convertFormat(uint32 glformat, bool &sRGB)
 
 
 	// DXT.
 	// DXT.
 	case KTX_GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
 	case KTX_GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
-		sRGB = true;
+		return PIXELFORMAT_DXT1_sRGB;
 	case KTX_GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
 	case KTX_GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
 		return PIXELFORMAT_DXT1_UNORM;
 		return PIXELFORMAT_DXT1_UNORM;
 	case KTX_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
 	case KTX_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
-		sRGB = true;
+		return PIXELFORMAT_DXT3_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
 	case KTX_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
 		return PIXELFORMAT_DXT3_UNORM;
 		return PIXELFORMAT_DXT3_UNORM;
 	case KTX_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
 	case KTX_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
-		sRGB = true;
+		return PIXELFORMAT_DXT5_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
 	case KTX_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
 		return PIXELFORMAT_DXT5_UNORM;
 		return PIXELFORMAT_DXT5_UNORM;
 
 
@@ -209,7 +204,7 @@ PixelFormat convertFormat(uint32 glformat, bool &sRGB)
 
 
 	// BC6 and BC7.
 	// BC6 and BC7.
 	case KTX_GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
 	case KTX_GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
-		sRGB = true;
+		return PIXELFORMAT_BC7_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_BPTC_UNORM:
 	case KTX_GL_COMPRESSED_RGBA_BPTC_UNORM:
 		return PIXELFORMAT_BC7_UNORM;
 		return PIXELFORMAT_BC7_UNORM;
 	case KTX_GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:
 	case KTX_GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:
@@ -219,61 +214,61 @@ PixelFormat convertFormat(uint32 glformat, bool &sRGB)
 
 
 	// ASTC.
 	// ASTC.
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
-		sRGB = true;
+		return PIXELFORMAT_ASTC_4x4_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_ASTC_4x4_KHR:
 	case KTX_GL_COMPRESSED_RGBA_ASTC_4x4_KHR:
-		return PIXELFORMAT_ASTC_4x4;
+		return PIXELFORMAT_ASTC_4x4_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:
-		sRGB = true;
+		return PIXELFORMAT_ASTC_5x4_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_ASTC_5x4_KHR:
 	case KTX_GL_COMPRESSED_RGBA_ASTC_5x4_KHR:
-		return PIXELFORMAT_ASTC_5x4;
+		return PIXELFORMAT_ASTC_5x4_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:
-		sRGB = true;
+		return PIXELFORMAT_ASTC_5x5_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_ASTC_5x5_KHR:
 	case KTX_GL_COMPRESSED_RGBA_ASTC_5x5_KHR:
-		return PIXELFORMAT_ASTC_5x5;
+		return PIXELFORMAT_ASTC_5x5_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:
-		sRGB = true;
+		return PIXELFORMAT_ASTC_6x5_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_ASTC_6x5_KHR:
 	case KTX_GL_COMPRESSED_RGBA_ASTC_6x5_KHR:
-		return PIXELFORMAT_ASTC_6x5;
+		return PIXELFORMAT_ASTC_6x5_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:
-		sRGB = true;
+		return PIXELFORMAT_ASTC_6x6_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_ASTC_6x6_KHR:
 	case KTX_GL_COMPRESSED_RGBA_ASTC_6x6_KHR:
-		return PIXELFORMAT_ASTC_6x6;
+		return PIXELFORMAT_ASTC_6x6_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:
-		sRGB = true;
+		return PIXELFORMAT_ASTC_8x5_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_ASTC_8x5_KHR:
 	case KTX_GL_COMPRESSED_RGBA_ASTC_8x5_KHR:
-		return PIXELFORMAT_ASTC_8x5;
+		return PIXELFORMAT_ASTC_8x5_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:
-		sRGB = true;
+		return PIXELFORMAT_ASTC_8x6_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_ASTC_8x6_KHR:
 	case KTX_GL_COMPRESSED_RGBA_ASTC_8x6_KHR:
-		return PIXELFORMAT_ASTC_8x6;
+		return PIXELFORMAT_ASTC_8x6_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:
-		sRGB = true;
+		return PIXELFORMAT_ASTC_8x8_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_ASTC_8x8_KHR:
 	case KTX_GL_COMPRESSED_RGBA_ASTC_8x8_KHR:
-		return PIXELFORMAT_ASTC_8x8;
+		return PIXELFORMAT_ASTC_8x8_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:
-		sRGB = true;
+		return PIXELFORMAT_ASTC_10x5_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_ASTC_10x5_KHR:
 	case KTX_GL_COMPRESSED_RGBA_ASTC_10x5_KHR:
-		return PIXELFORMAT_ASTC_10x5;
+		return PIXELFORMAT_ASTC_10x5_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:
-		sRGB = true;
+		return PIXELFORMAT_ASTC_10x6_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_ASTC_10x6_KHR:
 	case KTX_GL_COMPRESSED_RGBA_ASTC_10x6_KHR:
-		return PIXELFORMAT_ASTC_10x6;
+		return PIXELFORMAT_ASTC_10x6_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:
-		sRGB = true;
+		return PIXELFORMAT_ASTC_10x8_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_ASTC_10x8_KHR:
 	case KTX_GL_COMPRESSED_RGBA_ASTC_10x8_KHR:
-		return PIXELFORMAT_ASTC_10x8;
+		return PIXELFORMAT_ASTC_10x8_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:
-		sRGB = true;
+		return PIXELFORMAT_ASTC_10x10_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_ASTC_10x10_KHR:
 	case KTX_GL_COMPRESSED_RGBA_ASTC_10x10_KHR:
-		return PIXELFORMAT_ASTC_10x10;
+		return PIXELFORMAT_ASTC_10x10_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:
-		sRGB = true;
+		return PIXELFORMAT_ASTC_12x10_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_ASTC_12x10_KHR:
 	case KTX_GL_COMPRESSED_RGBA_ASTC_12x10_KHR:
-		return PIXELFORMAT_ASTC_12x10;
+		return PIXELFORMAT_ASTC_12x10_UNORM;
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:
 	case KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:
-		sRGB = true;
+		return PIXELFORMAT_ASTC_12x12_sRGB;
 	case KTX_GL_COMPRESSED_RGBA_ASTC_12x12_KHR:
 	case KTX_GL_COMPRESSED_RGBA_ASTC_12x12_KHR:
-		return PIXELFORMAT_ASTC_12x12;
+		return PIXELFORMAT_ASTC_12x12_UNORM;
 	default:
 	default:
 		return PIXELFORMAT_UNKNOWN;
 		return PIXELFORMAT_UNKNOWN;
 	}
 	}
@@ -298,7 +293,7 @@ bool KTXHandler::canParseCompressed(Data *data)
 	return true;
 	return true;
 }
 }
 
 
-StrongRef<ByteData> KTXHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
+StrongRef<ByteData> KTXHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format)
 {
 {
 	if (!canParseCompressed(filedata))
 	if (!canParseCompressed(filedata))
 		throw love::Exception("Could not decode compressed data (not a KTX file?)");
 		throw love::Exception("Could not decode compressed data (not a KTX file?)");
@@ -314,8 +309,7 @@ StrongRef<ByteData> KTXHandler::parseCompressed(Data *filedata, std::vector<Stro
 
 
 	header.numberOfMipmapLevels = std::max(header.numberOfMipmapLevels, 1u);
 	header.numberOfMipmapLevels = std::max(header.numberOfMipmapLevels, 1u);
 
 
-	bool isSRGB = false;
-	PixelFormat cformat = convertFormat(header.glInternalFormat, isSRGB);
+	PixelFormat cformat = convertFormat(header.glInternalFormat);
 
 
 	if (cformat == PIXELFORMAT_UNKNOWN)
 	if (cformat == PIXELFORMAT_UNKNOWN)
 		throw love::Exception("Unsupported image format in KTX file.");
 		throw love::Exception("Unsupported image format in KTX file.");
@@ -386,8 +380,6 @@ StrongRef<ByteData> KTXHandler::parseCompressed(Data *filedata, std::vector<Stro
 	}
 	}
 
 
 	format = cformat;
 	format = cformat;
-	sRGB = isSRGB;
-
 	return memory;
 	return memory;
 }
 }
 
 

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

@@ -44,7 +44,7 @@ public:
 
 
 	StrongRef<ByteData> parseCompressed(Data *filedata,
 	StrongRef<ByteData> parseCompressed(Data *filedata,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        std::vector<StrongRef<CompressedSlice>> &images,
-	        PixelFormat &format, bool &sRGB) override;
+	        PixelFormat &format) override;
 
 
 }; // KTXHandler
 }; // KTXHandler
 
 

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

@@ -114,7 +114,7 @@ bool PKMHandler::canParseCompressed(Data *data)
 	return true;
 	return true;
 }
 }
 
 
-StrongRef<ByteData> PKMHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
+StrongRef<ByteData> PKMHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format)
 {
 {
 	if (!canParseCompressed(filedata))
 	if (!canParseCompressed(filedata))
 		throw love::Exception("Could not decode compressed data (not a PKM file?)");
 		throw love::Exception("Could not decode compressed data (not a PKM file?)");
@@ -148,8 +148,6 @@ StrongRef<ByteData> PKMHandler::parseCompressed(Data *filedata, std::vector<Stro
 	images.emplace_back(new CompressedSlice(cformat, width, height, memory, 0, totalsize), Acquire::NORETAIN);
 	images.emplace_back(new CompressedSlice(cformat, width, height, memory, 0, totalsize), Acquire::NORETAIN);
 
 
 	format = cformat;
 	format = cformat;
-	sRGB = false;
-
 	return memory;
 	return memory;
 }
 }
 
 

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

@@ -44,7 +44,7 @@ public:
 
 
 	StrongRef<ByteData> parseCompressed(Data *filedata,
 	StrongRef<ByteData> parseCompressed(Data *filedata,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        std::vector<StrongRef<CompressedSlice>> &images,
-	        PixelFormat &format, bool &sRGB) override;
+	        PixelFormat &format) override;
 
 
 }; // PKMHandler
 }; // PKMHandler
 
 

+ 18 - 17
src/modules/image/magpie/PVRHandler.cpp

@@ -256,33 +256,33 @@ static PixelFormat convertFormat(PVRV3PixelFormat format, PVRV3ChannelType chann
 	case ePVRTPF_EAC_RG:
 	case ePVRTPF_EAC_RG:
 		return snorm ? PIXELFORMAT_EAC_RG_SNORM : PIXELFORMAT_EAC_RG_UNORM;
 		return snorm ? PIXELFORMAT_EAC_RG_SNORM : PIXELFORMAT_EAC_RG_UNORM;
 	case ePVRTPF_ASTC_4x4:
 	case ePVRTPF_ASTC_4x4:
-		return PIXELFORMAT_ASTC_4x4;
+		return PIXELFORMAT_ASTC_4x4_UNORM;
 	case ePVRTPF_ASTC_5x4:
 	case ePVRTPF_ASTC_5x4:
-		return PIXELFORMAT_ASTC_5x4;
+		return PIXELFORMAT_ASTC_5x4_UNORM;
 	case ePVRTPF_ASTC_5x5:
 	case ePVRTPF_ASTC_5x5:
-		return PIXELFORMAT_ASTC_5x5;
+		return PIXELFORMAT_ASTC_5x5_UNORM;
 	case ePVRTPF_ASTC_6x5:
 	case ePVRTPF_ASTC_6x5:
-		return PIXELFORMAT_ASTC_6x5;
+		return PIXELFORMAT_ASTC_6x5_UNORM;
 	case ePVRTPF_ASTC_6x6:
 	case ePVRTPF_ASTC_6x6:
-		return PIXELFORMAT_ASTC_6x6;
+		return PIXELFORMAT_ASTC_6x6_UNORM;
 	case ePVRTPF_ASTC_8x5:
 	case ePVRTPF_ASTC_8x5:
-		return PIXELFORMAT_ASTC_8x5;
+		return PIXELFORMAT_ASTC_8x5_UNORM;
 	case ePVRTPF_ASTC_8x6:
 	case ePVRTPF_ASTC_8x6:
-		return PIXELFORMAT_ASTC_8x6;
+		return PIXELFORMAT_ASTC_8x6_UNORM;
 	case ePVRTPF_ASTC_8x8:
 	case ePVRTPF_ASTC_8x8:
-		return PIXELFORMAT_ASTC_8x8;
+		return PIXELFORMAT_ASTC_8x8_UNORM;
 	case ePVRTPF_ASTC_10x5:
 	case ePVRTPF_ASTC_10x5:
-		return PIXELFORMAT_ASTC_10x5;
+		return PIXELFORMAT_ASTC_10x5_UNORM;
 	case ePVRTPF_ASTC_10x6:
 	case ePVRTPF_ASTC_10x6:
-		return PIXELFORMAT_ASTC_10x6;
+		return PIXELFORMAT_ASTC_10x6_UNORM;
 	case ePVRTPF_ASTC_10x8:
 	case ePVRTPF_ASTC_10x8:
-		return PIXELFORMAT_ASTC_10x8;
+		return PIXELFORMAT_ASTC_10x8_UNORM;
 	case ePVRTPF_ASTC_10x10:
 	case ePVRTPF_ASTC_10x10:
-		return PIXELFORMAT_ASTC_10x10;
+		return PIXELFORMAT_ASTC_10x10_UNORM;
 	case ePVRTPF_ASTC_12x10:
 	case ePVRTPF_ASTC_12x10:
-		return PIXELFORMAT_ASTC_12x10;
+		return PIXELFORMAT_ASTC_12x10_UNORM;
 	case ePVRTPF_ASTC_12x12:
 	case ePVRTPF_ASTC_12x12:
-		return PIXELFORMAT_ASTC_12x12;
+		return PIXELFORMAT_ASTC_12x12_UNORM;
 	default:
 	default:
 		return PIXELFORMAT_UNKNOWN;
 		return PIXELFORMAT_UNKNOWN;
 	}
 	}
@@ -475,7 +475,7 @@ bool PVRHandler::canParseCompressed(Data *data)
 	return false;
 	return false;
 }
 }
 
 
-StrongRef<ByteData> PVRHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
+StrongRef<ByteData> PVRHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format)
 {
 {
 	if (!canParseCompressed(filedata))
 	if (!canParseCompressed(filedata))
 		throw love::Exception("Could not decode compressed data (not a PVR file?)");
 		throw love::Exception("Could not decode compressed data (not a PVR file?)");
@@ -510,6 +510,9 @@ StrongRef<ByteData> PVRHandler::parseCompressed(Data *filedata, std::vector<Stro
 
 
 	PixelFormat cformat = convertFormat(pixelformat, channeltype);
 	PixelFormat cformat = convertFormat(pixelformat, channeltype);
 
 
+	if (header3.colorSpace == 1)
+		cformat = getSRGBPixelFormat(cformat);
+
 	if (cformat == PIXELFORMAT_UNKNOWN)
 	if (cformat == PIXELFORMAT_UNKNOWN)
 		throw love::Exception("Could not parse PVR file: unsupported image format.");
 		throw love::Exception("Could not parse PVR file: unsupported image format.");
 
 
@@ -551,8 +554,6 @@ StrongRef<ByteData> PVRHandler::parseCompressed(Data *filedata, std::vector<Stro
 	}
 	}
 
 
 	format = cformat;
 	format = cformat;
-	sRGB = (header3.colorSpace == 1);
-
 	return memory;
 	return memory;
 }
 }
 
 

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

@@ -42,7 +42,7 @@ public:
 
 
 	StrongRef<ByteData> parseCompressed(Data *filedata,
 	StrongRef<ByteData> parseCompressed(Data *filedata,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        std::vector<StrongRef<CompressedSlice>> &images,
-	        PixelFormat &format, bool &sRGB) override;
+	        PixelFormat &format) override;
 
 
 }; // PVRHandler
 }; // PVRHandler
 
 

+ 34 - 27
src/modules/image/magpie/ddsHandler.cpp

@@ -32,13 +32,10 @@ namespace image
 namespace magpie
 namespace magpie
 {
 {
 
 
-static PixelFormat convertFormat(dds::dxinfo::DXGIFormat dxformat, bool &sRGB, bool &bgra)
+static PixelFormat convertFormat(dds::dxinfo::DXGIFormat dxformat)
 {
 {
 	using namespace dds::dxinfo;
 	using namespace dds::dxinfo;
 
 
-	sRGB = false;
-	bgra = false;
-
 	switch (dxformat)
 	switch (dxformat)
 	{
 	{
 	case DXGI_FORMAT_R32G32B32A32_TYPELESS:
 	case DXGI_FORMAT_R32G32B32A32_TYPELESS:
@@ -65,9 +62,9 @@ static PixelFormat convertFormat(dds::dxinfo::DXGIFormat dxformat, bool &sRGB, b
 
 
 	case DXGI_FORMAT_R8G8B8A8_TYPELESS:
 	case DXGI_FORMAT_R8G8B8A8_TYPELESS:
 	case DXGI_FORMAT_R8G8B8A8_UNORM:
 	case DXGI_FORMAT_R8G8B8A8_UNORM:
-	case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
-		sRGB = (dxformat == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB);
 		return PIXELFORMAT_RGBA8_UNORM;
 		return PIXELFORMAT_RGBA8_UNORM;
+	case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+		return PIXELFORMAT_RGBA8_sRGB;
 
 
 	case DXGI_FORMAT_R16G16_TYPELESS:
 	case DXGI_FORMAT_R16G16_TYPELESS:
 	case DXGI_FORMAT_R16G16_FLOAT:
 	case DXGI_FORMAT_R16G16_FLOAT:
@@ -98,21 +95,21 @@ static PixelFormat convertFormat(dds::dxinfo::DXGIFormat dxformat, bool &sRGB, b
 
 
 	case DXGI_FORMAT_BC1_TYPELESS:
 	case DXGI_FORMAT_BC1_TYPELESS:
 	case DXGI_FORMAT_BC1_UNORM:
 	case DXGI_FORMAT_BC1_UNORM:
-	case DXGI_FORMAT_BC1_UNORM_SRGB:
-		sRGB = (dxformat == DXGI_FORMAT_BC1_UNORM_SRGB);
 		return PIXELFORMAT_DXT1_UNORM;
 		return PIXELFORMAT_DXT1_UNORM;
+	case DXGI_FORMAT_BC1_UNORM_SRGB:
+		return PIXELFORMAT_DXT1_sRGB;
 
 
 	case DXGI_FORMAT_BC2_TYPELESS:
 	case DXGI_FORMAT_BC2_TYPELESS:
 	case DXGI_FORMAT_BC2_UNORM:
 	case DXGI_FORMAT_BC2_UNORM:
-	case DXGI_FORMAT_BC2_UNORM_SRGB:
-		sRGB = (dxformat == DXGI_FORMAT_BC2_UNORM_SRGB);
 		return PIXELFORMAT_DXT3_UNORM;
 		return PIXELFORMAT_DXT3_UNORM;
+	case DXGI_FORMAT_BC2_UNORM_SRGB:
+		return PIXELFORMAT_DXT3_sRGB;
 
 
 	case DXGI_FORMAT_BC3_TYPELESS:
 	case DXGI_FORMAT_BC3_TYPELESS:
 	case DXGI_FORMAT_BC3_UNORM:
 	case DXGI_FORMAT_BC3_UNORM:
-	case DXGI_FORMAT_BC3_UNORM_SRGB:
-		sRGB = (dxformat == DXGI_FORMAT_BC3_UNORM_SRGB);
 		return PIXELFORMAT_DXT5_UNORM;
 		return PIXELFORMAT_DXT5_UNORM;
+	case DXGI_FORMAT_BC3_UNORM_SRGB:
+		return PIXELFORMAT_DXT5_sRGB;
 
 
 	case DXGI_FORMAT_BC4_TYPELESS:
 	case DXGI_FORMAT_BC4_TYPELESS:
 	case DXGI_FORMAT_BC4_UNORM:
 	case DXGI_FORMAT_BC4_UNORM:
@@ -136,10 +133,9 @@ static PixelFormat convertFormat(dds::dxinfo::DXGIFormat dxformat, bool &sRGB, b
 
 
 	case DXGI_FORMAT_B8G8R8A8_UNORM:
 	case DXGI_FORMAT_B8G8R8A8_UNORM:
 	case DXGI_FORMAT_B8G8R8A8_TYPELESS:
 	case DXGI_FORMAT_B8G8R8A8_TYPELESS:
+		return PIXELFORMAT_BGRA8_UNORM;
 	case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
 	case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
-		sRGB = (dxformat == DXGI_FORMAT_B8G8R8A8_UNORM_SRGB);
-		bgra = true;
-		return PIXELFORMAT_RGBA8_UNORM;
+		return PIXELFORMAT_BGRA8_sRGB;
 
 
 	case DXGI_FORMAT_BC6H_TYPELESS:
 	case DXGI_FORMAT_BC6H_TYPELESS:
 	case DXGI_FORMAT_BC6H_UF16:
 	case DXGI_FORMAT_BC6H_UF16:
@@ -150,9 +146,9 @@ static PixelFormat convertFormat(dds::dxinfo::DXGIFormat dxformat, bool &sRGB, b
 
 
 	case DXGI_FORMAT_BC7_TYPELESS:
 	case DXGI_FORMAT_BC7_TYPELESS:
 	case DXGI_FORMAT_BC7_UNORM:
 	case DXGI_FORMAT_BC7_UNORM:
-	case DXGI_FORMAT_BC7_UNORM_SRGB:
-		sRGB = (dxformat == DXGI_FORMAT_BC7_UNORM_SRGB);
 		return PIXELFORMAT_BC7_UNORM;
 		return PIXELFORMAT_BC7_UNORM;
+	case DXGI_FORMAT_BC7_UNORM_SRGB:
+		return PIXELFORMAT_BC7_sRGB;
 
 
 	default:
 	default:
 		return PIXELFORMAT_UNKNOWN;
 		return PIXELFORMAT_UNKNOWN;
@@ -164,9 +160,13 @@ bool DDSHandler::canDecode(Data *data)
 	using namespace dds::dxinfo;
 	using namespace dds::dxinfo;
 
 
 	DXGIFormat dxformat = dds::getDDSPixelFormat(data->getData(), data->getSize());
 	DXGIFormat dxformat = dds::getDDSPixelFormat(data->getData(), data->getSize());
-	bool isSRGB = false;
-	bool bgra = false;
-	PixelFormat format = convertFormat(dxformat, isSRGB, bgra);
+	PixelFormat format = convertFormat(dxformat);
+
+	// We convert BGRA to RGBA
+	if (format == PIXELFORMAT_BGRA8_UNORM)
+		format = PIXELFORMAT_RGBA8_UNORM;
+	else if (format == PIXELFORMAT_BGRA8_sRGB)
+		format = PIXELFORMAT_RGBA8_sRGB;
 
 
 	return ImageData::validPixelFormat(format);
 	return ImageData::validPixelFormat(format);
 }
 }
@@ -177,9 +177,19 @@ FormatHandler::DecodedImage DDSHandler::decode(Data *data)
 
 
 	dds::Parser parser(data->getData(), data->getSize());
 	dds::Parser parser(data->getData(), data->getSize());
 
 
-	bool isSRGB = false;
+	img.format = convertFormat(parser.getFormat());
+
 	bool bgra = false;
 	bool bgra = false;
-	img.format = convertFormat(parser.getFormat(), isSRGB, bgra);
+	if (img.format == PIXELFORMAT_BGRA8_UNORM)
+	{
+		img.format = PIXELFORMAT_RGBA8_UNORM;
+		bgra = true;
+	}
+	else if (img.format == PIXELFORMAT_BGRA8_sRGB)
+	{
+		img.format = PIXELFORMAT_RGBA8_sRGB;
+		bgra = true;
+	}
 
 
 	if (!ImageData::validPixelFormat(img.format))
 	if (!ImageData::validPixelFormat(img.format))
 		throw love::Exception("Could not parse DDS pixel data: Unsupported format.");
 		throw love::Exception("Could not parse DDS pixel data: Unsupported format.");
@@ -229,14 +239,12 @@ bool DDSHandler::canParseCompressed(Data *data)
 	return dds::isCompressedDDS(data->getData(), data->getSize());
 	return dds::isCompressedDDS(data->getData(), data->getSize());
 }
 }
 
 
-StrongRef<ByteData> DDSHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
+StrongRef<ByteData> DDSHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format)
 {
 {
 	if (!dds::isCompressedDDS(filedata->getData(), filedata->getSize()))
 	if (!dds::isCompressedDDS(filedata->getData(), filedata->getSize()))
 		throw love::Exception("Could not decode compressed data (not a DDS file?)");
 		throw love::Exception("Could not decode compressed data (not a DDS file?)");
 
 
 	PixelFormat texformat = PIXELFORMAT_UNKNOWN;
 	PixelFormat texformat = PIXELFORMAT_UNKNOWN;
-	bool isSRGB = false;
-	bool bgra = false;
 
 
 	size_t dataSize = 0;
 	size_t dataSize = 0;
 
 
@@ -245,7 +253,7 @@ StrongRef<ByteData> DDSHandler::parseCompressed(Data *filedata, std::vector<Stro
 	// Attempt to parse the dds file.
 	// Attempt to parse the dds file.
 	dds::Parser parser(filedata->getData(), filedata->getSize());
 	dds::Parser parser(filedata->getData(), filedata->getSize());
 
 
-	texformat = convertFormat(parser.getFormat(), isSRGB, bgra);
+	texformat = convertFormat(parser.getFormat());
 
 
 	if (texformat == PIXELFORMAT_UNKNOWN)
 	if (texformat == PIXELFORMAT_UNKNOWN)
 		throw love::Exception("Could not parse compressed data: Unsupported format.");
 		throw love::Exception("Could not parse compressed data: Unsupported format.");
@@ -280,7 +288,6 @@ StrongRef<ByteData> DDSHandler::parseCompressed(Data *filedata, std::vector<Stro
 	}
 	}
 
 
 	format = texformat;
 	format = texformat;
-	sRGB = isSRGB;
 	return memory;
 	return memory;
 }
 }
 
 

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

@@ -48,7 +48,7 @@ public:
 	bool canParseCompressed(Data *data) override;
 	bool canParseCompressed(Data *data) override;
 	StrongRef<ByteData> parseCompressed(Data *filedata,
 	StrongRef<ByteData> parseCompressed(Data *filedata,
 	        std::vector<StrongRef<CompressedSlice>> &images,
 	        std::vector<StrongRef<CompressedSlice>> &images,
-	        PixelFormat &format, bool &sRGB) override;
+	        PixelFormat &format) override;
 
 
 }; // DDSHandler
 }; // DDSHandler
 
 

+ 16 - 0
src/modules/image/wrap_CompressedImageData.cpp

@@ -103,6 +103,20 @@ int w_CompressedImageData_getFormat(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_CompressedImageData_setLinear(lua_State *L)
+{
+	CompressedImageData *t = luax_checkcompressedimagedata(L, 1);
+	t->setLinear(luax_checkboolean(L, 2));
+	return 0;
+}
+
+int w_CompressedImageData_isLinear(lua_State *L)
+{
+	CompressedImageData *t = luax_checkcompressedimagedata(L, 1);
+	luax_pushboolean(L, t->isLinear());
+	return 1;
+}
+
 static const luaL_Reg w_CompressedImageData_functions[] =
 static const luaL_Reg w_CompressedImageData_functions[] =
 {
 {
 	{ "clone", w_CompressedImageData_clone },
 	{ "clone", w_CompressedImageData_clone },
@@ -111,6 +125,8 @@ static const luaL_Reg w_CompressedImageData_functions[] =
 	{ "getDimensions", w_CompressedImageData_getDimensions },
 	{ "getDimensions", w_CompressedImageData_getDimensions },
 	{ "getMipmapCount", w_CompressedImageData_getMipmapCount },
 	{ "getMipmapCount", w_CompressedImageData_getMipmapCount },
 	{ "getFormat", w_CompressedImageData_getFormat },
 	{ "getFormat", w_CompressedImageData_getFormat },
+	{ "setLinear", w_CompressedImageData_setLinear },
+	{ "isLinear", w_CompressedImageData_isLinear },
 	{ 0, 0 },
 	{ 0, 0 },
 };
 };
 
 

+ 16 - 0
src/modules/image/wrap_ImageData.cpp

@@ -66,6 +66,20 @@ int w_ImageData_getFormat(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_ImageData_setLinear(lua_State *L)
+{
+	ImageData *t = luax_checkimagedata(L, 1);
+	t->setLinear(luax_checkboolean(L, 2));
+	return 0;
+}
+
+int w_ImageData_isLinear(lua_State *L)
+{
+	ImageData *t = luax_checkimagedata(L, 1);
+	luax_pushboolean(L, t->isLinear());
+	return 1;
+}
+
 int w_ImageData_getWidth(lua_State *L)
 int w_ImageData_getWidth(lua_State *L)
 {
 {
 	ImageData *t = luax_checkimagedata(L, 1);
 	ImageData *t = luax_checkimagedata(L, 1);
@@ -314,6 +328,8 @@ static const luaL_Reg w_ImageData_functions[] =
 {
 {
 	{ "clone", w_ImageData_clone },
 	{ "clone", w_ImageData_clone },
 	{ "getFormat", w_ImageData_getFormat },
 	{ "getFormat", w_ImageData_getFormat },
+	{ "setLinear", w_ImageData_setLinear },
+	{ "isLinear", w_ImageData_isLinear },
 	{ "getWidth", w_ImageData_getWidth },
 	{ "getWidth", w_ImageData_getWidth },
 	{ "getHeight", w_ImageData_getHeight },
 	{ "getHeight", w_ImageData_getHeight },
 	{ "getDimensions", w_ImageData_getDimensions },
 	{ "getDimensions", w_ImageData_getDimensions },

+ 24 - 6
src/modules/physics/box2d/Body.cpp

@@ -23,12 +23,12 @@
 #include "common/math.h"
 #include "common/math.h"
 
 
 #include "Shape.h"
 #include "Shape.h"
-#include "Fixture.h"
 #include "World.h"
 #include "World.h"
 #include "Physics.h"
 #include "Physics.h"
 
 
 // Needed for luax_pushjoint.
 // Needed for luax_pushjoint.
 #include "wrap_Joint.h"
 #include "wrap_Joint.h"
+#include "wrap_Shape.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -39,6 +39,7 @@ namespace box2d
 
 
 Body::Body(World *world, b2Vec2 p, Body::Type type)
 Body::Body(World *world, b2Vec2 p, Body::Type type)
 	: world(world)
 	: world(world)
+	, hasCustomMass(false)
 {
 {
 	b2BodyDef def;
 	b2BodyDef def;
 	def.position = Physics::scaleDown(p);
 	def.position = Physics::scaleDown(p);
@@ -249,6 +250,7 @@ void Body::setLinearDamping(float d)
 void Body::resetMassData()
 void Body::resetMassData()
 {
 {
 	body->ResetMassData();
 	body->ResetMassData();
+	hasCustomMass = false;
 }
 }
 
 
 void Body::setMassData(float x, float y, float m, float i)
 void Body::setMassData(float x, float y, float m, float i)
@@ -258,6 +260,7 @@ void Body::setMassData(float x, float y, float m, float i)
 	massData.mass = m;
 	massData.mass = m;
 	massData.I = Physics::scaleDown(Physics::scaleDown(i));
 	massData.I = Physics::scaleDown(Physics::scaleDown(i));
 	body->SetMassData(&massData);
 	body->SetMassData(&massData);
+	hasCustomMass = true;
 }
 }
 
 
 void Body::setMass(float m)
 void Body::setMass(float m)
@@ -266,6 +269,7 @@ void Body::setMass(float m)
 	body->GetMassData(&data);
 	body->GetMassData(&data);
 	data.mass = m;
 	data.mass = m;
 	body->SetMassData(&data);
 	body->SetMassData(&data);
+	hasCustomMass = true;
 }
 }
 
 
 void Body::setInertia(float i)
 void Body::setInertia(float i)
@@ -275,6 +279,7 @@ void Body::setInertia(float i)
 	massData.mass = body->GetMass();
 	massData.mass = body->GetMass();
 	massData.I = Physics::scaleDown(Physics::scaleDown(i));
 	massData.I = Physics::scaleDown(Physics::scaleDown(i));
 	body->SetMassData(&massData);
 	body->SetMassData(&massData);
+	hasCustomMass = true;
 }
 }
 
 
 void Body::setGravityScale(float scale)
 void Body::setGravityScale(float scale)
@@ -461,7 +466,20 @@ World *Body::getWorld() const
 	return world;
 	return world;
 }
 }
 
 
-int Body::getFixtures(lua_State *L) const
+Shape *Body::getShape() const
+{
+	b2Fixture *f = body->GetFixtureList();
+	if (f == nullptr)
+		return nullptr;
+
+	Shape *shape = (Shape *)(f->GetUserData().pointer);
+	if (!shape)
+		throw love::Exception("A Shape has escaped Memoizer!");
+
+	return shape;
+}
+
+int Body::getShapes(lua_State *L) const
 {
 {
 	lua_newtable(L);
 	lua_newtable(L);
 	b2Fixture *f = body->GetFixtureList();
 	b2Fixture *f = body->GetFixtureList();
@@ -470,10 +488,10 @@ int Body::getFixtures(lua_State *L) const
 	{
 	{
 		if (!f)
 		if (!f)
 			break;
 			break;
-		Fixture *fixture = (Fixture *)(f->GetUserData().pointer);
-		if (!fixture)
-			throw love::Exception("A fixture has escaped Memoizer!");
-		luax_pushtype(L, fixture);
+		Shape *shape = (Shape *)(f->GetUserData().pointer);
+		if (!shape)
+			throw love::Exception("A Shape has escaped Memoizer!");
+		luax_pushshape(L, shape);
 		lua_rawseti(L, -2, i);
 		lua_rawseti(L, -2, i);
 		i++;
 		i++;
 	}
 	}

+ 11 - 5
src/modules/physics/box2d/Body.h

@@ -39,7 +39,6 @@ namespace box2d
 // Forward declarations.
 // Forward declarations.
 class World;
 class World;
 class Shape;
 class Shape;
-class Fixture;
 
 
 /**
 /**
  * A Body is an entity which has position and orientation
  * A Body is an entity which has position and orientation
@@ -57,7 +56,6 @@ public:
 	friend class CircleShape;
 	friend class CircleShape;
 	friend class PolygonShape;
 	friend class PolygonShape;
 	friend class Shape;
 	friend class Shape;
-	friend class Fixture;
 
 
 	// Public because joints et al ask for b2body
 	// Public because joints et al ask for b2body
 	b2Body *body;
 	b2Body *body;
@@ -139,6 +137,8 @@ public:
 	 **/
 	 **/
 	int getMassData(lua_State *L);
 	int getMassData(lua_State *L);
 
 
+	bool hasCustomMassData() const { return hasCustomMass; }
+
 	/**
 	/**
 	 * Gets the Body's angular damping.
 	 * Gets the Body's angular damping.
 	 **/
 	 **/
@@ -391,10 +391,14 @@ public:
 	World *getWorld() const;
 	World *getWorld() const;
 
 
 	/**
 	/**
-	 * Get an array of all the Fixtures attached to this Body.
-	 * @return An array of Fixtures.
+	 * Gets the first Shape attached to this Body.
 	 **/
 	 **/
-	int getFixtures(lua_State *L) const;
+	Shape *getShape() const;
+
+	/**
+	 * Get an array of all the Shapes attached to this Body.
+	 **/
+	int getShapes(lua_State *L) const;
 
 
 	/**
 	/**
 	 * Get an array of all Joints attached to this Body.
 	 * Get an array of all Joints attached to this Body.
@@ -431,6 +435,8 @@ private:
 	// unowned?
 	// unowned?
 	World *world;
 	World *world;
 
 
+	bool hasCustomMass;
+
 	// Reference to arbitrary data.
 	// Reference to arbitrary data.
 	Reference* ref = nullptr;
 	Reference* ref = nullptr;
 
 

+ 16 - 17
src/modules/physics/box2d/ChainShape.cpp

@@ -34,8 +34,8 @@ namespace box2d
 
 
 love::Type ChainShape::type("ChainShape", &Shape::type);
 love::Type ChainShape::type("ChainShape", &Shape::type);
 
 
-ChainShape::ChainShape(b2ChainShape *c, bool own)
-	: Shape(c, own)
+ChainShape::ChainShape(Body *body, const b2ChainShape &c)
+	: Shape(body, c)
 {
 {
 }
 }
 
 
@@ -45,6 +45,7 @@ ChainShape::~ChainShape()
 
 
 void ChainShape::setNextVertex(float x, float y)
 void ChainShape::setNextVertex(float x, float y)
 {
 {
+	throwIfShapeNotValid();
 	b2Vec2 v(x, y);
 	b2Vec2 v(x, y);
 	b2ChainShape *c = (b2ChainShape *)shape;
 	b2ChainShape *c = (b2ChainShape *)shape;
 	c->m_nextVertex = Physics::scaleDown(v);
 	c->m_nextVertex = Physics::scaleDown(v);
@@ -52,6 +53,7 @@ void ChainShape::setNextVertex(float x, float y)
 
 
 void ChainShape::setPreviousVertex(float x, float y)
 void ChainShape::setPreviousVertex(float x, float y)
 {
 {
+	throwIfShapeNotValid();
 	b2Vec2 v(x, y);
 	b2Vec2 v(x, y);
 	b2ChainShape *c = (b2ChainShape *)shape;
 	b2ChainShape *c = (b2ChainShape *)shape;
 	c->m_prevVertex = Physics::scaleDown(v);
 	c->m_prevVertex = Physics::scaleDown(v);
@@ -59,44 +61,40 @@ void ChainShape::setPreviousVertex(float x, float y)
 
 
 b2Vec2 ChainShape::getNextVertex() const
 b2Vec2 ChainShape::getNextVertex() const
 {
 {
+	throwIfShapeNotValid();
 	b2ChainShape *c = (b2ChainShape *)shape;
 	b2ChainShape *c = (b2ChainShape *)shape;
-
 	return Physics::scaleUp(c->m_nextVertex);
 	return Physics::scaleUp(c->m_nextVertex);
 }
 }
 
 
 b2Vec2 ChainShape::getPreviousVertex() const
 b2Vec2 ChainShape::getPreviousVertex() const
 {
 {
+	throwIfShapeNotValid();
 	b2ChainShape *c = (b2ChainShape *)shape;
 	b2ChainShape *c = (b2ChainShape *)shape;
-
 	return Physics::scaleUp(c->m_prevVertex);
 	return Physics::scaleUp(c->m_prevVertex);
 }
 }
 
 
 EdgeShape *ChainShape::getChildEdge(int index) const
 EdgeShape *ChainShape::getChildEdge(int index) const
 {
 {
+	throwIfShapeNotValid();
+
 	b2ChainShape *c = (b2ChainShape *)shape;
 	b2ChainShape *c = (b2ChainShape *)shape;
-	b2EdgeShape *e = new b2EdgeShape;
-
-	try
-	{
-		c->GetChildEdge(e, index);
-	}
-	catch (love::Exception &)
-	{
-		delete e;
-		throw;
-	}
-
-	return new EdgeShape(e, true);
+
+	b2EdgeShape e;
+	c->GetChildEdge(&e, index);
+
+	return new EdgeShape(nullptr, e);
 }
 }
 
 
 int ChainShape::getVertexCount() const
 int ChainShape::getVertexCount() const
 {
 {
+	throwIfShapeNotValid();
 	b2ChainShape *c = (b2ChainShape *)shape;
 	b2ChainShape *c = (b2ChainShape *)shape;
 	return c->m_count;
 	return c->m_count;
 }
 }
 
 
 b2Vec2 ChainShape::getPoint(int index) const
 b2Vec2 ChainShape::getPoint(int index) const
 {
 {
+	throwIfShapeNotValid();
 	b2ChainShape *c = (b2ChainShape *)shape;
 	b2ChainShape *c = (b2ChainShape *)shape;
 	if (index < 0 || index >= c->m_count)
 	if (index < 0 || index >= c->m_count)
 		throw love::Exception("Physics error: index out of bounds");
 		throw love::Exception("Physics error: index out of bounds");
@@ -106,6 +104,7 @@ b2Vec2 ChainShape::getPoint(int index) const
 
 
 const b2Vec2 *ChainShape::getPoints() const
 const b2Vec2 *ChainShape::getPoints() const
 {
 {
+	throwIfShapeNotValid();
 	b2ChainShape *c = (b2ChainShape *)shape;
 	b2ChainShape *c = (b2ChainShape *)shape;
 	return c->m_vertices;
 	return c->m_vertices;
 }
 }

+ 1 - 1
src/modules/physics/box2d/ChainShape.h

@@ -45,7 +45,7 @@ public:
 	 * Create a new ChainShape from a Box2D chain shape.
 	 * Create a new ChainShape from a Box2D chain shape.
 	 * @param c The chain shape.
 	 * @param c The chain shape.
 	 **/
 	 **/
-	ChainShape(b2ChainShape *c, bool own = true);
+	ChainShape(Body *body, const b2ChainShape &c);
 
 
 	virtual ~ChainShape();
 	virtual ~ChainShape();
 
 

+ 6 - 2
src/modules/physics/box2d/CircleShape.cpp

@@ -34,8 +34,8 @@ namespace box2d
 
 
 love::Type CircleShape::type("CircleShape", &Shape::type);
 love::Type CircleShape::type("CircleShape", &Shape::type);
 
 
-CircleShape::CircleShape(b2CircleShape *c, bool own)
-	: Shape(c, own)
+CircleShape::CircleShape(Body *body, const b2CircleShape &c)
+	: Shape(body, c)
 {
 {
 }
 }
 
 
@@ -45,16 +45,19 @@ CircleShape::~CircleShape()
 
 
 float CircleShape::getRadius() const
 float CircleShape::getRadius() const
 {
 {
+	throwIfShapeNotValid();
 	return Physics::scaleUp(shape->m_radius);
 	return Physics::scaleUp(shape->m_radius);
 }
 }
 
 
 void CircleShape::setRadius(float r)
 void CircleShape::setRadius(float r)
 {
 {
+	throwIfShapeNotValid();
 	shape->m_radius = Physics::scaleDown(r);
 	shape->m_radius = Physics::scaleDown(r);
 }
 }
 
 
 void CircleShape::getPoint(float &x_o, float &y_o) const
 void CircleShape::getPoint(float &x_o, float &y_o) const
 {
 {
+	throwIfShapeNotValid();
 	b2CircleShape *c = (b2CircleShape *) shape;
 	b2CircleShape *c = (b2CircleShape *) shape;
 	x_o = Physics::scaleUp(c->m_p.x);
 	x_o = Physics::scaleUp(c->m_p.x);
 	y_o = Physics::scaleUp(c->m_p.y);
 	y_o = Physics::scaleUp(c->m_p.y);
@@ -62,6 +65,7 @@ void CircleShape::getPoint(float &x_o, float &y_o) const
 
 
 void CircleShape::setPoint(float x, float y)
 void CircleShape::setPoint(float x, float y)
 {
 {
+	throwIfShapeNotValid();
 	b2CircleShape *c = (b2CircleShape *) shape;
 	b2CircleShape *c = (b2CircleShape *) shape;
 	c->m_p = Physics::scaleDown(b2Vec2(x, y));
 	c->m_p = Physics::scaleDown(b2Vec2(x, y));
 }
 }

+ 1 - 1
src/modules/physics/box2d/CircleShape.h

@@ -50,7 +50,7 @@ public:
 	 * Create a new CircleShape from the a Box2D CircleShape definition.
 	 * Create a new CircleShape from the a Box2D CircleShape definition.
 	 * @param c The CircleShape definition.
 	 * @param c The CircleShape definition.
 	 **/
 	 **/
-	CircleShape(b2CircleShape *c, bool own = true);
+	CircleShape(Body *body, const b2CircleShape &c);
 
 
 	virtual ~CircleShape();
 	virtual ~CircleShape();
 
 

+ 8 - 9
src/modules/physics/box2d/Contact.cpp

@@ -35,7 +35,6 @@ Contact::Contact(World *world, b2Contact *contact)
 	: contact(contact)
 	: contact(contact)
 	, world(world)
 	, world(world)
 {
 {
-	//contact->user
 	world->registerObject(contact, this);
 	world->registerObject(contact, this);
 }
 }
 
 
@@ -46,16 +45,16 @@ Contact::~Contact()
 
 
 void Contact::invalidate()
 void Contact::invalidate()
 {
 {
-	if (contact != NULL)
+	if (contact != nullptr)
 	{
 	{
 		world->unregisterObject(contact);
 		world->unregisterObject(contact);
-		contact = NULL;
+		contact = nullptr;
 	}
 	}
 }
 }
 
 
 bool Contact::isValid()
 bool Contact::isValid()
 {
 {
-	return contact != NULL;
+	return contact != nullptr;
 }
 }
 
 
 int Contact::getPositions(lua_State *L)
 int Contact::getPositions(lua_State *L)
@@ -144,13 +143,13 @@ void Contact::getChildren(int &childA, int &childB)
 	childB = contact->GetChildIndexB();
 	childB = contact->GetChildIndexB();
 }
 }
 
 
-void Contact::getFixtures(Fixture *&fixtureA, Fixture *&fixtureB)
+void Contact::getShapes(Shape *&shapeA, Shape *&shapeB)
 {
 {
-	fixtureA = (Fixture *) (contact->GetFixtureA()->GetUserData().pointer);
-	fixtureB = (Fixture *) (contact->GetFixtureB()->GetUserData().pointer);
+	shapeA = (Shape *) (contact->GetFixtureA()->GetUserData().pointer);
+	shapeB = (Shape *) (contact->GetFixtureB()->GetUserData().pointer);
 
 
-	if (!fixtureA || !fixtureB)
-		throw love::Exception("A fixture has escaped Memoizer!");
+	if (!shapeA || !shapeB)
+		throw love::Exception("A Shape has escaped Memoizer!");
 }
 }
 
 
 } // box2d
 } // box2d

+ 2 - 2
src/modules/physics/box2d/Contact.h

@@ -151,9 +151,9 @@ public:
 	void getChildren(int &childA, int &childB);
 	void getChildren(int &childA, int &childB);
 
 
 	/**
 	/**
-	 * Gets the Fixtures associated with this Contact.
+	 * Gets the Shapes associated with this Contact.
 	 **/
 	 **/
-	void getFixtures(Fixture *&fixtureA, Fixture *&fixtureB);
+	void getShapes(Shape *&shapeA, Shape *&shapeB);
 
 
 private:
 private:
 
 

+ 7 - 4
src/modules/physics/box2d/EdgeShape.cpp

@@ -34,8 +34,8 @@ namespace box2d
 
 
 love::Type EdgeShape::type("EdgeShape", &Shape::type);
 love::Type EdgeShape::type("EdgeShape", &Shape::type);
 
 
-EdgeShape::EdgeShape(b2EdgeShape *e, bool own)
-	: Shape(e, own)
+EdgeShape::EdgeShape(Body *body, const b2EdgeShape &e)
+	: Shape(body, e)
 {
 {
 }
 }
 
 
@@ -45,6 +45,7 @@ EdgeShape::~EdgeShape()
 
 
 void EdgeShape::setNextVertex(float x, float y)
 void EdgeShape::setNextVertex(float x, float y)
 {
 {
+	throwIfShapeNotValid();
 	b2EdgeShape *e = (b2EdgeShape *)shape;
 	b2EdgeShape *e = (b2EdgeShape *)shape;
 	b2Vec2 v(x, y);
 	b2Vec2 v(x, y);
 	e->m_vertex3 = Physics::scaleDown(v);
 	e->m_vertex3 = Physics::scaleDown(v);
@@ -52,13 +53,14 @@ void EdgeShape::setNextVertex(float x, float y)
 
 
 b2Vec2 EdgeShape::getNextVertex() const
 b2Vec2 EdgeShape::getNextVertex() const
 {
 {
+	throwIfShapeNotValid();
 	b2EdgeShape *e = (b2EdgeShape *)shape;
 	b2EdgeShape *e = (b2EdgeShape *)shape;
-
 	return Physics::scaleUp(e->m_vertex3);
 	return Physics::scaleUp(e->m_vertex3);
 }
 }
 
 
 void EdgeShape::setPreviousVertex(float x, float y)
 void EdgeShape::setPreviousVertex(float x, float y)
 {
 {
+	throwIfShapeNotValid();
 	b2EdgeShape *e = (b2EdgeShape *)shape;
 	b2EdgeShape *e = (b2EdgeShape *)shape;
 	b2Vec2 v(x, y);
 	b2Vec2 v(x, y);
 	e->m_vertex0 = Physics::scaleDown(v);
 	e->m_vertex0 = Physics::scaleDown(v);
@@ -66,13 +68,14 @@ void EdgeShape::setPreviousVertex(float x, float y)
 
 
 b2Vec2 EdgeShape::getPreviousVertex() const
 b2Vec2 EdgeShape::getPreviousVertex() const
 {
 {
+	throwIfShapeNotValid();
 	b2EdgeShape *e = (b2EdgeShape *)shape;
 	b2EdgeShape *e = (b2EdgeShape *)shape;
-
 	return Physics::scaleUp(e->m_vertex0);
 	return Physics::scaleUp(e->m_vertex0);
 }
 }
 
 
 int EdgeShape::getPoints(lua_State *L)
 int EdgeShape::getPoints(lua_State *L)
 {
 {
+	throwIfShapeNotValid();
 	b2EdgeShape *e = (b2EdgeShape *)shape;
 	b2EdgeShape *e = (b2EdgeShape *)shape;
 	b2Vec2 v1 = Physics::scaleUp(e->m_vertex1);
 	b2Vec2 v1 = Physics::scaleUp(e->m_vertex1);
 	b2Vec2 v2 = Physics::scaleUp(e->m_vertex2);
 	b2Vec2 v2 = Physics::scaleUp(e->m_vertex2);

+ 1 - 1
src/modules/physics/box2d/EdgeShape.h

@@ -45,7 +45,7 @@ public:
 	 * Create a new EdgeShape from a Box2D edge shape.
 	 * Create a new EdgeShape from a Box2D edge shape.
 	 * @param e The edge shape.
 	 * @param e The edge shape.
 	 **/
 	 **/
-	EdgeShape(b2EdgeShape *e, bool own = true);
+	EdgeShape(Body *body, const b2EdgeShape &e);
 
 
 	virtual ~EdgeShape();
 	virtual ~EdgeShape();
 
 

+ 0 - 349
src/modules/physics/box2d/Fixture.cpp

@@ -1,349 +0,0 @@
-/**
- * Copyright (c) 2006-2023 LOVE Development Team
- *
- * This software is provided 'as-is', without any express or implied
- * warranty.  In no event will the authors be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- *    claim that you wrote the original software. If you use this software
- *    in a product, an acknowledgment in the product documentation would be
- *    appreciated but is not required.
- * 2. Altered source versions must be plainly marked as such, and must not be
- *    misrepresented as being the original software.
- * 3. This notice may not be removed or altered from any source distribution.
- **/
-
-#include "Fixture.h"
-
-// Module
-#include "Body.h"
-#include "World.h"
-#include "Physics.h"
-
-// STD
-#include <bitset>
-
-namespace love
-{
-namespace physics
-{
-namespace box2d
-{
-
-love::Type Fixture::type("Fixture", &Object::type);
-
-Fixture::Fixture(Body *body, Shape *shape, float density)
-	: body(body)
-	, fixture(nullptr)
-{
-	b2FixtureDef def;
-	def.shape = shape->shape;
-	def.userData.pointer = (uintptr_t)this;
-	def.density = density;
-	fixture = body->body->CreateFixture(&def);
-	this->retain();
-}
-
-Fixture::~Fixture()
-{
-	if (ref)
-		delete ref;
-}
-
-void Fixture::checkCreateShape()
-{
-	if (shape.get() != nullptr || fixture == nullptr || fixture->GetShape() == nullptr)
-		return;
-
-	b2Shape *bshape = fixture->GetShape();
-
-	switch (bshape->GetType())
-	{
-	case b2Shape::e_circle:
-		shape.set(new CircleShape((b2CircleShape *) bshape, false), Acquire::NORETAIN);
-		break;
-	case b2Shape::e_edge:
-		shape.set(new EdgeShape((b2EdgeShape *) bshape, false), Acquire::NORETAIN);
-		break;
-	case b2Shape::e_polygon:
-		shape.set(new PolygonShape((b2PolygonShape *) bshape, false), Acquire::NORETAIN);
-		break;
-	case b2Shape::e_chain:
-		shape.set(new ChainShape((b2ChainShape *) bshape, false), Acquire::NORETAIN);
-		break;
-	default:
-		break;
-	}
-}
-
-Shape::Type Fixture::getType()
-{
-	checkCreateShape();
-	if (shape.get() == nullptr)
-		return Shape::SHAPE_INVALID;
-	else
-		return shape->getType();
-}
-
-void Fixture::setFriction(float friction)
-{
-	fixture->SetFriction(friction);
-}
-
-void Fixture::setRestitution(float restitution)
-{
-	fixture->SetRestitution(restitution);
-}
-
-void Fixture::setDensity(float density)
-{
-	fixture->SetDensity(density);
-}
-
-void Fixture::setSensor(bool sensor)
-{
-	fixture->SetSensor(sensor);
-}
-
-float Fixture::getFriction() const
-{
-	return fixture->GetFriction();
-}
-
-float Fixture::getRestitution() const
-{
-	return fixture->GetRestitution();
-}
-
-float Fixture::getDensity() const
-{
-	return fixture->GetDensity();
-}
-
-bool Fixture::isSensor() const
-{
-	return fixture->IsSensor();
-}
-
-Body *Fixture::getBody() const
-{
-	return body;
-}
-
-Shape *Fixture::getShape()
-{
-	checkCreateShape();
-	return shape;
-}
-
-bool Fixture::isValid() const
-{
-	return fixture != nullptr;
-}
-
-void Fixture::setFilterData(int *v)
-{
-	b2Filter f;
-	f.categoryBits = (uint16) v[0];
-	f.maskBits = (uint16) v[1];
-	f.groupIndex = (int16) v[2];
-	fixture->SetFilterData(f);
-}
-
-void Fixture::getFilterData(int *v)
-{
-	b2Filter f = fixture->GetFilterData();
-	v[0] = (int) f.categoryBits;
-	v[1] = (int) f.maskBits;
-	v[2] = (int) f.groupIndex;
-}
-
-int Fixture::setCategory(lua_State *L)
-{
-	b2Filter f = fixture->GetFilterData();
-	f.categoryBits = (uint16)getBits(L);
-	fixture->SetFilterData(f);
-	return 0;
-}
-
-int Fixture::setMask(lua_State *L)
-{
-	b2Filter f = fixture->GetFilterData();
-	f.maskBits = ~(uint16)getBits(L);
-	fixture->SetFilterData(f);
-	return 0;
-}
-
-void Fixture::setGroupIndex(int index)
-{
-	b2Filter f = fixture->GetFilterData();
-	f.groupIndex = (uint16)index;
-	fixture->SetFilterData(f);
-}
-
-int Fixture::getGroupIndex() const
-{
-	b2Filter f = fixture->GetFilterData();
-	return f.groupIndex;
-}
-
-int Fixture::getCategory(lua_State *L)
-{
-	return pushBits(L, fixture->GetFilterData().categoryBits);
-}
-
-int Fixture::getMask(lua_State *L)
-{
-	return pushBits(L, ~(fixture->GetFilterData().maskBits));
-}
-
-uint16 Fixture::getBits(lua_State *L)
-{
-	// Get number of args.
-	bool istable = lua_istable(L, 1);
-	int argc = istable ? (int) luax_objlen(L, 1) : lua_gettop(L);
-
-	// The new bitset.
-	std::bitset<16> b;
-
-	for (int i = 1; i <= argc; i++)
-	{
-		size_t bpos = 0;
-
-		if (istable)
-		{
-			lua_rawgeti(L, 1, i);
-			bpos = (size_t) (lua_tointeger(L, -1) - 1);
-			lua_pop(L, 1);
-		}
-		else
-			bpos = (size_t) (lua_tointeger(L, i) - 1);
-
-		if (bpos >= 16)
-			luaL_error(L, "Values must be in range 1-16.");
-
-		b.set(bpos, true);
-	}
-
-	return (uint16)b.to_ulong();
-}
-
-int Fixture::pushBits(lua_State *L, uint16 bits)
-{
-	// Create a bitset.
-	std::bitset<16> b((int)bits);
-
-	// Push all set bits.
-	for (int i = 0; i<16; i++)
-		if (b.test(i))
-			lua_pushinteger(L, i+1);
-
-	// Count number of set bits.
-	return (int)b.count();
-}
-
-int Fixture::setUserData(lua_State *L)
-{
-	love::luax_assert_argc(L, 1, 1);
-
-	if(!ref)
-		ref = new Reference();
-
-	ref->ref(L);
-
-	return 0;
-}
-
-int Fixture::getUserData(lua_State *L)
-{
-	if (ref != nullptr)
-		ref->push(L);
-	else
-		lua_pushnil(L);
-
-	return 1;
-}
-
-bool Fixture::testPoint(float x, float y) const
-{
-	return fixture->TestPoint(Physics::scaleDown(b2Vec2(x, y)));
-}
-
-int Fixture::rayCast(lua_State *L) const
-{
-	float p1x = Physics::scaleDown((float)luaL_checknumber(L, 1));
-	float p1y = Physics::scaleDown((float)luaL_checknumber(L, 2));
-	float p2x = Physics::scaleDown((float)luaL_checknumber(L, 3));
-	float p2y = Physics::scaleDown((float)luaL_checknumber(L, 4));
-	float maxFraction = (float)luaL_checknumber(L, 5);
-	int childIndex = (int) luaL_optinteger(L, 6, 1) - 1; // Convert from 1-based index
-	b2RayCastInput input;
-	input.p1.Set(p1x, p1y);
-	input.p2.Set(p2x, p2y);
-	input.maxFraction = maxFraction;
-	b2RayCastOutput output;
-	if (!fixture->RayCast(&output, input, childIndex))
-		return 0; // Nothing hit.
-	lua_pushnumber(L, output.normal.x);
-	lua_pushnumber(L, output.normal.y);
-	lua_pushnumber(L, output.fraction);
-	return 3;
-}
-
-int Fixture::getBoundingBox(lua_State *L) const
-{
-	int childIndex = (int) luaL_optinteger(L, 1, 1) - 1; // Convert from 1-based index
-	b2AABB box;
-	luax_catchexcept(L, [&]() { box = fixture->GetAABB(childIndex); });
-	box = Physics::scaleUp(box);
-	lua_pushnumber(L, box.lowerBound.x);
-	lua_pushnumber(L, box.lowerBound.y);
-	lua_pushnumber(L, box.upperBound.x);
-	lua_pushnumber(L, box.upperBound.y);
-	return 4;
-}
-
-int Fixture::getMassData(lua_State *L) const
-{
-	b2MassData data;
-	fixture->GetMassData(&data);
-	b2Vec2 center = Physics::scaleUp(data.center);
-	lua_pushnumber(L, center.x);
-	lua_pushnumber(L, center.y);
-	lua_pushnumber(L, data.mass);
-	lua_pushnumber(L, data.I);
-	return 4;
-}
-
-void Fixture::destroy(bool implicit)
-{
-	if (body->world->world->IsLocked())
-	{
-		// Called during time step. Save reference for destruction afterwards.
-		this->retain();
-		body->world->destructFixtures.push_back(this);
-		return;
-	}
-
-	shape.set(nullptr);
-
-	if (!implicit && fixture != nullptr)
-		body->body->DestroyFixture(fixture);
-	fixture = nullptr;
-
-	// Remove userdata reference to avoid it sticking around after GC
-	if (ref)
-		ref->unref();
-
-	// Box2D fixture destroyed. Release its reference to the love Fixture.
-	this->release();
-}
-
-} // box2d
-} // physics
-} // love

+ 0 - 216
src/modules/physics/box2d/Fixture.h

@@ -1,216 +0,0 @@
-/**
- * Copyright (c) 2006-2023 LOVE Development Team
- *
- * This software is provided 'as-is', without any express or implied
- * warranty.  In no event will the authors be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- *    claim that you wrote the original software. If you use this software
- *    in a product, an acknowledgment in the product documentation would be
- *    appreciated but is not required.
- * 2. Altered source versions must be plainly marked as such, and must not be
- *    misrepresented as being the original software.
- * 3. This notice may not be removed or altered from any source distribution.
- **/
-
-#ifndef LOVE_PHYSICS_BOX2D_FIXTURE_H
-#define LOVE_PHYSICS_BOX2D_FIXTURE_H
-
-// LOVE
-#include "physics/Shape.h"
-#include "physics/box2d/Body.h"
-#include "physics/box2d/Shape.h"
-#include "common/Object.h"
-#include "common/Reference.h"
-
-// Box2D
-#include <box2d/Box2D.h>
-
-namespace love
-{
-namespace physics
-{
-namespace box2d
-{
-
-class World;
-
-/**
- * A Fixture is used to attach a shape to a body for collision detection.
- * A Fixture inherits its transform from its parent. Fixtures hold
- * additional non-geometric data such as friction, collision filters,
- * etc.
- **/
-class Fixture : public Object
-{
-public:
-	friend class Physics;
-
-	static love::Type type;
-
-	/**
-	 * Creates a Fixture.
-	 **/
-	Fixture(Body *body, Shape *shape, float density);
-
-	virtual ~Fixture();
-
-	/**
-	 * Gets the type of the Fixture's Shape. Useful for
-	 * debug drawing.
-	 **/
-	Shape::Type getType();
-
-	/**
-	 * Gets the Shape attached to this Fixture.
-	 **/
-	Shape *getShape();
-
-	/**
-	 * Returns true if the fixture is active in a Box2D world.
-	 **/
-	bool isValid() const;
-
-	/**
-	 * Checks whether this Fixture acts as a sensor.
-	 * @return True if sensor, false otherwise.
-	 **/
-	bool isSensor() const;
-
-	/**
-	 * Set whether this Fixture should be a sensor or not.
-	 * @param sensor True if sensor, false if not.
-	 **/
-	void setSensor(bool sensor);
-
-	/**
-	 * Gets the Body this Fixture is attached to.
-	 **/
-	Body *getBody() const;
-
-	/**
-	 * Sets the filter data. An integer array is used even though the
-	 * first two elements are unsigned shorts. The elements are:
-	 * category (16-bits), mask (16-bits) and group (32-bits/int).
-	 **/
-	void setFilterData(int *v);
-
-	/**
-	 * Gets the filter data. An integer array is used even though the
-	 * first two elements are unsigned shorts. The elements are:
-	 * category (16-bits), mask (16-bits) and group (32-bits/int).
-	 **/
-	void getFilterData(int *v);
-
-	/**
-	 * This function stores an in-C reference to
-	 * arbitrary Lua data in the Box2D Fixture object.
-	 **/
-	int setUserData(lua_State *L);
-
-	/**
-	 * Gets the data set with setData. If no
-	 * data is set, nil is returned.
-	 **/
-	int getUserData(lua_State *L);
-
-	/**
-	 * Sets the friction of the Fixture.
-	 * @param friction The new friction.
-	 **/
-	void setFriction(float friction);
-
-	/**
-	 * Sets the restitution for the Fixture.
-	 * @param restitution The restitution.
-	 **/
-	void setRestitution(float restitution);
-
-	/**
-	 * Sets the density of the Fixture.
-	 * @param density The density of the Fixture.
-	 **/
-	void setDensity(float density);
-
-	/**
-	 * Gets the friction of the Fixture.
-	 * @returns The friction.
-	 **/
-	float getFriction() const;
-
-	/**
-	 * Gets the restitution of the Fixture.
-	 * @return The restitution of the Fixture.
-	 **/
-	float getRestitution() const;
-
-	/**
-	 * Gets the density of the Fixture.
-	 * @return The density.
-	 **/
-	float getDensity() const;
-
-	/**
-	 * Checks if a point is inside the Fixture.
-	 * @param x The x-component of the point.
-	 * @param y The y-component of the point.
-	 **/
-	bool testPoint(float x, float y) const;
-
-	/**
-	 * Cast a ray against this Fixture.
-	 **/
-	int rayCast(lua_State *L) const;
-
-	void setGroupIndex(int index);
-	int getGroupIndex() const;
-
-	int setCategory(lua_State *L);
-	int setMask(lua_State *L);
-	int getCategory(lua_State *L);
-	int getMask(lua_State *L);
-	uint16 getBits(lua_State *L);
-	int pushBits(lua_State *L, uint16 bits);
-
-	/**
-	 * Gets the bounding box for this Fixture.
-	 * The function returns eight values which can be
-	 * passed directly to love.graphics.polygon.
-	 **/
-	int getBoundingBox(lua_State *L) const;
-
-	/**
-	 * Gets the mass data for this Fixture.
-	 * This operation may be expensive.
-	 **/
-	int getMassData(lua_State *L) const;
-
-	/**
-	 * Destroys this fixture.
-	 **/
-	void destroy(bool implicit = false);
-
-protected:
-
-	void checkCreateShape();
-
-	Body *body;
-	b2Fixture *fixture;
-
-	// Reference to arbitrary data.
-	Reference* ref = nullptr;
-
-	StrongRef<Shape> shape;
-
-};
-
-} // box2d
-} // physics
-} // love
-
-#endif // LOVE_PHYSICS_BOX2D_FIXTURE_H

+ 126 - 137
src/modules/physics/box2d/Physics.cpp

@@ -35,6 +35,7 @@ namespace box2d
 float Physics::meter = Physics::DEFAULT_METER;
 float Physics::meter = Physics::DEFAULT_METER;
 
 
 Physics::Physics()
 Physics::Physics()
+	: blockAllocator()
 {
 {
 	meter = DEFAULT_METER;
 	meter = DEFAULT_METER;
 }
 }
@@ -63,173 +64,164 @@ Body *Physics::newBody(World *world, Body::Type type)
 	return new Body(world, b2Vec2(0, 0), type);
 	return new Body(world, b2Vec2(0, 0), type);
 }
 }
 
 
-CircleShape *Physics::newCircleShape(float radius)
+Body *Physics::newCircleBody(World *world, Body::Type type, float x, float y, float radius)
 {
 {
-	return newCircleShape(0, 0, radius);
+	StrongRef<Body> body(newBody(world, x, y, type), Acquire::NORETAIN);
+	StrongRef<CircleShape> shape(newCircleShape(body, 0, 0, radius), Acquire::NORETAIN);
+	body->retain();
+	return body.get();
 }
 }
 
 
-CircleShape *Physics::newCircleShape(float x, float y, float radius)
+Body *Physics::newRectangleBody(World *world, Body::Type type, float x, float y, float w, float h, float angle)
 {
 {
-	b2CircleShape *s = new b2CircleShape();
-	s->m_p = Physics::scaleDown(b2Vec2(x, y));
-	s->m_radius = Physics::scaleDown(radius);
-	return new CircleShape(s);
+	StrongRef<Body> body(newBody(world, x, y, type), Acquire::NORETAIN);
+	StrongRef<PolygonShape> shape(newRectangleShape(body, 0, 0, w, h, angle), Acquire::NORETAIN);
+	body->retain();
+	return body.get();
 }
 }
 
 
-PolygonShape *Physics::newRectangleShape(float w, float h)
+Body *Physics::newPolygonBody(World *world, Body::Type type, const Vector2 *coords, int count)
 {
 {
-	return newRectangleShape(0, 0, w, h, 0);
+	Vector2 origin(0, 0);
+
+	for (int i = 0; i < count; i++)
+		origin += coords[i] / count;
+
+	std::vector<Vector2> localcoords;
+	for (int i = 0; i < count; i++)
+		localcoords.push_back(coords[i] - origin);
+
+	StrongRef<Body> body(newBody(world, origin.x, origin.y, type), Acquire::NORETAIN);
+	StrongRef<PolygonShape> shape(newPolygonShape(body, localcoords.data(), count), Acquire::NORETAIN);
+	body->retain();
+	return body.get();
+}
+
+Body *Physics::newEdgeBody(World *world, Body::Type type, float x1, float y1, float x2, float y2, bool oneSided)
+{
+	float wx = (x2 - x1) / 2.0f;
+	float wy = (y2 - y1) / 2.0f;
+	StrongRef<Body> body(newBody(world, wx, wy, type), Acquire::NORETAIN);
+	StrongRef<EdgeShape> shape(newEdgeShape(body, x1 - wx, y1 - wy, x2 - wx, y2 - wy, oneSided), Acquire::NORETAIN);
+	body->retain();
+	return body.get();
+}
+
+Body *Physics::newChainBody(World *world, Body::Type type, bool loop, const Vector2 *coords, int count)
+{
+	Vector2 origin(0, 0);
+
+	for (int i = 0; i < count; i++)
+		origin += coords[i] / count;
+
+	std::vector<Vector2> localcoords;
+	for (int i = 0; i < count; i++)
+		localcoords.push_back(coords[i] - origin);
+
+	StrongRef<Body> body(newBody(world, origin.x, origin.y, type), Acquire::NORETAIN);
+	StrongRef<ChainShape> shape(newChainShape(body, loop, localcoords.data(), count), Acquire::NORETAIN);
+	body->retain();
+	return body.get();
+}
+
+Shape *Physics::newAttachedShape(Body *body, Shape *prototype, float density)
+{
+	if (prototype->isValid())
+		throw love::Exception("The given Shape must not be part of the World.");
+
+	Shape *shape = nullptr;
+
+	switch (prototype->getType())
+	{
+	case Shape::SHAPE_CIRCLE:
+		shape = new CircleShape(body, *(b2CircleShape *) prototype->shape);
+		break;
+	case Shape::SHAPE_POLYGON:
+		shape = new PolygonShape(body, *(b2PolygonShape *) prototype->shape);
+		break;
+	case Shape::SHAPE_EDGE:
+		shape = new EdgeShape(body, *(b2EdgeShape *) prototype->shape);
+		break;
+	case Shape::SHAPE_CHAIN:
+		shape = new ChainShape(body, *(b2ChainShape *) prototype->shape);
+		break;
+	default:
+		throw love::Exception("Unknown shape type.");
+		break;
+	}
+
+	shape->setDensity(density);
+	body->resetMassData();
+
+	return shape;
 }
 }
 
 
-PolygonShape *Physics::newRectangleShape(float x, float y, float w, float h)
+CircleShape *Physics::newCircleShape(Body *body, float x, float y, float radius)
 {
 {
-	return newRectangleShape(x, y, w, h, 0);
+	b2CircleShape s;
+	s.m_p = Physics::scaleDown(b2Vec2(x, y));
+	s.m_radius = Physics::scaleDown(radius);
+	return new CircleShape(body, s);
 }
 }
 
 
-PolygonShape *Physics::newRectangleShape(float x, float y, float w, float h, float angle)
+PolygonShape *Physics::newRectangleShape(Body *body, float x, float y, float w, float h, float angle)
 {
 {
-	b2PolygonShape *s = new b2PolygonShape();
-	s->SetAsBox(Physics::scaleDown(w/2.0f), Physics::scaleDown(h/2.0f), Physics::scaleDown(b2Vec2(x, y)), angle);
-	return new PolygonShape(s);
+	b2PolygonShape s;
+	s.SetAsBox(Physics::scaleDown(w/2.0f), Physics::scaleDown(h/2.0f), Physics::scaleDown(b2Vec2(x, y)), angle);
+	return new PolygonShape(body, s);
 }
 }
 
 
-EdgeShape *Physics::newEdgeShape(float x1, float y1, float x2, float y2, bool oneSided)
+EdgeShape *Physics::newEdgeShape(Body *body, float x1, float y1, float x2, float y2, bool oneSided)
 {
 {
-	b2EdgeShape *s = new b2EdgeShape();
+	b2EdgeShape s;
 	if (oneSided)
 	if (oneSided)
 	{
 	{
 		b2Vec2 v1 = Physics::scaleDown(b2Vec2(x1, y1));
 		b2Vec2 v1 = Physics::scaleDown(b2Vec2(x1, y1));
 		b2Vec2 v2 = Physics::scaleDown(b2Vec2(x2, y2));
 		b2Vec2 v2 = Physics::scaleDown(b2Vec2(x2, y2));
-		s->SetOneSided(v1, v1, v2, v2);
+		s.SetOneSided(v1, v1, v2, v2);
 	}
 	}
 	else
 	else
 	{
 	{
-		s->SetTwoSided(Physics::scaleDown(b2Vec2(x1, y1)), Physics::scaleDown(b2Vec2(x2, y2)));
+		s.SetTwoSided(Physics::scaleDown(b2Vec2(x1, y1)), Physics::scaleDown(b2Vec2(x2, y2)));
 	}
 	}
-	return new EdgeShape(s);
+	return new EdgeShape(body, s);
 }
 }
 
 
-int Physics::newPolygonShape(lua_State *L)
+PolygonShape *Physics::newPolygonShape(Body *body, const Vector2 *coords, int count)
 {
 {
-	int argc = lua_gettop(L);
-
-	bool istable = lua_istable(L, 1);
-
-	if (istable)
-		argc = (int) luax_objlen(L, 1);
-
-	if (argc % 2 != 0)
-		return luaL_error(L, "Number of vertex components must be a multiple of two.");
-
 	// 3 to 8 (b2_maxPolygonVertices) vertices
 	// 3 to 8 (b2_maxPolygonVertices) vertices
-	int vcount = argc / 2;
-	if (vcount < 3)
-		return luaL_error(L, "Expected a minimum of 3 vertices, got %d.", vcount);
-	else if (vcount > b2_maxPolygonVertices)
-		return luaL_error(L, "Expected a maximum of %d vertices, got %d.", b2_maxPolygonVertices, vcount);
+	if (count < 3)
+		throw love::Exception("Expected a minimum of 3 vertices, got %d.", count);
+	else if (count > b2_maxPolygonVertices)
+		throw love::Exception("Expected a maximum of %d vertices, got %d.", b2_maxPolygonVertices, count);
 
 
 	b2Vec2 vecs[b2_maxPolygonVertices];
 	b2Vec2 vecs[b2_maxPolygonVertices];
 
 
-	if (istable)
-	{
-		for (int i = 0; i < vcount; i++)
-		{
-			lua_rawgeti(L, 1, 1 + i * 2);
-			lua_rawgeti(L, 1, 2 + i * 2);
-			float x = (float)luaL_checknumber(L, -2);
-			float y = (float)luaL_checknumber(L, -1);
-			vecs[i] = Physics::scaleDown(b2Vec2(x, y));
-			lua_pop(L, 2);
-		}
-	}
-	else
-	{
-		for (int i = 0; i < vcount; i++)
-		{
-			float x = (float)luaL_checknumber(L, 1 + i * 2);
-			float y = (float)luaL_checknumber(L, 2 + i * 2);
-			vecs[i] = Physics::scaleDown(b2Vec2(x, y));
-		}
-	}
+	for (int i = 0; i < count; i++)
+		vecs[i] = Physics::scaleDown(b2Vec2(coords[i].x, coords[i].y));
 
 
-	b2PolygonShape *s = new b2PolygonShape();
+	b2PolygonShape s;
 
 
-	try
-	{
-		s->Set(vecs, vcount);
-	}
-	catch (love::Exception &)
-	{
-		delete s;
-		throw;
-	}
+	s.Set(vecs, count);
 
 
-	PolygonShape *p = new PolygonShape(s);
-	luax_pushtype(L, p);
-	p->release();
-	return 1;
+	return new PolygonShape(body, s);
 }
 }
 
 
-int Physics::newChainShape(lua_State *L)
+ChainShape *Physics::newChainShape(Body *body, bool loop, const Vector2 *coords, int count)
 {
 {
-	int argc = lua_gettop(L)-1; // first argument is looping
-
-	bool istable = lua_istable(L, 2);
-
-	if (istable)
-		argc = (int) luax_objlen(L, 2);
+	std::vector<b2Vec2> vecs;
 
 
-	if (argc == 0 || argc % 2 != 0)
-		return luaL_error(L, "Number of vertex components must be a multiple of two.");
+	for (int i = 0; i < count; i++)
+		vecs.push_back(Physics::scaleDown(b2Vec2(coords[i].x, coords[i].y)));
 
 
-	int vcount = argc/2;
-	bool loop = luax_checkboolean(L, 1);
-	b2Vec2 *vecs = new b2Vec2[vcount];
+	b2ChainShape s;
 
 
-	if (istable)
-	{
-		for (int i = 0; i < vcount; i++)
-		{
-			lua_rawgeti(L, 2, 1 + i * 2);
-			lua_rawgeti(L, 2, 2 + i * 2);
-			float x = (float)lua_tonumber(L, -2);
-			float y = (float)lua_tonumber(L, -1);
-			vecs[i] = Physics::scaleDown(b2Vec2(x, y));
-			lua_pop(L, 2);
-		}
-	}
+	if (loop)
+		s.CreateLoop(vecs.data(), count);
 	else
 	else
-	{
-		for (int i = 0; i < vcount; i++)
-		{
-			float x = (float)luaL_checknumber(L, 2 + i * 2);
-			float y = (float)luaL_checknumber(L, 3 + i * 2);
-			vecs[i] = Physics::scaleDown(b2Vec2(x, y));
-		}
-	}
-
-	b2ChainShape *s = new b2ChainShape();
-
-	try
-	{
-		if (loop)
-			s->CreateLoop(vecs, vcount);
-		else
-			s->CreateChain(vecs, vcount, vecs[0], vecs[vcount-1]);
-	}
-	catch (love::Exception &)
-	{
-		delete[] vecs;
-		delete s;
-		throw;
-	}
+		s.CreateChain(vecs.data(), count, vecs[0], vecs[count - 1]);
 
 
-	delete[] vecs;
-
-	ChainShape *c = new ChainShape(s);
-	luax_pushtype(L, c);
-	c->release();
-	return 1;
+	return new ChainShape(body, s);
 }
 }
 
 
 DistanceJoint *Physics::newDistanceJoint(Body *body1, Body *body2, float x1, float y1, float x2, float y2, bool collideConnected)
 DistanceJoint *Physics::newDistanceJoint(Body *body1, Body *body2, float x1, float y1, float x2, float y2, bool collideConnected)
@@ -307,16 +299,10 @@ MotorJoint *Physics::newMotorJoint(Body *body1, Body *body2, float correctionFac
 	return new MotorJoint(body1, body2, correctionFactor, collideConnected);
 	return new MotorJoint(body1, body2, correctionFactor, collideConnected);
 }
 }
 
 
-
-Fixture *Physics::newFixture(Body *body, Shape *shape, float density)
-{
-	return new Fixture(body, shape, density);
-}
-
 int Physics::getDistance(lua_State *L)
 int Physics::getDistance(lua_State *L)
 {
 {
-	Fixture *fixtureA = luax_checktype<Fixture>(L, 1);
-	Fixture *fixtureB = luax_checktype<Fixture>(L, 2);
+	Shape *shapeA = luax_checktype<Shape>(L, 1);
+	Shape *shapeB = luax_checktype<Shape>(L, 2);
 	b2DistanceProxy pA, pB;
 	b2DistanceProxy pA, pB;
 	b2DistanceInput i;
 	b2DistanceInput i;
 	b2DistanceOutput o;
 	b2DistanceOutput o;
@@ -324,12 +310,15 @@ int Physics::getDistance(lua_State *L)
 	c.count = 0;
 	c.count = 0;
 
 
 	luax_catchexcept(L, [&]() {
 	luax_catchexcept(L, [&]() {
-		pA.Set(fixtureA->fixture->GetShape(), 0);
-		pB.Set(fixtureB->fixture->GetShape(), 0);
+		if (!shapeA->isValid() || !shapeB->isValid())
+			throw love::Exception("The given Shape is not active in the physics World.");
+
+		pA.Set(shapeA->fixture->GetShape(), 0);
+		pB.Set(shapeB->fixture->GetShape(), 0);
 		i.proxyA = pA;
 		i.proxyA = pA;
 		i.proxyB = pB;
 		i.proxyB = pB;
-		i.transformA = fixtureA->fixture->GetBody()->GetTransform();
-		i.transformB = fixtureB->fixture->GetBody()->GetTransform();
+		i.transformA = shapeA->fixture->GetBody()->GetTransform();
+		i.transformB = shapeB->fixture->GetBody()->GetTransform();
 		i.useRadii = true;
 		i.useRadii = true;
 		b2Distance(&o, &c, &i);
 		b2Distance(&o, &c, &i);
 	});
 	});

+ 23 - 36
src/modules/physics/box2d/Physics.h

@@ -23,10 +23,11 @@
 
 
 // LOVE
 // LOVE
 #include "common/Module.h"
 #include "common/Module.h"
+#include "common/Vector.h"
+
 #include "World.h"
 #include "World.h"
 #include "Contact.h"
 #include "Contact.h"
 #include "Body.h"
 #include "Body.h"
-#include "Fixture.h"
 #include "Shape.h"
 #include "Shape.h"
 #include "CircleShape.h"
 #include "CircleShape.h"
 #include "PolygonShape.h"
 #include "PolygonShape.h"
@@ -93,10 +94,18 @@ public:
 	Body *newBody(World *world, Body::Type type);
 	Body *newBody(World *world, Body::Type type);
 
 
 	/**
 	/**
-	 * Creates a new CircleShape at (0, 0).
-	 * @param radius The radius of the circle.
+	 * Convenience functions for creating a Body and Shape all in one call. The
+	 * body's world position is the center/average of the given coordinates,
+	 * and the shape is centered at the local origin.
 	 **/
 	 **/
-	CircleShape *newCircleShape(float radius);
+	Body *newCircleBody(World *world, Body::Type type, float x, float y, float radius);
+	Body *newRectangleBody(World *world, Body::Type type, float x, float y, float w, float h, float angle);
+	Body *newPolygonBody(World *world, Body::Type type, const Vector2 *coords, int count);
+	Body *newEdgeBody(World *world, Body::Type type, float x1, float y1, float x2, float y2, bool oneSided);
+	Body *newChainBody(World *world, Body::Type type, bool loop, const Vector2 *coords, int count);
+
+	// Necessary to support the deprecated newFixture API.
+	Shape *newAttachedShape(Body *body, Shape *prototype, float density);
 
 
 	/**
 	/**
 	 * Creates a new CircleShape at (x,y) in local coordinates.
 	 * Creates a new CircleShape at (x,y) in local coordinates.
@@ -104,25 +113,7 @@ public:
 	 * @param y The offset along the y-axis.
 	 * @param y The offset along the y-axis.
 	 * @param radius The radius of the circle.
 	 * @param radius The radius of the circle.
 	 **/
 	 **/
-	CircleShape *newCircleShape(float x, float y, float radius);
-
-	/**
-	 * Shorthand for creating rectangular PolygonShapes. The rectangle
-	 * will be created at the local origin.
-	 * @param w The width of the rectangle.
-	 * @param h The height of the rectangle.
-	 **/
-	PolygonShape *newRectangleShape(float w, float h);
-
-	/**
-	 * Shorthand for creating rectangular PolygonShapes. The rectangle
-	 * will be created at (x,y) in local coordinates.
-	 * @param x The offset along the x-axis.
-	 * @param y The offset along the y-axis.
-	 * @param w The width of the rectangle.
-	 * @param h The height of the rectangle.
-	 **/
-	PolygonShape *newRectangleShape(float x, float y, float w, float h);
+	CircleShape *newCircleShape(Body *body, float x, float y, float radius);
 
 
 	/**
 	/**
 	 * Shorthand for creating rectangular PolygonShapes. The rectangle
 	 * Shorthand for creating rectangular PolygonShapes. The rectangle
@@ -133,7 +124,7 @@ public:
 	 * @param h The height of the rectangle.
 	 * @param h The height of the rectangle.
 	 * @param angle The angle of the rectangle. (rad)
 	 * @param angle The angle of the rectangle. (rad)
 	 **/
 	 **/
-	PolygonShape *newRectangleShape(float x, float y, float w, float h, float angle);
+	PolygonShape *newRectangleShape(Body *body, float x, float y, float w, float h, float angle);
 
 
 	/**
 	/**
 	 * Creates a new EdgeShape. The edge will be created from
 	 * Creates a new EdgeShape. The edge will be created from
@@ -143,17 +134,17 @@ public:
 	 * @param x2 The x coordinate of the second point.
 	 * @param x2 The x coordinate of the second point.
 	 * @param y2 The y coordinate of the second point.
 	 * @param y2 The y coordinate of the second point.
 	 **/
 	 **/
-	EdgeShape *newEdgeShape(float x1, float y1, float x2, float y2, bool oneSided);
+	EdgeShape *newEdgeShape(Body *body, float x1, float y1, float x2, float y2, bool oneSided);
 
 
 	/**
 	/**
 	 * Creates a new PolygonShape from a variable number of vertices.
 	 * Creates a new PolygonShape from a variable number of vertices.
 	 **/
 	 **/
-	int newPolygonShape(lua_State *L);
+	PolygonShape *newPolygonShape(Body *body, const Vector2 *coords, int count);
 
 
 	/**
 	/**
 	 * Creates a new ChainShape from a variable number of vertices.
 	 * Creates a new ChainShape from a variable number of vertices.
 	 **/
 	 **/
-	int newChainShape(lua_State *L);
+	ChainShape *newChainShape(Body *body, bool loop, const Vector2 *coords, int count);
 
 
 	/**
 	/**
 	 * Creates a new DistanceJoint connecting body1 with body2.
 	 * Creates a new DistanceJoint connecting body1 with body2.
@@ -273,15 +264,6 @@ public:
 	MotorJoint *newMotorJoint(Body *body1, Body *body2);
 	MotorJoint *newMotorJoint(Body *body1, Body *body2);
 	MotorJoint *newMotorJoint(Body *body1, Body *body2, float correctionFactor, bool collideConnected);
 	MotorJoint *newMotorJoint(Body *body1, Body *body2, float correctionFactor, bool collideConnected);
 
 
-	/**
-	 * Creates a new Fixture attaching shape to body.
-	 * @param body The body to attach the Fixture to.
-	 * @param shape The shape to attach to the Fixture,
-	 * @param density The density of the Fixture.
-	 **/
-
-	Fixture *newFixture(Body *body, Shape *shape, float density);
-
 	/**
 	/**
 	 * Calculates the distance between two Fixtures.
 	 * Calculates the distance between two Fixtures.
 	 * @param fixtureA The first Fixture.
 	 * @param fixtureA The first Fixture.
@@ -403,10 +385,15 @@ public:
 	 **/
 	 **/
 	static void computeAngularFrequency(float &frequency, float &ratio, float stiffness, float damping, b2Body *bodyA, b2Body *bodyB);
 	static void computeAngularFrequency(float &frequency, float &ratio, float stiffness, float damping, b2Body *bodyA, b2Body *bodyB);
 
 
+	b2BlockAllocator *getBlockAllocator() { return &blockAllocator; }
+
 private:
 private:
 
 
 	// The length of one meter in pixels.
 	// The length of one meter in pixels.
 	static float meter;
 	static float meter;
+
+	b2BlockAllocator blockAllocator;
+
 }; // Physics
 }; // Physics
 
 
 } // box2d
 } // box2d

+ 4 - 2
src/modules/physics/box2d/PolygonShape.cpp

@@ -34,8 +34,8 @@ namespace box2d
 
 
 love::Type PolygonShape::type("PolygonShape", &Shape::type);
 love::Type PolygonShape::type("PolygonShape", &Shape::type);
 
 
-PolygonShape::PolygonShape(b2PolygonShape *p, bool own)
-	: Shape(p, own)
+PolygonShape::PolygonShape(Body *body, const b2PolygonShape &p)
+	: Shape(body, p)
 {
 {
 }
 }
 
 
@@ -45,6 +45,7 @@ PolygonShape::~PolygonShape()
 
 
 int PolygonShape::getPoints(lua_State *L)
 int PolygonShape::getPoints(lua_State *L)
 {
 {
+	throwIfShapeNotValid();
 	love::luax_assert_argc(L, 0);
 	love::luax_assert_argc(L, 0);
 	b2PolygonShape *p = (b2PolygonShape *)shape;
 	b2PolygonShape *p = (b2PolygonShape *)shape;
 	int count = p->m_count;
 	int count = p->m_count;
@@ -59,6 +60,7 @@ int PolygonShape::getPoints(lua_State *L)
 
 
 bool PolygonShape::validate() const
 bool PolygonShape::validate() const
 {
 {
+	throwIfShapeNotValid();
 	b2PolygonShape *p = (b2PolygonShape *)shape;
 	b2PolygonShape *p = (b2PolygonShape *)shape;
 	return p->Validate();
 	return p->Validate();
 }
 }

+ 1 - 1
src/modules/physics/box2d/PolygonShape.h

@@ -48,7 +48,7 @@ public:
 	 * Create a new PolygonShape from a Box2D polygon definition.
 	 * Create a new PolygonShape from a Box2D polygon definition.
 	 * @param p The polygon definition.
 	 * @param p The polygon definition.
 	 **/
 	 **/
-	PolygonShape(b2PolygonShape *p, bool own = true);
+	PolygonShape(Body *body, const b2PolygonShape &p);
 
 
 	virtual ~PolygonShape();
 	virtual ~PolygonShape();
 
 

+ 376 - 28
src/modules/physics/box2d/Shape.cpp

@@ -35,54 +35,357 @@ namespace physics
 namespace box2d
 namespace box2d
 {
 {
 
 
-Shape::Shape()
+Shape::Shape(Body *body, const b2Shape &shape)
 	: shape(nullptr)
 	: shape(nullptr)
 	, own(false)
 	, own(false)
+	, shapeType(SHAPE_INVALID)
+	, body(body)
+	, fixture(nullptr)
 {
 {
-}
+	if (body)
+	{
+		b2FixtureDef def;
+		def.shape = &shape;
+		def.userData.pointer = (uintptr_t)this;
 
 
-Shape::Shape(b2Shape *shape, bool own)
-	: shape(shape)
-	, own(own)
-{
+		// 0 density stops CreateFixture from calling b2Body::ResetMassData().
+		def.density = body->hasCustomMassData() ? 0.0f : 1.0f;
+
+		fixture = body->body->CreateFixture(&def);
+		this->shape = fixture->GetShape();
+
+		if (body->hasCustomMassData())
+			setDensity(1.0f);
+
+		retain(); // Shape::destroy does the release().
+	}
+	else
+	{
+		// Path to support deprecated APIs.
+		auto physics = Module::getInstance<Physics>(Module::M_PHYSICS);
+		this->shape = shape.Clone(physics->getBlockAllocator());
+		own = true;
+	}
+
+	switch (this->shape->GetType())
+	{
+	case b2Shape::e_circle:
+		shapeType = SHAPE_CIRCLE;
+		break;
+	case b2Shape::e_polygon:
+		shapeType = SHAPE_POLYGON;
+		break;
+	case b2Shape::e_edge:
+		shapeType = SHAPE_EDGE;
+		break;
+	case b2Shape::e_chain:
+		shapeType = SHAPE_CHAIN;
+		break;
+	default:
+		shapeType = SHAPE_INVALID;
+		break;
+	}
 }
 }
 
 
 Shape::~Shape()
 Shape::~Shape()
 {
 {
 	if (shape && own)
 	if (shape && own)
-		delete shape;
-	shape = nullptr;
+	{
+		auto physics = Module::getInstance<Physics>(Module::M_PHYSICS);
+		auto allocator = physics->getBlockAllocator();
+
+		// Taken from b2Fixture::Destroy. Not very pretty...
+		switch (shapeType)
+		{
+		case SHAPE_CIRCLE:
+		{
+			b2CircleShape *s = (b2CircleShape*)shape;
+			s->~b2CircleShape();
+			allocator->Free(s, sizeof(b2CircleShape));
+			break;
+		}
+		case SHAPE_EDGE:
+		{
+			b2EdgeShape *s = (b2EdgeShape*)shape;
+			s->~b2EdgeShape();
+			allocator->Free(s, sizeof(b2EdgeShape));
+			break;
+		}
+		case SHAPE_POLYGON:
+		{
+			b2PolygonShape *s = (b2PolygonShape*)shape;
+			s->~b2PolygonShape();
+			allocator->Free(s, sizeof(b2PolygonShape));
+			break;
+		}
+		case SHAPE_CHAIN:
+		{
+			b2ChainShape *s = (b2ChainShape*)shape;
+			s->~b2ChainShape();
+			allocator->Free(s, sizeof(b2ChainShape));
+			break;
+		}
+		default:
+			break;
+		}
+	}
+
+	if (ref)
+		delete ref;
 }
 }
 
 
-Shape::Type Shape::getType() const
+void Shape::destroy(bool implicit)
 {
 {
-	switch (shape->GetType())
+	if (fixture == nullptr)
+		return;
+
+	if (body->world->world->IsLocked())
 	{
 	{
-	case b2Shape::e_circle:
-		return SHAPE_CIRCLE;
-	case b2Shape::e_polygon:
-		return SHAPE_POLYGON;
-	case b2Shape::e_edge:
-		return SHAPE_EDGE;
-	case b2Shape::e_chain:
-		return SHAPE_CHAIN;
-	default:
-		return SHAPE_INVALID;
+		// Called during time step. Save reference for destruction afterwards.
+		this->retain();
+		body->world->destructShapes.push_back(this);
+		return;
 	}
 	}
+
+	if (!implicit && fixture != nullptr)
+		body->body->DestroyFixture(fixture);
+
+	fixture = nullptr;
+	shape = nullptr;
+	body = nullptr;
+
+	// Remove userdata reference to avoid it sticking around after GC
+	if (ref)
+		ref->unref();
+
+	// Box2D fixture destroyed. Release its reference to the love Shape.
+	release();
+}
+
+void Shape::throwIfFixtureNotValid() const
+{
+	if (fixture == nullptr)
+		throw love::Exception("Shape must be active in the physics World to use this method.");
+}
+
+void Shape::throwIfShapeNotValid() const
+{
+	if (shape == nullptr)
+		throw love::Exception("Cannot call this method on a destroyed Shape.");
+}
+
+Shape::Type Shape::getType() const
+{
+	return shapeType;
+}
+
+void Shape::setFriction(float friction)
+{
+	throwIfFixtureNotValid();
+	fixture->SetFriction(friction);
+}
+
+void Shape::setRestitution(float restitution)
+{
+	throwIfFixtureNotValid();
+	fixture->SetRestitution(restitution);
+}
+
+void Shape::setDensity(float density)
+{
+	throwIfFixtureNotValid();
+	fixture->SetDensity(density);
+	if (!body->hasCustomMassData())
+		body->resetMassData();
+}
+
+void Shape::setSensor(bool sensor)
+{
+	throwIfFixtureNotValid();
+	fixture->SetSensor(sensor);
+}
+
+float Shape::getFriction() const
+{
+	throwIfFixtureNotValid();
+	return fixture->GetFriction();
+}
+
+float Shape::getRestitution() const
+{
+	throwIfFixtureNotValid();
+	return fixture->GetRestitution();
+}
+
+float Shape::getDensity() const
+{
+	throwIfFixtureNotValid();
+	return fixture->GetDensity();
+}
+
+bool Shape::isSensor() const
+{
+	throwIfFixtureNotValid();
+	return fixture->IsSensor();
+}
+
+Body *Shape::getBody() const
+{
+	return body;
 }
 }
 
 
 float Shape::getRadius() const
 float Shape::getRadius() const
 {
 {
+	throwIfShapeNotValid();
 	return Physics::scaleUp(shape->m_radius);
 	return Physics::scaleUp(shape->m_radius);
 }
 }
 
 
 int Shape::getChildCount() const
 int Shape::getChildCount() const
 {
 {
+	throwIfShapeNotValid();
 	return shape->GetChildCount();
 	return shape->GetChildCount();
 }
 }
 
 
+void Shape::setFilterData(int *v)
+{
+	throwIfFixtureNotValid();
+	b2Filter f;
+	f.categoryBits = (uint16) v[0];
+	f.maskBits = (uint16) v[1];
+	f.groupIndex = (int16) v[2];
+	fixture->SetFilterData(f);
+}
+
+void Shape::getFilterData(int *v)
+{
+	throwIfFixtureNotValid();
+	b2Filter f = fixture->GetFilterData();
+	v[0] = (int) f.categoryBits;
+	v[1] = (int) f.maskBits;
+	v[2] = (int) f.groupIndex;
+}
+
+int Shape::setCategory(lua_State *L)
+{
+	throwIfFixtureNotValid();
+	b2Filter f = fixture->GetFilterData();
+	f.categoryBits = (uint16)getBits(L);
+	fixture->SetFilterData(f);
+	return 0;
+}
+
+int Shape::setMask(lua_State *L)
+{
+	throwIfFixtureNotValid();
+	b2Filter f = fixture->GetFilterData();
+	f.maskBits = ~(uint16)getBits(L);
+	fixture->SetFilterData(f);
+	return 0;
+}
+
+void Shape::setGroupIndex(int index)
+{
+	throwIfFixtureNotValid();
+	b2Filter f = fixture->GetFilterData();
+	f.groupIndex = (uint16)index;
+	fixture->SetFilterData(f);
+}
+
+int Shape::getGroupIndex() const
+{
+	throwIfFixtureNotValid();
+	b2Filter f = fixture->GetFilterData();
+	return f.groupIndex;
+}
+
+int Shape::getCategory(lua_State *L)
+{
+	throwIfFixtureNotValid();
+	return pushBits(L, fixture->GetFilterData().categoryBits);
+}
+
+int Shape::getMask(lua_State *L)
+{
+	throwIfFixtureNotValid();
+	return pushBits(L, ~(fixture->GetFilterData().maskBits));
+}
+
+uint16 Shape::getBits(lua_State *L)
+{
+	// Get number of args.
+	bool istable = lua_istable(L, 1);
+	int argc = istable ? (int) luax_objlen(L, 1) : lua_gettop(L);
+
+	// The new bitset.
+	std::bitset<16> b;
+
+	for (int i = 1; i <= argc; i++)
+	{
+		size_t bpos = 0;
+
+		if (istable)
+		{
+			lua_rawgeti(L, 1, i);
+			bpos = (size_t) (lua_tointeger(L, -1) - 1);
+			lua_pop(L, 1);
+		}
+		else
+			bpos = (size_t) (lua_tointeger(L, i) - 1);
+
+		if (bpos >= 16)
+			luaL_error(L, "Values must be in range 1-16.");
+
+		b.set(bpos, true);
+	}
+
+	return (uint16)b.to_ulong();
+}
+
+int Shape::pushBits(lua_State *L, uint16 bits)
+{
+	// Create a bitset.
+	std::bitset<16> b((int)bits);
+
+	// Push all set bits.
+	for (int i = 0; i<16; i++)
+		if (b.test(i))
+			lua_pushinteger(L, i+1);
+
+	// Count number of set bits.
+	return (int)b.count();
+}
+
+int Shape::setUserData(lua_State *L)
+{
+	love::luax_assert_argc(L, 1, 1);
+
+	if(!ref)
+		ref = new Reference();
+
+	ref->ref(L);
+
+	return 0;
+}
+
+int Shape::getUserData(lua_State *L)
+{
+	if (ref != nullptr)
+		ref->push(L);
+	else
+		lua_pushnil(L);
+
+	return 1;
+}
+
+bool Shape::testPoint(float x, float y) const
+{
+	throwIfFixtureNotValid();
+	return fixture->TestPoint(Physics::scaleDown(b2Vec2(x, y)));
+}
+
 bool Shape::testPoint(float x, float y, float r, float px, float py) const
 bool Shape::testPoint(float x, float y, float r, float px, float py) const
 {
 {
+	throwIfShapeNotValid();
 	b2Vec2 point(px, py);
 	b2Vec2 point(px, py);
 	b2Transform transform(Physics::scaleDown(b2Vec2(x, y)), b2Rot(r));
 	b2Transform transform(Physics::scaleDown(b2Vec2(x, y)), b2Rot(r));
 	return shape->TestPoint(transform, Physics::scaleDown(point));
 	return shape->TestPoint(transform, Physics::scaleDown(point));
@@ -95,18 +398,34 @@ int Shape::rayCast(lua_State *L) const
 	float p2x = Physics::scaleDown((float)luaL_checknumber(L, 3));
 	float p2x = Physics::scaleDown((float)luaL_checknumber(L, 3));
 	float p2y = Physics::scaleDown((float)luaL_checknumber(L, 4));
 	float p2y = Physics::scaleDown((float)luaL_checknumber(L, 4));
 	float maxFraction = (float)luaL_checknumber(L, 5);
 	float maxFraction = (float)luaL_checknumber(L, 5);
-	float x = Physics::scaleDown((float)luaL_checknumber(L, 6));
-	float y = Physics::scaleDown((float)luaL_checknumber(L, 7));
-	float r = (float)luaL_checknumber(L, 8);
-	int childIndex = (int) luaL_optinteger(L, 9, 1) - 1; // Convert from 1-based index
+
 	b2RayCastInput input;
 	b2RayCastInput input;
+	b2RayCastOutput output;
 	input.p1.Set(p1x, p1y);
 	input.p1.Set(p1x, p1y);
 	input.p2.Set(p2x, p2y);
 	input.p2.Set(p2x, p2y);
 	input.maxFraction = maxFraction;
 	input.maxFraction = maxFraction;
-	b2Transform transform(b2Vec2(x, y), b2Rot(r));
-	b2RayCastOutput output;
-	if (!shape->RayCast(&output, input, transform, childIndex))
-		return 0; // No hit.
+
+	if (lua_isnoneornil(L, 7))
+	{
+		throwIfFixtureNotValid();
+		int childIndex = (int) luaL_optinteger(L, 6, 1) - 1; // Convert from 1-based index
+		if (!fixture->RayCast(&output, input, childIndex))
+			return 0; // Nothing hit.
+	}
+	else
+	{
+		throwIfShapeNotValid();
+		float x = Physics::scaleDown((float)luaL_checknumber(L, 6));
+		float y = Physics::scaleDown((float)luaL_checknumber(L, 7));
+		float r = (float)luaL_checknumber(L, 8);
+		int childIndex = (int) luaL_optinteger(L, 9, 1) - 1; // Convert from 1-based index
+	
+		b2Transform transform(b2Vec2(x, y), b2Rot(r));
+		
+		if (!shape->RayCast(&output, input, transform, childIndex))
+			return 0; // No hit.
+	}
+
 	lua_pushnumber(L, output.normal.x);
 	lua_pushnumber(L, output.normal.x);
 	lua_pushnumber(L, output.normal.y);
 	lua_pushnumber(L, output.normal.y);
 	lua_pushnumber(L, output.fraction);
 	lua_pushnumber(L, output.fraction);
@@ -115,6 +434,7 @@ int Shape::rayCast(lua_State *L) const
 
 
 int Shape::computeAABB(lua_State *L) const
 int Shape::computeAABB(lua_State *L) const
 {
 {
+	throwIfShapeNotValid();
 	float x = Physics::scaleDown((float)luaL_checknumber(L, 1));
 	float x = Physics::scaleDown((float)luaL_checknumber(L, 1));
 	float y = Physics::scaleDown((float)luaL_checknumber(L, 2));
 	float y = Physics::scaleDown((float)luaL_checknumber(L, 2));
 	float r = (float)luaL_checknumber(L, 3);
 	float r = (float)luaL_checknumber(L, 3);
@@ -132,6 +452,7 @@ int Shape::computeAABB(lua_State *L) const
 
 
 int Shape::computeMass(lua_State *L) const
 int Shape::computeMass(lua_State *L) const
 {
 {
+	throwIfShapeNotValid();
 	float density = (float)luaL_checknumber(L, 1);
 	float density = (float)luaL_checknumber(L, 1);
 	b2MassData data;
 	b2MassData data;
 	shape->ComputeMass(&data, density);
 	shape->ComputeMass(&data, density);
@@ -143,6 +464,33 @@ int Shape::computeMass(lua_State *L) const
 	return 4;
 	return 4;
 }
 }
 
 
+int Shape::getBoundingBox(lua_State *L) const
+{
+	throwIfFixtureNotValid();
+	int childIndex = (int) luaL_optinteger(L, 1, 1) - 1; // Convert from 1-based index
+	b2AABB box;
+	luax_catchexcept(L, [&]() { box = fixture->GetAABB(childIndex); });
+	box = Physics::scaleUp(box);
+	lua_pushnumber(L, box.lowerBound.x);
+	lua_pushnumber(L, box.lowerBound.y);
+	lua_pushnumber(L, box.upperBound.x);
+	lua_pushnumber(L, box.upperBound.y);
+	return 4;
+}
+
+int Shape::getMassData(lua_State *L) const
+{
+	throwIfFixtureNotValid();
+	b2MassData data;
+	fixture->GetMassData(&data);
+	b2Vec2 center = Physics::scaleUp(data.center);
+	lua_pushnumber(L, center.x);
+	lua_pushnumber(L, center.y);
+	lua_pushnumber(L, data.mass);
+	lua_pushnumber(L, data.I);
+	return 4;
+}
+
 } // box2d
 } // box2d
 } // physics
 } // physics
 } // love
 } // love

+ 127 - 5
src/modules/physics/box2d/Shape.h

@@ -24,6 +24,7 @@
 // LOVE
 // LOVE
 #include "physics/Shape.h"
 #include "physics/Shape.h"
 #include "physics/box2d/Body.h"
 #include "physics/box2d/Body.h"
+#include "common/Reference.h"
 
 
 // Box2D
 // Box2D
 #include <box2d/Box2D.h>
 #include <box2d/Box2D.h>
@@ -45,34 +46,155 @@ class Shape : public love::physics::Shape
 {
 {
 public:
 public:
 
 
-	friend class Fixture;
+	friend class Physics;
 
 
 	/**
 	/**
 	 * Creates a Shape.
 	 * Creates a Shape.
 	 **/
 	 **/
-	Shape();
-	Shape(b2Shape *shape, bool own = true);
+	Shape(Body *body, const b2Shape &shape);
 
 
 	virtual ~Shape();
 	virtual ~Shape();
 
 
+	void destroy(bool implicit = false);
+
+	/**
+	 * Returns true if the shape is active in a Box2D world.
+	 **/
+	bool isValid() const { return fixture != nullptr; }
+	
+	/**
+	 * Returns true if the shape has not been destroyed.
+	 **/
+	bool isShapeValid() const { return shape != nullptr; }
+
+	/**
+	 * Checks whether this Shape acts as a sensor.
+	 **/
+	bool isSensor() const;
+
+	/**
+	 * Set whether this Shape should be a sensor or not.
+	 **/
+	void setSensor(bool sensor);
+
+	/**
+	 * Gets the Body this Shape is attached to.
+	 **/
+	Body *getBody() const;
+
+	/**
+	 * Sets the filter data. An integer array is used even though the
+	 * first two elements are unsigned shorts. The elements are:
+	 * category (16-bits), mask (16-bits) and group (32-bits/int).
+	 **/
+	void setFilterData(int *v);
+
+	/**
+	 * Gets the filter data. An integer array is used even though the
+	 * first two elements are unsigned shorts. The elements are:
+	 * category (16-bits), mask (16-bits) and group (32-bits/int).
+	 **/
+	void getFilterData(int *v);
+
+	/**
+	 * This function stores an in-C reference to
+	 * arbitrary Lua data in the Shape object.
+	 **/
+	int setUserData(lua_State *L);
+
+	/**
+	 * Gets the data set with setUserData. If no
+	 * data is set, nil is returned.
+	 **/
+	int getUserData(lua_State *L);
+
+	/**
+	 * Sets the friction of the Shape.
+	 **/
+	void setFriction(float friction);
+
+	/**
+	 * Sets the restitution for the Shape.
+	 **/
+	void setRestitution(float restitution);
+
+	/**
+	 * Sets the density of the Shape.
+	 **/
+	void setDensity(float density);
+
+	/**
+	 * Gets the friction of the Shape.
+	 **/
+	float getFriction() const;
+
+	/**
+	 * Gets the restitution of the Shape.
+	 **/
+	float getRestitution() const;
+
+	/**
+	 * Gets the density of the Shape.
+	 **/
+	float getDensity() const;
+
+	/**
+	 * Checks if a point is inside the Shape.
+	 **/
+	bool testPoint(float x, float y) const;
+	bool testPoint(float x, float y, float r, float px, float py) const;
+
 	/**
 	/**
 	 * Gets the type of Shape. Useful for
 	 * Gets the type of Shape. Useful for
 	 * debug drawing.
 	 * debug drawing.
 	 **/
 	 **/
 	Type getType() const;
 	Type getType() const;
+
 	float getRadius() const;
 	float getRadius() const;
 	int getChildCount() const;
 	int getChildCount() const;
-	bool testPoint(float x, float y, float r, float px, float py) const;
 	int rayCast(lua_State *L) const;
 	int rayCast(lua_State *L) const;
 	int computeAABB(lua_State *L) const;
 	int computeAABB(lua_State *L) const;
 	int computeMass(lua_State *L) const;
 	int computeMass(lua_State *L) const;
 
 
+	void setGroupIndex(int index);
+	int getGroupIndex() const;
+
+	int setCategory(lua_State *L);
+	int setMask(lua_State *L);
+	int getCategory(lua_State *L);
+	int getMask(lua_State *L);
+	uint16 getBits(lua_State *L);
+	int pushBits(lua_State *L, uint16 bits);
+
+	/**
+	 * Gets the bounding box for this Shape.
+	 **/
+	int getBoundingBox(lua_State *L) const;
+
+	/**
+	 * Gets the mass data for this Shape.
+	 * This operation may be expensive.
+	 **/
+	int getMassData(lua_State *L) const;
+
+	void throwIfFixtureNotValid() const;
+	void throwIfShapeNotValid() const;
+
 protected:
 protected:
 
 
 	// The Box2D shape.
 	// The Box2D shape.
 	b2Shape *shape;
 	b2Shape *shape;
 	bool own;
 	bool own;
-};
+
+	Shape::Type shapeType;
+
+	Body *body;
+	b2Fixture *fixture;
+
+	// Reference to arbitrary data.
+	Reference* ref = nullptr;
+
+}; // Shape
 
 
 } // box2d
 } // box2d
 } // physics
 } // physics

+ 54 - 56
src/modules/physics/box2d/World.cpp

@@ -20,7 +20,6 @@
 
 
 #include "World.h"
 #include "World.h"
 
 
-#include "Fixture.h"
 #include "Shape.h"
 #include "Shape.h"
 #include "Contact.h"
 #include "Contact.h"
 #include "Physics.h"
 #include "Physics.h"
@@ -28,6 +27,7 @@
 
 
 // Needed for World::getJoints. It should be moved to wrapper code...
 // Needed for World::getJoints. It should be moved to wrapper code...
 #include "wrap_Joint.h"
 #include "wrap_Joint.h"
+#include "wrap_Shape.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -58,22 +58,22 @@ void World::ContactCallback::process(b2Contact *contact, const b2ContactImpulse
 	{
 	{
 		ref->push(L);
 		ref->push(L);
 
 
-		// Push first fixture.
+		// Push first shape.
 		{
 		{
-			Fixture *a = (Fixture *)(contact->GetFixtureA()->GetUserData().pointer);
+			Shape *a = (Shape *)(contact->GetFixtureA()->GetUserData().pointer);
 			if (a != nullptr)
 			if (a != nullptr)
-				luax_pushtype(L, a);
+				luax_pushshape(L, a);
 			else
 			else
-				throw love::Exception("A fixture has escaped Memoizer!");
+				throw love::Exception("A Shape has escaped Memoizer!");
 		}
 		}
 
 
-		// Push second fixture.
+		// Push second shape.
 		{
 		{
-			Fixture *b = (Fixture *)(contact->GetFixtureB()->GetUserData().pointer);
+			Shape *b = (Shape *)(contact->GetFixtureB()->GetUserData().pointer);
 			if (b != nullptr)
 			if (b != nullptr)
-				luax_pushtype(L, b);
+				luax_pushshape(L, b);
 			else
 			else
-				throw love::Exception("A fixture has escaped Memoizer!");
+				throw love::Exception("A Shape has escaped Memoizer!");
 		}
 		}
 
 
 		Contact *cobj = (Contact *)world->findObject(contact);
 		Contact *cobj = (Contact *)world->findObject(contact);
@@ -112,32 +112,17 @@ World::ContactFilter::~ContactFilter()
 		delete ref;
 		delete ref;
 }
 }
 
 
-bool World::ContactFilter::process(Fixture *a, Fixture *b)
+bool World::ContactFilter::process(Shape *a, Shape *b)
 {
 {
-	// Handle masks, reimplemented from the manual
-	int filterA[3], filterB[3];
-	// [0] categoryBits
-	// [1] maskBits
-	// [2] groupIndex
-	a->getFilterData(filterA);
-	b->getFilterData(filterB);
-
-	if (filterA[2] != 0 && // 0 is the default group, so this does not count
-		filterA[2] == filterB[2]) // if they are in the same group
-		return filterA[2] > 0; // Negative indexes mean you don't collide
-
-	if ((filterA[1] & filterB[0]) == 0 ||
-		(filterB[1] & filterA[0]) == 0)
-		return false; // A and B aren't set to collide
-
 	if (ref != nullptr && L != nullptr)
 	if (ref != nullptr && L != nullptr)
 	{
 	{
 		ref->push(L);
 		ref->push(L);
-		luax_pushtype(L, a);
-		luax_pushtype(L, b);
+		luax_pushshape(L, a);
+		luax_pushshape(L, b);
 		lua_call(L, 2, 1);
 		lua_call(L, 2, 1);
 		return luax_toboolean(L, -1);
 		return luax_toboolean(L, -1);
 	}
 	}
+
 	return true;
 	return true;
 }
 }
 
 
@@ -159,10 +144,10 @@ bool World::QueryCallback::ReportFixture(b2Fixture *fixture)
 	if (L != nullptr)
 	if (L != nullptr)
 	{
 	{
 		lua_pushvalue(L, funcidx);
 		lua_pushvalue(L, funcidx);
-		Fixture *f = (Fixture *)(fixture->GetUserData().pointer);
+		Shape *f = (Shape *)(fixture->GetUserData().pointer);
 		if (!f)
 		if (!f)
-			throw love::Exception("A fixture has escaped Memoizer!");
-		luax_pushtype(L, f);
+			throw love::Exception("A Shape has escaped Memoizer!");
+		luax_pushshape(L, f);
 		for (int i = 1; i <= userargs; i++)
 		for (int i = 1; i <= userargs; i++)
 			lua_pushvalue(L, funcidx + i);
 			lua_pushvalue(L, funcidx + i);
 		lua_call(L, 1 + userargs, 1);
 		lua_call(L, 1 + userargs, 1);
@@ -191,10 +176,10 @@ bool World::CollectCallback::ReportFixture(b2Fixture *f)
 	if (categoryMask != 0xFFFF && (categoryMask & f->GetFilterData().categoryBits) == 0)
 	if (categoryMask != 0xFFFF && (categoryMask & f->GetFilterData().categoryBits) == 0)
 		return true;
 		return true;
 
 
-	Fixture *fixture = (Fixture *)(f->GetUserData().pointer);
-	if (!fixture)
-		throw love::Exception("A fixture has escaped Memoizer!");
-	luax_pushtype(L, fixture);
+	Shape *shape = (Shape *)(f->GetUserData().pointer);
+	if (!shape)
+		throw love::Exception("A Shape has escaped Memoizer!");
+	luax_pushshape(L, shape);
 	lua_rawseti(L, -2, i);
 	lua_rawseti(L, -2, i);
 	i++;
 	i++;
 	return true;
 	return true;
@@ -218,10 +203,10 @@ float World::RayCastCallback::ReportFixture(b2Fixture *fixture, const b2Vec2 &po
 	if (L != nullptr)
 	if (L != nullptr)
 	{
 	{
 		lua_pushvalue(L, funcidx);
 		lua_pushvalue(L, funcidx);
-		Fixture *f = (Fixture *)(fixture->GetUserData().pointer);
+		Shape *f = (Shape *)(fixture->GetUserData().pointer);
 		if (!f)
 		if (!f)
-			throw love::Exception("A fixture has escaped Memoizer!");
-		luax_pushtype(L, f);
+			throw love::Exception("A Shape has escaped Memoizer!");
+		luax_pushshape(L, f);
 		b2Vec2 scaledPoint = Physics::scaleUp(point);
 		b2Vec2 scaledPoint = Physics::scaleUp(point);
 		lua_pushnumber(L, scaledPoint.x);
 		lua_pushnumber(L, scaledPoint.x);
 		lua_pushnumber(L, scaledPoint.y);
 		lua_pushnumber(L, scaledPoint.y);
@@ -267,9 +252,9 @@ float World::RayCastOneCallback::ReportFixture(b2Fixture *fixture, const b2Vec2
 
 
 void World::SayGoodbye(b2Fixture *fixture)
 void World::SayGoodbye(b2Fixture *fixture)
 {
 {
-	Fixture *f = (Fixture *)(fixture->GetUserData().pointer);
+	Shape *s = (Shape *)(fixture->GetUserData().pointer);
 	// Hint implicit destruction with true.
 	// Hint implicit destruction with true.
-	if (f) f->destroy(true);
+	if (s) s->destroy(true);
 }
 }
 
 
 void World::SayGoodbye(b2Joint *joint)
 void World::SayGoodbye(b2Joint *joint)
@@ -336,11 +321,11 @@ void World::update(float dt, int velocityIterations, int positionIterations)
 		// Release for reference in vector.
 		// Release for reference in vector.
 		b->release();
 		b->release();
 	}
 	}
-	for (Fixture *f : destructFixtures)
+	for (Shape *s : destructShapes)
 	{
 	{
-		if (f->isValid()) f->destroy();
+		if (s->isValid()) s->destroy();
 		// Release for reference in vector.
 		// Release for reference in vector.
-		f->release();
+		s->release();
 	}
 	}
 	for (Joint *j : destructJoints)
 	for (Joint *j : destructJoints)
 	{
 	{
@@ -349,7 +334,7 @@ void World::update(float dt, int velocityIterations, int positionIterations)
 		j->release();
 		j->release();
 	}
 	}
 	destructBodies.clear();
 	destructBodies.clear();
-	destructFixtures.clear();
+	destructShapes.clear();
 	destructJoints.clear();
 	destructJoints.clear();
 
 
 	if (destructWorld)
 	if (destructWorld)
@@ -384,11 +369,24 @@ void World::PostSolve(b2Contact *contact, const b2ContactImpulse *impulse)
 
 
 bool World::ShouldCollide(b2Fixture *fixtureA, b2Fixture *fixtureB)
 bool World::ShouldCollide(b2Fixture *fixtureA, b2Fixture *fixtureB)
 {
 {
-	// Fixtures should be memoized, if we created them
-	Fixture *a = (Fixture *)(fixtureA->GetUserData().pointer);
-	Fixture *b = (Fixture *)(fixtureB->GetUserData().pointer);
+	const b2Filter &filterA = fixtureA->GetFilterData();
+	const b2Filter &filterB = fixtureB->GetFilterData();
+
+	// From b2_world_callbacks.cpp
+	// 0 is the default group index. If they're customized to be the same group,
+	// allow collisions if it's positive and disallow if it's negative.
+	if (filterA.groupIndex != 0 && filterA.groupIndex == filterB.groupIndex)
+		return filterA.groupIndex > 0;
+
+	if ((filterA.maskBits & filterB.categoryBits) == 0 || (filterA.categoryBits & filterB.maskBits) == 0)
+		return false;
+
+	// Shapes should be memoized, if we created them
+	Shape *a = (Shape *)(fixtureA->GetUserData().pointer);
+	Shape *b = (Shape *)(fixtureB->GetUserData().pointer);
 	if (!a || !b)
 	if (!a || !b)
-		throw love::Exception("A fixture has escaped Memoizer!");
+		throw love::Exception("A Shape has escaped Memoizer!");
+
 	return filter.process(a, b);
 	return filter.process(a, b);
 }
 }
 
 
@@ -597,7 +595,7 @@ b2Body *World::getGroundBody() const
 	return groundBody;
 	return groundBody;
 }
 }
 
 
-int World::queryFixturesInArea(lua_State *L)
+int World::queryShapesInArea(lua_State *L)
 {
 {
 	b2AABB box;
 	b2AABB box;
 	float lx = (float)luaL_checknumber(L, 1);
 	float lx = (float)luaL_checknumber(L, 1);
@@ -612,7 +610,7 @@ int World::queryFixturesInArea(lua_State *L)
 	return 0;
 	return 0;
 }
 }
 
 
-int World::getFixturesInArea(lua_State *L)
+int World::getShapesInArea(lua_State *L)
 {
 {
 	float lx = (float)luaL_checknumber(L, 1);
 	float lx = (float)luaL_checknumber(L, 1);
 	float ly = (float)luaL_checknumber(L, 2);
 	float ly = (float)luaL_checknumber(L, 2);
@@ -654,10 +652,10 @@ int World::rayCastAny(lua_State *L)
 	world->RayCast(&raycast, v1, v2);
 	world->RayCast(&raycast, v1, v2);
 	if (raycast.hitFixture)
 	if (raycast.hitFixture)
 	{
 	{
-		Fixture *f = (Fixture *)(raycast.hitFixture->GetUserData().pointer);
+		Shape *f = (Shape *)(raycast.hitFixture->GetUserData().pointer);
 		if (f == nullptr)
 		if (f == nullptr)
-			return luaL_error(L, "A fixture has escaped Memoizer!");
-		luax_pushtype(L, f);
+			return luaL_error(L, "A Shape has escaped Memoizer!");
+		luax_pushshape(L, f);
 
 
 		b2Vec2 hitPoint = Physics::scaleUp(raycast.hitPoint);
 		b2Vec2 hitPoint = Physics::scaleUp(raycast.hitPoint);
 		lua_pushnumber(L, hitPoint.x);
 		lua_pushnumber(L, hitPoint.x);
@@ -683,10 +681,10 @@ int World::rayCastClosest(lua_State *L)
 	world->RayCast(&raycast, v1, v2);
 	world->RayCast(&raycast, v1, v2);
 	if (raycast.hitFixture)
 	if (raycast.hitFixture)
 	{
 	{
-		Fixture *f = (Fixture *)(raycast.hitFixture->GetUserData().pointer);
+		Shape *f = (Shape *)(raycast.hitFixture->GetUserData().pointer);
 		if (f == nullptr)
 		if (f == nullptr)
-			return luaL_error(L, "A fixture has escaped Memoizer!");
-		luax_pushtype(L, f);
+			return luaL_error(L, "A Shape has escaped Memoizer!");
+		luax_pushshape(L, f);
 
 
 		b2Vec2 hitPoint = Physics::scaleUp(raycast.hitPoint);
 		b2Vec2 hitPoint = Physics::scaleUp(raycast.hitPoint);
 		lua_pushnumber(L, hitPoint.x);
 		lua_pushnumber(L, hitPoint.x);

+ 8 - 8
src/modules/physics/box2d/World.h

@@ -42,7 +42,7 @@ namespace box2d
 
 
 class Contact;
 class Contact;
 class Body;
 class Body;
-class Fixture;
+class Shape;
 class Joint;
 class Joint;
 
 
 /**
 /**
@@ -65,7 +65,7 @@ public:
 	friend class DistanceJoint;
 	friend class DistanceJoint;
 	friend class MouseJoint;
 	friend class MouseJoint;
 	friend class Body;
 	friend class Body;
-	friend class Fixture;
+	friend class Shape;
 
 
 	static love::Type type;
 	static love::Type type;
 
 
@@ -87,7 +87,7 @@ public:
 		lua_State *L;
 		lua_State *L;
 		ContactFilter();
 		ContactFilter();
 		~ContactFilter();
 		~ContactFilter();
-		bool process(Fixture *a, Fixture *b);
+		bool process(Shape *a, Shape *b);
 	};
 	};
 
 
 	class QueryCallback : public b2QueryCallback
 	class QueryCallback : public b2QueryCallback
@@ -302,14 +302,14 @@ public:
 	b2Body *getGroundBody() const;
 	b2Body *getGroundBody() const;
 
 
 	/**
 	/**
-	 * Calls a callback on all fixtures that overlap a given bounding box.
+	 * Calls a callback on all Shapes that overlap a given bounding box.
 	 **/
 	 **/
-	int queryFixturesInArea(lua_State *L);
+	int queryShapesInArea(lua_State *L);
 
 
 	/**
 	/**
-	 * Gets all fixtures that overlap a given bounding box.
+	 * Gets all Shapes that overlap a given bounding box.
 	 **/
 	 **/
-	int getFixturesInArea(lua_State *L);
+	int getShapesInArea(lua_State *L);
 
 
 	/**
 	/**
 	 * Raycasts the World for all Fixtures in the path of the ray.
 	 * Raycasts the World for all Fixtures in the path of the ray.
@@ -338,7 +338,7 @@ private:
 
 
 	// The list of to be destructed bodies.
 	// The list of to be destructed bodies.
 	std::vector<Body *> destructBodies;
 	std::vector<Body *> destructBodies;
-	std::vector<Fixture *> destructFixtures;
+	std::vector<Shape *> destructShapes;
 	std::vector<Joint *> destructJoints;
 	std::vector<Joint *> destructJoints;
 	bool destructWorld;
 	bool destructWorld;
 
 

+ 33 - 3
src/modules/physics/box2d/wrap_Body.cpp

@@ -20,6 +20,7 @@
 
 
 #include "wrap_Body.h"
 #include "wrap_Body.h"
 #include "wrap_Physics.h"
 #include "wrap_Physics.h"
+#include "wrap_Shape.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -161,6 +162,13 @@ int w_Body_getMassData(lua_State *L)
 	return t->getMassData(L);
 	return t->getMassData(L);
 }
 }
 
 
+int w_Body_hasCustomMassData(lua_State *L)
+{
+	Body *t = luax_checkbody(L, 1);
+	luax_pushboolean(L, t->hasCustomMassData());
+	return 1;
+}
+
 int w_Body_getAngularDamping(lua_State *L)
 int w_Body_getAngularDamping(lua_State *L)
 {
 {
 	Body *t = luax_checkbody(L, 1);
 	Body *t = luax_checkbody(L, 1);
@@ -599,15 +607,32 @@ int w_Body_getWorld(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-int w_Body_getFixtures(lua_State *L)
+int w_Body_getShape(lua_State *L)
+{
+	Body *t = luax_checkbody(L, 1);
+	Shape *s = t->getShape();
+	if (s)
+		luax_pushshape(L, s);
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+int w_Body_getShapes(lua_State *L)
 {
 {
 	Body *t = luax_checkbody(L, 1);
 	Body *t = luax_checkbody(L, 1);
 	lua_remove(L, 1);
 	lua_remove(L, 1);
 	int n = 0;
 	int n = 0;
-	luax_catchexcept(L, [&](){ n = t->getFixtures(L); });
+	luax_catchexcept(L, [&](){ n = t->getShapes(L); });
 	return n;
 	return n;
 }
 }
 
 
+int w_Body_getFixtures(lua_State *L)
+{
+	luax_markdeprecated(L, 1, "Body:getFixtures", API_METHOD, DEPRECATED_REPLACED, "Body:getShapes");
+	return w_Body_getShapes(L);
+}
+
 int w_Body_getJoints(lua_State *L)
 int w_Body_getJoints(lua_State *L)
 {
 {
 	Body *t = luax_checkbody(L, 1);
 	Body *t = luax_checkbody(L, 1);
@@ -670,6 +695,7 @@ static const luaL_Reg w_Body_functions[] =
 	{ "getMass", w_Body_getMass },
 	{ "getMass", w_Body_getMass },
 	{ "getInertia", w_Body_getInertia },
 	{ "getInertia", w_Body_getInertia },
 	{ "getMassData", w_Body_getMassData },
 	{ "getMassData", w_Body_getMassData },
+	{ "hasCustomMassData", w_Body_hasCustomMassData },
 	{ "getAngularDamping", w_Body_getAngularDamping },
 	{ "getAngularDamping", w_Body_getAngularDamping },
 	{ "getLinearDamping", w_Body_getLinearDamping },
 	{ "getLinearDamping", w_Body_getLinearDamping },
 	{ "getGravityScale", w_Body_getGravityScale },
 	{ "getGravityScale", w_Body_getGravityScale },
@@ -713,7 +739,8 @@ static const luaL_Reg w_Body_functions[] =
 	{ "isFixedRotation", w_Body_isFixedRotation },
 	{ "isFixedRotation", w_Body_isFixedRotation },
 	{ "isTouching", w_Body_isTouching },
 	{ "isTouching", w_Body_isTouching },
 	{ "getWorld", w_Body_getWorld },
 	{ "getWorld", w_Body_getWorld },
-	{ "getFixtures", w_Body_getFixtures },
+	{ "getShape", w_Body_getShape },
+	{ "getShapes", w_Body_getShapes },
 	{ "getJoints", w_Body_getJoints },
 	{ "getJoints", w_Body_getJoints },
 	{ "getContacts", w_Body_getContacts },
 	{ "getContacts", w_Body_getContacts },
 	{ "destroy", w_Body_destroy },
 	{ "destroy", w_Body_destroy },
@@ -721,6 +748,9 @@ static const luaL_Reg w_Body_functions[] =
 	{ "setUserData", w_Body_setUserData },
 	{ "setUserData", w_Body_setUserData },
 	{ "getUserData", w_Body_getUserData },
 	{ "getUserData", w_Body_getUserData },
 
 
+	// Deprecated
+	{ "getFixtures", w_Body_getFixtures },
+
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };
 
 

+ 10 - 5
src/modules/physics/box2d/wrap_ChainShape.cpp

@@ -54,9 +54,10 @@ int w_ChainShape_setPreviousVertex(lua_State *L)
 
 
 int w_ChainShape_getChildEdge(lua_State *L)
 int w_ChainShape_getChildEdge(lua_State *L)
 {
 {
+	luax_markdeprecated(L, 1, "ChainShape:getChildEdge", API_METHOD, DEPRECATED_NO_REPLACEMENT, nullptr);
 	ChainShape *c = luax_checkchainshape(L, 1);
 	ChainShape *c = luax_checkchainshape(L, 1);
 	int index = (int) luaL_checkinteger(L, 2) - 1; // Convert from 1-based index
 	int index = (int) luaL_checkinteger(L, 2) - 1; // Convert from 1-based index
-	EdgeShape *e = 0;
+	EdgeShape *e = nullptr;
 	luax_catchexcept(L, [&](){ e = c->getChildEdge(index); });
 	luax_catchexcept(L, [&](){ e = c->getChildEdge(index); });
 	luax_pushtype(L, e);
 	luax_pushtype(L, e);
 	e->release();
 	e->release();
@@ -66,7 +67,8 @@ int w_ChainShape_getChildEdge(lua_State *L)
 int w_ChainShape_getVertexCount(lua_State *L)
 int w_ChainShape_getVertexCount(lua_State *L)
 {
 {
 	ChainShape *c = luax_checkchainshape(L, 1);
 	ChainShape *c = luax_checkchainshape(L, 1);
-	int count = c->getVertexCount();
+	int count = 0;
+	luax_catchexcept(L, [&]() { count = c->getVertexCount(); });
 	lua_pushinteger(L, count);
 	lua_pushinteger(L, count);
 	return 1;
 	return 1;
 }
 }
@@ -85,7 +87,8 @@ int w_ChainShape_getPoint(lua_State *L)
 int w_ChainShape_getNextVertex(lua_State *L)
 int w_ChainShape_getNextVertex(lua_State *L)
 {
 {
 	ChainShape *c = luax_checkchainshape(L, 1);
 	ChainShape *c = luax_checkchainshape(L, 1);
-	b2Vec2 v = c->getNextVertex();
+	b2Vec2 v;
+	luax_catchexcept(L, [&]() { v = c->getNextVertex(); });
 	lua_pushnumber(L, v.x);
 	lua_pushnumber(L, v.x);
 	lua_pushnumber(L, v.y);
 	lua_pushnumber(L, v.y);
 	return 2;
 	return 2;
@@ -94,7 +97,8 @@ int w_ChainShape_getNextVertex(lua_State *L)
 int w_ChainShape_getPreviousVertex(lua_State *L)
 int w_ChainShape_getPreviousVertex(lua_State *L)
 {
 {
 	ChainShape *c = luax_checkchainshape(L, 1);
 	ChainShape *c = luax_checkchainshape(L, 1);
-	b2Vec2 v = c->getPreviousVertex();
+	b2Vec2 v;
+	luax_catchexcept(L, [&]() { v = c->getPreviousVertex(); });
 	lua_pushnumber(L, v.x);
 	lua_pushnumber(L, v.x);
 	lua_pushnumber(L, v.y);
 	lua_pushnumber(L, v.y);
 	return 2;
 	return 2;
@@ -103,7 +107,8 @@ int w_ChainShape_getPreviousVertex(lua_State *L)
 int w_ChainShape_getPoints(lua_State *L)
 int w_ChainShape_getPoints(lua_State *L)
 {
 {
 	ChainShape *c = luax_checkchainshape(L, 1);
 	ChainShape *c = luax_checkchainshape(L, 1);
-	const b2Vec2 *verts = c->getPoints();
+	const b2Vec2 *verts;
+	luax_catchexcept(L, [&]() { verts = c->getPoints(); });
 	int count = c->getVertexCount();
 	int count = c->getVertexCount();
 	if (!lua_checkstack(L, count*2))
 	if (!lua_checkstack(L, count*2))
 		return luaL_error(L, "Too many return values");
 		return luaL_error(L, "Too many return values");

+ 5 - 3
src/modules/physics/box2d/wrap_CircleShape.cpp

@@ -35,7 +35,9 @@ CircleShape *luax_checkcircleshape(lua_State *L, int idx)
 int w_CircleShape_getRadius(lua_State *L)
 int w_CircleShape_getRadius(lua_State *L)
 {
 {
 	CircleShape *c = luax_checkcircleshape(L, 1);
 	CircleShape *c = luax_checkcircleshape(L, 1);
-	lua_pushnumber(L, c->getRadius());
+	float r = 0;
+	luax_catchexcept(L, [&]() { r = c->getRadius(); });
+	lua_pushnumber(L, r);
 	return 1;
 	return 1;
 }
 }
 
 
@@ -43,7 +45,7 @@ int w_CircleShape_setRadius(lua_State *L)
 {
 {
 	CircleShape *c = luax_checkcircleshape(L, 1);
 	CircleShape *c = luax_checkcircleshape(L, 1);
 	float r = (float)luaL_checknumber(L, 2);
 	float r = (float)luaL_checknumber(L, 2);
-	c->setRadius(r);
+	luax_catchexcept(L, [&]() { c->setRadius(r); });
 	return 0;
 	return 0;
 }
 }
 
 
@@ -51,7 +53,7 @@ int w_CircleShape_getPoint(lua_State *L)
 {
 {
 	CircleShape *c = luax_checkcircleshape(L, 1);
 	CircleShape *c = luax_checkcircleshape(L, 1);
 	float x, y;
 	float x, y;
-	c->getPoint(x, y);
+	luax_catchexcept(L, [&]() { c->getPoint(x, y); });
 	lua_pushnumber(L, x);
 	lua_pushnumber(L, x);
 	lua_pushnumber(L, y);
 	lua_pushnumber(L, y);
 	return 2;
 	return 2;

+ 18 - 8
src/modules/physics/box2d/wrap_Contact.cpp

@@ -19,7 +19,7 @@
  **/
  **/
 
 
 #include "wrap_Contact.h"
 #include "wrap_Contact.h"
-#include "Fixture.h"
+#include "wrap_Shape.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -139,18 +139,24 @@ int w_Contact_getChildren(lua_State *L)
 	return 2;
 	return 2;
 }
 }
 
 
-int w_Contact_getFixtures(lua_State *L)
+int w_Contact_getShapes(lua_State *L)
 {
 {
 	Contact *t = luax_checkcontact(L, 1);
 	Contact *t = luax_checkcontact(L, 1);
-	Fixture *a = nullptr;
-	Fixture *b = nullptr;
-	luax_catchexcept(L, [&](){ t->getFixtures(a, b); });
+	Shape *a = nullptr;
+	Shape *b = nullptr;
+	luax_catchexcept(L, [&](){ t->getShapes(a, b); });
 
 
-	luax_pushtype(L, a);
-	luax_pushtype(L, b);
+	luax_pushshape(L, a);
+	luax_pushshape(L, b);
 	return 2;
 	return 2;
 }
 }
 
 
+int w_Contact_getFixtures(lua_State *L)
+{
+	luax_markdeprecated(L, 1, "Contact:getFixtures", API_METHOD, DEPRECATED_REPLACED, "Contact:getShapes");
+	return w_Contact_getShapes(L);
+}
+
 int w_Contact_isDestroyed(lua_State *L)
 int w_Contact_isDestroyed(lua_State *L)
 {
 {
 	Contact *c = luax_checktype<Contact>(L, 1);
 	Contact *c = luax_checktype<Contact>(L, 1);
@@ -174,8 +180,12 @@ static const luaL_Reg w_Contact_functions[] =
 	{ "setTangentSpeed", w_Contact_setTangentSpeed },
 	{ "setTangentSpeed", w_Contact_setTangentSpeed },
 	{ "getTangentSpeed", w_Contact_getTangentSpeed },
 	{ "getTangentSpeed", w_Contact_getTangentSpeed },
 	{ "getChildren", w_Contact_getChildren },
 	{ "getChildren", w_Contact_getChildren },
-	{ "getFixtures", w_Contact_getFixtures },
+	{ "getShapes", w_Contact_getShapes },
 	{ "isDestroyed", w_Contact_isDestroyed },
 	{ "isDestroyed", w_Contact_isDestroyed },
+
+	// Deprecated
+	{ "getFixtures", w_Contact_getFixtures },
+
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };
 
 

+ 9 - 5
src/modules/physics/box2d/wrap_EdgeShape.cpp

@@ -37,7 +37,7 @@ int w_EdgeShape_setNextVertex(lua_State *L)
 	EdgeShape *t = luax_checkedgeshape(L, 1);
 	EdgeShape *t = luax_checkedgeshape(L, 1);
 	float x = (float)luaL_checknumber(L, 2);
 	float x = (float)luaL_checknumber(L, 2);
 	float y = (float)luaL_checknumber(L, 3);
 	float y = (float)luaL_checknumber(L, 3);
-	t->setNextVertex(x, y);
+	luax_catchexcept(L, [&]() { t->setNextVertex(x, y); });
 	return 0;
 	return 0;
 }
 }
 
 
@@ -46,14 +46,15 @@ int w_EdgeShape_setPreviousVertex(lua_State *L)
 	EdgeShape *t = luax_checkedgeshape(L, 1);
 	EdgeShape *t = luax_checkedgeshape(L, 1);
 	float x = (float)luaL_checknumber(L, 2);
 	float x = (float)luaL_checknumber(L, 2);
 	float y = (float)luaL_checknumber(L, 3);
 	float y = (float)luaL_checknumber(L, 3);
-	t->setPreviousVertex(x, y);
+	luax_catchexcept(L, [&]() { t->setPreviousVertex(x, y); });
 	return 0;
 	return 0;
 }
 }
 
 
 int w_EdgeShape_getNextVertex(lua_State *L)
 int w_EdgeShape_getNextVertex(lua_State *L)
 {
 {
 	EdgeShape *t = luax_checkedgeshape(L, 1);
 	EdgeShape *t = luax_checkedgeshape(L, 1);
-	b2Vec2 v = t->getNextVertex();
+	b2Vec2 v;
+	luax_catchexcept(L, [&]() { v = t->getNextVertex(); });
 	lua_pushnumber(L, v.x);
 	lua_pushnumber(L, v.x);
 	lua_pushnumber(L, v.y);
 	lua_pushnumber(L, v.y);
 	return 2;
 	return 2;
@@ -62,7 +63,8 @@ int w_EdgeShape_getNextVertex(lua_State *L)
 int w_EdgeShape_getPreviousVertex(lua_State *L)
 int w_EdgeShape_getPreviousVertex(lua_State *L)
 {
 {
 	EdgeShape *t = luax_checkedgeshape(L, 1);
 	EdgeShape *t = luax_checkedgeshape(L, 1);
-	b2Vec2 v = t->getPreviousVertex();
+	b2Vec2 v;
+	luax_catchexcept(L, [&]() { v = t->getPreviousVertex(); });
 	lua_pushnumber(L, v.x);
 	lua_pushnumber(L, v.x);
 	lua_pushnumber(L, v.y);
 	lua_pushnumber(L, v.y);
 	return 2;
 	return 2;
@@ -72,7 +74,9 @@ int w_EdgeShape_getPoints(lua_State *L)
 {
 {
 	EdgeShape *t = luax_checkedgeshape(L, 1);
 	EdgeShape *t = luax_checkedgeshape(L, 1);
 	lua_remove(L, 1);
 	lua_remove(L, 1);
-	return t->getPoints(L);
+	int ret = 0;
+	luax_catchexcept(L, [&]() { ret = t->getPoints(L); });
+	return ret;
 }
 }
 
 
 static const luaL_Reg w_EdgeShape_functions[] =
 static const luaL_Reg w_EdgeShape_functions[] =

+ 0 - 311
src/modules/physics/box2d/wrap_Fixture.cpp

@@ -1,311 +0,0 @@
-/**
- * Copyright (c) 2006-2023 LOVE Development Team
- *
- * This software is provided 'as-is', without any express or implied
- * warranty.  In no event will the authors be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- *    claim that you wrote the original software. If you use this software
- *    in a product, an acknowledgment in the product documentation would be
- *    appreciated but is not required.
- * 2. Altered source versions must be plainly marked as such, and must not be
- *    misrepresented as being the original software.
- * 3. This notice may not be removed or altered from any source distribution.
- **/
-
-#include "wrap_Fixture.h"
-#include "common/StringMap.h"
-
-namespace love
-{
-namespace physics
-{
-namespace box2d
-{
-
-Fixture *luax_checkfixture(lua_State *L, int idx)
-{
-	Fixture *f = luax_checktype<Fixture>(L, idx);
-	if (!f->isValid())
-		luaL_error(L, "Attempt to use destroyed fixture.");
-	return f;
-}
-
-int w_Fixture_getType(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	const char *type = "";
-	Shape::getConstant(t->getType(), type);
-	lua_pushstring(L, type);
-	return 1;
-}
-
-int w_Fixture_setFriction(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	float arg1 = (float)luaL_checknumber(L, 2);
-	t->setFriction(arg1);
-	return 0;
-}
-
-int w_Fixture_setRestitution(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	float arg1 = (float)luaL_checknumber(L, 2);
-	t->setRestitution(arg1);
-	return 0;
-}
-
-int w_Fixture_setDensity(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	float arg1 = (float)luaL_checknumber(L, 2);
-	luax_catchexcept(L, [&](){ t->setDensity(arg1); });
-	return 0;
-}
-
-int w_Fixture_setSensor(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	bool arg1 = luax_checkboolean(L, 2);
-	t->setSensor(arg1);
-	return 0;
-}
-
-int w_Fixture_getFriction(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	lua_pushnumber(L, t->getFriction());
-	return 1;
-}
-
-int w_Fixture_getRestitution(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	lua_pushnumber(L, t->getRestitution());
-	return 1;
-}
-
-int w_Fixture_getDensity(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	lua_pushnumber(L, t->getDensity());
-	return 1;
-}
-
-int w_Fixture_isSensor(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	luax_pushboolean(L, t->isSensor());
-	return 1;
-}
-
-int w_Fixture_getBody(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	Body *body = t->getBody();
-	if (body == 0)
-		return 0;
-	luax_pushtype(L, body);
-	return 1;
-}
-
-int w_Fixture_getShape(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	Shape * shape = t->getShape();
-	if (shape == nullptr)
-		return 0;
-	switch (shape->getType())
-	{
-	case Shape::SHAPE_EDGE:
-		luax_pushtype(L, dynamic_cast<EdgeShape *>(shape));
-		break;
-	case Shape::SHAPE_CHAIN:
-		luax_pushtype(L, dynamic_cast<ChainShape *>(shape));
-		break;
-	case Shape::SHAPE_CIRCLE:
-		luax_pushtype(L, dynamic_cast<CircleShape *>(shape));
-		break;
-	case Shape::SHAPE_POLYGON:
-		luax_pushtype(L, dynamic_cast<PolygonShape *>(shape));
-		break;
-	default:
-		luax_pushtype(L, shape);
-		break;
-	}
-	return 1;
-}
-
-int w_Fixture_testPoint(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	float x = (float)luaL_checknumber(L, 2);
-	float y = (float)luaL_checknumber(L, 3);
-	luax_pushboolean(L, t->testPoint(x, y));
-	return 1;
-}
-
-int w_Fixture_rayCast(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	lua_remove(L, 1);
-	int ret = 0;
-	luax_catchexcept(L, [&](){ ret = t->rayCast(L); });
-	return ret;
-}
-
-int w_Fixture_setFilterData(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	int v[3];
-	v[0] = (int) luaL_checkinteger(L, 2);
-	v[1] = (int) luaL_checkinteger(L, 3);
-	v[2] = (int) luaL_checkinteger(L, 4);
-	t->setFilterData(v);
-	return 0;
-}
-
-int w_Fixture_getFilterData(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	int v[3];
-	t->getFilterData(v);
-	lua_pushinteger(L, v[0]);
-	lua_pushinteger(L, v[1]);
-	lua_pushinteger(L, v[2]);
-	return 3;
-}
-
-int w_Fixture_setCategory(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	lua_remove(L, 1);
-	return t->setCategory(L);
-}
-
-int w_Fixture_getCategory(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	lua_remove(L, 1);
-	return t->getCategory(L);
-}
-
-int w_Fixture_setMask(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	lua_remove(L, 1);
-	return t->setMask(L);
-}
-
-int w_Fixture_getMask(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	lua_remove(L, 1);
-	return t->getMask(L);
-}
-
-int w_Fixture_setUserData(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	lua_remove(L, 1);
-	return t->setUserData(L);
-}
-
-int w_Fixture_getUserData(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	lua_remove(L, 1);
-	return t->getUserData(L);
-}
-
-int w_Fixture_getBoundingBox(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	lua_remove(L, 1);
-	return t->getBoundingBox(L);
-}
-
-int w_Fixture_getMassData(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	lua_remove(L, 1);
-	return t->getMassData(L);
-}
-
-int w_Fixture_getGroupIndex(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	int i = t->getGroupIndex();
-	lua_pushinteger(L, i);
-	return 1;
-}
-
-int w_Fixture_setGroupIndex(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	int i = (int) luaL_checkinteger(L, 2);
-	t->setGroupIndex(i);
-	return 0;
-}
-
-int w_Fixture_destroy(lua_State *L)
-{
-	Fixture *t = luax_checkfixture(L, 1);
-	luax_catchexcept(L, [&](){ t->destroy(); });
-	return 0;
-}
-
-int w_Fixture_isDestroyed(lua_State *L)
-{
-	Fixture *f = luax_checktype<Fixture>(L, 1);
-	luax_pushboolean(L, !f->isValid());
-	return 1;
-}
-
-static const luaL_Reg w_Fixture_functions[] =
-{
-	{ "getType", w_Fixture_getType },
-	{ "setFriction", w_Fixture_setFriction },
-	{ "setRestitution", w_Fixture_setRestitution },
-	{ "setDensity", w_Fixture_setDensity },
-	{ "setSensor", w_Fixture_setSensor },
-	{ "getFriction", w_Fixture_getFriction },
-	{ "getRestitution", w_Fixture_getRestitution },
-	{ "getDensity", w_Fixture_getDensity },
-	{ "getBody", w_Fixture_getBody },
-	{ "getShape", w_Fixture_getShape },
-	{ "isSensor", w_Fixture_isSensor },
-	{ "testPoint", w_Fixture_testPoint },
-	{ "rayCast", w_Fixture_rayCast },
-	{ "setFilterData", w_Fixture_setFilterData },
-	{ "getFilterData", w_Fixture_getFilterData },
-	{ "setCategory", w_Fixture_setCategory },
-	{ "getCategory", w_Fixture_getCategory },
-	{ "setMask", w_Fixture_setMask },
-	{ "getMask", w_Fixture_getMask },
-	{ "setUserData", w_Fixture_setUserData },
-	{ "getUserData", w_Fixture_getUserData },
-	{ "getBoundingBox", w_Fixture_getBoundingBox },
-	{ "getMassData", w_Fixture_getMassData },
-	{ "getGroupIndex", w_Fixture_getGroupIndex },
-	{ "setGroupIndex", w_Fixture_setGroupIndex },
-	{ "destroy", w_Fixture_destroy },
-	{ "isDestroyed", w_Fixture_isDestroyed },
-	{ 0, 0 }
-};
-
-extern "C" int luaopen_fixture(lua_State *L)
-{
-	return luax_register_type(L, &Fixture::type, w_Fixture_functions, nullptr);
-}
-
-} // box2d
-} // physics
-} // love
-

+ 0 - 43
src/modules/physics/box2d/wrap_Fixture.h

@@ -1,43 +0,0 @@
-/**
- * Copyright (c) 2006-2023 LOVE Development Team
- *
- * This software is provided 'as-is', without any express or implied
- * warranty.  In no event will the authors be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- *    claim that you wrote the original software. If you use this software
- *    in a product, an acknowledgment in the product documentation would be
- *    appreciated but is not required.
- * 2. Altered source versions must be plainly marked as such, and must not be
- *    misrepresented as being the original software.
- * 3. This notice may not be removed or altered from any source distribution.
- **/
-
-#ifndef LOVE_PHYSICS_BOX2D_WRAP_FIXTURE_H
-#define LOVE_PHYSICS_BOX2D_WRAP_FIXTURE_H
-
-// LOVE
-#include "common/runtime.h"
-#include "Fixture.h"
-#include "wrap_Physics.h"
-
-namespace love
-{
-namespace physics
-{
-namespace box2d
-{
-
-Fixture *luax_checkfixture(lua_State *L, int idx);
-extern "C" int luaopen_fixture(lua_State *L);
-
-} // box2d
-} // physics
-} // love
-
-#endif // LOVE_PHYSICS_BOX2D_WRAP_FIXTURE_H

+ 316 - 36
src/modules/physics/box2d/wrap_Physics.cpp

@@ -23,7 +23,6 @@
 #include "wrap_World.h"
 #include "wrap_World.h"
 #include "wrap_Contact.h"
 #include "wrap_Contact.h"
 #include "wrap_Body.h"
 #include "wrap_Body.h"
-#include "wrap_Fixture.h"
 #include "wrap_Shape.h"
 #include "wrap_Shape.h"
 #include "wrap_CircleShape.h"
 #include "wrap_CircleShape.h"
 #include "wrap_PolygonShape.h"
 #include "wrap_PolygonShape.h"
@@ -83,38 +82,225 @@ int w_newBody(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_newCircleBody(lua_State *L)
+{
+	World *world = luax_checkworld(L, 1);
+
+	const char *typestr = luaL_checkstring(L, 2);
+	Body::Type btype = Body::BODY_STATIC;
+	if (!Body::getConstant(typestr, btype))
+		return luax_enumerror(L, "Body type", Body::getConstants(btype), typestr);
+
+	float x = (float)luaL_checknumber(L, 3);
+	float y = (float)luaL_checknumber(L, 4);
+	float radius = (float)luaL_checknumber(L, 5);
+
+	Body *body = nullptr;
+	luax_catchexcept(L, [&]() { body = instance()->newCircleBody(world, btype, x, y, radius); });
+
+	luax_pushtype(L, body);
+	body->release();
+	return 1;
+}
+
+int w_newRectangleBody(lua_State *L)
+{
+	World *world = luax_checkworld(L, 1);
+
+	const char *typestr = luaL_checkstring(L, 2);
+	Body::Type btype = Body::BODY_STATIC;
+	if (!Body::getConstant(typestr, btype))
+		return luax_enumerror(L, "Body type", Body::getConstants(btype), typestr);
+
+	float x = (float)luaL_checknumber(L, 3);
+	float y = (float)luaL_checknumber(L, 4);
+	float w = (float)luaL_checknumber(L, 5);
+	float h = (float)luaL_checknumber(L, 6);
+	float angle = (float)luaL_optnumber(L, 7, 0.0);
+
+	Body *body = nullptr;
+	luax_catchexcept(L, [&]() { body = instance()->newRectangleBody(world, btype, x, y, w, h, angle); });
+
+	luax_pushtype(L, body);
+	body->release();
+	return 1;
+}
+
+int w_newPolygonBody(lua_State *L)
+{
+	World *world = luax_checkworld(L, 1);
+
+	const char *typestr = luaL_checkstring(L, 2);
+	Body::Type btype = Body::BODY_STATIC;
+	if (!Body::getConstant(typestr, btype))
+		return luax_enumerror(L, "Body type", Body::getConstants(btype), typestr);
+
+	int argc = lua_gettop(L);
+
+	bool istable = lua_istable(L, 3);
+	if (istable)
+		argc = (int)luax_objlen(L, 3);
+
+	if (argc % 2 != 0)
+		return luaL_error(L, "Number of vertex components must be a multiple of two.");
+
+	int vcount = argc / 2;
+	std::vector<Vector2> coords;
+
+	if (istable)
+	{
+		for (int i = 0; i < vcount; i++)
+		{
+			lua_rawgeti(L, 3, 1 + i * 2);
+			lua_rawgeti(L, 3, 2 + i * 2);
+			float x = (float)luaL_checknumber(L, -2);
+			float y = (float)luaL_checknumber(L, -1);
+			coords.emplace_back(x, y);
+			lua_pop(L, 2);
+		}
+	}
+	else
+	{
+		for (int i = 0; i < vcount; i++)
+		{
+			float x = (float)luaL_checknumber(L, 3 + i * 2);
+			float y = (float)luaL_checknumber(L, 4 + i * 2);
+			coords.emplace_back(x, y);
+		}
+	}
+
+	Body *body = nullptr;
+	luax_catchexcept(L, [&]() { body = instance()->newPolygonBody(world, btype, coords.data(), (int)coords.size()); });
+
+	luax_pushtype(L, body);
+	body->release();
+	return 1;
+}
+
+int w_newEdgeBody(lua_State *L)
+{
+	World *world = luax_checkworld(L, 1);
+
+	const char *typestr = luaL_checkstring(L, 2);
+	Body::Type btype = Body::BODY_STATIC;
+	if (!Body::getConstant(typestr, btype))
+		return luax_enumerror(L, "Body type", Body::getConstants(btype), typestr);
+
+	float x1 = (float)luaL_checknumber(L, 3);
+	float y1 = (float)luaL_checknumber(L, 4);
+	float x2 = (float)luaL_checknumber(L, 5);
+	float y2 = (float)luaL_checknumber(L, 6);
+	bool oneSided = luax_optboolean(L, 7, false);
+
+	Body *body = nullptr;
+	luax_catchexcept(L, [&]() { body = instance()->newEdgeBody(world, btype, x1, y1, x2, y2, oneSided); });
+
+	luax_pushtype(L, body);
+	body->release();
+	return 1;
+}
+
+int w_newChainBody(lua_State *L)
+{
+	World *world = luax_checkworld(L, 1);
+
+	const char *typestr = luaL_checkstring(L, 2);
+	Body::Type btype = Body::BODY_STATIC;
+	if (!Body::getConstant(typestr, btype))
+		return luax_enumerror(L, "Body type", Body::getConstants(btype), typestr);
+
+	bool loop = luax_checkboolean(L, 3);
+
+	int argc = lua_gettop(L) - 3;
+
+	bool istable = lua_istable(L, 4);
+	if (istable)
+		argc = (int)luax_objlen(L, 4);
+
+	if (argc == 0 || argc % 2 != 0)
+		return luaL_error(L, "Number of vertex components must be a multiple of two.");
+
+	int vcount = argc / 2;
+	std::vector<Vector2> coords;
+
+	if (istable)
+	{
+		for (int i = 0; i < vcount; i++)
+		{
+			lua_rawgeti(L, 4, 1 + i * 2);
+			lua_rawgeti(L, 4, 2 + i * 2);
+			float x = (float)lua_tonumber(L, -2);
+			float y = (float)lua_tonumber(L, -1);
+			coords.emplace_back(x, y);
+			lua_pop(L, 2);
+		}
+	}
+	else
+	{
+		for (int i = 0; i < vcount; i++)
+		{
+			float x = (float)luaL_checknumber(L, 4 + i * 2);
+			float y = (float)luaL_checknumber(L, 5 + i * 2);
+			coords.emplace_back(x, y);
+		}
+	}
+
+	Body *body = nullptr;
+	luax_catchexcept(L, [&]() { body = instance()->newChainBody(world, btype, loop, coords.data(), (int)coords.size()); });
+
+	luax_pushtype(L, body);
+	body->release();
+	return 1;
+}
+
 int w_newFixture(lua_State *L)
 int w_newFixture(lua_State *L)
 {
 {
+	luax_markdeprecated(L, 1, "love.physics.newFixture", API_FUNCTION, DEPRECATED_REPLACED, "love.physics.newCircle/Rectangle/Polygon/Edge/ChainShape");
+
 	Body *body = luax_checkbody(L, 1);
 	Body *body = luax_checkbody(L, 1);
 	Shape *shape = luax_checkshape(L, 2);
 	Shape *shape = luax_checkshape(L, 2);
 	float density = (float)luaL_optnumber(L, 3, 1.0f);
 	float density = (float)luaL_optnumber(L, 3, 1.0f);
-	Fixture *fixture;
-	luax_catchexcept(L, [&](){ fixture = instance()->newFixture(body, shape, density); });
-	luax_pushtype(L, fixture);
-	fixture->release();
+
+	Shape *newShape;
+	luax_catchexcept(L, [&]() { newShape = instance()->newAttachedShape(body, shape, density); });
+
+	luax_pushshape(L, newShape);
+	newShape->release();
 	return 1;
 	return 1;
 }
 }
 
 
+static Body *luax_optbodyforshape(lua_State *L, int idx, const char *name)
+{
+	if (lua_isnoneornil(L, idx) || luax_istype(L, idx, Object::type))
+		return luax_checkbody(L, idx);
+
+	luax_markdeprecated(L, 1, name, API_FUNCTION_VARIANT, DEPRECATED_REPLACED, "variant with Body parameter");
+	return nullptr;
+}
+
 int w_newCircleShape(lua_State *L)
 int w_newCircleShape(lua_State *L)
 {
 {
-	int top = lua_gettop(L);
+	Body *body = luax_optbodyforshape(L, 1, "love.physics.newCircleShape");
+	int bodyidx = body ? 1 : 0;
+
+	int top = lua_gettop(L) - bodyidx;
 
 
 	if (top == 1)
 	if (top == 1)
 	{
 	{
-		float radius = (float)luaL_checknumber(L, 1);
+		float radius = (float)luaL_checknumber(L, bodyidx + 1);
 		CircleShape *shape;
 		CircleShape *shape;
-		luax_catchexcept(L, [&](){ shape = instance()->newCircleShape(radius); });
+		luax_catchexcept(L, [&](){ shape = instance()->newCircleShape(body, 0, 0, radius); });
 		luax_pushtype(L, shape);
 		luax_pushtype(L, shape);
 		shape->release();
 		shape->release();
 		return 1;
 		return 1;
 	}
 	}
 	else if (top == 3)
 	else if (top == 3)
 	{
 	{
-		float x = (float)luaL_checknumber(L, 1);
-		float y = (float)luaL_checknumber(L, 2);
-		float radius = (float)luaL_checknumber(L, 3);
+		float x = (float)luaL_checknumber(L, bodyidx + 1);
+		float y = (float)luaL_checknumber(L, bodyidx + 2);
+		float radius = (float)luaL_checknumber(L, bodyidx + 3);
 		CircleShape *shape;
 		CircleShape *shape;
-		luax_catchexcept(L, [&](){ shape = instance()->newCircleShape(x, y, radius); });
+		luax_catchexcept(L, [&](){ shape = instance()->newCircleShape(body, x, y, radius); });
 		luax_pushtype(L, shape);
 		luax_pushtype(L, shape);
 		shape->release();
 		shape->release();
 		return 1;
 		return 1;
@@ -125,27 +311,30 @@ int w_newCircleShape(lua_State *L)
 
 
 int w_newRectangleShape(lua_State *L)
 int w_newRectangleShape(lua_State *L)
 {
 {
-	int top = lua_gettop(L);
+	Body *body = luax_optbodyforshape(L, 1, "love.physics.newRectangleShape");
+	int bodyidx = body ? 1 : 0;
+
+	int top = lua_gettop(L) - bodyidx;
 
 
 	if (top == 2)
 	if (top == 2)
 	{
 	{
-		float w = (float)luaL_checknumber(L, 1);
-		float h = (float)luaL_checknumber(L, 2);
+		float w = (float)luaL_checknumber(L, bodyidx + 1);
+		float h = (float)luaL_checknumber(L, bodyidx + 2);
 		PolygonShape *shape;
 		PolygonShape *shape;
-		luax_catchexcept(L, [&](){ shape = instance()->newRectangleShape(w, h); });
+		luax_catchexcept(L, [&](){ shape = instance()->newRectangleShape(body, 0, 0, w, h, 0); });
 		luax_pushtype(L, shape);
 		luax_pushtype(L, shape);
 		shape->release();
 		shape->release();
 		return 1;
 		return 1;
 	}
 	}
 	else if (top == 4 || top == 5)
 	else if (top == 4 || top == 5)
 	{
 	{
-		float x = (float)luaL_checknumber(L, 1);
-		float y = (float)luaL_checknumber(L, 2);
-		float w = (float)luaL_checknumber(L, 3);
-		float h = (float)luaL_checknumber(L, 4);
-		float angle = (float)luaL_optnumber(L, 5, 0);
+		float x = (float)luaL_checknumber(L, bodyidx + 1);
+		float y = (float)luaL_checknumber(L, bodyidx + 2);
+		float w = (float)luaL_checknumber(L, bodyidx + 3);
+		float h = (float)luaL_checknumber(L, bodyidx + 4);
+		float angle = (float)luaL_optnumber(L, bodyidx + 5, 0);
 		PolygonShape *shape;
 		PolygonShape *shape;
-		luax_catchexcept(L, [&](){ shape = instance()->newRectangleShape(x, y, w, h, angle); });
+		luax_catchexcept(L, [&](){ shape = instance()->newRectangleShape(body, x, y, w, h, angle); });
 		luax_pushtype(L, shape);
 		luax_pushtype(L, shape);
 		shape->release();
 		shape->release();
 		return 1;
 		return 1;
@@ -156,13 +345,16 @@ int w_newRectangleShape(lua_State *L)
 
 
 int w_newEdgeShape(lua_State *L)
 int w_newEdgeShape(lua_State *L)
 {
 {
-	float x1 = (float)luaL_checknumber(L, 1);
-	float y1 = (float)luaL_checknumber(L, 2);
-	float x2 = (float)luaL_checknumber(L, 3);
-	float y2 = (float)luaL_checknumber(L, 4);
-	bool oneSided = luax_optboolean(L, 5, false);
+	Body *body = luax_optbodyforshape(L, 1, "love.physics.newEdgeShape");
+	int bodyidx = body ? 1 : 0;
+
+	float x1 = (float)luaL_checknumber(L, bodyidx + 1);
+	float y1 = (float)luaL_checknumber(L, bodyidx + 2);
+	float x2 = (float)luaL_checknumber(L, bodyidx + 3);
+	float y2 = (float)luaL_checknumber(L, bodyidx + 4);
+	bool oneSided = luax_optboolean(L, bodyidx + 5, false);
 	EdgeShape *shape;
 	EdgeShape *shape;
-	luax_catchexcept(L, [&](){ shape = instance()->newEdgeShape(x1, y1, x2, y2, oneSided); });
+	luax_catchexcept(L, [&](){ shape = instance()->newEdgeShape(body, x1, y1, x2, y2, oneSided); });
 	luax_pushtype(L, shape);
 	luax_pushtype(L, shape);
 	shape->release();
 	shape->release();
 	return 1;
 	return 1;
@@ -170,16 +362,97 @@ int w_newEdgeShape(lua_State *L)
 
 
 int w_newPolygonShape(lua_State *L)
 int w_newPolygonShape(lua_State *L)
 {
 {
-	int ret = 0;
-	luax_catchexcept(L, [&](){ ret = instance()->newPolygonShape(L); });
-	return ret;
+	Body *body = luax_optbodyforshape(L, 1, "love.physics.newPolygonShape");
+	int bodyidx = body ? 1 : 0;
+
+	int argc = lua_gettop(L) - bodyidx;
+
+	bool istable = lua_istable(L, bodyidx + 1);
+
+	if (istable)
+		argc = (int)luax_objlen(L, bodyidx + 1);
+
+	if (argc % 2 != 0)
+		return luaL_error(L, "Number of vertex components must be a multiple of two.");
+
+	int vcount = argc / 2;
+	std::vector<Vector2> coords;
+
+	if (istable)
+	{
+		for (int i = 0; i < vcount; i++)
+		{
+			lua_rawgeti(L, bodyidx + 1, 1 + i * 2);
+			lua_rawgeti(L, bodyidx + 1, 2 + i * 2);
+			float x = (float)luaL_checknumber(L, -2);
+			float y = (float)luaL_checknumber(L, -1);
+			coords.emplace_back(x, y);
+			lua_pop(L, 2);
+		}
+	}
+	else
+	{
+		for (int i = 0; i < vcount; i++)
+		{
+			float x = (float)luaL_checknumber(L, bodyidx + 1 + i * 2);
+			float y = (float)luaL_checknumber(L, bodyidx + 2 + i * 2);
+			coords.emplace_back(x, y);
+		}
+	}
+
+	PolygonShape *shape = nullptr;
+	luax_catchexcept(L, [&](){ shape = instance()->newPolygonShape(body, coords.data(), (int)coords.size()); });
+	luax_pushtype(L, shape);
+	shape->release();
+	return 1;
 }
 }
 
 
 int w_newChainShape(lua_State *L)
 int w_newChainShape(lua_State *L)
 {
 {
-	int ret = 0;
-	luax_catchexcept(L, [&](){ ret = instance()->newChainShape(L); });
-	return ret;
+	Body *body = luax_optbodyforshape(L, 1, "love.physics.newChainShape");
+	int bodyidx = body ? 1 : 0;
+
+	int argc = lua_gettop(L) - 1 - bodyidx; // first argument is looping
+
+	bool istable = lua_istable(L, bodyidx + 2);
+
+	if (istable)
+		argc = (int)luax_objlen(L, bodyidx + 2);
+
+	if (argc == 0 || argc % 2 != 0)
+		return luaL_error(L, "Number of vertex components must be a multiple of two.");
+
+	int vcount = argc / 2;
+	bool loop = luax_checkboolean(L, bodyidx + 1);
+	std::vector<Vector2> coords;
+
+	if (istable)
+	{
+		for (int i = 0; i < vcount; i++)
+		{
+			lua_rawgeti(L, bodyidx + 2, 1 + i * 2);
+			lua_rawgeti(L, bodyidx + 2, 2 + i * 2);
+			float x = (float)lua_tonumber(L, -2);
+			float y = (float)lua_tonumber(L, -1);
+			coords.emplace_back(x, y);
+			lua_pop(L, 2);
+		}
+	}
+	else
+	{
+		for (int i = 0; i < vcount; i++)
+		{
+			float x = (float)luaL_checknumber(L, bodyidx + 2 + i * 2);
+			float y = (float)luaL_checknumber(L, bodyidx + 3 + i * 2);
+			coords.emplace_back(x, y);
+		}
+	}
+
+	ChainShape *shape = nullptr;
+	luax_catchexcept(L, [&]() { shape = instance()->newChainShape(body, loop, coords.data(), coords.size()); });
+	luax_pushtype(L, shape);
+	shape->release();
+	return 1;
 }
 }
 
 
 int w_newDistanceJoint(lua_State *L)
 int w_newDistanceJoint(lua_State *L)
@@ -573,7 +846,11 @@ static const luaL_Reg functions[] =
 {
 {
 	{ "newWorld", w_newWorld },
 	{ "newWorld", w_newWorld },
 	{ "newBody", w_newBody },
 	{ "newBody", w_newBody },
-	{ "newFixture", w_newFixture },
+	{ "newCircleBody", w_newCircleBody },
+	{ "newRectangleBody", w_newRectangleBody },
+	{ "newPolygonBody", w_newPolygonBody },
+	{ "newEdgeBody", w_newEdgeBody },
+	{ "newChainBody", w_newChainBody },
 	{ "newCircleShape", w_newCircleShape },
 	{ "newCircleShape", w_newCircleShape },
 	{ "newRectangleShape", w_newRectangleShape },
 	{ "newRectangleShape", w_newRectangleShape },
 	{ "newPolygonShape", w_newPolygonShape },
 	{ "newPolygonShape", w_newPolygonShape },
@@ -597,6 +874,10 @@ static const luaL_Reg functions[] =
 	{ "computeLinearFrequency", w_computeLinearFrequency },
 	{ "computeLinearFrequency", w_computeLinearFrequency },
 	{ "computeAngularStiffness", w_computeAngularStiffness },
 	{ "computeAngularStiffness", w_computeAngularStiffness },
 	{ "computeAngularFrequency", w_computeAngularFrequency },
 	{ "computeAngularFrequency", w_computeAngularFrequency },
+
+	// Deprecated
+	{ "newFixture", w_newFixture },
+
 	{ 0, 0 },
 	{ 0, 0 },
 };
 };
 
 
@@ -605,7 +886,6 @@ static const lua_CFunction types[] =
 	luaopen_world,
 	luaopen_world,
 	luaopen_contact,
 	luaopen_contact,
 	luaopen_body,
 	luaopen_body,
-	luaopen_fixture,
 	luaopen_shape,
 	luaopen_shape,
 	luaopen_circleshape,
 	luaopen_circleshape,
 	luaopen_polygonshape,
 	luaopen_polygonshape,

+ 6 - 2
src/modules/physics/box2d/wrap_PolygonShape.cpp

@@ -36,13 +36,17 @@ int w_PolygonShape_getPoints(lua_State *L)
 {
 {
 	PolygonShape *t = luax_checkpolygonshape(L, 1);
 	PolygonShape *t = luax_checkpolygonshape(L, 1);
 	lua_remove(L, 1);
 	lua_remove(L, 1);
-	return t->getPoints(L);
+	int ret = 0;
+	luax_catchexcept(L, [&]() { ret = t->getPoints(L); });
+	return ret;
 }
 }
 
 
 int w_PolygonShape_validate(lua_State *L)
 int w_PolygonShape_validate(lua_State *L)
 {
 {
 	PolygonShape *t = luax_checkpolygonshape(L, 1);
 	PolygonShape *t = luax_checkpolygonshape(L, 1);
-	luax_pushboolean(L, t->validate());
+	bool valid = false;
+	luax_catchexcept(L, [&]() { valid = t->validate(); });
+	luax_pushboolean(L, valid);
 	return 1;
 	return 1;
 }
 }
 
 

+ 285 - 8
src/modules/physics/box2d/wrap_Shape.cpp

@@ -33,6 +33,35 @@ Shape *luax_checkshape(lua_State *L, int idx)
 	return luax_checktype<Shape>(L, idx);
 	return luax_checktype<Shape>(L, idx);
 }
 }
 
 
+void luax_pushshape(lua_State *L, Shape *shape)
+{
+	if (shape != nullptr)
+	{
+		switch (shape->getType())
+		{
+		case Shape::SHAPE_CIRCLE:
+			luax_pushtype(L, (CircleShape *) shape);
+			break;
+		case Shape::SHAPE_POLYGON:
+			luax_pushtype(L, (PolygonShape *) shape);
+			break;
+		case Shape::SHAPE_EDGE:
+			luax_pushtype(L, (EdgeShape *) shape);
+			break;
+		case Shape::SHAPE_CHAIN:
+			luax_pushtype(L, (ChainShape *) shape);
+			break;
+		default:
+			luax_pushtype(L, shape);
+			break;
+		}
+	}
+	else
+	{
+		lua_pushnil(L);
+	}
+}
+
 int w_Shape_getType(lua_State *L)
 int w_Shape_getType(lua_State *L)
 {
 {
 	Shape *t = luax_checkshape(L, 1);
 	Shape *t = luax_checkshape(L, 1);
@@ -45,7 +74,8 @@ int w_Shape_getType(lua_State *L)
 int w_Shape_getRadius(lua_State *L)
 int w_Shape_getRadius(lua_State *L)
 {
 {
 	Shape *t = luax_checkshape(L, 1);
 	Shape *t = luax_checkshape(L, 1);
-	float radius = t->getRadius();
+	float radius = 0;
+	luax_catchexcept(L, [&]() { radius = t->getRadius(); });
 	lua_pushnumber(L, radius);
 	lua_pushnumber(L, radius);
 	return 1;
 	return 1;
 }
 }
@@ -53,20 +83,115 @@ int w_Shape_getRadius(lua_State *L)
 int w_Shape_getChildCount(lua_State *L)
 int w_Shape_getChildCount(lua_State *L)
 {
 {
 	Shape *t = luax_checkshape(L, 1);
 	Shape *t = luax_checkshape(L, 1);
-	int childCount = t->getChildCount();
+	int childCount = 0;
+	luax_catchexcept(L, [&]() { childCount = t->getChildCount(); });
 	lua_pushinteger(L, childCount);
 	lua_pushinteger(L, childCount);
 	return 1;
 	return 1;
 }
 }
 
 
+int w_Shape_setFriction(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	float arg1 = (float)luaL_checknumber(L, 2);
+	luax_catchexcept(L, [&]() { t->setFriction(arg1); });
+	return 0;
+}
+
+int w_Shape_setRestitution(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	float arg1 = (float)luaL_checknumber(L, 2);
+	luax_catchexcept(L, [&]() { t->setRestitution(arg1); });
+	return 0;
+}
+
+int w_Shape_setDensity(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	float arg1 = (float)luaL_checknumber(L, 2);
+	luax_catchexcept(L, [&](){ t->setDensity(arg1); });
+	return 0;
+}
+
+int w_Shape_setSensor(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	bool arg1 = luax_checkboolean(L, 2);
+	luax_catchexcept(L, [&]() { t->setSensor(arg1); });
+	return 0;
+}
+
+int w_Shape_getFriction(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	float friction = 0;
+	luax_catchexcept(L, [&]() { friction = t->getFriction(); });
+	lua_pushnumber(L, friction);
+	return 1;
+}
+
+int w_Shape_getRestitution(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	float r = 0;
+	luax_catchexcept(L, [&]() { r = t->getRestitution(); });
+	lua_pushnumber(L, r);
+	return 1;
+}
+
+int w_Shape_getDensity(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	float d = 0;
+	luax_catchexcept(L, [&]() { d = t->getDensity(); });
+	lua_pushnumber(L, d);
+	return 1;
+}
+
+int w_Shape_isSensor(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	bool sensor = false;
+	luax_catchexcept(L, [&]() { sensor = t->isSensor(); });
+	luax_pushboolean(L, sensor);
+	return 1;
+}
+
+int w_Shape_getBody(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	Body *body = t->getBody();
+	if (body == nullptr)
+		return 0;
+	luax_pushtype(L, body);
+	return 1;
+}
+
+int w_Shape_getShape(lua_State *L)
+{
+	luax_markdeprecated(L, 1, "Fixture:getShape", API_METHOD, DEPRECATED_NO_REPLACEMENT, nullptr);
+	Shape *t = luax_checkshape(L, 1);
+	luax_pushshape(L, t);
+	return 1;
+}
+
 int w_Shape_testPoint(lua_State *L)
 int w_Shape_testPoint(lua_State *L)
 {
 {
 	Shape *t = luax_checkshape(L, 1);
 	Shape *t = luax_checkshape(L, 1);
 	float x = (float)luaL_checknumber(L, 2);
 	float x = (float)luaL_checknumber(L, 2);
 	float y = (float)luaL_checknumber(L, 3);
 	float y = (float)luaL_checknumber(L, 3);
-	float r = (float)luaL_checknumber(L, 4);
-	float px = (float)luaL_checknumber(L, 5);
-	float py = (float)luaL_checknumber(L, 6);
-	bool result = t->testPoint(x, y, r, px, py);
+	bool result = false;
+	if (!lua_isnoneornil(L, 4))
+	{
+		float r = (float)luaL_checknumber(L, 4);
+		float px = (float)luaL_checknumber(L, 5);
+		float py = (float)luaL_checknumber(L, 6);
+		result = luax_catchexcept(L, [&]() { t->testPoint(x, y, r, px, py); });
+	}
+	else
+	{
+		result = luax_catchexcept(L, [&]() { t->testPoint(x, y); });
+	}
 	lua_pushboolean(L, result);
 	lua_pushboolean(L, result);
 	return 1;
 	return 1;
 }
 }
@@ -84,14 +209,143 @@ int w_Shape_computeAABB(lua_State *L)
 {
 {
 	Shape *t = luax_checkshape(L, 1);
 	Shape *t = luax_checkshape(L, 1);
 	lua_remove(L, 1);
 	lua_remove(L, 1);
-	return t->computeAABB(L);
+	int ret = 0;
+	luax_catchexcept(L, [&]() { ret = t->computeAABB(L); });
+	return ret;
 }
 }
 
 
 int w_Shape_computeMass(lua_State *L)
 int w_Shape_computeMass(lua_State *L)
 {
 {
 	Shape *t = luax_checkshape(L, 1);
 	Shape *t = luax_checkshape(L, 1);
 	lua_remove(L, 1);
 	lua_remove(L, 1);
-	return t->computeMass(L);
+	int ret = 0;
+	luax_catchexcept(L, [&]() { ret = t->computeMass(L); });
+	return ret;
+}
+
+int w_Shape_setFilterData(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	int v[3];
+	v[0] = (int) luaL_checkinteger(L, 2);
+	v[1] = (int) luaL_checkinteger(L, 3);
+	v[2] = (int) luaL_checkinteger(L, 4);
+	luax_catchexcept(L, [&]() { t->setFilterData(v); });
+	return 0;
+}
+
+int w_Shape_getFilterData(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	int v[3];
+	luax_catchexcept(L, [&]() { t->getFilterData(v); });
+	lua_pushinteger(L, v[0]);
+	lua_pushinteger(L, v[1]);
+	lua_pushinteger(L, v[2]);
+	return 3;
+}
+
+int w_Shape_setCategory(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	lua_remove(L, 1);
+	int ret = 0;
+	luax_catchexcept(L, [&]() { ret = t->setCategory(L); });
+	return ret;
+}
+
+int w_Shape_getCategory(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	lua_remove(L, 1);
+	int ret = 0;
+	luax_catchexcept(L, [&]() { ret = t->getCategory(L); });
+	return ret;
+}
+
+int w_Shape_setMask(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	lua_remove(L, 1);
+	int ret = 0;
+	luax_catchexcept(L, [&]() { ret = t->setMask(L); });
+	return ret;
+}
+
+int w_Shape_getMask(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	lua_remove(L, 1);
+	int ret = 0;
+	luax_catchexcept(L, [&]() { ret = t->getMask(L); });
+	return ret;
+}
+
+int w_Shape_setUserData(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	lua_remove(L, 1);
+	int ret = 0;
+	luax_catchexcept(L, [&]() { ret = t->setUserData(L); });
+	return ret;
+}
+
+int w_Shape_getUserData(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	lua_remove(L, 1);
+	int ret = 0;
+	luax_catchexcept(L, [&]() { ret = t->getUserData(L); });
+	return ret;
+}
+
+int w_Shape_getBoundingBox(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	lua_remove(L, 1);
+	int ret = 0;
+	luax_catchexcept(L, [&]() { ret = t->getBoundingBox(L); });
+	return ret;
+}
+
+int w_Shape_getMassData(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	lua_remove(L, 1);
+	int ret = 0;
+	luax_catchexcept(L, [&]() { ret = t->getMassData(L); });
+	return ret;
+}
+
+int w_Shape_getGroupIndex(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	int i = 0;
+	luax_catchexcept(L, [&]() { i = t->getGroupIndex(); });
+	lua_pushinteger(L, i);
+	return 1;
+}
+
+int w_Shape_setGroupIndex(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	int i = (int) luaL_checkinteger(L, 2);
+	luax_catchexcept(L, [&]() { t->setGroupIndex(i); });
+	return 0;
+}
+
+int w_Shape_destroy(lua_State *L)
+{
+	Shape *t = luax_checkshape(L, 1);
+	luax_catchexcept(L, [&](){ t->destroy(); });
+	return 0;
+}
+
+int w_Shape_isDestroyed(lua_State *L)
+{
+	Shape *f = luax_checktype<Shape>(L, 1);
+	luax_pushboolean(L, !f->isValid());
+	return 1;
 }
 }
 
 
 const luaL_Reg w_Shape_functions[] =
 const luaL_Reg w_Shape_functions[] =
@@ -99,10 +353,33 @@ const luaL_Reg w_Shape_functions[] =
 	{ "getType", w_Shape_getType },
 	{ "getType", w_Shape_getType },
 	{ "getRadius", w_Shape_getRadius },
 	{ "getRadius", w_Shape_getRadius },
 	{ "getChildCount", w_Shape_getChildCount },
 	{ "getChildCount", w_Shape_getChildCount },
+	{ "setFriction", w_Shape_setFriction },
+	{ "setRestitution", w_Shape_setRestitution },
+	{ "setDensity", w_Shape_setDensity },
+	{ "setSensor", w_Shape_setSensor },
+	{ "getFriction", w_Shape_getFriction },
+	{ "getRestitution", w_Shape_getRestitution },
+	{ "getDensity", w_Shape_getDensity },
+	{ "getBody", w_Shape_getBody },
+	{ "isSensor", w_Shape_isSensor },
 	{ "testPoint", w_Shape_testPoint },
 	{ "testPoint", w_Shape_testPoint },
 	{ "rayCast", w_Shape_rayCast },
 	{ "rayCast", w_Shape_rayCast },
 	{ "computeAABB", w_Shape_computeAABB },
 	{ "computeAABB", w_Shape_computeAABB },
 	{ "computeMass", w_Shape_computeMass },
 	{ "computeMass", w_Shape_computeMass },
+	{ "setFilterData", w_Shape_setFilterData },
+	{ "getFilterData", w_Shape_getFilterData },
+	{ "setCategory", w_Shape_setCategory },
+	{ "getCategory", w_Shape_getCategory },
+	{ "setMask", w_Shape_setMask },
+	{ "getMask", w_Shape_getMask },
+	{ "setUserData", w_Shape_setUserData },
+	{ "getUserData", w_Shape_getUserData },
+	{ "getBoundingBox", w_Shape_getBoundingBox },
+	{ "getMassData", w_Shape_getMassData },
+	{ "getGroupIndex", w_Shape_getGroupIndex },
+	{ "setGroupIndex", w_Shape_setGroupIndex },
+	{ "destroy", w_Shape_destroy },
+	{ "isDestroyed", w_Shape_isDestroyed },
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };
 
 

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