ソースを参照

merge

--HG--
branch : minor
Kyle McLamb 8 年 前
コミット
f6600953ec
100 ファイル変更2226 行追加1453 行削除
  1. 6 10
      CMakeLists.txt
  2. 40 9
      changes.txt
  3. 129 53
      license.txt
  4. 28 48
      platform/xcode/liblove.xcodeproj/project.pbxproj
  5. 4 0
      platform/xcode/love.xcodeproj/project.pbxproj
  6. 3 6
      src/common/Color.h
  7. 16 0
      src/common/android.cpp
  8. 2 0
      src/common/android.h
  9. 10 0
      src/common/ios.h
  10. 23 0
      src/common/ios.mm
  11. 14 16
      src/libraries/ddsparse/ddsinfo.h
  12. 14 16
      src/libraries/ddsparse/ddsparse.cpp
  13. 14 16
      src/libraries/ddsparse/ddsparse.h
  14. 445 276
      src/libraries/stb/stb_image.h
  15. 18 2
      src/modules/audio/Audio.cpp
  16. 8 2
      src/modules/audio/Audio.h
  17. 1 1
      src/modules/audio/Effect.cpp
  18. 1 1
      src/modules/audio/Effect.h
  19. 12 7
      src/modules/audio/RecordingDevice.h
  20. 1 1
      src/modules/audio/Source.h
  21. 1 1
      src/modules/audio/null/Audio.cpp
  22. 1 1
      src/modules/audio/null/Audio.h
  23. 9 9
      src/modules/audio/null/RecordingDevice.cpp
  24. 2 2
      src/modules/audio/null/RecordingDevice.h
  25. 1 1
      src/modules/audio/null/Source.cpp
  26. 1 1
      src/modules/audio/null/Source.h
  27. 1 1
      src/modules/audio/openal/Audio.cpp
  28. 1 1
      src/modules/audio/openal/Audio.h
  29. 12 17
      src/modules/audio/openal/RecordingDevice.cpp
  30. 10 6
      src/modules/audio/openal/RecordingDevice.h
  31. 3 3
      src/modules/audio/openal/Source.cpp
  32. 1 1
      src/modules/audio/openal/Source.h
  33. 39 11
      src/modules/audio/wrap_Audio.cpp
  34. 14 9
      src/modules/audio/wrap_RecordingDevice.cpp
  35. 6 6
      src/modules/audio/wrap_Source.cpp
  36. 6 7
      src/modules/filesystem/physfs/File.cpp
  37. 49 29
      src/modules/filesystem/wrap_Filesystem.cpp
  38. 11 9
      src/modules/font/BMFontRasterizer.cpp
  39. 9 12
      src/modules/font/ImageRasterizer.cpp
  40. 2 2
      src/modules/font/ImageRasterizer.h
  41. 3 3
      src/modules/font/wrap_Font.cpp
  42. 1 1
      src/modules/graphics/Buffer.cpp
  43. 1 1
      src/modules/graphics/Buffer.h
  44. 1 37
      src/modules/graphics/Canvas.cpp
  45. 172 52
      src/modules/graphics/Graphics.cpp
  46. 14 12
      src/modules/graphics/Graphics.h
  47. 78 1
      src/modules/graphics/Mesh.cpp
  48. 4 2
      src/modules/graphics/Mesh.h
  49. 10 3
      src/modules/graphics/ParticleSystem.cpp
  50. 5 2
      src/modules/graphics/ParticleSystem.h
  51. 8 8
      src/modules/graphics/Polyline.cpp
  52. 8 8
      src/modules/graphics/Polyline.h
  53. 10 10
      src/modules/graphics/Shader.cpp
  54. 4 4
      src/modules/graphics/Shader.h
  55. 62 8
      src/modules/graphics/SpriteBatch.cpp
  56. 8 2
      src/modules/graphics/SpriteBatch.h
  57. 31 0
      src/modules/graphics/Text.cpp
  58. 5 0
      src/modules/graphics/Text.h
  59. 57 0
      src/modules/graphics/Texture.cpp
  60. 2 0
      src/modules/graphics/Texture.h
  61. 1 1
      src/modules/graphics/opengl/Canvas.h
  62. 6 118
      src/modules/graphics/opengl/Graphics.cpp
  63. 2 2
      src/modules/graphics/opengl/Graphics.h
  64. 1 15
      src/modules/graphics/opengl/Image.cpp
  65. 8 74
      src/modules/graphics/opengl/Mesh.cpp
  66. 4 1
      src/modules/graphics/opengl/Mesh.h
  67. 2 1
      src/modules/graphics/opengl/OpenGL.cpp
  68. 1 1
      src/modules/graphics/opengl/OpenGL.h
  69. 1 12
      src/modules/graphics/opengl/ParticleSystem.cpp
  70. 4 1
      src/modules/graphics/opengl/ParticleSystem.h
  71. 4 4
      src/modules/graphics/opengl/Shader.cpp
  72. 9 56
      src/modules/graphics/opengl/SpriteBatch.cpp
  73. 3 2
      src/modules/graphics/opengl/SpriteBatch.h
  74. 2 30
      src/modules/graphics/opengl/Text.cpp
  75. 3 2
      src/modules/graphics/opengl/Text.h
  76. 1 1
      src/modules/graphics/vertex.h
  77. 1 1
      src/modules/graphics/wrap_Canvas.cpp
  78. 157 77
      src/modules/graphics/wrap_Graphics.cpp
  79. 16 9
      src/modules/graphics/wrap_Graphics.lua
  80. 3 3
      src/modules/graphics/wrap_Image.cpp
  81. 3 3
      src/modules/graphics/wrap_Mesh.cpp
  82. 1 1
      src/modules/graphics/wrap_ParticleSystem.cpp
  83. 1 1
      src/modules/graphics/wrap_Quad.cpp
  84. 5 5
      src/modules/graphics/wrap_SpriteBatch.cpp
  85. 3 3
      src/modules/graphics/wrap_Text.cpp
  86. 9 15
      src/modules/image/CompressedFormatHandler.h
  87. 36 40
      src/modules/image/CompressedImageData.cpp
  88. 12 47
      src/modules/image/CompressedImageData.h
  89. 45 21
      src/modules/image/CompressedSlice.cpp
  90. 72 0
      src/modules/image/CompressedSlice.h
  91. 2 5
      src/modules/image/FormatHandler.cpp
  92. 12 11
      src/modules/image/FormatHandler.h
  93. 84 0
      src/modules/image/Image.cpp
  94. 20 7
      src/modules/image/Image.h
  95. 204 8
      src/modules/image/ImageData.cpp
  96. 26 31
      src/modules/image/ImageData.h
  97. 3 3
      src/modules/image/magpie/ASTCHandler.cpp
  98. 4 7
      src/modules/image/magpie/ASTCHandler.h
  99. 0 85
      src/modules/image/magpie/CompressedImageData.cpp
  100. 3 2
      src/modules/image/magpie/EXRHandler.cpp

+ 6 - 10
CMakeLists.txt

@@ -255,6 +255,7 @@ endfunction()
 set(LOVE_SRC_COMMON
 	src/common/b64.cpp
 	src/common/b64.h
+	src/common/Color.h
 	src/common/config.h
 	src/common/Data.cpp
 	src/common/Data.h
@@ -474,7 +475,6 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT
 	src/modules/graphics/Buffer.h
 	src/modules/graphics/Canvas.cpp
 	src/modules/graphics/Canvas.h
-	src/modules/graphics/Color.h
 	src/modules/graphics/depthstencil.cpp
 	src/modules/graphics/depthstencil.h
 	src/modules/graphics/Drawable.cpp
@@ -575,8 +575,13 @@ source_group("modules\\graphics\\opengl" FILES ${LOVE_SRC_MODULE_GRAPHICS_OPENGL
 #
 
 set(LOVE_SRC_MODULE_IMAGE_ROOT
+	src/modules/image/CompressedFormatHandler.h
 	src/modules/image/CompressedImageData.cpp
 	src/modules/image/CompressedImageData.h
+	src/modules/image/CompressedSlice.cpp
+	src/modules/image/CompressedSlice.h
+	src/modules/image/FormatHandler.cpp
+	src/modules/image/FormatHandler.h
 	src/modules/image/Image.cpp
 	src/modules/image/Image.h
 	src/modules/image/ImageData.cpp
@@ -594,19 +599,10 @@ set(LOVE_SRC_MODULE_IMAGE_ROOT
 set(LOVE_SRC_MODULE_IMAGE_MAGPIE
 	src/modules/image/magpie/ASTCHandler.cpp
 	src/modules/image/magpie/ASTCHandler.h
-	src/modules/image/magpie/CompressedImageData.cpp
-	src/modules/image/magpie/CompressedImageData.h
-	src/modules/image/magpie/CompressedFormatHandler.h
 	src/modules/image/magpie/ddsHandler.cpp
 	src/modules/image/magpie/ddsHandler.h
 	src/modules/image/magpie/EXRHandler.cpp
 	src/modules/image/magpie/EXRHandler.h
-	src/modules/image/magpie/FormatHandler.cpp
-	src/modules/image/magpie/FormatHandler.h
-	src/modules/image/magpie/Image.cpp
-	src/modules/image/magpie/Image.h
-	src/modules/image/magpie/ImageData.cpp
-	src/modules/image/magpie/ImageData.h
 	src/modules/image/magpie/KTXHandler.cpp
 	src/modules/image/magpie/KTXHandler.h
 	src/modules/image/magpie/PKMHandler.cpp

+ 40 - 9
changes.txt

@@ -3,54 +3,80 @@ LOVE 0.11.0 []
 
 Released: N/A
 
+  * Added Object:release.
+  * Added Data:clone.
+  * Added queueable audio sources.
+  * Added audio input support.
+  * Added Source filters: low gain, high gain and band pass.
+  * Added audio effect APIs (reverb, echo, etc.)
   * Added a variant to World:update, which accepts the number of iterations to run. The defaults are now 8 and 3.
   * Added a click count argument to love.mousepressed and love.mousereleased.
   * Added love.filesystem.get/setCRequirePath, and use that to find c libraries for require.
   * Added Channel:hasRead, which checks if a message has been read. Takes an id, which Channel:push will now return.
   * Added love.math.encode/decode, which support hex and base64.
   * Added love.math.hash, which supports MD5, SHA-1 and the SHA-2 family.
+  * Added Transform objects to love.math.
   * Added support for different ImageData formats, including RGBA8 (the default), RGBA16, RGBA16F, and RGBA32F.
   * Added the ability to load Radiance HDR, OpenEXR, and 16 bit PNG images.
   * Added love.graphics.getImageFormats (replaces love.graphics.getCompressedImageFormats).
   * Added the ability to specify a per-object pixel density scale factor when creating Images, Canvases, Fonts, and Videos. Affects drawing.
   * Added Texture:getPixelWidth/Height and love.graphics.getPixelWidth/Height.
   * Added Texture:getPixelDensity, love.graphics.getPixelDensity, and Font:getPixelDensity.
+  * Added Array, Cubemap, and Volume texture types and corresponding Image and Canvas APIs.
+  * Added love.graphics.getTextureTypes, returns a table with fields indicating support for each texture type.
+  * Added mipmapping support to Canvases, including both auto-generated mipmaps and manually rendering to a specific mipmap level.
+  * Added 'stencil8', 'depth24stencil8', 'depth32fstencil8', 'depth16', 'depth24', and 'depth32f' pixel formats for Canvases.
+  * Added optional 'readable' boolean field to the table passed into love.graphics.newCanvas.
+  * Added optional 'depthstencil' field to the table passed into love.graphics.setCanvas, for using a depth/stencil Canvas.
+  * Added optional 'depth' and 'stencil' boolean fields to the table passed into setCanvas, for enabling depth and stencil buffers if 'depthstencil' isn't used.
+  * Added shadow sampler support for Canvases.
+  * Added variant of love.graphics.getCanvasFormats which takes a 'readable' boolean.
+  * Added love.graphics.drawLayer and SpriteBatch:add/setLayer for easily drawing layers of Array Textures.
+  * Added variants of love.graphics.print and printf which take a Font argument.
+  * Added variants of love.graphics.clear to control how the active depth and stencil buffers are cleared.
+  * Added love.graphics.flushBatch for manually flushing automatically batched draws.
   * Added SpriteBatch:setDrawRange.
   * Added per-shader opt in support for the GLSL 3.30 and GLSL ES 3.00 shading languages.
+  * Added 'void effect()' pixel shader entry point.
   * Added love.graphics.validateShader.
   * Added Shader:hasUniform.
   * Added support for non-square shader uniform matrices on desktop platforms and on mobile GLSL 3.
   * Added Shader:send(matrixname, is_column_major, matrix, ...) which specifies how to interpret the matrix table arguments.
+  * Added 'borderellipse' and 'borderrectangle' ParticleSystem distributions.
+  * Added ParticleSystem:set/getAreaSpreadAngle and set/getAreaSpreadIsRelativeDirection.
   * Added love.graphics.captureScreenshot (replaces love.graphics.newScreenshot).
-  * Added love.window.updateMode.
-  * Added the ability to prevent love from creating a stencil buffer for the window.
-  * Added Object:release.
-  * Added queueable audio sources.
-  * Added audio input support.
-  * Added Transform objects to love.math.
-  * Added 'glsl3', 'instancing', 'fullnpot', and 'pixelshaderhighp' graphics features.
+  * Added 'glsl3', 'instancing', 'fullnpot','pixelshaderhighp', and 'shaderderivatives' graphics features.
   * Added 'anisotropy' graphics limit.
-  * Added Mesh instancing support.
+  * Added Mesh instancing support via love.graphics.drawInstanced and Mesh:attachAttribute.
   * Added a Mesh:attachAttribute variant that takes a different target attribute name.
   * Added Mesh:detachAttribute.
   * Added a variant of Mesh:setVertexMap which accepts a Data object.
-  * Added Source filters: low gain, high gain and band pass.
+  * Added love.window.updateMode.
+  * Added the ability to prevent love from creating a stencil buffer for the window.
 
   * Changed all color values to be in the range 0-1, rather than 0-255.
   * Changed high-dpi functionality to require much less code (often none at all) for things to appear at the correct sizes and positions.
   * Changed love.graphics.print and friends to ignore carriage returns.
   * Changed the 'multiply' blend mode to error if not used with the 'premultiplied' blend alpha mode, since the formula only works with that anyway.
   * Changed some love.graphics, love.window, and love.event APIs to cause an error if a Canvas is active.
+  * Changed stenciling functionality with a Canvas active to require stencil=true (or a custom stencil-formatted Canvas) to be set in setCanvas.
   * Changed Mesh:setDrawRange to take 'start' and 'count' parameters instead of 'min' and 'max'.
   * Changed the 'vsync' field of love.window.setMode and t.window in love.conf. It's now an integer with 0 disabling vsync.
   * Changed the audio playback APIs drastically.
   * Changed enet to no longer set the 'enet' global, again matching luasocket.
   * Changed Source seeking behaviour, all kinds of Sources now behave similarly when seeking past the boundaries.
 
+  * Fixed a memory leak when sending love objects to threads which never load that object's module.
+  * Fixed os.execute always returning -1 in Linux.
+  * Fixed the default reference angle for WeldJoint, PrismaticJoint, and RevoluteJoint.
+  * Fixed love.graphics.set/getClipboard text to error instead of crashing, when a window hasn't been created.
   * Fixed Joystick:getGamepadMapping to work with xinput controllers.
+  * Fixed baseline calculation when rendering text.
+  * Fixed VaryingTexCoords and love_ScreenSize in shaders to be 'highp' in OpenGL ES, when supported.
 
   * Improved performance when drawing textures, shapes, lines, and points by automatically batching their draw calls together when possible.
   * Improved performance of Shader:send when the Shader is not active.
+  * Improved performance of love.math.randomNormal when LuaJIT's JIT compiler is enabled. 
 
   * Renamed love.window.getPixelScale to love.window.getPixelDensity.
 
@@ -58,6 +84,7 @@ Released: N/A
   * Removed variant of love.filesystem.newFileData which takes base64 data, use love.math.decode instead.
   * Removed the no-argument variant of Text:set, use Text:clear instead.
   * Removed love.graphics.getCompressedImageFormats, use love.graphics.getImageFormats instead.
+  * Removed the 'void effects(...)' pixel shader entry point. Use the new 'void effect()' instead.
   * Removed Shader:getExternVariable, use Shader:hasUniform instead.
   * Removed love.graphics.newScreenshot, use love.graphics.captureScreenshot instead.
   * Removed deprecated enet function host:socket_get_address.
@@ -66,9 +93,12 @@ Released: N/A
     * Removed love.window.isCreated (use love.window.isOpen instead).
 
   * Updated Source:seek to work if the Source isn't playing.
+  * Updated love.event.poll to stop allocating memory unnecessarily.
   * Updated love.math.random to have improved numeric distribution.
   * Updated love.graphics to support Core Profile OpenGL 3.3+ when available.
+  * Updated shaders to always expose derivative functions (dFdx, dFdy, fwidth) when available in OpenGL ES.
   * Updated love.graphics.circle/ellipse/arc/rectangle to take transformation scale into account when determining the number of segments to use.
+  * Updated Font glyph generation to improve antialiasing.
   * Updated Canvas:newImageData to return an ImageData with a format that matches the Canvas' as closely as possible.
   * Updated love.graphics.newImage to treat file names ending with "@2x", "@3x", etc. as a pixel density scale factor if none is explicitly supplied.
   * Updated luasocket to version 3.0rc1.
@@ -83,6 +113,7 @@ Released: N/A
   * Fixed Shader:send and Shader:sendColor ignoring the last argument for an array.
   * Fixed a crash when love.graphics.pop is called after a love.window.setMode while the transformation stack was not empty.
   * Fixed BezierCurves to error instead of hanging in some situations.
+  * Fixed compilation of luasocket with newer luajit 2.1.0 beta versions.
 
   * Improved command line argument handling.
   * Improved seeking support, especially for short video files.

+ 129 - 53
license.txt

@@ -23,9 +23,9 @@ distribution.
 
 ---------
 
-This software uses LuaJIT:
+This software uses ENet:
 
-LuaJIT is Copyright (c) 2005-2016 Mike Pall
+Copyright (c) 2002-2016 Lee Salzman
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -47,6 +47,31 @@ THE SOFTWARE.
 
 ---------
 
+This software uses GLAD:
+
+ The MIT License (MIT)
+ 
+ Copyright (c) 2013 David Herberth, modified by Alex Szpakowski
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
+ this software and associated documentation files (the "Software"), to deal in
+ the Software without restriction, including without limitation the rights to
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ the Software, and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+---------
+
 This software uses glslang:
 
 Copyright (C) 2002-2005  3Dlabs Inc. Ltd.
@@ -85,9 +110,9 @@ POSSIBILITY OF SUCH DAMAGE.
 
 ---------
 
-This software uses ENet:
+This software uses lua-enet:
 
-Copyright (c) 2002-2016 Lee Salzman
+Copyright (C) 2011 by Leaf Corcoran
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -109,9 +134,9 @@ THE SOFTWARE.
 
 ---------
 
-This software uses lua-enet:
+This software uses LuaJIT:
 
-Copyright (C) 2011 by Leaf Corcoran
+LuaJIT is Copyright (c) 2005-2016 Mike Pall
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -133,30 +158,52 @@ THE SOFTWARE.
 
 ---------
 
-This software uses UTF8-CPP:
+This software uses Lua's UTF-8 module:
 
-Copyright 2006 Nemanja Trifunovic
+Copyright (C) 1994-2015 Lua.org, PUC-Rio, 2015 LOVE Development Team.
 
-Permission is hereby granted, free of charge, to any person or organization
-obtaining a copy of the software and accompanying documentation covered by
-this license (the "Software") to use, reproduce, display, distribute,
-execute, and transmit the Software, and to prepare derivative works of the
-Software, and to permit third-parties to whom the Software is furnished to
-do so, all subject to the following:
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
 
-The copyright notices in the Software and this entire statement, including
-the above license grant, this restriction and the following disclaimer,
-must be included in all copies of the Software, in whole or in part, and
-all derivative works of the Software, unless such copies or derivative
-works are solely in the form of machine-executable object code generated by
-a source language processor.
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+---------
+
+This software uses luasocket:
+
+LuaSocket 3.0 license
+Copyright (C) 2004-2013 Diego Nehab
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
-SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
-FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
-ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 DEALINGS IN THE SOFTWARE.
 
 ---------
@@ -197,6 +244,63 @@ You can contact the author at :
 
 ---------
 
+This software uses TinyEXR:
+
+Copyright (c) 2014 - 2016, Syoyo Fujita
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the <organization> nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+---------
+
+This software uses UTF8-CPP:
+
+Copyright 2006 Nemanja Trifunovic
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+---------
+
 This software uses xxHash:
 
 xxHash - Extremely Fast Hash algorithm
@@ -232,41 +336,13 @@ You can contact the author at :
 
 ---------
 
-This software uses TinyEXR:
-
-Copyright (c) 2014 - 2016, Syoyo Fujita
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-    * Redistributions of source code must retain the above copyright
-      notice, this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright
-      notice, this list of conditions and the following disclaimer in the
-      documentation and/or other materials provided with the distribution.
-    * Neither the name of the <organization> nor the
-      names of its contributors may be used to endorse or promote products
-      derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
----------
-
 This software uses the following LGPL libraries on Windows, Mac OS X, Linux,
 and Android:
 
  - libmpg123
      Website: http://www.mpg123.de/
      Source download: http://sourceforge.net/projects/mpg123/files/latest/download
+
  - OpenAL Soft
      Website: http://kcat.strangesoft.net/openal.html
      Source download: http://kcat.strangesoft.net/openal.html#download

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

@@ -415,7 +415,6 @@
 		FA0B7D2B1A95902C000E1D17 /* wrap_Rasterizer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B851A95902C000E1D17 /* wrap_Rasterizer.cpp */; };
 		FA0B7D2C1A95902C000E1D17 /* wrap_Rasterizer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B851A95902C000E1D17 /* wrap_Rasterizer.cpp */; };
 		FA0B7D2D1A95902C000E1D17 /* wrap_Rasterizer.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7B861A95902C000E1D17 /* wrap_Rasterizer.h */; };
-		FA0B7D2E1A95902C000E1D17 /* Color.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7B881A95902C000E1D17 /* Color.h */; };
 		FA0B7D2F1A95902C000E1D17 /* Drawable.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7B891A95902C000E1D17 /* Drawable.h */; };
 		FA0B7D301A95902C000E1D17 /* Graphics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */; };
 		FA0B7D311A95902C000E1D17 /* Graphics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */; };
@@ -469,22 +468,9 @@
 		FA0B7D861A95902C000E1D17 /* ImageData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BC61A95902C000E1D17 /* ImageData.cpp */; };
 		FA0B7D871A95902C000E1D17 /* ImageData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BC61A95902C000E1D17 /* ImageData.cpp */; };
 		FA0B7D881A95902C000E1D17 /* ImageData.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7BC71A95902C000E1D17 /* ImageData.h */; };
-		FA0B7D891A95902C000E1D17 /* CompressedImageData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BC91A95902C000E1D17 /* CompressedImageData.cpp */; };
-		FA0B7D8A1A95902C000E1D17 /* CompressedImageData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BC91A95902C000E1D17 /* CompressedImageData.cpp */; };
-		FA0B7D8B1A95902C000E1D17 /* CompressedImageData.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7BCA1A95902C000E1D17 /* CompressedImageData.h */; };
-		FA0B7D8C1A95902C000E1D17 /* CompressedFormatHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7BCB1A95902C000E1D17 /* CompressedFormatHandler.h */; };
 		FA0B7D8D1A95902C000E1D17 /* ddsHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BCC1A95902C000E1D17 /* ddsHandler.cpp */; };
 		FA0B7D8E1A95902C000E1D17 /* ddsHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BCC1A95902C000E1D17 /* ddsHandler.cpp */; };
 		FA0B7D8F1A95902C000E1D17 /* ddsHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7BCD1A95902C000E1D17 /* ddsHandler.h */; };
-		FA0B7D901A95902C000E1D17 /* FormatHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BCE1A95902C000E1D17 /* FormatHandler.cpp */; };
-		FA0B7D911A95902C000E1D17 /* FormatHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BCE1A95902C000E1D17 /* FormatHandler.cpp */; };
-		FA0B7D921A95902C000E1D17 /* FormatHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7BCF1A95902C000E1D17 /* FormatHandler.h */; };
-		FA0B7D931A95902C000E1D17 /* Image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BD01A95902C000E1D17 /* Image.cpp */; };
-		FA0B7D941A95902C000E1D17 /* Image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BD01A95902C000E1D17 /* Image.cpp */; };
-		FA0B7D951A95902C000E1D17 /* Image.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7BD11A95902C000E1D17 /* Image.h */; };
-		FA0B7D961A95902C000E1D17 /* ImageData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BD21A95902C000E1D17 /* ImageData.cpp */; };
-		FA0B7D971A95902C000E1D17 /* ImageData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BD21A95902C000E1D17 /* ImageData.cpp */; };
-		FA0B7D981A95902C000E1D17 /* ImageData.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7BD31A95902C000E1D17 /* ImageData.h */; };
 		FA0B7D9F1A95902C000E1D17 /* KTXHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BD81A95902C000E1D17 /* KTXHandler.cpp */; };
 		FA0B7DA01A95902C000E1D17 /* KTXHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BD81A95902C000E1D17 /* KTXHandler.cpp */; };
 		FA0B7DA11A95902C000E1D17 /* KTXHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7BD91A95902C000E1D17 /* KTXHandler.h */; };
@@ -938,6 +924,7 @@
 		FA620A371AA2F8DB005DB4C2 /* wrap_Texture.h in Headers */ = {isa = PBXBuildFile; fileRef = FA620A311AA2F8DB005DB4C2 /* wrap_Texture.h */; };
 		FA620A3A1AA305F6005DB4C2 /* types.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA620A391AA305F6005DB4C2 /* types.cpp */; };
 		FA620A3B1AA305F6005DB4C2 /* types.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA620A391AA305F6005DB4C2 /* types.cpp */; };
+		FA6BDE5C1F31725300786805 /* Color.h in Headers */ = {isa = PBXBuildFile; fileRef = FA6BDE5B1F31725300786805 /* Color.h */; };
 		FA7550A81AEBE276003E311E /* libluajit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA7550A71AEBE276003E311E /* libluajit.a */; };
 		FA76344A1E28722A0066EF9E /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7634481E28722A0066EF9E /* StreamBuffer.cpp */; };
 		FA76344B1E28722A0066EF9E /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7634481E28722A0066EF9E /* StreamBuffer.cpp */; };
@@ -948,6 +935,9 @@
 		FA91591E1CF1ED7500A7053F /* halffloat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA91591C1CF1ED7500A7053F /* halffloat.cpp */; };
 		FA91591F1CF1ED7500A7053F /* halffloat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA91591C1CF1ED7500A7053F /* halffloat.cpp */; };
 		FA9159201CF1ED7500A7053F /* halffloat.h in Headers */ = {isa = PBXBuildFile; fileRef = FA91591D1CF1ED7500A7053F /* halffloat.h */; };
+		FA93C4521F315B960087CCD4 /* CompressedFormatHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = FA93C44F1F315B960087CCD4 /* CompressedFormatHandler.h */; };
+		FA93C4531F315B960087CCD4 /* FormatHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = FA93C4501F315B960087CCD4 /* FormatHandler.h */; };
+		FA93C4541F315B960087CCD4 /* FormatHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA93C4511F315B960087CCD4 /* FormatHandler.cpp */; };
 		FA9B4A0816E1578300074F42 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA9B4A0716E1578300074F42 /* SDL2.framework */; };
 		FA9D8DD11DEB56C3002CD881 /* pixelformat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA9D8DCF1DEB56C3002CD881 /* pixelformat.cpp */; };
 		FA9D8DD21DEB56C3002CD881 /* pixelformat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA9D8DCF1DEB56C3002CD881 /* pixelformat.cpp */; };
@@ -1036,6 +1026,10 @@
 		FADF543D1E3DAFF700012CC0 /* wrap_Graphics.h in Headers */ = {isa = PBXBuildFile; fileRef = FADF543A1E3DAFF700012CC0 /* wrap_Graphics.h */; };
 		FAE272521C05A15B00A67640 /* ParticleSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAE272501C05A15B00A67640 /* ParticleSystem.cpp */; };
 		FAE272531C05A15B00A67640 /* ParticleSystem.h in Headers */ = {isa = PBXBuildFile; fileRef = FAE272511C05A15B00A67640 /* ParticleSystem.h */; };
+		FAECA1B21F3164700095D008 /* CompressedSlice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAECA1B01F3164700095D008 /* CompressedSlice.cpp */; };
+		FAECA1B31F3164700095D008 /* CompressedSlice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAECA1B01F3164700095D008 /* CompressedSlice.cpp */; };
+		FAECA1B41F3164700095D008 /* CompressedSlice.h in Headers */ = {isa = PBXBuildFile; fileRef = FAECA1B11F3164700095D008 /* CompressedSlice.h */; };
+		FAECA1B51F31648A0095D008 /* FormatHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA93C4511F315B960087CCD4 /* FormatHandler.cpp */; };
 		FAF140531E20934C00F898D2 /* CodeGen.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAF13FC21E20934C00F898D2 /* CodeGen.cpp */; };
 		FAF140541E20934C00F898D2 /* CodeGen.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAF13FC21E20934C00F898D2 /* CodeGen.cpp */; };
 		FAF140551E20934C00F898D2 /* Link.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAF13FC31E20934C00F898D2 /* Link.cpp */; };
@@ -1445,7 +1439,6 @@
 		FA0B7B841A95902C000E1D17 /* wrap_GlyphData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_GlyphData.h; sourceTree = "<group>"; };
 		FA0B7B851A95902C000E1D17 /* wrap_Rasterizer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Rasterizer.cpp; sourceTree = "<group>"; };
 		FA0B7B861A95902C000E1D17 /* wrap_Rasterizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Rasterizer.h; sourceTree = "<group>"; };
-		FA0B7B881A95902C000E1D17 /* Color.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Color.h; sourceTree = "<group>"; };
 		FA0B7B891A95902C000E1D17 /* Drawable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Drawable.h; sourceTree = "<group>"; };
 		FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = Graphics.cpp; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.cpp; };
 		FA0B7B8B1A95902C000E1D17 /* Graphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Graphics.h; sourceTree = "<group>"; };
@@ -1482,17 +1475,8 @@
 		FA0B7BC51A95902C000E1D17 /* Image.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Image.h; sourceTree = "<group>"; };
 		FA0B7BC61A95902C000E1D17 /* ImageData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImageData.cpp; sourceTree = "<group>"; };
 		FA0B7BC71A95902C000E1D17 /* ImageData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageData.h; sourceTree = "<group>"; };
-		FA0B7BC91A95902C000E1D17 /* CompressedImageData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CompressedImageData.cpp; sourceTree = "<group>"; };
-		FA0B7BCA1A95902C000E1D17 /* CompressedImageData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CompressedImageData.h; sourceTree = "<group>"; };
-		FA0B7BCB1A95902C000E1D17 /* CompressedFormatHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CompressedFormatHandler.h; sourceTree = "<group>"; };
 		FA0B7BCC1A95902C000E1D17 /* ddsHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ddsHandler.cpp; sourceTree = "<group>"; };
 		FA0B7BCD1A95902C000E1D17 /* ddsHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ddsHandler.h; sourceTree = "<group>"; };
-		FA0B7BCE1A95902C000E1D17 /* FormatHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FormatHandler.cpp; sourceTree = "<group>"; };
-		FA0B7BCF1A95902C000E1D17 /* FormatHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FormatHandler.h; sourceTree = "<group>"; };
-		FA0B7BD01A95902C000E1D17 /* Image.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Image.cpp; sourceTree = "<group>"; };
-		FA0B7BD11A95902C000E1D17 /* Image.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Image.h; sourceTree = "<group>"; };
-		FA0B7BD21A95902C000E1D17 /* ImageData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImageData.cpp; sourceTree = "<group>"; };
-		FA0B7BD31A95902C000E1D17 /* ImageData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageData.h; sourceTree = "<group>"; };
 		FA0B7BD81A95902C000E1D17 /* KTXHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KTXHandler.cpp; sourceTree = "<group>"; };
 		FA0B7BD91A95902C000E1D17 /* KTXHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KTXHandler.h; sourceTree = "<group>"; };
 		FA0B7BDA1A95902C000E1D17 /* PKMHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PKMHandler.cpp; sourceTree = "<group>"; };
@@ -1800,6 +1784,7 @@
 		FA620A301AA2F8DB005DB4C2 /* wrap_Texture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Texture.cpp; sourceTree = "<group>"; };
 		FA620A311AA2F8DB005DB4C2 /* wrap_Texture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Texture.h; sourceTree = "<group>"; };
 		FA620A391AA305F6005DB4C2 /* types.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = types.cpp; sourceTree = "<group>"; };
+		FA6BDE5B1F31725300786805 /* Color.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Color.h; sourceTree = "<group>"; };
 		FA7550A71AEBE276003E311E /* libluajit.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libluajit.a; sourceTree = "<group>"; };
 		FA7634481E28722A0066EF9E /* StreamBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StreamBuffer.cpp; sourceTree = "<group>"; };
 		FA7634491E28722A0066EF9E /* StreamBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamBuffer.h; sourceTree = "<group>"; };
@@ -1808,6 +1793,9 @@
 		FA8951A11AA2EDF300EC385A /* wrap_Event.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Event.h; sourceTree = "<group>"; };
 		FA91591C1CF1ED7500A7053F /* halffloat.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = halffloat.cpp; sourceTree = "<group>"; };
 		FA91591D1CF1ED7500A7053F /* halffloat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = halffloat.h; sourceTree = "<group>"; };
+		FA93C44F1F315B960087CCD4 /* CompressedFormatHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CompressedFormatHandler.h; sourceTree = "<group>"; };
+		FA93C4501F315B960087CCD4 /* FormatHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FormatHandler.h; sourceTree = "<group>"; };
+		FA93C4511F315B960087CCD4 /* FormatHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FormatHandler.cpp; sourceTree = "<group>"; };
 		FA9B4A0716E1578300074F42 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = /Library/Frameworks/SDL2.framework; sourceTree = "<absolute>"; };
 		FA9D8DCF1DEB56C3002CD881 /* pixelformat.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pixelformat.cpp; sourceTree = "<group>"; };
 		FA9D8DD01DEB56C3002CD881 /* pixelformat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pixelformat.h; sourceTree = "<group>"; };
@@ -1870,6 +1858,8 @@
 		FADF543A1E3DAFF700012CC0 /* wrap_Graphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Graphics.h; sourceTree = "<group>"; };
 		FAE272501C05A15B00A67640 /* ParticleSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ParticleSystem.cpp; sourceTree = "<group>"; };
 		FAE272511C05A15B00A67640 /* ParticleSystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ParticleSystem.h; sourceTree = "<group>"; };
+		FAECA1B01F3164700095D008 /* CompressedSlice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CompressedSlice.cpp; sourceTree = "<group>"; };
+		FAECA1B11F3164700095D008 /* CompressedSlice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CompressedSlice.h; sourceTree = "<group>"; };
 		FAF13FC21E20934C00F898D2 /* CodeGen.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CodeGen.cpp; sourceTree = "<group>"; };
 		FAF13FC31E20934C00F898D2 /* Link.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Link.cpp; sourceTree = "<group>"; };
 		FAF13FC51E20934C00F898D2 /* arrays.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = arrays.h; sourceTree = "<group>"; };
@@ -2029,6 +2019,7 @@
 				FAA3A9AD1B7D465A00CED060 /* android.h */,
 				FA0B78F71A958E3B000E1D17 /* b64.cpp */,
 				FA0B78F81A958E3B000E1D17 /* b64.h */,
+				FA6BDE5B1F31725300786805 /* Color.h */,
 				FA0B78F91A958E3B000E1D17 /* config.h */,
 				FA9D8DD41DEF8411002CD881 /* Data.cpp */,
 				FA0B78FA1A958E3B000E1D17 /* Data.h */,
@@ -2660,7 +2651,6 @@
 				FADF53F71E3C7ACD00012CC0 /* Buffer.h */,
 				FA1BA0A51E16F20600AA2803 /* Canvas.cpp */,
 				FA1BA0A61E16F20600AA2803 /* Canvas.h */,
-				FA0B7B881A95902C000E1D17 /* Color.h */,
 				FAF1889E1E9DBC4B008C1479 /* depthstencil.cpp */,
 				FAF1889D1E9DBBC8008C1479 /* depthstencil.h */,
 				FA9D8DDC1DEF842A002CD881 /* Drawable.cpp */,
@@ -2760,8 +2750,13 @@
 		FA0B7BC21A95902C000E1D17 /* image */ = {
 			isa = PBXGroup;
 			children = (
+				FA93C44F1F315B960087CCD4 /* CompressedFormatHandler.h */,
 				FA0B7BC31A95902C000E1D17 /* CompressedImageData.cpp */,
 				FA0B7BC41A95902C000E1D17 /* CompressedImageData.h */,
+				FAECA1B01F3164700095D008 /* CompressedSlice.cpp */,
+				FAECA1B11F3164700095D008 /* CompressedSlice.h */,
+				FA93C4511F315B960087CCD4 /* FormatHandler.cpp */,
+				FA93C4501F315B960087CCD4 /* FormatHandler.h */,
 				FA9D8DDF1DEF843D002CD881 /* Image.cpp */,
 				FA0B7BC51A95902C000E1D17 /* Image.h */,
 				FA0B7BC61A95902C000E1D17 /* ImageData.cpp */,
@@ -2785,19 +2780,10 @@
 			children = (
 				FA41A3C61C0A1F950084430C /* ASTCHandler.cpp */,
 				FA41A3C71C0A1F950084430C /* ASTCHandler.h */,
-				FA0B7BCB1A95902C000E1D17 /* CompressedFormatHandler.h */,
-				FA0B7BC91A95902C000E1D17 /* CompressedImageData.cpp */,
-				FA0B7BCA1A95902C000E1D17 /* CompressedImageData.h */,
 				FA0B7BCC1A95902C000E1D17 /* ddsHandler.cpp */,
 				FA0B7BCD1A95902C000E1D17 /* ddsHandler.h */,
 				FA1557C11CE90BD200AFF582 /* EXRHandler.cpp */,
 				FA1557C21CE90BD200AFF582 /* EXRHandler.h */,
-				FA0B7BCE1A95902C000E1D17 /* FormatHandler.cpp */,
-				FA0B7BCF1A95902C000E1D17 /* FormatHandler.h */,
-				FA0B7BD01A95902C000E1D17 /* Image.cpp */,
-				FA0B7BD11A95902C000E1D17 /* Image.h */,
-				FA0B7BD21A95902C000E1D17 /* ImageData.cpp */,
-				FA0B7BD31A95902C000E1D17 /* ImageData.h */,
 				FA0B7BD81A95902C000E1D17 /* KTXHandler.cpp */,
 				FA0B7BD91A95902C000E1D17 /* KTXHandler.h */,
 				FA0B7BDA1A95902C000E1D17 /* PKMHandler.cpp */,
@@ -3550,7 +3536,6 @@
 				FA76344C1E28722A0066EF9E /* StreamBuffer.h in Headers */,
 				217DFBF31D9F6D490055D849 /* mime.h in Headers */,
 				FA0B7B361A958EA3000E1D17 /* wuff_convert.h in Headers */,
-				FA0B7D981A95902C000E1D17 /* ImageData.h in Headers */,
 				FA0B7CDE1A95902C000E1D17 /* Source.h in Headers */,
 				FA0B7E141A95902C000E1D17 /* GearJoint.h in Headers */,
 				FA0B7D051A95902C000E1D17 /* wrap_DroppedFile.h in Headers */,
@@ -3612,6 +3597,7 @@
 				FA0B7CD21A95902C000E1D17 /* Audio.h in Headers */,
 				FA0B79421A958E3B000E1D17 /* utf8.h in Headers */,
 				FA0B7D171A95902C000E1D17 /* Font.h in Headers */,
+				FAECA1B41F3164700095D008 /* CompressedSlice.h in Headers */,
 				FA0B7A6D1A958EA3000E1D17 /* b2World.h in Headers */,
 				FA0B7EAE1A95902C000E1D17 /* wrap_SoundData.h in Headers */,
 				FA0B7CFF1A95902C000E1D17 /* File.h in Headers */,
@@ -3661,6 +3647,7 @@
 				FA0B7E351A95902C000E1D17 /* WeldJoint.h in Headers */,
 				FA0B7E2F1A95902C000E1D17 /* RopeJoint.h in Headers */,
 				FA0B7D141A95902C000E1D17 /* Font.h in Headers */,
+				FA93C4521F315B960087CCD4 /* CompressedFormatHandler.h in Headers */,
 				FA0B7D411A95902C000E1D17 /* Mesh.h in Headers */,
 				FA0B7E591A95902C000E1D17 /* wrap_Joint.h in Headers */,
 				FA0B79351A958E3B000E1D17 /* macosx.h in Headers */,
@@ -3668,6 +3655,7 @@
 				FA0B7E771A95902C000E1D17 /* wrap_WeldJoint.h in Headers */,
 				FA0B7A911A958EA3000E1D17 /* b2FrictionJoint.h in Headers */,
 				FA0B7E291A95902C000E1D17 /* PulleyJoint.h in Headers */,
+				FA6BDE5C1F31725300786805 /* Color.h in Headers */,
 				FA0B7D471A95902C000E1D17 /* ParticleSystem.h in Headers */,
 				FA0B7E231A95902C000E1D17 /* PolygonShape.h in Headers */,
 				FA0B791E1A958E3B000E1D17 /* config.h in Headers */,
@@ -3720,6 +3708,7 @@
 				FA0B7ECD1A95902C000E1D17 /* wrap_Channel.h in Headers */,
 				FAF140AF1E20934C00F898D2 /* osinclude.h in Headers */,
 				FA0B7A431A958EA3000E1D17 /* b2CircleShape.h in Headers */,
+				FA93C4531F315B960087CCD4 /* FormatHandler.h in Headers */,
 				FA0B7CD81A95902C000E1D17 /* Audio.h in Headers */,
 				FA0B7CF61A95902C000E1D17 /* File.h in Headers */,
 				FA0B7E961A95902C000E1D17 /* Mpg123Decoder.h in Headers */,
@@ -3728,7 +3717,6 @@
 				FA0B7DD51A95902C000E1D17 /* BezierCurve.h in Headers */,
 				FA0B79271A958E3B000E1D17 /* int.h in Headers */,
 				FA27B3A21B498151008A9DCE /* VideoStream.h in Headers */,
-				FA0B7D8C1A95902C000E1D17 /* CompressedFormatHandler.h in Headers */,
 				FA0B7E531A95902C000E1D17 /* wrap_FrictionJoint.h in Headers */,
 				FA0B7EB71A95902C000E1D17 /* wrap_System.h in Headers */,
 				FA27B3A91B498151008A9DCE /* Video.h in Headers */,
@@ -3738,7 +3726,6 @@
 				FAB17BF21ABFB37500F9BA27 /* wrap_CompressedData.h in Headers */,
 				FA0B7E801A95902C000E1D17 /* Joint.h in Headers */,
 				FA0B7D2F1A95902C000E1D17 /* Drawable.h in Headers */,
-				FA0B7D951A95902C000E1D17 /* Image.h in Headers */,
 				217DFBE21D9F6D490055D849 /* ftp.lua.h in Headers */,
 				FA0B7EC41A95902C000E1D17 /* Thread.h in Headers */,
 				FA0B7DFF1A95902C000E1D17 /* ChainShape.h in Headers */,
@@ -3794,7 +3781,6 @@
 				FA0B7CF91A95902C000E1D17 /* FileData.h in Headers */,
 				FA0B7DA71A95902C000E1D17 /* PNGHandler.h in Headers */,
 				FA0B7AC41A958EA3000E1D17 /* protocol.h in Headers */,
-				FA0B7D8B1A95902C000E1D17 /* CompressedImageData.h in Headers */,
 				FAF140601E20934C00F898D2 /* revision.h in Headers */,
 				FAF140A21E20934C00F898D2 /* RemoveTree.h in Headers */,
 				FA0B7A8E1A958EA3000E1D17 /* b2DistanceJoint.h in Headers */,
@@ -3876,7 +3862,6 @@
 				FA9D8DD31DEB56C3002CD881 /* pixelformat.h in Headers */,
 				FAF140A61E20934C00F898D2 /* ScanContext.h in Headers */,
 				FA0B7E4A1A95902C000E1D17 /* wrap_DistanceJoint.h in Headers */,
-				FA0B7D2E1A95902C000E1D17 /* Color.h in Headers */,
 				FA0B7A6A1A958EA3000E1D17 /* b2TimeStep.h in Headers */,
 				FA0B7A281A958EA3000E1D17 /* Box2D.h in Headers */,
 				FA0B7DE41A95902C000E1D17 /* wrap_RandomGenerator.h in Headers */,
@@ -3895,7 +3880,6 @@
 				FADF54041E3D77B500012CC0 /* wrap_Text.h in Headers */,
 				FA0B7ED31A95902C000E1D17 /* wrap_ThreadModule.h in Headers */,
 				FA0B7A511A958EA3000E1D17 /* b2GrowableStack.h in Headers */,
-				FA0B7D921A95902C000E1D17 /* FormatHandler.h in Headers */,
 				FAC756F61E4F99B400B91289 /* Effect.h in Headers */,
 				FA0B7ADD1A958EA3000E1D17 /* gladfuncs.hpp in Headers */,
 				FAF1405D1E20934C00F898D2 /* intermediate.h in Headers */,
@@ -4051,7 +4035,6 @@
 				FA0B7ABE1A958EA3000E1D17 /* compress.c in Sources */,
 				FA0B7CF21A95902C000E1D17 /* DroppedFile.cpp in Sources */,
 				FA4F2C141DE936FE00CA37D7 /* usocket.c in Sources */,
-				FA0B7D8A1A95902C000E1D17 /* CompressedImageData.cpp in Sources */,
 				FAF140831E20934C00F898D2 /* ParseContextBase.cpp in Sources */,
 				FA0B7AD21A958EA3000E1D17 /* protocol.c in Sources */,
 				FAF140A41E20934C00F898D2 /* Scan.cpp in Sources */,
@@ -4190,15 +4173,14 @@
 				FADF54211E3DA52C00012CC0 /* wrap_ParticleSystem.cpp in Sources */,
 				FA0B791C1A958E3B000E1D17 /* b64.cpp in Sources */,
 				FA1E88851DF363E100E808AA /* Filter.cpp in Sources */,
-				FA0B7D941A95902C000E1D17 /* Image.cpp in Sources */,
 				FA0B7DA01A95902C000E1D17 /* KTXHandler.cpp in Sources */,
 				FA27B3A11B498151008A9DCE /* VideoStream.cpp in Sources */,
 				FA0B7A5C1A958EA3000E1D17 /* b2Timer.cpp in Sources */,
 				FA0B7CEC1A95902C000E1D17 /* Event.cpp in Sources */,
 				FA27B3AB1B498151008A9DCE /* VideoStream.cpp in Sources */,
 				FA0B7A7E1A958EA3000E1D17 /* b2ContactSolver.cpp in Sources */,
-				FA0B7D971A95902C000E1D17 /* ImageData.cpp in Sources */,
 				FA0B7E581A95902C000E1D17 /* wrap_Joint.cpp in Sources */,
+				FAECA1B51F31648A0095D008 /* FormatHandler.cpp in Sources */,
 				FA4F2C0A1DE936E600CA37D7 /* mime.c in Sources */,
 				FA0B7E311A95902C000E1D17 /* Shape.cpp in Sources */,
 				FA0B7E491A95902C000E1D17 /* wrap_DistanceJoint.cpp in Sources */,
@@ -4287,13 +4269,13 @@
 				FA0B7DB21A95902C000E1D17 /* wrap_Image.cpp in Sources */,
 				FA0B7E891A95902C000E1D17 /* Decoder.cpp in Sources */,
 				FA0B7A591A958EA3000E1D17 /* b2StackAllocator.cpp in Sources */,
+				FAECA1B31F3164700095D008 /* CompressedSlice.cpp in Sources */,
 				FA0B7E3D1A95902C000E1D17 /* wrap_Body.cpp in Sources */,
 				FA0B7D7A1A95902C000E1D17 /* Quad.cpp in Sources */,
 				FA620A3B1AA305F6005DB4C2 /* types.cpp in Sources */,
 				FA0B7DD41A95902C000E1D17 /* BezierCurve.cpp in Sources */,
 				FA0B7E7C1A95902C000E1D17 /* wrap_World.cpp in Sources */,
 				FA4F2C0E1DE936FE00CA37D7 /* tcp.c in Sources */,
-				FA0B7D911A95902C000E1D17 /* FormatHandler.cpp in Sources */,
 				FA0B7D431A95902C000E1D17 /* OpenGL.cpp in Sources */,
 				FA0B7DBF1A95902C000E1D17 /* JoystickModule.cpp in Sources */,
 				FA0B7D4F1A95902C000E1D17 /* SpriteBatch.cpp in Sources */,
@@ -4410,7 +4392,6 @@
 				FA0B7D091A95902C000E1D17 /* wrap_FileData.cpp in Sources */,
 				FA0B7B341A958EA3000E1D17 /* wuff_convert.c in Sources */,
 				FA0B7CF11A95902C000E1D17 /* DroppedFile.cpp in Sources */,
-				FA0B7D891A95902C000E1D17 /* CompressedImageData.cpp in Sources */,
 				FAF140821E20934C00F898D2 /* ParseContextBase.cpp in Sources */,
 				FA0B7D1E1A95902C000E1D17 /* ImageRasterizer.cpp in Sources */,
 				FAF140A31E20934C00F898D2 /* Scan.cpp in Sources */,
@@ -4545,7 +4526,6 @@
 				FA0B7A4E1A958EA3000E1D17 /* b2Draw.cpp in Sources */,
 				FAD19A171DFF8CA200D5398A /* ImageDataBase.cpp in Sources */,
 				FA27B3C01B4985BF008A9DCE /* wrap_VideoStream.cpp in Sources */,
-				FA0B7D931A95902C000E1D17 /* Image.cpp in Sources */,
 				FADF54201E3DA52C00012CC0 /* wrap_ParticleSystem.cpp in Sources */,
 				FA0B7D9F1A95902C000E1D17 /* KTXHandler.cpp in Sources */,
 				FA1E88831DF363DB00E808AA /* Filter.cpp in Sources */,
@@ -4554,9 +4534,9 @@
 				FA0B7CEB1A95902C000E1D17 /* Event.cpp in Sources */,
 				FA1557C31CE90BD200AFF582 /* EXRHandler.cpp in Sources */,
 				FA27B3AA1B498151008A9DCE /* VideoStream.cpp in Sources */,
-				FA0B7D961A95902C000E1D17 /* ImageData.cpp in Sources */,
 				FA0B7E571A95902C000E1D17 /* wrap_Joint.cpp in Sources */,
 				FA0B7E301A95902C000E1D17 /* Shape.cpp in Sources */,
+				FA93C4541F315B960087CCD4 /* FormatHandler.cpp in Sources */,
 				FA0B7E481A95902C000E1D17 /* wrap_DistanceJoint.cpp in Sources */,
 				FA0B7A8F1A958EA3000E1D17 /* b2FrictionJoint.cpp in Sources */,
 				FA0B792F1A958E3B000E1D17 /* Module.cpp in Sources */,
@@ -4597,6 +4577,7 @@
 				FA0B7E8B1A95902C000E1D17 /* FLACDecoder.cpp in Sources */,
 				FA0B7B3A1A958EA3000E1D17 /* wuff_memory.c in Sources */,
 				FA0B7A381A958EA3000E1D17 /* b2DynamicTree.cpp in Sources */,
+				FAECA1B21F3164700095D008 /* CompressedSlice.cpp in Sources */,
 				FA0B7D481A95902C000E1D17 /* Polyline.cpp in Sources */,
 				217DFC111D9F6D490055D849 /* usocket.c in Sources */,
 				FAF140751E20934C00F898D2 /* IntermTraverse.cpp in Sources */,
@@ -4647,7 +4628,6 @@
 				FA91591E1CF1ED7500A7053F /* halffloat.cpp in Sources */,
 				FA0B7E7B1A95902C000E1D17 /* wrap_World.cpp in Sources */,
 				FA0B7B281A958EA3000E1D17 /* simplexnoise1234.cpp in Sources */,
-				FA0B7D901A95902C000E1D17 /* FormatHandler.cpp in Sources */,
 				FA0B7D421A95902C000E1D17 /* OpenGL.cpp in Sources */,
 				FA0B7A671A958EA3000E1D17 /* b2Island.cpp in Sources */,
 				FA0B7DBE1A95902C000E1D17 /* JoystickModule.cpp in Sources */,

+ 4 - 0
platform/xcode/love.xcodeproj/project.pbxproj

@@ -17,6 +17,7 @@
 		A9D307F2106635D3004FEDF8 /* physfs.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9D307E9106635C3004FEDF8 /* physfs.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		A9F169AC109E825000FC83D1 /* mpg123.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9F169A6109E824900FC83D1 /* mpg123.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		A9F169AD109E825000FC83D1 /* libmodplug.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9F16926109E7BAD00FC83D1 /* libmodplug.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
+		CE73F8001EEB64150052DAB3 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE73F7FF1EEB64150052DAB3 /* AVFoundation.framework */; };
 		FA0797991BF480A200034B7C /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA0797981BF480A200034B7C /* GameController.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
 		FA08F69616C766E000F007B5 /* love.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA08F69116C765A200F007B5 /* love.framework */; };
 		FA08F69716C766E700F007B5 /* love.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = FA08F69116C765A200F007B5 /* love.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
@@ -110,6 +111,7 @@
 		A9D307E9106635C3004FEDF8 /* physfs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = physfs.framework; path = /Library/Frameworks/physfs.framework; sourceTree = "<absolute>"; };
 		A9F16926109E7BAD00FC83D1 /* libmodplug.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = libmodplug.framework; path = /Library/Frameworks/libmodplug.framework; sourceTree = "<absolute>"; };
 		A9F169A6109E824900FC83D1 /* mpg123.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = mpg123.framework; path = /Library/Frameworks/mpg123.framework; sourceTree = "<absolute>"; };
+		CE73F7FF1EEB64150052DAB3 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.3.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
 		FA0797981BF480A200034B7C /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.1.sdk/System/Library/Frameworks/GameController.framework; sourceTree = DEVELOPER_DIR; };
 		FA08F69116C765A200F007B5 /* love.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = love.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		FA0B7F061A95AAF3000E1D17 /* love.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = love.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -149,6 +151,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				CE73F8001EEB64150052DAB3 /* AVFoundation.framework in Frameworks */,
 				FA5D24D11A96E73300C6FC8F /* liblove.a in Frameworks */,
 				FA5D24C21A96D78000C6FC8F /* Foundation.framework in Frameworks */,
 				FA5D24961A96CAC200C6FC8F /* CoreMotion.framework in Frameworks */,
@@ -169,6 +172,7 @@
 		1058C7A0FEA54F0111CA2CBB /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				CE73F7FF1EEB64150052DAB3 /* AVFoundation.framework */,
 				FA5D24801A96C97900C6FC8F /* ios */,
 				FA0B7EEC1A959249000E1D17 /* macosx */,
 			);

+ 3 - 6
src/modules/graphics/Color.h → src/common/Color.h

@@ -18,13 +18,11 @@
  * 3. This notice may not be removed or altered from any source distribution.
  **/
 
-#ifndef LOVE_GRAPHICS_COLOR_H
-#define LOVE_GRAPHICS_COLOR_H
+#ifndef LOVE_COLOR_H
+#define LOVE_COLOR_H
 
 namespace love
 {
-namespace graphics
-{
 
 template <typename T>
 struct ColorT
@@ -153,7 +151,6 @@ inline Colorf toColorf(Color c)
 	return Colorf(c.r / 255.0f, c.g / 255.0f, c.b / 255.0f, c.a / 255.0f);
 }
 
-} // graphics
 } // love
 
-#endif // LOVE_GRAPHICS_COLOR_H
+#endif // LOVE_COLOR_H

+ 16 - 0
src/common/android.cpp

@@ -225,6 +225,22 @@ bool createStorageDirectories()
 	return true;
 }
 
+bool hasBackgroundMusic()
+{
+	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
+	jobject activity = (jobject) SDL_AndroidGetActivity();
+
+	jclass clazz(env->GetObjectClass(activity));
+	jmethodID method_id = env->GetMethodID(clazz, "hasBackgroundMusic", "()Z");
+
+	jboolean result = env->CallBooleanMethod(activity, method_id);
+
+	env->DeleteLocalRef(activity);
+	env->DeleteLocalRef(clazz);
+
+	return result;
+}
+
 } // android
 } // love
 

+ 2 - 0
src/common/android.h

@@ -66,6 +66,8 @@ bool mkdir(const char *path);
 
 bool createStorageDirectories();
 
+bool hasBackgroundMusic();
+
 } // android
 } // love
 

+ 10 - 0
src/common/ios.h

@@ -64,6 +64,16 @@ std::string getExecutablePath();
  **/
 void vibrate();
 
+/**
+ * Enable mix mode (e.g. with background music apps) and playback with a muted device.
+ **/
+void setAudioMixWithOthers(bool mixEnabled);
+
+/**
+ * Returns whether another application is playing audio.
+ **/
+bool hasBackgroundMusic();
+
 } // ios
 } // love
 

+ 23 - 0
src/common/ios.mm

@@ -26,6 +26,7 @@
 #import <UIKit/UIKit.h>
 
 #import <AudioToolbox/AudioServices.h>
+#import <AVFoundation/AVFoundation.h>
 
 #include <vector>
 
@@ -344,6 +345,28 @@ void vibrate()
 	}
 }
 
+void setAudioMixWithOthers(bool mixEnabled)
+{
+	@autoreleasepool
+	{
+		NSString *category = AVAudioSessionCategorySoloAmbient;
+		NSError *err;
+
+		if (mixEnabled)
+			category = AVAudioSessionCategoryAmbient;
+
+		if (![[AVAudioSession sharedInstance] setCategory:category error:&err])
+			NSLog(@"Error in AVAudioSession setCategory: %@", [err localizedDescription]);
+	}
+}
+
+bool hasBackgroundMusic()
+{
+	if ([[AVAudioSession sharedInstance] respondsToSelector:@selector(secondaryAudioShouldBeSilencedHint)])
+		return [[AVAudioSession sharedInstance] secondaryAudioShouldBeSilencedHint];
+	return false;
+}
+
 } // ios
 } // love
 

+ 14 - 16
src/libraries/ddsparse/ddsinfo.h

@@ -1,25 +1,23 @@
 /**
  * Simple DDS data parser for compressed 2D textures.
  *
- * Copyright (c) 2013 Alexander Szpakowski.
+ * Copyright (c) 2013-2017 Alex Szpakowski
  *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
+ * 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.
  *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the 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:
  *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
+ * 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.
  *
  *
  * Enums and structs copied from Microsoft.

+ 14 - 16
src/libraries/ddsparse/ddsparse.cpp

@@ -1,25 +1,23 @@
 /**
  * Simple DDS data parser for compressed 2D textures.
  *
- * Copyright (c) 2013 Alexander Szpakowski.
+ * Copyright (c) 2013-2017 Alex Szpakowski
  *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
+ * 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.
  *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the 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:
  *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
+ * 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 "ddsparse.h"

+ 14 - 16
src/libraries/ddsparse/ddsparse.h

@@ -1,25 +1,23 @@
 /**
  * Simple DDS data parser for compressed 2D textures.
  *
- * Copyright (c) 2013 Alexander Szpakowski.
+ * Copyright (c) 2013-2017 Alex Szpakowski
  *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
+ * 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.
  *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the 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:
  *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
+ * 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 DDS_PARSE_H

ファイルの差分が大きいため隠しています
+ 445 - 276
src/libraries/stb/stb_image.h


+ 18 - 2
src/modules/audio/Audio.cpp

@@ -13,18 +13,34 @@
  *    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 Audio versions must be plainly marked as such, and must not be
+ * 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 Audio distribution.
+ * 3. This notice may not be removed or altered from any source distribution.
  **/
 
 #include "Audio.h"
+#include "common/config.h"
+
+#ifdef LOVE_IOS
+#include "common/ios.h"
+#endif
 
 namespace love
 {
 namespace audio
 {
 
+bool Audio::setMixWithSystem(bool mix)
+{
+#ifdef LOVE_IOS
+	love::ios::setAudioMixWithOthers(mix);
+	return true;
+#else
+	LOVE_UNUSED(mix);
+	return false;
+#endif
+}
+
 StringMap<Audio::DistanceModel, Audio::DISTANCE_MAX_ENUM>::Entry Audio::distanceModelEntries[] =
 {
 	{"none", Audio::DISTANCE_NONE},

+ 8 - 2
src/modules/audio/Audio.h

@@ -9,7 +9,7 @@
  * 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 = 0; you must not
+ * 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.
@@ -236,7 +236,7 @@ public:
 	 * @param list List of EFX names to fill.
 	 * @return true if effect was present, false otherwise.
 	 */
-	virtual bool getEffectsList(std::vector<std::string> &list) = 0;
+	virtual bool getActiveEffects(std::vector<std::string> &list) const = 0;
 
 	/**
 	 * Gets maximum number of scene EFX effects.
@@ -256,6 +256,12 @@ public:
 	 */
 	virtual bool isEFXsupported() const = 0;
 
+	/**
+	 * Sets whether audio from other apps mixes with love.audio or is muted,
+	 * on supported platforms.
+	 **/
+	bool setMixWithSystem(bool mix);
+
 private:
 
 	static StringMap<DistanceModel, DISTANCE_MAX_ENUM>::Entry distanceModelEntries[];

+ 1 - 1
src/modules/audio/Effect.cpp

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2006-2016 LOVE Development Team
+ * Copyright (c) 2006-2017 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

+ 1 - 1
src/modules/audio/Effect.h

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2006-2016 LOVE Development Team
+ * Copyright (c) 2006-2017 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

+ 12 - 7
src/modules/audio/RecordingDevice.h

@@ -9,7 +9,7 @@
  * 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 = 0; you must not
+ * 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.
@@ -37,15 +37,14 @@ public:
 
 	static love::Type type;
 
+	static const int DEFAULT_SAMPLES = 8192;
+	static const int DEFAULT_SAMPLE_RATE = 8000;
+	static const int DEFAULT_BIT_DEPTH = 16;
+	static const int DEFAULT_CHANNELS = 1;
+
 	RecordingDevice();
 	virtual ~RecordingDevice();
 
-	/**
-	 * Begins audio input recording process. using default (previous) parameters.
-	 * @return True if recording started successfully.
-	 **/
-	virtual bool start() = 0;
-
 	/**
 	 * Begins audio input recording process.
 	 * @param samples Number of samples to buffer.
@@ -77,6 +76,11 @@ public:
 	 **/
 	virtual int getSampleCount() const = 0;
 
+	/**
+	 * Gets the maximum number of samples that will be buffered, as set by start().
+	 **/
+	virtual int getMaxSamples() const = 0;
+
 	/**
 	 * @return Sample rate for recording.
 	 **/
@@ -96,6 +100,7 @@ public:
 	 * @return True if currently recording.
 	 **/
 	virtual bool isRecording() const = 0;
+
 }; //RecordingDevice
 
 } //audio

+ 1 - 1
src/modules/audio/Source.h

@@ -117,7 +117,7 @@ public:
 	virtual bool setEffect(const char *effect, const std::map<Filter::Parameter, float> &params) = 0;
 	virtual bool unsetEffect(const char *effect) = 0;
 	virtual bool getEffect(const char *effect, std::map<Filter::Parameter, float> &params) = 0;
-	virtual bool getEffectsList(std::vector<std::string> &list) = 0;
+	virtual bool getActiveEffects(std::vector<std::string> &list) const = 0;
 
 	virtual int getFreeBufferCount() const = 0;
 	virtual bool queue(void *data, size_t length, int dataSampleRate, int dataBitDepth, int dataChannels) = 0;

+ 1 - 1
src/modules/audio/null/Audio.cpp

@@ -183,7 +183,7 @@ bool Audio::getEffect(const char *, std::map<Effect::Parameter, float> &)
 	return false;
 }
 
-bool Audio::getEffectsList(std::vector<std::string> &list)
+bool Audio::getActiveEffects(std::vector<std::string> &) const
 {
 	return false;
 }

+ 1 - 1
src/modules/audio/null/Audio.h

@@ -81,7 +81,7 @@ public:
 	bool setEffect(const char *, std::map<Effect::Parameter, float> &params);
 	bool unsetEffect(const char *);
 	bool getEffect(const char *, std::map<Effect::Parameter, float> &params);
-	bool getEffectsList(std::vector<std::string> &list);
+	bool getActiveEffects(std::vector<std::string> &list) const;
 	int getMaxSceneEffects() const;
 	int getMaxSourceEffects() const;
 	bool isEFXsupported() const;

+ 9 - 9
src/modules/audio/null/RecordingDevice.cpp

@@ -9,7 +9,7 @@
  * 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 = 0; you must not
+ * 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.
@@ -38,11 +38,6 @@ RecordingDevice::~RecordingDevice()
 {
 }
 
-bool RecordingDevice::start()
-{
-	return false;
-}
-
 bool RecordingDevice::start(int, int, int, int)
 {
 	return false;
@@ -62,19 +57,24 @@ int RecordingDevice::getSampleCount() const
 	return 0;
 }
 
+int RecordingDevice::getMaxSamples() const
+{
+	return 0;
+}
+
 int RecordingDevice::getSampleRate() const
 {
-	return 8000;
+	return 0;
 }
 
 int RecordingDevice::getBitDepth() const
 {
-	return 16;
+	return 0;
 }
 
 int RecordingDevice::getChannels() const
 {
-	return 1;
+	return 0;
 }
 
 const char *RecordingDevice::getName() const

+ 2 - 2
src/modules/audio/null/RecordingDevice.h

@@ -9,7 +9,7 @@
  * 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 = 0; you must not
+ * 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.
@@ -36,11 +36,11 @@ class RecordingDevice : public love::audio::RecordingDevice
 public:
 	RecordingDevice(const char *name);
 	virtual ~RecordingDevice();
-	virtual bool start();
 	virtual bool start(int samples, int sampleRate, int bitDepth, int channels);
 	virtual void stop();
 	virtual love::sound::SoundData *getData();
 	virtual const char *getName() const;
+	virtual int getMaxSamples() const;
 	virtual int getSampleCount() const;
 	virtual int getSampleRate() const;
 	virtual int getBitDepth() const;

+ 1 - 1
src/modules/audio/null/Source.cpp

@@ -274,7 +274,7 @@ bool Source::getEffect(const char *, std::map<Filter::Parameter, float> &)
 	return false;
 }
 
-bool Source::getEffectsList(std::vector<std::string> &)
+bool Source::getActiveEffects(std::vector<std::string> &) const
 {
 	return false;
 }

+ 1 - 1
src/modules/audio/null/Source.h

@@ -90,7 +90,7 @@ public:
 	virtual bool setEffect(const char *effect, const std::map<Filter::Parameter, float> &params);
 	virtual bool unsetEffect(const char *effect);
 	virtual bool getEffect(const char *effect, std::map<Filter::Parameter, float> &params);
-	virtual bool getEffectsList(std::vector<std::string> &list);
+	virtual bool getActiveEffects(std::vector<std::string> &list) const;
 
 private:
 

+ 1 - 1
src/modules/audio/openal/Audio.cpp

@@ -543,7 +543,7 @@ bool Audio::getEffect(const char *name, std::map<Effect::Parameter, float> &para
 	return true;
 }
 
-bool Audio::getEffectsList(std::vector<std::string> &list)
+bool Audio::getActiveEffects(std::vector<std::string> &list) const
 {
 	if (effectmap.empty())
 		return false;

+ 1 - 1
src/modules/audio/openal/Audio.h

@@ -118,7 +118,7 @@ public:
 	bool setEffect(const char *name, std::map<Effect::Parameter, float> &params);
 	bool unsetEffect(const char *name);
 	bool getEffect(const char *name, std::map<Effect::Parameter, float> &params);
-	bool getEffectsList(std::vector<std::string> &list);
+	bool getActiveEffects(std::vector<std::string> &list) const;
 	int getMaxSceneEffects() const;
 	int getMaxSourceEffects() const;
 	bool isEFXsupported() const;

+ 12 - 17
src/modules/audio/openal/RecordingDevice.cpp

@@ -9,7 +9,7 @@
  * 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 = 0; you must not
+ * 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.
@@ -49,26 +49,11 @@ RecordingDevice::RecordingDevice(const char *name)
 
 RecordingDevice::~RecordingDevice()
 {
-	if (!isRecording())
-		return;
-
-	alcCaptureStop(device);
-	alcCaptureCloseDevice(device);
-}
-
-bool RecordingDevice::start()
-{
-	return start(samples, sampleRate, bitDepth, channels);
+	stop();
 }
 
 bool RecordingDevice::start(int samples, int sampleRate, int bitDepth, int channels)
 {
-	if (isRecording())
-	{
-		alcCaptureStop(device);
-		alcCaptureCloseDevice(device);
-	}
-
 	ALenum format = Audio::getFormat(bitDepth, channels);
 	if (format == AL_NONE)
 		throw InvalidFormatException(channels, bitDepth);
@@ -79,15 +64,20 @@ bool RecordingDevice::start(int samples, int sampleRate, int bitDepth, int chann
 	if (sampleRate <= 0)
 		throw love::Exception("Invalid sample rate.");
 
+	if (isRecording())
+		stop();
+
 	device = alcCaptureOpenDevice(name.c_str(), sampleRate, format, samples);
 	if (device == nullptr)
 		return false;
 
 	alcCaptureStart(device);
+
 	this->samples = samples;
 	this->sampleRate = sampleRate;
 	this->bitDepth = bitDepth;
 	this->channels = channels;
+
 	return true;
 }
 
@@ -127,6 +117,11 @@ int RecordingDevice::getSampleCount() const
 	return (int)samples;
 }
 
+int RecordingDevice::getMaxSamples() const
+{
+	return samples;
+}
+
 int RecordingDevice::getSampleRate() const
 {
 	return sampleRate;

+ 10 - 6
src/modules/audio/openal/RecordingDevice.h

@@ -9,7 +9,7 @@
  * 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 = 0; you must not
+ * 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.
@@ -49,26 +49,30 @@ namespace openal
 class RecordingDevice : public love::audio::RecordingDevice
 {
 public:
+
 	RecordingDevice(const char *name);
 	virtual ~RecordingDevice();
-	virtual bool start();
 	virtual bool start(int samples, int sampleRate, int bitDepth, int channels);
 	virtual void stop();
 	virtual love::sound::SoundData *getData();
 	virtual const char *getName() const;
 	virtual int getSampleCount() const;
+	virtual int getMaxSamples() const;
 	virtual int getSampleRate() const;
 	virtual int getBitDepth() const;
 	virtual int getChannels() const;
 	virtual bool isRecording() const;
 
 private:
-	int samples = 8192;
-	int sampleRate = 8000;
-	int bitDepth = 16;
-	int channels = 1;
+
+	int samples = DEFAULT_SAMPLES;
+	int sampleRate = DEFAULT_SAMPLE_RATE;
+	int bitDepth = DEFAULT_BIT_DEPTH;
+	int channels = DEFAULT_CHANNELS;
+
 	std::string name;
 	ALCdevice *device = nullptr;
+
 }; //RecordingDevice
 
 } //openal

+ 3 - 3
src/modules/audio/openal/Source.cpp

@@ -1041,7 +1041,7 @@ void Source::stop(const std::vector<love::audio::Source*> &sources)
 			sourceIds.push_back(source->source);
 	}
 
-	alSourceStopv((ALsizei) sources.size(), &sourceIds[0]);
+	alSourceStopv((ALsizei) sourceIds.size(), &sourceIds[0]);
 
 	for (auto &_source : sources)
 	{
@@ -1068,7 +1068,7 @@ void Source::pause(const std::vector<love::audio::Source*> &sources)
 			sourceIds.push_back(source->source);
 	}
 
-	alSourcePausev((ALsizei) sources.size(), &sourceIds[0]);
+	alSourcePausev((ALsizei) sourceIds.size(), &sourceIds[0]);
 }
 
 std::vector<love::audio::Source*> Source::pause(Pool *pool)
@@ -1488,7 +1488,7 @@ bool Source::getEffect(const char *name, std::map<Filter::Parameter, float> &par
 	return true;
 }
 
-bool Source::getEffectsList(std::vector<std::string> &list)
+bool Source::getActiveEffects(std::vector<std::string> &list) const
 {
 	if (effectmap.empty())
 		return false;

+ 1 - 1
src/modules/audio/openal/Source.h

@@ -152,7 +152,7 @@ public:
 	virtual bool setEffect(const char *effect, const std::map<Filter::Parameter, float> &params);
 	virtual bool unsetEffect(const char *effect);
 	virtual bool getEffect(const char *effect, std::map<Filter::Parameter, float> &params);
-	virtual bool getEffectsList(std::vector<std::string> &list);
+	virtual bool getActiveEffects(std::vector<std::string> &list) const;
 
 	virtual int getFreeBufferCount() const;
 	virtual bool queue(void *data, size_t length, int dataSampleRate, int dataBitDepth, int dataChannels);

+ 39 - 11
src/modules/audio/wrap_Audio.cpp

@@ -84,7 +84,7 @@ int w_newQueueableSource(lua_State *L)
 	Source *t = nullptr;
 
 	luax_catchexcept(L, [&]() {
-		t = instance()->newSource((int)luaL_checknumber(L, 1), (int)luaL_checknumber(L, 2), (int)luaL_checknumber(L, 3), (int)luaL_optnumber(L, 4, 0));
+		t = instance()->newSource((int)luaL_checkinteger(L, 1), (int)luaL_checkinteger(L, 2), (int)luaL_checkinteger(L, 3), (int)luaL_optinteger(L, 4, 0));
 	});
 
 	if (t != nullptr)
@@ -115,16 +115,34 @@ static std::vector<Source*> readSourceList(lua_State *L, int n)
 	return sources;
 }
 
+static std::vector<Source*> readSourceVararg(lua_State *L, int i)
+{
+	const int top = lua_gettop(L);
+
+	if (i < 0)
+		i += top + 1;
+
+	int items = top - i + 1;
+	std::vector<Source*> sources(items);
+
+	for (int pos = 0; i <= top; i++, pos++)
+		sources[pos] = luax_checksource(L, i);
+
+	return sources;
+}
+
 int w_play(lua_State *L)
 {
 	if (lua_istable(L, 1))
-	{
 		luax_pushboolean(L, instance()->play(readSourceList(L, 1)));
-		return 1;
+	else if (lua_gettop(L) > 1)
+		luax_pushboolean(L, instance()->play(readSourceVararg(L, 1)));
+	else
+	{
+		Source *s = luax_checksource(L, 1);
+		luax_pushboolean(L, instance()->play(s));
 	}
 
-	Source *s = luax_checksource(L, 1);
-	luax_pushboolean(L, instance()->play(s));
 	return 1;
 }
 
@@ -134,6 +152,8 @@ int w_stop(lua_State *L)
 		instance()->stop();
 	else if (lua_istable(L, 1))
 		instance()->stop(readSourceList(L, 1));
+	else if (lua_gettop(L) > 1)
+		instance()->stop(readSourceVararg(L, 1));
 	else
 	{
 		Source *s = luax_checksource(L, 1);
@@ -158,6 +178,8 @@ int w_pause(lua_State *L)
 	}
 	else if (lua_istable(L, 1))
 		instance()->pause(readSourceList(L, 1));
+	else if (lua_gettop(L) > 1)
+		instance()->pause(readSourceVararg(L, 1));
 	else
 	{
 		Source *s = luax_checksource(L, 1);
@@ -463,14 +485,13 @@ int w_getEffect(lua_State *L)
 	return 1;
 }
 
-int w_getEffectsList(lua_State *L)
+int w_getActiveEffects(lua_State *L)
 {
 	std::vector<std::string> list;
-	if (!instance()->getEffectsList(list))
-		return 0;
+	instance()->getActiveEffects(list);
 
-	lua_createtable(L, 0, list.size());
-	for (unsigned int i = 0; i < list.size(); i++)
+	lua_createtable(L, 0, (int) list.size());
+	for (int i = 0; i < (int) list.size(); i++)
 	{
 		lua_pushnumber(L, i + 1);
 		lua_pushstring(L, list[i].c_str());
@@ -497,6 +518,12 @@ int w_isEffectsSupported(lua_State *L)
 	return 1;
 }
 
+int w_setMixWithSystem(lua_State *L)
+{
+	luax_pushboolean(L, instance()->setMixWithSystem(luax_toboolean(L, 1)));
+	return 1;
+}
+
 // List of functions to wrap.
 static const luaL_Reg functions[] =
 {
@@ -523,10 +550,11 @@ static const luaL_Reg functions[] =
 	{ "getRecordingDevices", w_getRecordingDevices },
 	{ "setEffect", w_setEffect },
 	{ "getEffect", w_getEffect },
-	{ "getEffectsList", w_getEffectsList },
+	{ "getActiveEffects", w_getActiveEffects },
 	{ "getMaxSceneEffects", w_getMaxSceneEffects },
 	{ "getMaxSourceEffects", w_getMaxSourceEffects },
 	{ "isEffectsSupported", w_isEffectsSupported },
+	{ "setMixWithSystem", w_setMixWithSystem },
 	{ 0, 0 }
 };
 

+ 14 - 9
src/modules/audio/wrap_RecordingDevice.cpp

@@ -35,19 +35,24 @@ RecordingDevice *luax_checkrecordingdevice(lua_State *L, int idx)
 int w_RecordingDevice_start(lua_State *L)
 {
 	RecordingDevice *d = luax_checkrecordingdevice(L, 1);
+
+	int samples = d->getMaxSamples();
+	int samplerate = d->getSampleRate();
+	int bitdepth = d->getBitDepth();
+	int channels = d->getChannels();
+
 	if (lua_gettop(L) > 1)
 	{
-		int samples = (int) luaL_checkinteger(L, 2);
-		int sampleRate = (int) luaL_checkinteger(L, 3);
-		int bitDepth = (int) luaL_checkinteger(L, 4);
-		int channels = (int) luaL_checkinteger(L, 5);
-		luax_catchexcept(L, [&](){ 
-			lua_pushboolean(L, d->start(samples, sampleRate, bitDepth, channels));
-		});
+		samples = (int) luaL_checkinteger(L, 2);
+		samplerate = (int) luaL_optinteger(L, 3, RecordingDevice::DEFAULT_SAMPLE_RATE);
+		bitdepth = (int) luaL_optinteger(L, 4, RecordingDevice::DEFAULT_BIT_DEPTH);
+		channels = (int) (int) luaL_optinteger(L, 5, RecordingDevice::DEFAULT_CHANNELS);
 	}
-	else
-		luax_catchexcept(L, [&](){ lua_pushboolean(L, d->start()); });
 
+	bool success = false;
+	luax_catchexcept(L, [&]() { success = d->start(samples, samplerate, bitdepth, channels); });
+
+	luax_pushboolean(L, success);
 	return 1;
 }
 

+ 6 - 6
src/modules/audio/wrap_Source.cpp

@@ -493,15 +493,15 @@ int w_Source_getEffect(lua_State *L)
 	return 1;
 }
 
-int w_Source_getEffectsList(lua_State *L)
+int w_Source_getActiveEffects(lua_State *L)
 {
 	Source *t = luax_checksource(L, 1);
+
 	std::vector<std::string> list;
-	if (!t->getEffectsList(list))
-		return 0;
+	t->getActiveEffects(list);
 
-	lua_createtable(L, 0, list.size());
-	for (unsigned int i = 0; i < list.size(); i++)
+	lua_createtable(L, 0, (int) list.size());
+	for (int i = 0; i < (int) list.size(); i++)
 	{
 		lua_pushnumber(L, i + 1);
 		lua_pushstring(L, list[i].c_str());
@@ -624,7 +624,7 @@ static const luaL_Reg w_Source_functions[] =
 	{ "getFilter", w_Source_getFilter },
 	{ "setEffect", w_Source_setEffect },
 	{ "getEffect", w_Source_getEffect },
-	{ "getEffectsList", w_Source_getEffectsList },
+	{ "getActiveEffects", w_Source_getActiveEffects },
 
 	{ "getFreeBufferCount", w_Source_getFreeBufferCount },
 	{ "queue", w_Source_queue },

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

@@ -65,7 +65,7 @@ bool File::open(Mode mode)
 		throw love::Exception("Could not open file %s. Does not exist.", filename.c_str());
 
 	// Check whether the write directory is set.
-	if ((mode == MODE_APPEND || mode == MODE_WRITE) && (PHYSFS_getWriteDir() == 0) && !hack_setupWriteDirectory())
+	if ((mode == MODE_APPEND || mode == MODE_WRITE) && (PHYSFS_getWriteDir() == nullptr) && !hack_setupWriteDirectory())
 		throw love::Exception("Could not set write directory.");
 
 	// File already open?
@@ -151,8 +151,6 @@ int64 File::read(void *dst, int64 size)
 	int64 max = (int64)PHYSFS_fileLength(file);
 	size = (size == ALL) ? max : size;
 	size = (size > max) ? max : size;
-	// Sadly, we'll have to clamp to 32 bits here
-	size = (size > LOVE_UINT32_MAX) ? LOVE_UINT32_MAX : size;
 
 	if (size < 0)
 		throw love::Exception("Invalid read size.");
@@ -160,6 +158,8 @@ int64 File::read(void *dst, int64 size)
 #ifdef LOVE_USE_PHYSFS_2_1
 	int64 read = PHYSFS_readBytes(file, dst, (PHYSFS_uint64) size);
 #else
+	// Sadly, we'll have to clamp to 32 bits here
+	size = (size > LOVE_UINT32_MAX) ? LOVE_UINT32_MAX : size;
 	int64 read = (int64)PHYSFS_read(file, dst, 1, (PHYSFS_uint32) size);
 #endif
 
@@ -171,9 +171,6 @@ bool File::write(const void *data, int64 size)
 	if (!file || (mode != MODE_WRITE && mode != MODE_APPEND))
 		throw love::Exception("File is not opened for writing.");
 
-	// Another clamp, for the time being.
-	size = (size > LOVE_UINT32_MAX) ? LOVE_UINT32_MAX : size;
-
 	if (size < 0)
 		throw love::Exception("Invalid write size.");
 
@@ -181,6 +178,8 @@ bool File::write(const void *data, int64 size)
 #ifdef LOVE_USE_PHYSFS_2_1
 	int64 written = PHYSFS_writeBytes(file, data, (PHYSFS_uint64) size);
 #else
+	// Another clamp, for the time being.
+	size = (size > LOVE_UINT32_MAX) ? LOVE_UINT32_MAX : size;
 	int64 written = (int64) PHYSFS_write(file, data, 1, (PHYSFS_uint32) size);
 #endif
 
@@ -191,7 +190,7 @@ bool File::write(const void *data, int64 size)
 	// Manually flush the buffer in BUFFER_LINE mode if we find a newline.
 	if (bufferMode == BUFFER_LINE && bufferSize > size)
 	{
-		if (memchr(data, '\n', (size_t) size) != NULL)
+		if (memchr(data, '\n', (size_t) size) != nullptr)
 			flush();
 	}
 

+ 49 - 29
src/modules/filesystem/wrap_Filesystem.cpp

@@ -366,7 +366,7 @@ int w_read(lua_State *L)
 	const char *filename = luaL_checkstring(L, 1);
 	int64 len = (int64) luaL_optinteger(L, 2, File::ALL);
 
-	Data *data = 0;
+	Data *data = nullptr;
 	try
 	{
 		data = instance()->read(filename, len);
@@ -376,7 +376,7 @@ int w_read(lua_State *L)
 		return luax_ioError(L, "%s", e.what());
 	}
 
-	if (data == 0)
+	if (data == nullptr)
 		return luax_ioError(L, "File could not be read.");
 
 	// Push the string.
@@ -395,7 +395,7 @@ static int w_write_or_append(lua_State *L, File::Mode mode)
 {
 	const char *filename = luaL_checkstring(L, 1);
 
-	const char *input = 0;
+	const char *input = nullptr;
 	size_t len = 0;
 
 	if (luax_istype(L, 2, love::Data::type))
@@ -459,11 +459,9 @@ int w_getDirectoryItems(lua_State *L)
 
 int w_lines(lua_State *L)
 {
-	File *file;
-
 	if (lua_isstring(L, 1))
 	{
-		file = instance()->newFile(lua_tostring(L, 1));
+		File *file = instance()->newFile(lua_tostring(L, 1));
 		bool success = false;
 
 		luax_catchexcept(L, [&](){ success = file->open(File::MODE_READ); });
@@ -488,7 +486,7 @@ int w_load(lua_State *L)
 {
 	std::string filename = std::string(luaL_checkstring(L, 1));
 
-	Data *data = 0;
+	Data *data = nullptr;
 	try
 	{
 		data = instance()->read(filename.c_str());
@@ -634,6 +632,22 @@ int w_setCRequirePath(lua_State *L)
 	return 0;
 }
 
+static void replaceAll(std::string &str, const std::string &substr, const std::string &replacement)
+{
+	std::vector<size_t> locations;
+	size_t pos = 0;
+	size_t sublen = substr.length();
+
+	while ((pos = str.find(substr, pos)) != std::string::npos)
+	{
+		locations.push_back(pos);
+		pos += sublen;
+	}
+
+	for (int i = (int) locations.size() - 1; i >= 0; i--)
+		str.replace(locations[i], sublen, replacement);
+}
+
 int loader(lua_State *L)
 {
 	std::string modulename = luax_tostring(L, 1);
@@ -647,9 +661,7 @@ int loader(lua_State *L)
 	auto *inst = instance();
 	for (std::string element : inst->getRequirePath())
 	{
-		size_t pos = 0;
-		while ((pos = element.find('?', pos)) != std::string::npos)
-			element.replace(pos, 1, modulename);
+		replaceAll(element, "?", modulename);
 
 		if (inst->isFile(element.c_str()))
 		{
@@ -665,14 +677,16 @@ int loader(lua_State *L)
 	return 1;
 }
 
-inline const char *library_extension()
+static const char *library_extensions[] =
 {
 #ifdef LOVE_WINDOWS
-	return ".dll";
+	".dll"
+#elif defined(LOVE_MACOSX) || defined(LOVE_IOS)
+	".dylib", ".so"
 #else
-	return ".so";
+	".so"
 #endif
-}
+};
 
 int extloader(lua_State *L)
 {
@@ -694,25 +708,31 @@ int extloader(lua_State *L)
 
 	void *handle = nullptr;
 	auto *inst = instance();
-	for (std::string element : inst->getCRequirePath())
+
+	for (const std::string &el : inst->getCRequirePath())
 	{
-		// Replace ?? with the filename and extension
-		size_t pos = element.find("??");
-		if (pos != std::string::npos)
-			element.replace(pos, 2, tokenized_name + library_extension());
-		// Or ? with just the filename
-		pos = element.find('?');
-		if (pos != std::string::npos)
-			element.replace(pos, 1, tokenized_name);
+		for (const char *ext : library_extensions)
+		{
+			std::string element = el;
 
-		if (!inst->isFile(element.c_str()))
-			continue;
+			// Replace ?? with the filename and extension
+			replaceAll(element, "??", tokenized_name + ext);
 
-		// Now resolve the full path, as we're bypassing physfs for the next part.
-		element = inst->getRealDirectory(element.c_str()) + LOVE_PATH_SEPARATOR + element;
+			// And ? with just the filename
+			replaceAll(element, "?", tokenized_name);
+
+			if (!inst->isFile(element.c_str()))
+				continue;
+
+			// Now resolve the full path, as we're bypassing physfs for the next part.
+			std::string filepath = inst->getRealDirectory(element.c_str()) + LOVE_PATH_SEPARATOR + element;
+
+			handle = SDL_LoadObject(filepath.c_str());
+			// Can fail, for instance if it turned out the source was a zip
+			if (handle)
+				break;
+		}
 
-		handle = SDL_LoadObject(element.c_str());
-		// Can fail, for instance if it turned out the source was a zip
 		if (handle)
 			break;
 	}

+ 11 - 9
src/modules/font/BMFontRasterizer.cpp

@@ -146,7 +146,12 @@ BMFontRasterizer::BMFontRasterizer(love::filesystem::FileData *fontdef, const st
 
 	// The parseConfig function will try to load any missing page images.
 	for (int i = 0; i < (int) imagelist.size(); i++)
+	{
+		if (imagelist[i]->getFormat() != PIXELFORMAT_RGBA8)
+			throw love::Exception("Only 32-bit RGBA images are supported in BMFonts.");
+
 		images[i] = imagelist[i];
+	}
 
 	std::string configtext((const char *) fontdef->getData(), fontdef->getSize());
 
@@ -296,29 +301,26 @@ GlyphData *BMFontRasterizer::getGlyphData(uint32 glyph) const
 		return new GlyphData(glyph, GlyphMetrics(), PIXELFORMAT_RGBA8);
 
 	const BMFontCharacter &c = it->second;
-	GlyphData *g = new GlyphData(glyph, c.metrics, PIXELFORMAT_RGBA8);
-
 	const auto &imagepair = images.find(c.page);
 
 	if (imagepair == images.end())
-	{
-		g->release();
 		return new GlyphData(glyph, GlyphMetrics(), PIXELFORMAT_RGBA8);
-	}
 
 	image::ImageData *imagedata = imagepair->second.get();
+	GlyphData *g = new GlyphData(glyph, c.metrics, PIXELFORMAT_RGBA8);
 
 	size_t pixelsize = imagedata->getPixelSize();
-	image::pixel *pixels = (image::pixel *) g->getData();
-	const image::pixel *ipixels = (const image::pixel *) imagedata->getData();
+
+	uint8 *pixels = (uint8 *) g->getData();
+	const uint8 *ipixels = (const uint8 *) imagedata->getData();
 
 	love::thread::Lock lock(imagedata->getMutex());
 
 	// Copy the subsection of the texture from the ImageData to the GlyphData.
 	for (int y = 0; y < c.metrics.height; y++)
 	{
-		size_t idindex = (c.y + y) * imagedata->getWidth() + c.x;
-		memcpy(&pixels[y * c.metrics.width], &ipixels[idindex], pixelsize * c.metrics.width);
+		size_t idindex = ((c.y + y) * imagedata->getWidth() + c.x) * pixelsize;
+		memcpy(&pixels[y * c.metrics.width * pixelsize], &ipixels[idindex], pixelsize * c.metrics.width);
 	}
 
 	return g;

+ 9 - 12
src/modules/font/ImageRasterizer.cpp

@@ -29,10 +29,7 @@ namespace love
 namespace font
 {
 
-inline bool equal(const love::image::pixel &a, const love::image::pixel &b)
-{
-	return (a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a);
-}
+static_assert(sizeof(Color) == 4, "sizeof(Color) must equal 4 bytes!");
 
 ImageRasterizer::ImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int numglyphs, int extraspacing, float pixeldensity)
 	: imageData(data)
@@ -79,17 +76,17 @@ GlyphData *ImageRasterizer::getGlyphData(uint32 glyph) const
 	// We don't want another thread modifying our ImageData mid-copy.
 	love::thread::Lock lock(imageData->getMutex());
 
-	love::image::pixel *gdpixels = (love::image::pixel *) g->getData();
-	love::image::pixel *imagepixels = (love::image::pixel *) imageData->getData();
+	Color *gdpixels = (Color *) g->getData();
+	const Color *imagepixels = (const Color *) imageData->getData();
 
 	// copy glyph pixels from imagedata to glyphdata
 	for (int i = 0; i < g->getWidth() * g->getHeight(); i++)
 	{
-		love::image::pixel p = imagepixels[it->second.x + (i % gm.width) + (imageData->getWidth() * (i / gm.width))];
+		Color p = imagepixels[it->second.x + (i % gm.width) + (imageData->getWidth() * (i / gm.width))];
 
 		// Use transparency instead of the spacer color
-		if (equal(p, spacer))
-			gdpixels[i].r = gdpixels[i].g = gdpixels[i].b = gdpixels[i].a = 0;
+		if (p == spacer)
+			gdpixels[i] = Color(0, 0, 0, 0);
 		else
 			gdpixels[i] = p;
 	}
@@ -99,7 +96,7 @@ GlyphData *ImageRasterizer::getGlyphData(uint32 glyph) const
 
 void ImageRasterizer::load()
 {
-	love::image::pixel *pixels = (love::image::pixel *) imageData->getData();
+	const Color *pixels = (const Color *) imageData->getData();
 
 	int imgw = imageData->getWidth();
 	int imgh = imageData->getHeight();
@@ -121,13 +118,13 @@ void ImageRasterizer::load()
 		start = end;
 
 		// Finds out where the first character starts
-		while (start < imgw && equal(pixels[start], spacer))
+		while (start < imgw && pixels[start] == spacer)
 			++start;
 
 		end = start;
 
 		// Find where glyph ends.
-		while (end < imgw && !equal(pixels[end], spacer))
+		while (end < imgw && pixels[end] != spacer)
 			++end;
 
 		if (start >= end)

+ 2 - 2
src/modules/font/ImageRasterizer.h

@@ -22,9 +22,9 @@
 #define LOVE_FONT_IMAGE_RASTERIZER_H
 
 // LOVE
-#include "filesystem/File.h"
 #include "font/Rasterizer.h"
 #include "image/ImageData.h"
+#include "common/Color.h"
 
 #include <map>
 
@@ -76,7 +76,7 @@ private:
 	std::map<uint32, ImageGlyphData> imageGlyphs;
 
 	// Color used to identify glyph separation in the source ImageData
-	love::image::pixel spacer;
+	Color spacer;
 
 }; // ImageRasterizer
 

+ 3 - 3
src/modules/font/wrap_Font.cpp

@@ -72,7 +72,7 @@ int w_newTrueTypeRasterizer(lua_State *L)
 	if (lua_type(L, 1) == LUA_TNUMBER || lua_isnone(L, 1))
 	{
 		// First argument is a number: use the default TrueType font.
-		int size = (int) luaL_optnumber(L, 1, 12);
+		int size = (int) luaL_optinteger(L, 1, 12);
 
 		const char *hintstr = lua_isnoneornil(L, 2) ? nullptr : luaL_checkstring(L, 2);
 		if (hintstr && !TrueTypeRasterizer::getConstant(hintstr, hinting))
@@ -98,7 +98,7 @@ int w_newTrueTypeRasterizer(lua_State *L)
 		else
 			d = filesystem::luax_getfiledata(L, 1);
 
-		int size = (int) luaL_optnumber(L, 2, 12);
+		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))
@@ -180,7 +180,7 @@ int w_newImageRasterizer(lua_State *L)
 
 	image::ImageData *d = luax_checktype<image::ImageData>(L, 1);
 	std::string glyphs = luax_checkstring(L, 2);
-	int extraspacing = (int) luaL_optnumber(L, 3, 0);
+	int extraspacing = (int) luaL_optinteger(L, 3, 0);
 	float pixeldensity = (float) luaL_optnumber(L, 4, 1.0);
 
 	luax_catchexcept(L, [&](){ t = instance()->newImageRasterizer(d, glyphs, extraspacing, pixeldensity); });

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

@@ -151,7 +151,7 @@ IndexDataType QuadIndices::getType(size_t s) const
 	return vertex::getIndexDataTypeFromMax(getIndexCount(s));
 }
 
-size_t QuadIndices::getElementSize()
+size_t QuadIndices::getElementSize() const
 {
 	return elementSize;
 }

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

@@ -238,7 +238,7 @@ public:
 	 * Can be used with getPointer to calculate an offset into the array based
 	 * on a number of elements.
 	 **/
-	size_t getElementSize();
+	size_t getElementSize() const;
 
 	/**
 	 * Returns the pointer to the Buffer.

+ 1 - 37
src/modules/graphics/Canvas.cpp

@@ -97,43 +97,7 @@ Canvas::Canvas(const Settings &settings)
 		throw love::Exception("%s textures are not supported on this system!", textypestr);
 	}
 
-	int maxsize = 0;
-	switch (texType)
-	{
-	case TEXTURE_2D:
-		maxsize = (int) caps.limits[Graphics::LIMIT_TEXTURE_SIZE];
-		if (pixelWidth > maxsize)
-			throw TextureTooLargeException("width", pixelWidth);
-		else if (pixelHeight > maxsize)
-			throw TextureTooLargeException("height", pixelHeight);
-		break;
-	case TEXTURE_VOLUME:
-		maxsize = (int) caps.limits[Graphics::LIMIT_VOLUME_TEXTURE_SIZE];
-		if (pixelWidth > maxsize)
-			throw TextureTooLargeException("width", pixelWidth);
-		else if (pixelHeight > maxsize)
-			throw TextureTooLargeException("height", pixelHeight);
-		else if (depth > maxsize)
-			throw TextureTooLargeException("depth", depth);
-		break;
-	case TEXTURE_2D_ARRAY:
-		maxsize = (int) caps.limits[Graphics::LIMIT_TEXTURE_SIZE];
-		if (pixelWidth > maxsize)
-			throw TextureTooLargeException("width", pixelWidth);
-		else if (pixelHeight > maxsize)
-			throw TextureTooLargeException("height", pixelHeight);
-		else if (layers > (int) caps.limits[Graphics::LIMIT_TEXTURE_LAYERS])
-			throw TextureTooLargeException("array layer count", layers);
-		break;
-	case TEXTURE_CUBE:
-		if (pixelWidth != pixelHeight)
-			throw love::Exception("Cubemap textures must have equal width and height.");
-		else if (pixelWidth > (int) caps.limits[Graphics::LIMIT_CUBE_TEXTURE_SIZE])
-			throw TextureTooLargeException("width", pixelWidth);
-		break;
-	default:
-		break;
-	}
+	validateDimensions(true);
 
 	canvasCount++;
 }

+ 172 - 52
src/modules/graphics/Graphics.cpp

@@ -112,6 +112,7 @@ Graphics::Graphics()
 	, streamBufferState()
 	, projectionMatrix()
 	, canvasSwitchCount(0)
+	, drawCallsBatched(0)
 	, capabilities()
 {
 	transformStack.reserve(16);
@@ -430,6 +431,128 @@ void Graphics::setCanvas(const RenderTargetsStrongRef &rts)
 	return setCanvas(targets);
 }
 
+void Graphics::setCanvas(const RenderTargets &rts)
+{
+	DisplayState &state = states.back();
+	int ncanvases = (int) rts.colors.size();
+
+	if (ncanvases == 0 && rts.depthStencil.canvas == nullptr)
+		return setCanvas();
+	else if (ncanvases == 0)
+		throw love::Exception("At least one color render target is required when using a custom depth/stencil buffer.");
+
+	const auto &prevRTs = state.renderTargets;
+
+	if (ncanvases == (int) prevRTs.colors.size())
+	{
+		bool modified = false;
+
+		for (int i = 0; i < ncanvases; i++)
+		{
+			if (rts.colors[i].canvas != prevRTs.colors[i].canvas.get()
+				|| rts.colors[i].slice != prevRTs.colors[i].slice
+				|| rts.colors[i].mipmap != prevRTs.colors[i].mipmap)
+			{
+				modified = true;
+				break;
+			}
+		}
+
+		if (!modified && (rts.depthStencil.canvas != prevRTs.depthStencil.canvas
+						  || rts.depthStencil.slice != prevRTs.depthStencil.slice
+						  || rts.depthStencil.mipmap != prevRTs.depthStencil.mipmap))
+		{
+			modified = true;
+		}
+
+		if (rts.temporaryRTFlags != prevRTs.temporaryRTFlags)
+			modified = true;
+
+		if (!modified)
+			return;
+	}
+
+	if (ncanvases > capabilities.limits[LIMIT_MULTI_CANVAS])
+		throw love::Exception("This system can't simultaneously render to %d canvases.", ncanvases);
+
+	love::graphics::Canvas *firstcanvas = rts.colors[0].canvas;
+
+	bool multiformatsupported = capabilities.features[FEATURE_MULTI_CANVAS_FORMATS];
+	PixelFormat firstformat = firstcanvas->getPixelFormat();
+
+	if (isPixelFormatDepthStencil(firstformat))
+		throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas.");
+
+	if (rts.colors[0].mipmap < 0 || rts.colors[0].mipmap >= firstcanvas->getMipmapCount())
+		throw love::Exception("Invalid mipmap level %d.", rts.colors[0].mipmap + 1);
+
+	bool hasSRGBcanvas = firstformat == PIXELFORMAT_sRGBA8;
+	int pixelw = firstcanvas->getPixelWidth(rts.colors[0].mipmap);
+	int pixelh = firstcanvas->getPixelHeight(rts.colors[0].mipmap);
+
+	for (int i = 1; i < ncanvases; i++)
+	{
+		love::graphics::Canvas *c = rts.colors[i].canvas;
+		PixelFormat format = c->getPixelFormat();
+		int mip = rts.colors[i].mipmap;
+
+		if (mip < 0 || mip >= c->getMipmapCount())
+			throw love::Exception("Invalid mipmap level %d.", mip + 1);
+
+		if (c->getPixelWidth(mip) != pixelw || c->getPixelHeight(mip) != pixelh)
+			throw love::Exception("All canvases must have the same pixel dimensions.");
+
+		if (!multiformatsupported && format != firstformat)
+			throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats.");
+
+		if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA())
+			throw love::Exception("All Canvases must have the same MSAA value.");
+
+		if (isPixelFormatDepthStencil(format))
+			throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas.");
+
+		if (format == PIXELFORMAT_sRGBA8)
+			hasSRGBcanvas = true;
+	}
+
+	if (rts.depthStencil.canvas != nullptr)
+	{
+		love::graphics::Canvas *c = rts.depthStencil.canvas;
+		int mip = rts.depthStencil.mipmap;
+
+		if (!isPixelFormatDepthStencil(c->getPixelFormat()))
+			throw love::Exception("Only depth/stencil format Canvases can be used with the 'depthstencil' field of the table passed into setCanvas.");
+
+		if (c->getPixelWidth(mip) != pixelw || c->getPixelHeight(mip) != pixelh)
+			throw love::Exception("All canvases must have the same pixel dimensions.");
+
+		if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA())
+			throw love::Exception("All Canvases must have the same MSAA value.");
+
+		if (mip < 0 || mip >= c->getMipmapCount())
+			throw love::Exception("Invalid mipmap level %d.", mip + 1);
+	}
+
+	int w = firstcanvas->getWidth(rts.colors[0].mipmap);
+	int h = firstcanvas->getHeight(rts.colors[0].mipmap);
+
+	flushStreamDraws();
+	setCanvasInternal(rts, w, h, pixelw, pixelh, hasSRGBcanvas);
+
+	RenderTargetsStrongRef refs;
+	refs.colors.reserve(rts.colors.size());
+
+	for (auto c : rts.colors)
+		refs.colors.emplace_back(c.canvas, c.slice);
+
+	refs.depthStencil = RenderTargetStrongRef(rts.depthStencil.canvas, rts.depthStencil.slice);
+	refs.temporaryRTFlags = rts.temporaryRTFlags;
+
+	std::swap(state.renderTargets, refs);
+
+	canvasSwitchCount++;
+}
+
 Graphics::RenderTargets Graphics::getCanvas() const
 {
 	const auto &curRTs = states.back().renderTargets;
@@ -757,6 +880,9 @@ Graphics::StreamVertexData Graphics::requestStreamDraw(const StreamDrawRequest &
 		}
 	}
 
+	if (state.vertexCount > 0)
+		drawCallsBatched++;
+
 	state.vertexCount += req.vertexCount;
 	state.indexCount  += reqIndexCount;
 
@@ -829,7 +955,7 @@ void Graphics::printf(const std::vector<Font::ColoredString> &str, Font *font, f
  * Primitives (points, shapes, lines).
  **/
 
-void Graphics::points(const float *coords, const Colorf *colors, size_t numpoints)
+void Graphics::points(const Vector2 *positions, const Colorf *colors, size_t numpoints)
 {
 	const Matrix4 &t = getTransform();
 	bool is2D = t.isAffine2DTransform();
@@ -843,9 +969,9 @@ void Graphics::points(const float *coords, const Colorf *colors, size_t numpoint
 	StreamVertexData data = requestStreamDraw(req);
 
 	if (is2D)
-		t.transformXY((Vector2 *) data.stream[0], (const Vector2 *) coords, req.vertexCount);
+		t.transformXY((Vector2 *) data.stream[0], positions, req.vertexCount);
 	else
-		t.transformXY0((Vector3 *) data.stream[0], (const Vector2 *) coords, req.vertexCount);
+		t.transformXY0((Vector3 *) data.stream[0], positions, req.vertexCount);
 
 	Color *colordata = (Color *) data.stream[1];
 
@@ -886,7 +1012,7 @@ int Graphics::calculateEllipsePoints(float rx, float ry) const
 	return std::max(points, 8);
 }
 
-void Graphics::polyline(const float *coords, size_t count)
+void Graphics::polyline(const Vector2 *vertices, size_t count)
 {
 	float halfwidth = getLineWidth() * 0.5f;
 	LineJoin linejoin = getLineJoin();
@@ -897,27 +1023,27 @@ void Graphics::polyline(const float *coords, size_t count)
 	if (linejoin == LINE_JOIN_NONE)
 	{
 		NoneJoinPolyline line;
-		line.render(coords, count, halfwidth, pixelsize, linestyle == LINE_SMOOTH);
+		line.render(vertices, count, halfwidth, pixelsize, linestyle == LINE_SMOOTH);
 		line.draw(this);
 	}
 	else if (linejoin == LINE_JOIN_BEVEL)
 	{
 		BevelJoinPolyline line;
-		line.render(coords, count, halfwidth, pixelsize, linestyle == LINE_SMOOTH);
+		line.render(vertices, count, halfwidth, pixelsize, linestyle == LINE_SMOOTH);
 		line.draw(this);
 	}
 	else if (linejoin == LINE_JOIN_MITER)
 	{
 		MiterJoinPolyline line;
-		line.render(coords, count, halfwidth, pixelsize, linestyle == LINE_SMOOTH);
+		line.render(vertices, count, halfwidth, pixelsize, linestyle == LINE_SMOOTH);
 		line.draw(this);
 	}
 }
 
 void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h)
 {
-	float coords[] = {x,y, x,y+h, x+w,y+h, x+w,y, x,y};
-	polygon(mode, coords, 5 * 2);
+	Vector2 coords[] = {Vector2(x,y), Vector2(x,y+h), Vector2(x+w,y+h), Vector2(x+w,y), Vector2(x,y)};
+	polygon(mode, coords, 5);
 }
 
 void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h, float rx, float ry, int points)
@@ -940,44 +1066,43 @@ void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h, floa
 	const float half_pi = static_cast<float>(LOVE_M_PI / 2);
 	float angle_shift = half_pi / ((float) points + 1.0f);
 
-	int num_coords = (points + 2) * 8;
-	float *coords = getScratchBuffer<float>(num_coords + 2);
+	int num_coords = (points + 2) * 4;
+	Vector2 *coords = getScratchBuffer<Vector2>(num_coords + 1);
 	float phi = .0f;
 
 	for (int i = 0; i <= points + 2; ++i, phi += angle_shift)
 	{
-		coords[2 * i + 0] = x + rx * (1 - cosf(phi));
-		coords[2 * i + 1] = y + ry * (1 - sinf(phi));
+		coords[i].x = x + rx * (1 - cosf(phi));
+		coords[i].y = y + ry * (1 - sinf(phi));
 	}
 
 	phi = half_pi;
 
 	for (int i = points + 2; i <= 2 * (points + 2); ++i, phi += angle_shift)
 	{
-		coords[2 * i + 0] = x + w - rx * (1 + cosf(phi));
-		coords[2 * i + 1] = y + ry * (1 - sinf(phi));
+		coords[i].x = x + w - rx * (1 + cosf(phi));
+		coords[i].y = y +     ry * (1 - sinf(phi));
 	}
 
 	phi = 2 * half_pi;
 
 	for (int i = 2 * (points + 2); i <= 3 * (points + 2); ++i, phi += angle_shift)
 	{
-		coords[2 * i + 0] = x + w - rx * (1 + cosf(phi));
-		coords[2 * i + 1] = y + h - ry * (1 + sinf(phi));
+		coords[i].x = x + w - rx * (1 + cosf(phi));
+		coords[i].y = y + h - ry * (1 + sinf(phi));
 	}
 
-	phi =  3 * half_pi;
+	phi = 3 * half_pi;
 
 	for (int i = 3 * (points + 2); i <= 4 * (points + 2); ++i, phi += angle_shift)
 	{
-		coords[2 * i + 0] = x + rx * (1 - cosf(phi));
-		coords[2 * i + 1] = y + h - ry * (1 + sinf(phi));
+		coords[i].x = x +     rx * (1 - cosf(phi));
+		coords[i].y = y + h - ry * (1 + sinf(phi));
 	}
 
-	coords[num_coords + 0] = coords[0];
-	coords[num_coords + 1] = coords[1];
+	coords[num_coords] = coords[0];
 
-	polygon(mode, coords, num_coords + 2);
+	polygon(mode, coords, num_coords + 1);
 }
 
 void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h, float rx, float ry)
@@ -1002,17 +1127,16 @@ void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b, int po
 	float angle_shift = (two_pi / points);
 	float phi = .0f;
 
-	float *coords = getScratchBuffer<float>(2 * (points + 1));
+	Vector2 *coords = getScratchBuffer<Vector2>(points + 1);
 	for (int i = 0; i < points; ++i, phi += angle_shift)
 	{
-		coords[2*i+0] = x + a * cosf(phi);
-		coords[2*i+1] = y + b * sinf(phi);
+		coords[i].x = x + a * cosf(phi);
+		coords[i].y = y + b * sinf(phi);
 	}
 
-	coords[2*points+0] = coords[0];
-	coords[2*points+1] = coords[1];
+	coords[points] = coords[0];
 
-	polygon(mode, coords, (points + 1) * 2);
+	polygon(mode, coords, points + 1);
 }
 
 void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b)
@@ -1051,45 +1175,43 @@ void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float r
 
 	float phi = angle1;
 
-	float *coords = nullptr;
+	Vector2 *coords = nullptr;
 	int num_coords = 0;
 
-	const auto createPoints = [&](float *coordinates)
+	const auto createPoints = [&](Vector2 *coordinates)
 	{
 		for (int i = 0; i <= points; ++i, phi += angle_shift)
 		{
-			coordinates[2 * i + 0] = x + radius * cosf(phi);
-			coordinates[2 * i + 1] = y + radius * sinf(phi);
+			coordinates[i].x = x + radius * cosf(phi);
+			coordinates[i].y = y + radius * sinf(phi);
 		}
 	};
 
 	if (arcmode == ARC_PIE)
 	{
-		num_coords = (points + 3) * 2;
-		coords = getScratchBuffer<float>(num_coords);
+		num_coords = points + 3;
+		coords = getScratchBuffer<Vector2>(num_coords);
 
-		coords[0] = coords[num_coords - 2] = x;
-		coords[1] = coords[num_coords - 1] = y;
+		coords[0] = coords[num_coords - 1] = Vector2(x, y);
 
-		createPoints(coords + 2);
+		createPoints(coords + 1);
 	}
 	else if (arcmode == ARC_OPEN)
 	{
-		num_coords = (points + 1) * 2;
-		coords = getScratchBuffer<float>(num_coords);
+		num_coords = points + 1;
+		coords = getScratchBuffer<Vector2>(num_coords);
 
 		createPoints(coords);
 	}
 	else // ARC_CLOSED
 	{
-		num_coords = (points + 2) * 2;
-		coords = getScratchBuffer<float>(num_coords);
+		num_coords = points + 2;
+		coords = getScratchBuffer<Vector2>(num_coords);
 
 		createPoints(coords);
 
 		// Connect the ends of the arc.
-		coords[num_coords - 2] = coords[0];
-		coords[num_coords - 1] = coords[1];
+		coords[num_coords - 1] = coords[0];
 	}
 
 	polygon(drawmode, coords, num_coords);
@@ -1107,13 +1229,10 @@ void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float r
 	arc(drawmode, arcmode, x, y, radius, angle1, angle2, (int) (points + 0.5f));
 }
 
-/// @param mode    the draw mode
-/// @param coords  the coordinate array
-/// @param count   the number of coordinates/size of the array
-void Graphics::polygon(DrawMode mode, const float *coords, size_t count)
+void Graphics::polygon(DrawMode mode, const Vector2 *coords, size_t count)
 {
 	// coords is an array of a closed loop of vertices, i.e.
-	// coords[count-2] = coords[0], coords[count-1] = coords[1]
+	// coords[count-1] == coords[0]
 	if (mode == DRAW_LINE)
 	{
 		polyline(coords, count);
@@ -1127,14 +1246,14 @@ void Graphics::polygon(DrawMode mode, const float *coords, size_t count)
 		req.formats[0] = vertex::getSinglePositionFormat(is2D);
 		req.formats[1] = vertex::CommonFormat::RGBAub;
 		req.indexMode = vertex::TriangleIndexMode::FAN;
-		req.vertexCount = (int)count/2 - 1;
+		req.vertexCount = (int)count - 1;
 
 		StreamVertexData data = requestStreamDraw(req);
 
 		if (is2D)
-			t.transformXY((Vector2 *) data.stream[0], (const Vector2 *) coords, req.vertexCount);
+			t.transformXY((Vector2 *) data.stream[0], coords, req.vertexCount);
 		else
-			t.transformXY0((Vector3 *) data.stream[0], (const Vector2 *) coords, req.vertexCount);
+			t.transformXY0((Vector3 *) data.stream[0], coords, req.vertexCount);
 		
 		Color c = toColor(getColor());
 		Color *colordata = (Color *) data.stream[1];
@@ -1158,6 +1277,7 @@ Graphics::Stats Graphics::getStats() const
 		stats.drawCalls++;
 
 	stats.canvasSwitches = canvasSwitchCount;
+	stats.drawCallsBatched = drawCallsBatched;
 	stats.canvases = Canvas::canvasCount;
 	stats.images = Image::imageCount;
 	stats.fonts = Font::fontCount;

+ 14 - 12
src/modules/graphics/Graphics.h

@@ -28,9 +28,9 @@
 #include "common/Vector.h"
 #include "common/Optional.h"
 #include "common/int.h"
+#include "common/Color.h"
 #include "StreamBuffer.h"
 #include "vertex.h"
-#include "Color.h"
 #include "Texture.h"
 #include "Canvas.h"
 #include "Font.h"
@@ -216,6 +216,7 @@ public:
 	struct Stats
 	{
 		int drawCalls;
+		int drawCallsBatched;
 		int canvasSwitches;
 		int shaderSwitches;
 		int canvases;
@@ -478,7 +479,7 @@ public:
 	Shader *getShader() const;
 
 	void setCanvas(RenderTarget rt, uint32 temporaryRTFlags);
-	virtual void setCanvas(const RenderTargets &rts) = 0;
+	void setCanvas(const RenderTargets &rts);
 	void setCanvas(const RenderTargetsStrongRef &rts);
 	virtual void setCanvas() = 0;
 
@@ -620,18 +621,16 @@ public:
 	void printf(const std::vector<Font::ColoredString> &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m);
 
 	/**
-	 * Draws a point at (x,y).
-	 * @param x Point along x-axis.
-	 * @param y Point along y-axis.
+	 * Draws a series of points at the specified positions.
 	 **/
-	void points(const float *coords, const Colorf *colors, size_t numpoints);
+	void points(const Vector2 *positions, const Colorf *colors, size_t numpoints);
 
 	/**
 	 * Draws a series of lines connecting the given vertices.
-	 * @param coords Vertex components (x1, y1, ..., xn, yn). If x1,y1 == xn,yn the line will be drawn closed.
-	 * @param count Number of items in the array, i.e. count = 2 * n
+	 * @param coords Vertex positions (v1, ..., vn). If v1 == vn the line will be drawn closed.
+	 * @param count Number of vertices.
 	 **/
-	void polyline(const float *coords, size_t count);
+	void polyline(const Vector2 *vertices, size_t count);
 
 	/**
 	 * Draws a rectangle.
@@ -696,10 +695,10 @@ public:
 	/**
 	 * Draws a polygon with an arbitrary number of vertices.
 	 * @param mode The type of drawing (line/filled).
-	 * @param coords Vertex components (x1, y1, x2, y2, etc.)
-	 * @param count Coord array size
+	 * @param coords Vertex positions.
+	 * @param count Vertex array size.
 	 **/
-	void polygon(DrawMode mode, const float *coords, size_t count);
+	void polygon(DrawMode mode, const Vector2 *vertices, size_t count);
 
 	/**
 	 * Gets the graphics capabilities (feature support, limit values, and
@@ -861,6 +860,8 @@ protected:
 
 	virtual StreamBuffer *newStreamBuffer(BufferType type, size_t size) = 0;
 
+	virtual void setCanvasInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBcanvas) = 0;
+
 	virtual void initCapabilities() = 0;
 	virtual void getAPIStats(int &drawcalls, int &shaderswitches) const = 0;
 
@@ -900,6 +901,7 @@ protected:
 	std::vector<Canvas *> temporaryCanvases;
 
 	int canvasSwitchCount;
+	int drawCallsBatched;
 
 	Capabilities capabilities;
 

+ 78 - 1
src/modules/graphics/Mesh.cpp

@@ -564,11 +564,88 @@ bool Mesh::getDrawRange(int &start, int &count) const
 	return true;
 }
 
-void Mesh::draw(love::graphics::Graphics *gfx, const love::Matrix4 &m)
+void Mesh::draw(Graphics *gfx, const love::Matrix4 &m)
 {
 	drawInstanced(gfx, m, 1);
 }
 
+void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
+{
+	if (vertexCount <= 0 || instancecount <= 0)
+		return;
+
+	if (instancecount > 1 && !gfx->getCapabilities().features[Graphics::FEATURE_INSTANCING])
+		throw love::Exception("Instancing is not supported on this system.");
+
+	gfx->flushStreamDraws();
+
+	if (Shader::isDefaultActive())
+		Shader::attachDefault(Shader::STANDARD_DEFAULT);
+
+	if (Shader::current && texture.get())
+		Shader::current->checkMainTexture(texture);
+
+	uint32 enabledattribs = 0;
+	uint32 instancedattribs = 0;
+
+	for (const auto &attrib : attachedAttributes)
+	{
+		if (!attrib.second.enabled)
+			continue;
+
+		love::graphics::Mesh *mesh = attrib.second.mesh;
+		int location = mesh->bindAttributeToShaderInput(attrib.second.index, attrib.first);
+
+		if (location >= 0)
+		{
+			uint32 bit = 1u << (uint32) location;
+
+			enabledattribs |= bit;
+
+			if (attrib.second.step == STEP_PER_INSTANCE)
+				instancedattribs |= bit;
+		}
+	}
+
+	// Not supported on all platforms or GL versions, I believe.
+	if (!(enabledattribs & ATTRIBFLAG_POS))
+		throw love::Exception("Mesh must have an enabled VertexPosition attribute to be drawn.");
+
+	bool useindexbuffer = useIndexBuffer && ibo != nullptr && elementCount > 0;
+
+	int start = 0;
+	int count = 0;
+
+	if (useindexbuffer)
+	{
+		// Make sure the index buffer isn't mapped (sends data to GPU if needed.)
+		ibo->unmap();
+
+		start = std::min(std::max(0, rangeStart), (int) elementCount - 1);
+
+		count = (int) elementCount;
+		if (rangeCount > 0)
+			count = std::min(count, rangeCount);
+
+		count = std::min(count, (int) elementCount - start);
+	}
+	else
+	{
+		start = std::min(std::max(0, rangeStart), (int) vertexCount - 1);
+
+		count = (int) vertexCount;
+		if (rangeCount > 0)
+			count = std::min(count, rangeCount);
+
+		count = std::min(count, (int) vertexCount - start);
+	}
+
+	Graphics::TempTransform transform(gfx, m);
+
+	if (count > 0)
+		drawInternal(start, count, instancecount, useindexbuffer, enabledattribs, instancedattribs);
+}
+
 size_t Mesh::getAttribFormatSize(const AttribFormat &format)
 {
 	switch (format.type)

+ 4 - 2
src/modules/graphics/Mesh.h

@@ -195,11 +195,11 @@ public:
 
 	virtual int bindAttributeToShaderInput(int attributeindex, const std::string &inputname) = 0;
 
-	virtual void drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount) = 0;
-
 	// Implements Drawable.
 	void draw(Graphics *gfx, const Matrix4 &m) override;
 
+	void drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount);
+
 	static bool getConstant(const char *in, DrawMode &out);
 	static bool getConstant(DrawMode in, const char *&out);
 
@@ -223,6 +223,8 @@ protected:
 	void calculateAttributeSizes();
 	size_t getAttributeOffset(size_t attribindex) const;
 
+	virtual void drawInternal(int start, int count, int instancecount, bool useindexbuffer, uint32 attribflags, uint32 instancedattribflags) const = 0;
+
 	static size_t getAttribFormatSize(const AttribFormat &format);
 	static std::vector<AttribFormat> getDefaultVertexFormat();
 

+ 10 - 3
src/modules/graphics/ParticleSystem.cpp

@@ -1048,15 +1048,21 @@ void ParticleSystem::update(float dt)
 	prevPosition = position;
 }
 
-bool ParticleSystem::prepareDraw(Graphics *gfx)
+void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
 {
 	uint32 pCount = getCount();
 
 	if (pCount == 0 || texture.get() == nullptr || pMem == nullptr || buffer == nullptr)
-		return false;
+		return;
 
 	gfx->flushStreamDraws();
 
+	if (Shader::isDefaultActive())
+		Shader::attachDefault(Shader::STANDARD_DEFAULT);
+
+	if (Shader::current && texture.get())
+		Shader::current->checkMainTexture(texture);
+
 	const Vector2 *positions = texture->getQuad()->getVertexPositions();
 	const Vector2 *texcoords = texture->getQuad()->getVertexTexCoords();
 
@@ -1098,7 +1104,8 @@ bool ParticleSystem::prepareDraw(Graphics *gfx)
 
 	buffer->unmap();
 
-	return true;
+	Graphics::TempTransform transform(gfx, m);
+	drawInternal();
 }
 
 bool ParticleSystem::getConstant(const char *in, AreaSpreadDistribution &out)

+ 5 - 2
src/modules/graphics/ParticleSystem.h

@@ -25,8 +25,8 @@
 #include "common/int.h"
 #include "common/math.h"
 #include "common/Vector.h"
+#include "common/Color.h"
 #include "Drawable.h"
-#include "Color.h"
 #include "Quad.h"
 #include "Texture.h"
 #include "Buffer.h"
@@ -569,6 +569,9 @@ public:
 	 **/
 	void update(float dt);
 
+	// Implements Drawable.
+	void draw(Graphics *gfx, const Matrix4 &m) override;
+
 	static bool getConstant(const char *in, AreaSpreadDistribution &out);
 	static bool getConstant(AreaSpreadDistribution in, const char *&out);
 
@@ -612,7 +615,7 @@ protected:
 		int quadIndex;
 	};
 
-	bool prepareDraw(Graphics *gfx);
+	virtual void drawInternal() const = 0;
 
 	// Pointer to the beginning of the allocated memory.
 	Particle *pMem;

+ 8 - 8
src/modules/graphics/Polyline.cpp

@@ -33,7 +33,7 @@ namespace love
 namespace graphics
 {
 
-void Polyline::render(const float *coords, size_t count, size_t size_hint, float halfwidth, float pixel_size, bool draw_overdraw)
+void Polyline::render(const Vector2 *coords, size_t count, size_t size_hint, float halfwidth, float pixel_size, bool draw_overdraw)
 {
 	static std::vector<Vector2> anchors;
 	anchors.clear();
@@ -48,26 +48,26 @@ void Polyline::render(const float *coords, size_t count, size_t size_hint, float
 		halfwidth -= pixel_size * 0.3f;
 
 	// compute sleeve
-	bool is_looping = (coords[0] == coords[count - 2]) && (coords[1] == coords[count - 1]);
+	bool is_looping = (coords[0] == coords[count - 1]);
 	Vector2 s;
 	if (!is_looping) // virtual starting point at second point mirrored on first point
-		s = Vector2(coords[2] - coords[0], coords[3] - coords[1]);
+		s = coords[1] - coords[0];
 	else // virtual starting point at last vertex
-		s = Vector2(coords[0] - coords[count - 4], coords[1] - coords[count - 3]);
+		s = coords[0] - coords[count - 2];
 
 	float len_s = s.getLength();
 	Vector2 ns = s.getNormal(halfwidth / len_s);
 
-	Vector2 q, r(coords[0], coords[1]);
-	for (size_t i = 0; i + 3 < count; i += 2)
+	Vector2 q, r(coords[0]);
+	for (size_t i = 0; i + 1 < count; i++)
 	{
 		q = r;
-		r = Vector2(coords[i + 2], coords[i + 3]);
+		r = coords[i + 1];
 		renderEdge(anchors, normals, s, len_s, ns, q, r, halfwidth);
 	}
 
 	q = r;
-	r = is_looping ? Vector2(coords[2], coords[3]) : r + s;
+	r = is_looping ? coords[1] : r + s;
 	renderEdge(anchors, normals, s, len_s, ns, q, r, halfwidth);
 
 	vertex_count = normals.size();

+ 8 - 8
src/modules/graphics/Polyline.h

@@ -57,13 +57,13 @@ public:
 
 	/**
 	 * @param vertices      Vertices defining the core line segments
-	 * @param count         Number of coordinates (= size of the array vertices)
+	 * @param count         Number of vertices
 	 * @param size_hint     Expected number of vertices of the rendering sleeve around the core line.
 	 * @param halfwidth     linewidth / 2.
 	 * @param pixel_size    Dimension of one pixel on the screen in world coordinates.
 	 * @param draw_overdraw Fake antialias the line.
 	 */
-	void render(const float *vertices, size_t count, size_t size_hint, float halfwidth, float pixel_size, bool draw_overdraw);
+	void render(const Vector2 *vertices, size_t count, size_t size_hint, float halfwidth, float pixel_size, bool draw_overdraw);
 
 	/** Draws the line on the screen
 	 */
@@ -112,9 +112,9 @@ public:
 		: Polyline(vertex::TriangleIndexMode::QUADS)
 	{}
 
-	void render(const float *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)
+	void render(const Vector2 *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)
 	{
-		Polyline::render(vertices, count, 2 * count - 4, halfwidth, pixel_size, draw_overdraw);
+		Polyline::render(vertices, count, 4 * count - 4, halfwidth, pixel_size, draw_overdraw);
 
 		// discard the first and last two vertices. (these are redundant)
 		for (size_t i = 0; i < vertex_count - 4; ++i)
@@ -149,9 +149,9 @@ class MiterJoinPolyline : public Polyline
 {
 public:
 
-	void render(const float *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)
+	void render(const Vector2 *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)
 	{
-		Polyline::render(vertices, count, count, halfwidth, pixel_size, draw_overdraw);
+		Polyline::render(vertices, count, 2 * count, halfwidth, pixel_size, draw_overdraw);
 	}
 
 protected:
@@ -171,9 +171,9 @@ class BevelJoinPolyline : public Polyline
 {
 public:
 
-	void render(const float *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)
+	void render(const Vector2 *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)
 	{
-		Polyline::render(vertices, count, 2 * count - 4, halfwidth, pixel_size, draw_overdraw);
+		Polyline::render(vertices, count, 4 * count - 4, halfwidth, pixel_size, draw_overdraw);
 	}
 
 protected:

+ 10 - 10
src/modules/graphics/Shader.cpp

@@ -351,16 +351,16 @@ StringMap<Shader::ShaderStage, Shader::STAGE_MAX_ENUM> Shader::stageNames(Shader
 
 StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM>::Entry Shader::builtinNameEntries[] =
 {
-	{ "MainTex",                   BUILTIN_TEXTURE_MAIN                },
-	{ "love_VideoYChannel",        BUILTIN_TEXTURE_VIDEO_Y             },
-	{ "love_VideoCbChannel",       BUILTIN_TEXTURE_VIDEO_CB            },
-	{ "love_VideoCrChannel",       BUILTIN_TEXTURE_VIDEO_CR            },
-	{ "TransformMatrix",           BUILTIN_MATRIX_TRANSFORM            },
-	{ "ProjectionMatrix",          BUILTIN_MATRIX_PROJECTION           },
-	{ "TransformProjectionMatrix", BUILTIN_MATRIX_TRANSFORM_PROJECTION },
-	{ "NormalMatrix",              BUILTIN_MATRIX_NORMAL               },
-	{ "love_PointSize",            BUILTIN_POINT_SIZE                  },
-	{ "love_ScreenSize",           BUILTIN_SCREEN_SIZE                 },
+	{ "MainTex",             BUILTIN_TEXTURE_MAIN                  },
+	{ "love_VideoYChannel",  BUILTIN_TEXTURE_VIDEO_Y               },
+	{ "love_VideoCbChannel", BUILTIN_TEXTURE_VIDEO_CB              },
+	{ "love_VideoCrChannel", BUILTIN_TEXTURE_VIDEO_CR              },
+	{ "ViewSpaceFromLocal",  BUILTIN_MATRIX_VIEW_FROM_LOCAL        },
+	{ "ClipSpaceFromView",   BUILTIN_MATRIX_CLIP_FROM_VIEW         },
+	{ "ClipSpaceFromLocal",  BUILTIN_MATRIX_CLIP_FROM_LOCAL        },
+	{ "ViewNormalFromLocal", BUILTIN_MATRIX_VIEW_NORMAL_FROM_LOCAL },
+	{ "love_PointSize",      BUILTIN_POINT_SIZE                    },
+	{ "love_ScreenSize",     BUILTIN_SCREEN_SIZE                   },
 };
 
 StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM> Shader::builtinNames(Shader::builtinNameEntries, sizeof(Shader::builtinNameEntries));

+ 4 - 4
src/modules/graphics/Shader.h

@@ -73,10 +73,10 @@ public:
 		BUILTIN_TEXTURE_VIDEO_Y,
 		BUILTIN_TEXTURE_VIDEO_CB,
 		BUILTIN_TEXTURE_VIDEO_CR,
-		BUILTIN_MATRIX_TRANSFORM,
-		BUILTIN_MATRIX_PROJECTION,
-		BUILTIN_MATRIX_TRANSFORM_PROJECTION,
-		BUILTIN_MATRIX_NORMAL,
+		BUILTIN_MATRIX_VIEW_FROM_LOCAL,
+		BUILTIN_MATRIX_CLIP_FROM_VIEW,
+		BUILTIN_MATRIX_CLIP_FROM_LOCAL,
+		BUILTIN_MATRIX_VIEW_NORMAL_FROM_LOCAL,
 		BUILTIN_POINT_SIZE,
 		BUILTIN_SCREEN_SIZE,
 		BUILTIN_MAX_ENUM

+ 62 - 8
src/modules/graphics/SpriteBatch.cpp

@@ -62,9 +62,9 @@ SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usag
 	else
 		vertex_format = vertex::CommonFormat::XYf_STf_RGBAub;
 
-	format_stride = vertex::getFormatStride(vertex_format);
+	vertex_stride = vertex::getFormatStride(vertex_format);
 
-	size_t vertex_size = format_stride * 4 * size;
+	size_t vertex_size = vertex_stride * 4 * size;
 	array_buf = gfx->newBuffer(vertex_size, nullptr, BUFFER_VERTEX, usage, Buffer::MAP_EXPLICIT_RANGE_MODIFY);
 }
 
@@ -95,7 +95,7 @@ int SpriteBatch::add(Quad *quad, const Matrix4 &m, int index /*= -1*/)
 	const Vector2 *quadtexcoords = quad->getVertexTexCoords();
 
 	// Always keep the VBO mapped when adding data (it'll be unmapped on draw.)
-	size_t offset = (index == -1 ? next : index) * format_stride * 4;
+	size_t offset = (index == -1 ? next : index) * vertex_stride * 4;
 	auto verts = (XYf_STf_RGBAub *) ((uint8 *) array_buf->map() + offset);
 
 	m.transformXY(verts, quadpositions, 4);
@@ -107,7 +107,7 @@ int SpriteBatch::add(Quad *quad, const Matrix4 &m, int index /*= -1*/)
 		verts[i].color = color;
 	}
 
-	array_buf->setMappedRangeModified(offset, format_stride * 4);
+	array_buf->setMappedRangeModified(offset, vertex_stride * 4);
 
 	// Increment counter.
 	if (index == -1)
@@ -141,7 +141,7 @@ int SpriteBatch::addLayer(int layer, Quad *quad, const Matrix4 &m, int index)
 	const Vector2 *quadtexcoords = quad->getVertexTexCoords();
 
 	// Always keep the VBO mapped when adding data (it'll be unmapped on draw.)
-	size_t offset = (index == -1 ? next : index) * format_stride * 4;
+	size_t offset = (index == -1 ? next : index) * vertex_stride * 4;
 	auto verts = (XYf_STPf_RGBAub *) ((uint8 *) array_buf->map() + offset);
 
 	m.transformXY(verts, quadpositions, 4);
@@ -154,7 +154,7 @@ int SpriteBatch::addLayer(int layer, Quad *quad, const Matrix4 &m, int index)
 		verts[i].color = color;
 	}
 
-	array_buf->setMappedRangeModified(offset, format_stride * 4);
+	array_buf->setMappedRangeModified(offset, vertex_stride * 4);
 
 	// Increment counter.
 	if (index == -1)
@@ -225,7 +225,7 @@ void SpriteBatch::setBufferSize(int newsize)
 	if (newsize == size)
 		return;
 
-	size_t vertex_size = format_stride * 4 * newsize;
+	size_t vertex_size = vertex_stride * 4 * newsize;
 	love::graphics::Buffer *new_array_buf = nullptr;
 
 	int new_next = std::min(next, newsize);
@@ -236,7 +236,7 @@ void SpriteBatch::setBufferSize(int newsize)
 		new_array_buf = gfx->newBuffer(vertex_size, nullptr, array_buf->getType(), array_buf->getUsage(), array_buf->getMapFlags());
 
 		// Copy as much of the old data into the new GLBuffer as can fit.
-		size_t copy_size = format_stride * 4 * new_next;
+		size_t copy_size = vertex_stride * 4 * new_next;
 		array_buf->copyTo(0, copy_size, new_array_buf, 0);
 
 		quad_indices = QuadIndices(gfx, newsize);
@@ -307,5 +307,59 @@ bool SpriteBatch::getDrawRange(int &start, int &count) const
 	return true;
 }
 
+void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
+{
+	using namespace vertex;
+
+	if (next == 0)
+		return;
+
+	gfx->flushStreamDraws();
+
+	if (texture.get())
+	{
+		if (Shader::isDefaultActive())
+		{
+			Shader::StandardShader defaultshader = Shader::STANDARD_DEFAULT;
+			if (texture->getTextureType() == TEXTURE_2D_ARRAY)
+				defaultshader = Shader::STANDARD_ARRAY;
+
+			Shader::attachDefault(defaultshader);
+		}
+
+		if (Shader::current)
+			Shader::current->checkMainTexture(texture);
+	}
+
+	// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
+	array_buf->unmap();
+
+	CommonFormat format = vertex_format;
+
+	if (!color_active)
+	{
+		if (format == CommonFormat::XYf_STPf_RGBAub)
+			format = CommonFormat::XYf_STPf;
+		else
+			format = CommonFormat::XYf_STf;
+	}
+
+	int start = std::min(std::max(0, range_start), next - 1);
+
+	int count = next;
+	if (range_count > 0)
+		count = std::min(count, range_count);
+
+	count = std::min(count, next - start);
+
+	size_t indexbytestart = quad_indices.getIndexCount(start) * quad_indices.getElementSize();
+	size_t indexcount = quad_indices.getIndexCount(count);
+
+	Graphics::TempTransform transform(gfx, m);
+
+	if (count > 0)
+		drawInternal(format, indexbytestart, indexcount);
+}
+
 } // graphics
 } // love

+ 8 - 2
src/modules/graphics/SpriteBatch.h

@@ -29,9 +29,10 @@
 // LOVE
 #include "common/math.h"
 #include "common/Matrix.h"
+#include "common/Color.h"
 #include "Drawable.h"
-#include "Color.h"
 #include "Mesh.h"
+#include "vertex.h"
 
 namespace love
 {
@@ -105,6 +106,9 @@ public:
 	void setDrawRange();
 	bool getDrawRange(int &start, int &count) const;
 
+	// Implements Drawable.
+	void draw(Graphics *gfx, const Matrix4 &m) override;
+
 protected:
 
 	struct AttachedAttribute
@@ -119,6 +123,8 @@ protected:
 	 **/
 	void setBufferSize(int newsize);
 
+	virtual void drawInternal(vertex::CommonFormat format, size_t indexbytestart, size_t indexcount) = 0;
+
 	StrongRef<Texture> texture;
 
 	// Max number of sprites in the batch.
@@ -133,7 +139,7 @@ protected:
 	bool color_active;
 
 	vertex::CommonFormat vertex_format;
-	size_t format_stride;
+	size_t vertex_stride;
 	
 	love::graphics::Buffer *array_buf;
 	QuadIndices quad_indices;

+ 31 - 0
src/modules/graphics/Text.cpp

@@ -235,5 +235,36 @@ int Text::getHeight(int index) const
 	return text_data[index].text_info.height;
 }
 
+void Text::draw(Graphics *gfx, const Matrix4 &m)
+{
+	if (vbo == nullptr || draw_commands.empty())
+		return;
+
+	gfx->flushStreamDraws();
+
+	if (Shader::isDefaultActive())
+		Shader::attachDefault(Shader::STANDARD_DEFAULT);
+
+	if (Shader::current)
+		Shader::current->checkMainTextureType(TEXTURE_2D, false);
+
+	// Re-generate the text if the Font's texture cache was invalidated.
+	if (font->getTextureCacheID() != texture_cache_id)
+		regenerateVertices();
+
+	int totalverts = 0;
+	for (const Font::DrawCommand &cmd : draw_commands)
+		totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts);
+
+	if ((size_t) totalverts / 4 > quadIndices.getSize())
+		quadIndices = QuadIndices(gfx, (size_t) totalverts / 4);
+
+	vbo->unmap(); // Make sure all pending data is flushed to the GPU.
+
+	Graphics::TempTransform transform(gfx, m);
+
+	drawInternal(draw_commands);
+}
+
 } // graphics
 } // love

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

@@ -63,6 +63,9 @@ public:
 	 **/
 	int getHeight(int index = 0) const;
 
+	// Implements Drawable.
+	void draw(love::graphics::Graphics *gfx, const Matrix4 &m) override;
+
 protected:
 
 	struct TextData
@@ -80,6 +83,8 @@ protected:
 	void regenerateVertices();
 	void addTextData(const TextData &s);
 
+	virtual void drawInternal(const std::vector<Font::DrawCommand> &commands) const = 0;
+
 	StrongRef<Font> font;
 	Buffer *vbo;
 	QuadIndices quadIndices;

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

@@ -317,6 +317,63 @@ int Texture::getMipmapCount(int w, int h, int d)
 	return (int) log2(std::max(std::max(w, h), d)) + 1;
 }
 
+bool Texture::validateDimensions(bool throwException) const
+{
+	bool success = true;
+
+	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
+	if (gfx == nullptr)
+		return false;
+
+	const Graphics::Capabilities &caps = gfx->getCapabilities();
+
+	int max2Dsize   = (int) caps.limits[Graphics::LIMIT_TEXTURE_SIZE];
+	int max3Dsize   = (int) caps.limits[Graphics::LIMIT_VOLUME_TEXTURE_SIZE];
+	int maxcubesize = (int) caps.limits[Graphics::LIMIT_CUBE_TEXTURE_SIZE];
+	int maxlayers   = (int) caps.limits[Graphics::LIMIT_TEXTURE_LAYERS];
+
+	int largestdim = 0;
+	const char *largestname = nullptr;
+
+	if ((texType == TEXTURE_2D || texType == TEXTURE_2D_ARRAY) && (pixelWidth > max2Dsize || pixelHeight > max2Dsize))
+	{
+		success = false;
+		largestdim = std::max(pixelWidth, pixelHeight);
+		largestname = pixelWidth > pixelHeight ? "pixel width" : "pixel height";
+	}
+	else if (texType == TEXTURE_2D_ARRAY && layers > maxlayers)
+	{
+		success = false;
+		largestdim = layers;
+		largestname = "array layer count";
+	}
+	else if (texType == TEXTURE_CUBE && (pixelWidth > maxcubesize || pixelWidth != pixelHeight))
+	{
+		success = false;
+		largestdim = std::max(pixelWidth, pixelHeight);
+		largestname = pixelWidth > pixelHeight ? "pixel width" : "pixel height";
+
+		if (throwException && pixelWidth != pixelHeight)
+			throw love::Exception("Cubemap textures must have equal width and height.");
+	}
+	else if (texType == TEXTURE_VOLUME && (pixelWidth > max3Dsize || pixelHeight > max3Dsize || depth > max3Dsize))
+	{
+		success = false;
+		largestdim = std::max(std::max(pixelWidth, pixelHeight), depth);
+		if (largestdim == pixelWidth)
+			largestname = "pixel width";
+		else if (largestdim == pixelHeight)
+			largestname = "pixel height";
+		else
+			largestname = "pixel depth";
+	}
+
+	if (throwException && largestname != nullptr)
+		throw love::Exception("Cannot create texture: %s of %d is too large for this system.", largestname, largestdim);
+
+	return success;
+}
+
 bool Texture::getConstant(const char *in, TextureType &out)
 {
 	return texTypes.find(in, out);

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

@@ -174,6 +174,8 @@ protected:
 	void initQuad();
 	void setGraphicsMemorySize(int64 size);
 
+	bool validateDimensions(bool throwException) const;
+
 	TextureType texType;
 
 	PixelFormat format;

+ 1 - 1
src/modules/graphics/opengl/Canvas.h

@@ -22,7 +22,7 @@
 #define LOVE_GRAPHICS_OPENGL_CANVAS_H
 
 #include "common/config.h"
-#include "graphics/Color.h"
+#include "common/Color.h"
 #include "common/int.h"
 #include "graphics/Canvas.h"
 #include "graphics/Volatile.h"

+ 6 - 118
src/modules/graphics/opengl/Graphics.cpp

@@ -492,107 +492,9 @@ void Graphics::setDebug(bool enable)
 	::printf("OpenGL debug output enabled (LOVE_GRAPHICS_DEBUG=1)\n");
 }
 
-void Graphics::setCanvas(const RenderTargets &rts)
+void Graphics::setCanvasInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBcanvas)
 {
-	DisplayState &state = states.back();
-	int ncanvases = (int) rts.colors.size();
-
-	if (ncanvases == 0 && rts.depthStencil.canvas == nullptr)
-		return setCanvas();
-	else if (ncanvases == 0)
-		throw love::Exception("At least one color render target is required when using a custom depth/stencil buffer.");
-
-	const auto &prevRTs = state.renderTargets;
-
-	if (ncanvases == (int) prevRTs.colors.size())
-	{
-		bool modified = false;
-
-		for (int i = 0; i < ncanvases; i++)
-		{
-			if (rts.colors[i].canvas != prevRTs.colors[i].canvas.get()
-				|| rts.colors[i].slice != prevRTs.colors[i].slice
-				|| rts.colors[i].mipmap != prevRTs.colors[i].mipmap)
-			{
-				modified = true;
-				break;
-			}
-		}
-
-		if (!modified && (rts.depthStencil.canvas != prevRTs.depthStencil.canvas
-			|| rts.depthStencil.slice != prevRTs.depthStencil.slice
-			|| rts.depthStencil.mipmap != prevRTs.depthStencil.mipmap))
-		{
-			modified = true;
-		}
-
-		if (rts.temporaryRTFlags != prevRTs.temporaryRTFlags)
-			modified = true;
-
-		if (!modified)
-			return;
-	}
-
-	if (ncanvases > gl.getMaxRenderTargets())
-		throw love::Exception("This system can't simultaneously render to %d canvases.", ncanvases);
-
-	love::graphics::Canvas *firstcanvas = rts.colors[0].canvas;
-
-	bool multiformatsupported = Canvas::isMultiFormatMultiCanvasSupported();
-	PixelFormat firstformat = firstcanvas->getPixelFormat();
-
-	if (isPixelFormatDepthStencil(firstformat))
-		throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas.");
-
-	if (rts.colors[0].mipmap < 0 || rts.colors[0].mipmap >= firstcanvas->getMipmapCount())
-		throw love::Exception("Invalid mipmap level %d.", rts.colors[0].mipmap + 1);
-
-	bool hasSRGBcanvas = firstformat == PIXELFORMAT_sRGBA8;
-	int pixelwidth = firstcanvas->getPixelWidth(rts.colors[0].mipmap);
-	int pixelheight = firstcanvas->getPixelHeight(rts.colors[0].mipmap);
-
-	for (int i = 1; i < ncanvases; i++)
-	{
-		love::graphics::Canvas *c = rts.colors[i].canvas;
-		PixelFormat format = c->getPixelFormat();
-		int mip = rts.colors[i].mipmap;
-
-		if (mip < 0 || mip >= c->getMipmapCount())
-			throw love::Exception("Invalid mipmap level %d.", mip + 1);
-
-		if (c->getPixelWidth(mip) != pixelwidth || c->getPixelHeight(mip) != pixelheight)
-			throw love::Exception("All canvases must have the same pixel dimensions.");
-
-		if (!multiformatsupported && format != firstformat)
-			throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats.");
-
-		if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA())
-			throw love::Exception("All Canvases must have the same MSAA value.");
-
-		if (isPixelFormatDepthStencil(format))
-			throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas.");
-
-		if (format == PIXELFORMAT_sRGBA8)
-			hasSRGBcanvas = true;
-	}
-
-	if (rts.depthStencil.canvas != nullptr)
-	{
-		love::graphics::Canvas *c = rts.depthStencil.canvas;
-		int mip = rts.depthStencil.mipmap;
-
-		if (!isPixelFormatDepthStencil(c->getPixelFormat()))
-			throw love::Exception("Only depth/stencil format Canvases can be used with the 'depthstencil' field of the table passed into setCanvas.");
-
-		if (c->getPixelWidth(mip) != pixelwidth || c->getPixelHeight(mip) != pixelheight)
-			throw love::Exception("All canvases must have the same pixel dimensions.");
-
-		if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA())
-			throw love::Exception("All Canvases must have the same MSAA value.");
-
-		if (mip < 0 || mip >= c->getMipmapCount())
-			throw love::Exception("Invalid mipmap level %d.", mip + 1);
-	}
+	const DisplayState &state = states.back();
 
 	OpenGL::TempDebugGroup debuggroup("setCanvas(...)");
 
@@ -600,15 +502,13 @@ void Graphics::setCanvas(const RenderTargets &rts)
 
 	bindCachedFBO(rts);
 
-	gl.setViewport({0, 0, pixelwidth, pixelheight});
+	gl.setViewport({0, 0, pixelw, pixelh});
 
 	// Re-apply the scissor if it was active, since the rectangle passed to
 	// glScissor is affected by the viewport dimensions.
 	if (state.scissor)
 		setScissor(state.scissorRect);
 
-	int w = firstcanvas->getWidth(rts.colors[0].mipmap);
-	int h = firstcanvas->getHeight(rts.colors[0].mipmap);
 	projectionMatrix = Matrix4::ortho(0.0, (float) w, 0.0, (float) h);
 
 	// Make sure the correct sRGB setting is used when drawing to the canvases.
@@ -619,19 +519,6 @@ void Graphics::setCanvas(const RenderTargets &rts)
 		else if (!hasSRGBcanvas && gl.hasFramebufferSRGB())
 			gl.setFramebufferSRGB(false);
 	}
-
-	RenderTargetsStrongRef refs;
-	refs.colors.reserve(rts.colors.size());
-
-	for (auto c : rts.colors)
-		refs.colors.emplace_back(c.canvas, c.slice);
-
-	refs.depthStencil = RenderTargetStrongRef(rts.depthStencil.canvas, rts.depthStencil.slice);
-	refs.temporaryRTFlags = rts.temporaryRTFlags;
-
-	std::swap(state.renderTargets, refs);
-
-	canvasSwitchCount++;
 }
 
 void Graphics::setCanvas()
@@ -643,6 +530,7 @@ void Graphics::setCanvas()
 
 	OpenGL::TempDebugGroup debuggroup("setCanvas()");
 
+	flushStreamDraws();
 	endPass();
 
 	state.renderTargets = RenderTargetsStrongRef();
@@ -673,8 +561,6 @@ void Graphics::setCanvas()
 
 void Graphics::endPass()
 {
-	flushStreamDraws();
-
 	auto &rts = states.back().renderTargets;
 	love::graphics::Canvas *depthstencil = rts.depthStencil.canvas.get();
 
@@ -1013,6 +899,7 @@ void Graphics::present(void *screenshotCallbackData)
 	if (isCanvasActive())
 		throw love::Exception("present cannot be called while a Canvas is active.");
 
+	flushStreamDraws();
 	endPass();
 
 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO());
@@ -1123,6 +1010,7 @@ void Graphics::present(void *screenshotCallbackData)
 	gl.stats.drawCalls = 0;
 	gl.stats.shaderSwitches = 0;
 	canvasSwitchCount = 0;
+	drawCallsBatched = 0;
 }
 
 void Graphics::setScissor(const Rect &rect)

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

@@ -31,7 +31,7 @@
 
 // LOVE
 #include "graphics/Graphics.h"
-#include "graphics/Color.h"
+#include "common/Color.h"
 
 #include "image/Image.h"
 #include "image/ImageData.h"
@@ -97,7 +97,6 @@ public:
 
 	void setColor(Colorf c) override;
 
-	void setCanvas(const RenderTargets &rts) override;
 	void setCanvas() override;
 
 	void setScissor(const Rect &rect) override;
@@ -128,6 +127,7 @@ public:
 private:
 
 	love::graphics::StreamBuffer *newStreamBuffer(BufferType type, size_t size) override;
+	void setCanvasInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBcanvas) override;
 	void initCapabilities() override;
 	void getAPIStats(int &drawcalls, int &shaderswitches) const override;
 

+ 1 - 15
src/modules/graphics/opengl/Image.cpp

@@ -221,22 +221,8 @@ bool Image::loadVolatile()
 	glGenTextures(1, &texture);
 	gl.bindTextureToUnit(this, 0, false);
 
-	bool loaddefault = false;
-
-	int max2Dsize = gl.getMax2DTextureSize();
-	int max3Dsize = gl.getMax3DTextureSize();
-
-	if ((texType == TEXTURE_2D || texType == TEXTURE_2D_ARRAY) && (pixelWidth > max2Dsize || pixelHeight > max2Dsize))
-		loaddefault = true;
-	else if (texType == TEXTURE_2D_ARRAY && layers > gl.getMaxTextureLayers())
-		loaddefault = true;
-	else if (texType == TEXTURE_CUBE && (pixelWidth > gl.getMaxCubeTextureSize() || pixelWidth != pixelHeight))
-		loaddefault = true;
-	else if (texType == TEXTURE_VOLUME && (pixelWidth > max3Dsize || pixelHeight > max3Dsize || depth > max3Dsize))
-		loaddefault = true;
-
 	// Use a default texture if the size is too big for the system.
-	if (loaddefault)
+	if (!validateDimensions(false))
 	{
 		loadDefaultTexture();
 		return true;

+ 8 - 74
src/modules/graphics/opengl/Mesh.cpp

@@ -91,94 +91,28 @@ int Mesh::bindAttributeToShaderInput(int attributeindex, const std::string &inpu
 	return attriblocation;
 }
 
-void Mesh::drawInstanced(love::graphics::Graphics *gfx, const love::Matrix4 &m, int instancecount)
+void Mesh::drawInternal(int start, int count, int instancecount, bool useindexbuffer, uint32 attribflags, uint32 instancedattribflags) const
 {
-	if (vertexCount <= 0 || instancecount <= 0)
-		return;
-
-	if (instancecount > 1 && !gl.isInstancingSupported())
-		throw love::Exception("Instancing is not supported on this system.");
-
-	gfx->flushStreamDraws();
-
-	if (Shader::isDefaultActive())
-		Shader::attachDefault(Shader::STANDARD_DEFAULT);
-
-	if (Shader::current && texture.get())
-		Shader::current->checkMainTexture(texture);
-
 	OpenGL::TempDebugGroup debuggroup("Mesh draw");
 
-	uint32 enabledattribs = 0;
-	uint32 instancedattribs = 0;
-
-	for (const auto &attrib : attachedAttributes)
-	{
-		if (!attrib.second.enabled)
-			continue;
-
-		love::graphics::Mesh *mesh = attrib.second.mesh;
-		int location = mesh->bindAttributeToShaderInput(attrib.second.index, attrib.first);
-
-		if (location >= 0)
-		{
-			uint32 bit = 1u << (uint32) location;
-
-			enabledattribs |= bit;
-
-			if (attrib.second.step == STEP_PER_INSTANCE)
-				instancedattribs |= bit;
-		}
-	}
-
-	// Not supported on all platforms or GL versions, I believe.
-	if (!(enabledattribs & ATTRIBFLAG_POS))
-		throw love::Exception("Mesh must have an enabled VertexPosition attribute to be drawn.");
-
-	gl.useVertexAttribArrays(enabledattribs, instancedattribs);
-
+	gl.useVertexAttribArrays(attribflags, instancedattribflags);
 	gl.bindTextureToUnit(texture, 0, false);
-
-	Graphics::TempTransform transform(gfx, m);
-
 	gl.prepareDraw();
 
-	if (useIndexBuffer && ibo && elementCount > 0)
-	{
-		// Use the custom vertex map (index buffer) to draw the vertices.
-		gl.bindBuffer(BUFFER_INDEX, (GLuint) ibo->getHandle());
-
-		// Make sure the index buffer isn't mapped (sends data to GPU if needed.)
-		ibo->unmap();
-
-		int start = std::min(std::max(0, rangeStart), (int) elementCount - 1);
-
-		int count = (int) elementCount;
-		if (rangeCount > 0)
-			count = std::min(count, rangeCount);
-
-		count = std::min(count, (int) elementCount - start);
+	GLenum gldrawmode = getGLDrawMode(drawMode);
 
+	if (useindexbuffer)
+	{
 		size_t elementsize = vertex::getIndexDataSize(elementDataType);
 		const void *indices = BUFFER_OFFSET(start * elementsize);
 		GLenum type = OpenGL::getGLIndexDataType(elementDataType);
 
-		if (count > 0)
-			gl.drawElements(getGLDrawMode(drawMode), count, type, indices, instancecount);
+		gl.bindBuffer(BUFFER_INDEX, (GLuint) ibo->getHandle());
+		gl.drawElements(gldrawmode, count, type, indices, instancecount);
 	}
 	else
 	{
-		int start = std::min(std::max(0, rangeStart), (int) vertexCount - 1);
-
-		int count = (int) vertexCount;
-		if (rangeCount > 0)
-			count = std::min(count, rangeCount);
-
-		count = std::min(count, (int) vertexCount - start);
-
-		// Normal non-indexed drawing (no custom vertex map.)
-		if (count > 0)
-			gl.drawArrays(getGLDrawMode(drawMode), start, count, instancecount);
+		gl.drawArrays(gldrawmode, start, count, instancecount);
 	}
 }
 

+ 4 - 1
src/modules/graphics/opengl/Mesh.h

@@ -44,7 +44,10 @@ public:
 	virtual ~Mesh();
 
 	int bindAttributeToShaderInput(int attributeindex, const std::string &inputname) override;
-	void drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount) override;
+
+protected:
+
+	void drawInternal(int start, int count, int instancecount, bool useindexbuffer, uint32 attribflags, uint32 instancedattribflags) const override;
 
 private:
 

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

@@ -261,7 +261,8 @@ void OpenGL::initVendor()
 
 	// http://feedback.wildfiregames.com/report/opengl/feature/GL_VENDOR
 	// http://stackoverflow.com/questions/2093594/opengl-extensions-available-on-different-android-devices
-	if (strstr(vstr, "ATI Technologies"))
+	// http://opengl.gpuinfo.org/gl_stats_caps_single.php?listreportsbycap=GL_VENDOR
+	if (strstr(vstr, "ATI Technologies") || strstr(vstr, "AMD") || strstr(vstr, "Advanced Micro Devices"))
 		vendor = VENDOR_AMD;
 	else if (strstr(vstr, "NVIDIA"))
 		vendor = VENDOR_NVIDIA;

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

@@ -25,7 +25,7 @@
 #include "common/config.h"
 #include "common/int.h"
 #include "common/math.h"
-#include "graphics/Color.h"
+#include "common/Color.h"
 #include "graphics/Texture.h"
 #include "graphics/vertex.h"
 #include "graphics/depthstencil.h"

+ 1 - 12
src/modules/graphics/opengl/ParticleSystem.cpp

@@ -50,21 +50,10 @@ ParticleSystem *ParticleSystem::clone()
 	return new ParticleSystem(*this);
 }
 
-void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
+void ParticleSystem::drawInternal() const
 {
 	using namespace vertex;
 
-	if (!prepareDraw(gfx))
-		return;
-
-	Graphics::TempTransform transform(gfx, m);
-
-	if (Shader::isDefaultActive())
-		Shader::attachDefault(Shader::STANDARD_DEFAULT);
-
-	if (Shader::current && texture.get())
-		Shader::current->checkMainTexture(texture);
-
 	OpenGL::TempDebugGroup debuggroup("ParticleSystem draw");
 
 	gl.bindTextureToUnit(texture, 0, false);

+ 4 - 1
src/modules/graphics/opengl/ParticleSystem.h

@@ -44,7 +44,10 @@ public:
 	virtual ~ParticleSystem();
 
 	ParticleSystem *clone() override;
-	void draw(Graphics *gfx, const Matrix4 &m) override;
+
+private:
+
+	void drawInternal() const override;
 
 }; // ParticleSystem
 

+ 4 - 4
src/modules/graphics/opengl/Shader.cpp

@@ -855,14 +855,14 @@ void Shader::updateBuiltinUniforms()
 	// Only upload the matrices if they've changed.
 	if (memcmp(curxform.getElements(), lastTransformMatrix.getElements(), sizeof(float) * 16) != 0)
 	{
-		GLint location = builtinUniforms[BUILTIN_MATRIX_TRANSFORM];
+		GLint location = builtinUniforms[BUILTIN_MATRIX_VIEW_FROM_LOCAL];
 		if (location >= 0)
 			glUniformMatrix4fv(location, 1, GL_FALSE, curxform.getElements());
 
 		// Also upload the re-calculated normal matrix, if possible. The normal
 		// matrix is the transpose of the inverse of the rotation portion
 		// (top-left 3x3) of the transform matrix.
-		location = builtinUniforms[BUILTIN_MATRIX_NORMAL];
+		location = builtinUniforms[BUILTIN_MATRIX_VIEW_NORMAL_FROM_LOCAL];
 		if (location >= 0)
 		{
 			Matrix3 normalmatrix = Matrix3(curxform).transposedInverse();
@@ -875,7 +875,7 @@ void Shader::updateBuiltinUniforms()
 
 	if (memcmp(curproj.getElements(), lastProjectionMatrix.getElements(), sizeof(float) * 16) != 0)
 	{
-		GLint location = builtinUniforms[BUILTIN_MATRIX_PROJECTION];
+		GLint location = builtinUniforms[BUILTIN_MATRIX_CLIP_FROM_VIEW];
 		if (location >= 0)
 			glUniformMatrix4fv(location, 1, GL_FALSE, curproj.getElements());
 
@@ -885,7 +885,7 @@ void Shader::updateBuiltinUniforms()
 
 	if (tpmatrixneedsupdate)
 	{
-		GLint location = builtinUniforms[BUILTIN_MATRIX_TRANSFORM_PROJECTION];
+		GLint location = builtinUniforms[BUILTIN_MATRIX_CLIP_FROM_LOCAL];
 		if (location >= 0)
 		{
 			Matrix4 tp_matrix(curproj, curxform);

+ 9 - 56
src/modules/graphics/opengl/SpriteBatch.cpp

@@ -51,52 +51,15 @@ SpriteBatch::~SpriteBatch()
 {
 }
 
-void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
+void SpriteBatch::drawInternal(vertex::CommonFormat format, size_t indexbytestart, size_t indexcount)
 {
-	using namespace vertex;
-
-	if (next == 0)
-		return;
-
-	gfx->flushStreamDraws();
-
-	if (texture.get())
-	{
-		if (Shader::isDefaultActive())
-		{
-			Shader::StandardShader defaultshader = Shader::STANDARD_DEFAULT;
-			if (texture->getTextureType() == TEXTURE_2D_ARRAY)
-				defaultshader = Shader::STANDARD_ARRAY;
-
-			Shader::attachDefault(defaultshader);
-		}
-
-		if (Shader::current)
-			Shader::current->checkMainTexture(texture);
-	}
-
 	OpenGL::TempDebugGroup debuggroup("SpriteBatch draw");
 
-	Graphics::TempTransform transform(gfx, m);
-
-	gl.bindTextureToUnit(texture, 0, false);
-
-	// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
-	array_buf->unmap();
-
-	CommonFormat format = vertex_format;
-
-	if (!color_active)
-	{
-		if (format == CommonFormat::XYf_STPf_RGBAub)
-			format = CommonFormat::XYf_STPf;
-		else
-			format = CommonFormat::XYf_STf;
-	}
-
 	uint32 enabledattribs = getFormatFlags(format);
 
-	gl.setVertexPointers(format, array_buf, format_stride, 0);
+	// We want attached attributes to override local attributes, so we should
+	// call this before binding attached attributes.
+	gl.setVertexPointers(format, array_buf, vertex_stride, 0);
 
 	for (const auto &it : attached_attributes)
 	{
@@ -114,26 +77,16 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 	}
 
 	gl.useVertexAttribArrays(enabledattribs);
+	gl.bindTextureToUnit(texture, 0, false);
 
 	gl.prepareDraw();
 
-	int start = std::min(std::max(0, range_start), next - 1);
+	gl.bindBuffer(BUFFER_INDEX, (GLuint) quad_indices.getBuffer()->getHandle());
 
-	int count = next;
-	if (range_count > 0)
-		count = std::min(count, range_count);
+	const void *indices = BUFFER_OFFSET(indexbytestart);
+	GLenum gltype = OpenGL::getGLIndexDataType(quad_indices.getType());
 
-	count = std::min(count, next - start);
-
-	if (count > 0)
-	{
-		gl.bindBuffer(BUFFER_INDEX, (GLuint) quad_indices.getBuffer()->getHandle());
-
-		const void *indices = BUFFER_OFFSET(start * quad_indices.getElementSize());
-		GLenum gltype = OpenGL::getGLIndexDataType(quad_indices.getType());
-
-		gl.drawElements(GL_TRIANGLES, (GLsizei) quad_indices.getIndexCount(count), gltype, indices);
-	}
+	gl.drawElements(GL_TRIANGLES, (GLsizei) indexcount, gltype, indices);
 }
 
 } // opengl

+ 3 - 2
src/modules/graphics/opengl/SpriteBatch.h

@@ -37,8 +37,9 @@ public:
 	SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usage usage);
 	virtual ~SpriteBatch();
 
-	// Implements Drawable.
-	void draw(Graphics *gfx, const Matrix4 &m) override;
+protected:
+
+	void drawInternal(vertex::CommonFormat format, size_t indexbytestart, size_t indexcount) override;
 
 }; // SpriteBatch
 

+ 2 - 30
src/modules/graphics/opengl/Text.cpp

@@ -40,36 +40,10 @@ Text::~Text()
 {
 }
 
-void Text::draw(Graphics *gfx, const Matrix4 &m)
+void Text::drawInternal(const std::vector<Font::DrawCommand> &commands) const
 {
-	if (vbo == nullptr || draw_commands.empty())
-		return;
-
-	gfx->flushStreamDraws();
-
-	if (Shader::isDefaultActive())
-		Shader::attachDefault(Shader::STANDARD_DEFAULT);
-
-	if (Shader::current)
-		Shader::current->checkMainTextureType(TEXTURE_2D, false);
-
 	OpenGL::TempDebugGroup debuggroup("Text object draw");
 
-	// Re-generate the text if the Font's texture cache was invalidated.
-	if (font->getTextureCacheID() != texture_cache_id)
-		regenerateVertices();
-
-	int totalverts = 0;
-	for (const Font::DrawCommand &cmd : draw_commands)
-		totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts);
-
-	if ((size_t) totalverts / 4 > quadIndices.getSize())
-		quadIndices = QuadIndices(gfx, (size_t) totalverts / 4);
-
-	vbo->unmap(); // Make sure all pending data is flushed to the GPU.
-
-	Graphics::TempTransform transform(gfx, m);
-
 	gl.prepareDraw();
 
 	gl.setVertexPointers(Font::vertexFormat, vbo, 0);
@@ -82,14 +56,12 @@ void Text::draw(Graphics *gfx, const Matrix4 &m)
 
 	// We need a separate draw call for every section of the text which uses a
 	// different texture than the previous section.
-	for (const Font::DrawCommand &cmd : draw_commands)
+	for (const Font::DrawCommand &cmd : commands)
 	{
 		GLsizei count = (cmd.vertexcount / 4) * 6;
 		size_t offset = (cmd.startvertex / 4) * 6 * elemsize;
 
-		// TODO: Use glDrawElementsBaseVertex when supported?
 		gl.bindTextureToUnit(cmd.texture, 0, false);
-
 		gl.drawElements(GL_TRIANGLES, count, gltype, BUFFER_OFFSET(offset));
 	}
 }

+ 3 - 2
src/modules/graphics/opengl/Text.h

@@ -38,8 +38,9 @@ public:
 	Text(love::graphics::Graphics *gfx, love::graphics::Font *font, const std::vector<Font::ColoredString> &text = {});
 	virtual ~Text();
 
-	// Implements Drawable.
-	void draw(love::graphics::Graphics *gfx, const Matrix4 &m) override;
+protected:
+
+	void drawInternal(const std::vector<Font::DrawCommand> &commands) const override;
 
 }; // Text
 

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

@@ -22,7 +22,7 @@
 
 // LOVE
 #include "common/int.h"
-#include "Color.h"
+#include "common/Color.h"
 
 // C
 #include <stddef.h>

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

@@ -46,7 +46,7 @@ int w_Canvas_renderTo(lua_State *L)
 
 	if (rt.canvas->getTextureType() != TEXTURE_2D)
 	{
-		rt.slice = (int) luaL_checknumber(L, 2) - 1;
+		rt.slice = (int) luaL_checkinteger(L, 2) - 1;
 		startidx++;
 	}
 

+ 157 - 77
src/modules/graphics/wrap_Graphics.cpp

@@ -24,6 +24,7 @@
 #include "image/ImageData.h"
 #include "image/Image.h"
 #include "font/Rasterizer.h"
+#include "filesystem/Filesystem.h"
 #include "filesystem/wrap_Filesystem.h"
 #include "video/VideoStream.h"
 #include "image/wrap_Image.h"
@@ -321,7 +322,7 @@ int w_setCanvas(lua_State *L)
 
 			if (i == 1 && type != TEXTURE_2D)
 			{
-				target.slice = (int) luaL_checknumber(L, i + 1) - 1;
+				target.slice = (int) luaL_checkinteger(L, i + 1) - 1;
 				target.mipmap = (int) luaL_optinteger(L, i + 2, 1) - 1;
 				targets.colors.push_back(target);
 				break;
@@ -439,8 +440,54 @@ static void screenshotCallback(love::image::ImageData *i, Reference *ref, void *
 		delete ref;
 }
 
+static int screenshotSaveToFile(lua_State *L)
+{
+	image::ImageData *id = image::luax_checkimagedata(L, 1);
+
+	const char *filename = luaL_checkstring(L, lua_upvalueindex(1));
+	const char *ext = luaL_checkstring(L, lua_upvalueindex(2));
+
+	image::FormatHandler::EncodedFormat format;
+	if (!image::ImageData::getConstant(ext, format))
+		return 0;
+
+	try
+	{
+		id->encode(format, filename, true);
+	}
+	catch (love::Exception &e)
+	{
+		printf("Screenshot encoding or saving failed: %s", e.what());
+		// Do nothing...
+	}
+
+	return 0;
+}
+
 int w_captureScreenshot(lua_State *L)
 {
+	if (lua_isstring(L, 1))
+	{
+		std::string filename = luax_checkstring(L, 1);
+		std::string ext;
+
+		size_t dotpos = filename.rfind('.');
+
+		if (dotpos != std::string::npos)
+			ext = filename.substr(dotpos + 1);
+
+		std::transform(ext.begin(), ext.end(), ext.begin(), tolower);
+
+		image::FormatHandler::EncodedFormat format;
+		if (!image::ImageData::getConstant(ext.c_str(), format))
+			return luaL_error(L, "Invalid encoded image format: %s", ext.c_str());
+
+		lua_pushvalue(L, 1);
+		luax_pushstring(L, ext);
+		lua_pushcclosure(L, screenshotSaveToFile, 2);
+		lua_replace(L, 1);
+	}
+
 	luaL_checktype(L, 1, LUA_TFUNCTION);
 
 	Graphics::ScreenshotInfo info;
@@ -470,10 +517,10 @@ int w_setScissor(lua_State *L)
 	}
 
 	Rect rect;
-	rect.x = (int) luaL_checknumber(L, 1);
-	rect.y = (int) luaL_checknumber(L, 2);
-	rect.w = (int) luaL_checknumber(L, 3);
-	rect.h = (int) luaL_checknumber(L, 4);
+	rect.x = (int) luaL_checkinteger(L, 1);
+	rect.y = (int) luaL_checkinteger(L, 2);
+	rect.w = (int) luaL_checkinteger(L, 3);
+	rect.h = (int) luaL_checkinteger(L, 4);
 
 	if (rect.w < 0 || rect.h < 0)
 		return luaL_error(L, "Can't set scissor with negative width and/or height.");
@@ -485,10 +532,10 @@ int w_setScissor(lua_State *L)
 int w_intersectScissor(lua_State *L)
 {
 	Rect rect;
-	rect.x = (int) luaL_checknumber(L, 1);
-	rect.y = (int) luaL_checknumber(L, 2);
-	rect.w = (int) luaL_checknumber(L, 3);
-	rect.h = (int) luaL_checknumber(L, 4);
+	rect.x = (int) luaL_checkinteger(L, 1);
+	rect.y = (int) luaL_checkinteger(L, 2);
+	rect.w = (int) luaL_checkinteger(L, 3);
+	rect.h = (int) luaL_checkinteger(L, 4);
 
 	if (rect.w < 0 || rect.h < 0)
 		return luaL_error(L, "Can't set scissor with negative width and/or height.");
@@ -524,7 +571,7 @@ int w_stencil(lua_State *L)
 			return luaL_error(L, "Invalid stencil draw action: %s", actionstr);
 	}
 
-	int stencilvalue = (int) luaL_optnumber(L, 3, 1);
+	int stencilvalue = (int) luaL_optinteger(L, 3, 1);
 
 	// Fourth argument: whether to keep the contents of the stencil buffer.
 	OptionalInt stencilclear;
@@ -561,7 +608,7 @@ int w_setStencilTest(lua_State *L)
 		if (!getConstant(comparestr, compare))
 			return luaL_error(L, "Invalid compare mode: %s", comparestr);
 
-		comparevalue = (int) luaL_checknumber(L, 2);
+		comparevalue = (int) luaL_checkinteger(L, 2);
 	}
 
 	luax_catchexcept(L, [&](){ instance()->setStencilTest(compare, comparevalue); });
@@ -942,14 +989,14 @@ int w_newQuad(lua_State *L)
 	}
 	else if (luax_istype(L, 6, Texture::type))
 	{
-		layer = (int) luaL_checknumber(L, 5) - 1;
+		layer = (int) luaL_checkinteger(L, 5) - 1;
 		Texture *texture = luax_checktexture(L, 6);
 		sw = texture->getWidth();
 		sh = texture->getHeight();
 	}
 	else if (!lua_isnoneornil(L, 7))
 	{
-		layer = (int) luaL_checknumber(L, 5) - 1;
+		layer = (int) luaL_checkinteger(L, 5) - 1;
 		sw = luaL_checknumber(L, 6);
 		sh = luaL_checknumber(L, 7);
 	}
@@ -1030,7 +1077,7 @@ int w_newSpriteBatch(lua_State *L)
 	luax_checkgraphicscreated(L);
 
 	Texture *texture = luax_checktexture(L, 1);
-	int size = (int) luaL_optnumber(L, 2, 1000);
+	int size = (int) luaL_optinteger(L, 2, 1000);
 	vertex::Usage usage = vertex::USAGE_DYNAMIC;
 	if (lua_gettop(L) > 2)
 	{
@@ -1075,8 +1122,8 @@ int w_newCanvas(lua_State *L)
 	Canvas::Settings settings;
 
 	// check if width and height are given. else default to screen dimensions.
-	settings.width  = (int) luaL_optnumber(L, 1, instance()->getWidth());
-	settings.height = (int) luaL_optnumber(L, 2, instance()->getHeight());
+	settings.width  = (int) luaL_optinteger(L, 1, instance()->getWidth());
+	settings.height = (int) luaL_optinteger(L, 2, instance()->getHeight());
 
 	// Default to the screen's current pixel density scale.
 	settings.pixeldensity = instance()->getScreenPixelDensity();
@@ -1085,7 +1132,7 @@ int w_newCanvas(lua_State *L)
 
 	if (lua_isnumber(L, 3))
 	{
-		settings.layers = (int) luaL_checknumber(L, 3);
+		settings.layers = (int) luaL_checkinteger(L, 3);
 		settings.type = TEXTURE_2D_ARRAY;
 		startidx = 4;
 	}
@@ -1142,36 +1189,48 @@ int w_newCanvas(lua_State *L)
 
 static int w_getShaderSource(lua_State *L, int startidx, bool gles, Shader::ShaderSource &source)
 {
+	using namespace love::filesystem;
+
 	luax_checkgraphicscreated(L);
 
+	auto fs = Module::getInstance<Filesystem>(Module::M_FILESYSTEM);
+
 	// read any filepath arguments
 	for (int i = startidx; i < startidx + 2; i++)
 	{
 		if (!lua_isstring(L, i))
-			continue;
+		{
+			if (luax_cangetfiledata(L, i))
+			{
+				FileData *fd = luax_getfiledata(L, i);
 
-		// call love.filesystem.isFile(arg_i)
-		luax_getfunction(L, "filesystem", "isFile");
-		lua_pushvalue(L, i);
-		lua_call(L, 1, 1);
+				lua_pushlstring(L, (const char *) fd->getData(), fd->getSize());
+				fd->release();
 
-		bool isFile = luax_toboolean(L, -1);
-		lua_pop(L, 1);
+				lua_replace(L, i);
+			}
 
-		if (isFile)
+			continue;
+		}
+
+		size_t slen = 0;
+		const char *str = lua_tolstring(L, i, &slen);
+
+		if (fs != nullptr && fs->isFile(str))
 		{
-			luax_getfunction(L, "filesystem", "read");
-			lua_pushvalue(L, i);
-			lua_call(L, 1, 1);
+			FileData *fd = nullptr;
+			luax_catchexcept(L, [&](){ fd = fs->read(str); });
+
+			lua_pushlstring(L, (const char *) fd->getData(), fd->getSize());
+			fd->release();
+
 			lua_replace(L, i);
 		}
 		else
 		{
 			// Check if the argument looks like a filepath - we want a nicer
 			// error for misspelled filepath arguments.
-			size_t slen = 0;
-			const char *str = lua_tolstring(L, i, &slen);
-			if (slen > 0 && slen < 256 && !strchr(str, '\n'))
+			if (slen > 0 && slen < 64 && !strchr(str, '\n'))
 			{
 				const char *ext = strchr(str, '.');
 				if (ext != nullptr && !strchr(ext, ';') && !strchr(ext, ' '))
@@ -1352,7 +1411,7 @@ static Mesh *newStandardMesh(lua_State *L)
 	}
 	else
 	{
-		int count = (int) luaL_checknumber(L, 1);
+		int count = (int) luaL_checkinteger(L, 1);
 		luax_catchexcept(L, [&](){ t = instance()->newMesh(count, drawmode, usage); });
 	}
 
@@ -1397,7 +1456,7 @@ static Mesh *newCustomMesh(lua_State *L)
 			return nullptr;
 		}
 
-		format.components = (int) luaL_checknumber(L, -1);
+		format.components = (int) luaL_checkinteger(L, -1);
 		if (format.components <= 0 || format.components > 4)
 		{
 			luaL_error(L, "Number of vertex attribute components must be between 1 and 4 (got %d)", format.components);
@@ -1410,7 +1469,7 @@ static Mesh *newCustomMesh(lua_State *L)
 
 	if (lua_isnumber(L, 2))
 	{
-		int vertexcount = (int) luaL_checknumber(L, 2);
+		int vertexcount = (int) luaL_checkinteger(L, 2);
 		luax_catchexcept(L, [&](){ t = instance()->newMesh(vertexformat, vertexcount, drawmode, usage); });
 	}
 	else if (luax_istype(L, 2, Data::type))
@@ -2073,6 +2132,9 @@ int w_getStats(lua_State *L)
 	lua_pushinteger(L, stats.drawCalls);
 	lua_setfield(L, -2, "drawcalls");
 
+	lua_pushinteger(L, stats.drawCallsBatched);
+	lua_setfield(L, -2, "drawcallsbatched");
+
 	lua_pushinteger(L, stats.canvasSwitches);
 	lua_setfield(L, -2, "canvasswitches");
 
@@ -2135,7 +2197,7 @@ int w_drawLayer(lua_State *L)
 {
 	Texture *texture = luax_checktexture(L, 1);
 	Quad *quad = nullptr;
-	int layer = (int) luaL_checknumber(L, 2) - 1;
+	int layer = (int) luaL_checkinteger(L, 2) - 1;
 	int startidx = 3;
 
 	if (luax_istype(L, startidx, Quad::type))
@@ -2279,23 +2341,23 @@ int w_points(lua_State *L)
 	if (args % 2 != 0 && !is_table_of_tables)
 		return luaL_error(L, "Number of vertex components must be a multiple of two");
 
-	int numpoints = args / 2;
+	int numpositions = args / 2;
 	if (is_table_of_tables)
-		numpoints = args;
+		numpositions = args;
 
-	float *coords = nullptr;
+	Vector2 *positions = nullptr;
 	Colorf *colors = nullptr;
 
 	if (is_table_of_tables)
 	{
-		size_t datasize = (sizeof(float) * 2 + sizeof(Colorf)) * numpoints;
+		size_t datasize = (sizeof(Vector2) + sizeof(Colorf)) * numpositions;
 		uint8 *data = instance()->getScratchBuffer<uint8>(datasize);
 
-		coords = (float *) data;
-		colors = (Colorf *) (data + sizeof(float) * numpoints * 2);
+		positions = (Vector2 *) data;
+		colors = (Colorf *) (data + sizeof(Vector2) * numpositions);
 	}
 	else
-		coords = instance()->getScratchBuffer<float>(numpoints * 2);
+		positions = instance()->getScratchBuffer<Vector2>(numpositions);
 
 	if (is_table)
 	{
@@ -2308,13 +2370,13 @@ int w_points(lua_State *L)
 				for (int j = 1; j <= 6; j++)
 					lua_rawgeti(L, -j, j);
 
-				coords[i * 2 + 0] = luax_tofloat(L, -6);
-				coords[i * 2 + 1] = luax_tofloat(L, -5);
+				positions[i].x = luax_tofloat(L, -6);
+				positions[i].y = luax_tofloat(L, -5);
 
-				colors[i].r = luaL_optnumber(L, -4, 1.0);
-				colors[i].g = luaL_optnumber(L, -3, 1.0);
-				colors[i].b = luaL_optnumber(L, -2, 1.0);
-				colors[i].a = luaL_optnumber(L, -1, 1.0);
+				colors[i].r = (float) luaL_optnumber(L, -4, 1.0);
+				colors[i].g = (float) luaL_optnumber(L, -3, 1.0);
+				colors[i].b = (float) luaL_optnumber(L, -2, 1.0);
+				colors[i].a = (float) luaL_optnumber(L, -1, 1.0);
 
 				lua_pop(L, 7);
 			}
@@ -2322,21 +2384,26 @@ int w_points(lua_State *L)
 		else
 		{
 			// points({x1, y1, x2, y2, ...})
-			for (int i = 0; i < args; i++)
+			for (int i = 0; i < numpositions; i++)
 			{
-				lua_rawgeti(L, 1, i + 1);
-				coords[i] = luax_tofloat(L, -1);
-				lua_pop(L, 1);
+				lua_rawgeti(L, 1, i * 2 + 1);
+				lua_rawgeti(L, 1, i * 2 + 2);
+				positions[i].x = luax_tofloat(L, -2);
+				positions[i].y = luax_tofloat(L, -1);
+				lua_pop(L, 2);
 			}
 		}
 	}
 	else
 	{
-		for (int i = 0; i < args; i++)
-			coords[i] = luax_tofloat(L, i + 1);
+		for (int i = 0; i < numpositions; i++)
+		{
+			positions[i].x = luax_tofloat(L, i * 2 + 1);
+			positions[i].y = luax_tofloat(L, i * 2 + 2);
+		}
 	}
 
-	luax_catchexcept(L, [&](){ instance()->points(coords, colors, numpoints); });
+	luax_catchexcept(L, [&](){ instance()->points(positions, colors, numpositions); });
 	return 0;
 }
 
@@ -2355,24 +2422,31 @@ int w_line(lua_State *L)
 	else if (args < 4)
 		return luaL_error(L, "Need at least two vertices to draw a line");
 
-	float *coords = instance()->getScratchBuffer<float>(args);
+	int numvertices = args / 2;
+
+	Vector2 *coords = instance()->getScratchBuffer<Vector2>(numvertices);
 	if (is_table)
 	{
-		for (int i = 0; i < args; ++i)
+		for (int i = 0; i < numvertices; ++i)
 		{
-			lua_rawgeti(L, 1, i + 1);
-			coords[i] = luax_tofloat(L, -1);
-			lua_pop(L, 1);
+			lua_rawgeti(L, 1, (i * 2) + 1);
+			lua_rawgeti(L, 1, (i * 2) + 2);
+			coords[i].x = luax_tofloat(L, -2);
+			coords[i].y = luax_tofloat(L, -1);
+			lua_pop(L, 2);
 		}
 	}
 	else
 	{
-		for (int i = 0; i < args; ++i)
-			coords[i] = luax_tofloat(L, i + 1);
+		for (int i = 0; i < numvertices; ++i)
+		{
+			coords[i].x = luax_tofloat(L, (i * 2) + 1);
+			coords[i].y = luax_tofloat(L, (i * 2) + 2);
+		}
 	}
 
 	luax_catchexcept(L,
-		[&](){ instance()->polyline(coords, args); }
+		[&](){ instance()->polyline(coords, numvertices); }
 	);
 
 	return 0;
@@ -2403,7 +2477,7 @@ int w_rectangle(lua_State *L)
 		luax_catchexcept(L, [&](){ instance()->rectangle(mode, x, y, w, h, rx, ry); });
 	else
 	{
-		int points = (int) luaL_checknumber(L, 8);
+		int points = (int) luaL_checkinteger(L, 8);
 		luax_catchexcept(L, [&](){ instance()->rectangle(mode, x, y, w, h, rx, ry, points); });
 	}
 
@@ -2425,7 +2499,7 @@ int w_circle(lua_State *L)
 		luax_catchexcept(L, [&](){ instance()->circle(mode, x, y, radius); });
 	else
 	{
-		int points = (int) luaL_checknumber(L, 5);
+		int points = (int) luaL_checkinteger(L, 5);
 		luax_catchexcept(L, [&](){ instance()->circle(mode, x, y, radius, points); });
 	}
 
@@ -2448,7 +2522,7 @@ int w_ellipse(lua_State *L)
 		luax_catchexcept(L, [&](){ instance()->ellipse(mode, x, y, a, b); });
 	else
 	{
-		int points = (int) luaL_checknumber(L, 6);
+		int points = (int) luaL_checkinteger(L, 6);
 		luax_catchexcept(L, [&](){ instance()->ellipse(mode, x, y, a, b, points); });
 	}
 
@@ -2485,7 +2559,7 @@ int w_arc(lua_State *L)
 		luax_catchexcept(L, [&](){ instance()->arc(drawmode, arcmode, x, y, radius, angle1, angle2); });
 	else
 	{
-		int points = (int) luaL_checknumber(L, startidx + 5);
+		int points = (int) luaL_checkinteger(L, startidx + 5);
 		luax_catchexcept(L, [&](){ instance()->arc(drawmode, arcmode, x, y, radius, angle1, angle2, points); });
 	}
 
@@ -2513,28 +2587,34 @@ int w_polygon(lua_State *L)
 	else if (args < 6)
 		return luaL_error(L, "Need at least three vertices to draw a polygon");
 
+	int numvertices = args / 2;
+
 	// fetch coords
-	float *coords = instance()->getScratchBuffer<float>(args + 2);
+	Vector2 *coords = instance()->getScratchBuffer<Vector2>(numvertices + 1);
 	if (is_table)
 	{
-		for (int i = 0; i < args; ++i)
+		for (int i = 0; i < numvertices; ++i)
 		{
-			lua_rawgeti(L, 2, i + 1);
-			coords[i] = luax_tofloat(L, -1);
-			lua_pop(L, 1);
+			lua_rawgeti(L, 2, (i * 2) + 1);
+			lua_rawgeti(L, 2, (i * 2) + 2);
+			coords[i].x = luax_tofloat(L, -2);
+			coords[i].y = luax_tofloat(L, -1);
+			lua_pop(L, 2);
 		}
 	}
 	else
 	{
-		for (int i = 0; i < args; ++i)
-			coords[i] = luax_tofloat(L, i + 2);
+		for (int i = 0; i < numvertices; ++i)
+		{
+			coords[i].x = luax_tofloat(L, (i * 2) + 2);
+			coords[i].y = luax_tofloat(L, (i * 2) + 3);
+		}
 	}
 
 	// make a closed loop
-	coords[args]   = coords[0];
-	coords[args+1] = coords[1];
+	coords[numvertices] = coords[0];
 
-	luax_catchexcept(L, [&](){ instance()->polygon(mode, coords, args+2); });
+	luax_catchexcept(L, [&](){ instance()->polygon(mode, coords, numvertices+1); });
 	return 0;
 }
 

+ 16 - 9
src/modules/graphics/wrap_Graphics.lua

@@ -72,11 +72,18 @@ GLSL.UNIFORMS = [[
 // According to the GLSL ES 1.0 spec, uniform precision must match between stages,
 // but we can't guarantee that highp is always supported in fragment shaders...
 // We *really* don't want to use mediump for these in vertex shaders though.
-uniform LOVE_HIGHP_OR_MEDIUMP mat4 TransformMatrix;
-uniform LOVE_HIGHP_OR_MEDIUMP mat4 ProjectionMatrix;
-uniform LOVE_HIGHP_OR_MEDIUMP mat4 TransformProjectionMatrix;
-uniform LOVE_HIGHP_OR_MEDIUMP mat3 NormalMatrix;
-uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_ScreenSize;]]
+uniform LOVE_HIGHP_OR_MEDIUMP mat4 ViewSpaceFromLocal;
+uniform LOVE_HIGHP_OR_MEDIUMP mat4 ClipSpaceFromView;
+uniform LOVE_HIGHP_OR_MEDIUMP mat4 ClipSpaceFromLocal;
+uniform LOVE_HIGHP_OR_MEDIUMP mat3 ViewNormalFromLocal;
+uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_ScreenSize;
+
+// Compatibility
+#define TransformMatrix ViewSpaceFromLocal
+#define ProjectionMatrix ClipSpaceFromView
+#define TransformProjectionMatrix ClipSpaceFromLocal
+#define NormalMatrix ViewNormalFromLocal
+]]
 
 GLSL.FUNCTIONS = [[
 #ifdef GL_ES
@@ -222,13 +229,13 @@ attribute vec4 ConstantColor;
 varying vec4 VaryingTexCoord;
 varying vec4 VaryingColor;
 
-vec4 position(mat4 transform_proj, vec4 vertpos);
+vec4 position(mat4 clipSpaceFromLocal, vec4 localPosition);
 
 void main() {
 	VaryingTexCoord = VertexTexCoord;
 	VaryingColor = gammaCorrectColor(VertexColor) * ConstantColor;
 	setPointSize();
-	love_Position = position(TransformProjectionMatrix, VertexPosition);
+	love_Position = position(ClipSpaceFromLocal, VertexPosition);
 }]],
 }
 
@@ -424,8 +431,8 @@ end
 
 local defaultcode = {
 	vertex = [[
-vec4 position(mat4 transform_proj, vec4 vertpos) {
-	return transform_proj * vertpos;
+vec4 position(mat4 clipSpaceFromLocal, vec4 localPosition) {
+	return clipSpaceFromLocal * localPosition;
 }]],
 	pixel = [[
 vec4 effect(vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord) {

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

@@ -56,12 +56,12 @@ int w_Image_replacePixels(lua_State *L)
 
 	if (i->getTextureType() != TEXTURE_2D)
 	{
-		slice = (int) luaL_checknumber(L, 3) - 1;
+		slice = (int) luaL_checkinteger(L, 3) - 1;
 		if (!reloadmipmaps)
-			mipmap = (int) luaL_optnumber(L, 4, 1) - 1;
+			mipmap = (int) luaL_optinteger(L, 4, 1) - 1;
 	}
 	else if (!reloadmipmaps)
-		mipmap = (int) luaL_optnumber(L, 3, 1) - 1;
+		mipmap = (int) luaL_optinteger(L, 3, 1) - 1;
 
 	luax_catchexcept(L, [&](){ i->replacePixels(id, slice, mipmap, reloadmipmaps); });
 	return 0;

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

@@ -386,7 +386,7 @@ int w_Mesh_setVertexMap(lua_State *L)
 
 		size_t datatypesize = vertex::getIndexDataSize(indextype);
 
-		int indexcount = (int) luaL_optnumber(L, 4, d->getSize() / datatypesize);
+		int indexcount = (int) luaL_optinteger(L, 4, d->getSize() / datatypesize);
 
 		if (indexcount < 1 || indexcount * datatypesize > d->getSize())
 			return luaL_error(L, "Invalid index count: %d", indexcount);
@@ -515,8 +515,8 @@ int w_Mesh_setDrawRange(lua_State *L)
 		t->setDrawRange();
 	else
 	{
-		int start = (int) luaL_checknumber(L, 2) - 1;
-		int count = (int) luaL_checknumber(L, 3);
+		int start = (int) luaL_checkinteger(L, 2) - 1;
+		int count = (int) luaL_checkinteger(L, 3);
 		luax_catchexcept(L, [&](){ t->setDrawRange(start, count); });
 	}
 

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

@@ -699,7 +699,7 @@ int w_ParticleSystem_reset(lua_State *L)
 int w_ParticleSystem_emit(lua_State *L)
 {
 	ParticleSystem *t = luax_checkparticlesystem(L, 1);
-	int num = (int) luaL_checknumber(L, 2);
+	int num = (int) luaL_checkinteger(L, 2);
 	t->emit(num);
 	return 0;
 }

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

@@ -77,7 +77,7 @@ int w_Quad_getTextureDimensions(lua_State *L)
 int w_Quad_setLayer(lua_State *L)
 {
 	Quad *quad = luax_checkquad(L, 1);
-	int layer = (int) luaL_checknumber(L, 2) - 1;
+	int layer = (int) luaL_checkinteger(L, 2) - 1;
 	quad->setLayer(layer);
 	return 0;
 }

+ 5 - 5
src/modules/graphics/wrap_SpriteBatch.cpp

@@ -63,7 +63,7 @@ static inline int w_SpriteBatch_add_or_set(lua_State *L, SpriteBatch *t, int sta
 static int w_SpriteBatch_addLayer_or_setLayer(lua_State *L, SpriteBatch *t, int startidx, int index)
 {
 	Quad *quad = nullptr;
-	int layer = (int) luaL_checknumber(L, startidx) - 1;
+	int layer = (int) luaL_checkinteger(L, startidx) - 1;
 	startidx++;
 
 	if (luax_istype(L, startidx, Quad::type))
@@ -101,7 +101,7 @@ int w_SpriteBatch_add(lua_State *L)
 int w_SpriteBatch_set(lua_State *L)
 {
 	SpriteBatch *t = luax_checkspritebatch(L, 1);
-	int index = (int) luaL_checknumber(L, 2) - 1;
+	int index = (int) luaL_checkinteger(L, 2) - 1;
 
 	w_SpriteBatch_add_or_set(L, t, 3, index);
 
@@ -121,7 +121,7 @@ int w_SpriteBatch_addLayer(lua_State *L)
 int w_SpriteBatch_setLayer(lua_State *L)
 {
 	SpriteBatch *t = luax_checkspritebatch(L, 1);
-	int index = (int) luaL_checknumber(L, 2) - 1;
+	int index = (int) luaL_checkinteger(L, 2) - 1;
 
 	w_SpriteBatch_addLayer_or_setLayer(L, t, 3, index);
 	
@@ -251,8 +251,8 @@ int w_SpriteBatch_setDrawRange(lua_State *L)
 		t->setDrawRange();
 	else
 	{
-		int start = (int) luaL_checknumber(L, 2) - 1;
-		int count = (int) luaL_checknumber(L, 3);
+		int start = (int) luaL_checkinteger(L, 2) - 1;
+		int count = (int) luaL_checkinteger(L, 3);
 		luax_catchexcept(L, [&](){ t->setDrawRange(start, count); });
 	}
 

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

@@ -183,7 +183,7 @@ int w_Text_getFont(lua_State *L)
 int w_Text_getWidth(lua_State *L)
 {
 	Text *t = luax_checktext(L, 1);
-	int index = (int) luaL_optnumber(L, 2, 0) - 1;
+	int index = (int) luaL_optinteger(L, 2, 0) - 1;
 	lua_pushnumber(L, t->getWidth(index));
 	return 1;
 }
@@ -191,7 +191,7 @@ int w_Text_getWidth(lua_State *L)
 int w_Text_getHeight(lua_State *L)
 {
 	Text *t = luax_checktext(L, 1);
-	int index = (int) luaL_optnumber(L, 2, 0) - 1;
+	int index = (int) luaL_optinteger(L, 2, 0) - 1;
 	lua_pushnumber(L, t->getHeight(index));
 	return 1;
 }
@@ -199,7 +199,7 @@ int w_Text_getHeight(lua_State *L)
 int w_Text_getDimensions(lua_State *L)
 {
 	Text *t = luax_checktext(L, 1);
-	int index = (int) luaL_optnumber(L, 2, 0) - 1;
+	int index = (int) luaL_optinteger(L, 2, 0) - 1;
 	lua_pushnumber(L, t->getWidth(index));
 	lua_pushnumber(L, t->getHeight(index));
 	return 2;

+ 9 - 15
src/modules/image/magpie/CompressedFormatHandler.h → src/modules/image/CompressedFormatHandler.h

@@ -18,20 +18,17 @@
  * 3. This notice may not be removed or altered from any source distribution.
  **/
 
-#ifndef LOVE_IMAGE_MAGPIE_COMPRESSED_HANDLER_H
-#define LOVE_IMAGE_MAGPIE_COMPRESSED_HANDLER_H
+#pragma once
 
 // LOVE
-#include "filesystem/FileData.h"
-#include "image/CompressedImageData.h"
 #include "common/Object.h"
+#include "filesystem/FileData.h"
+#include "CompressedSlice.h"
 
 namespace love
 {
 namespace image
 {
-namespace magpie
-{
 
 /**
  * Base class for all CompressedImageData parser library interfaces.
@@ -45,8 +42,8 @@ public:
 	virtual ~CompressedFormatHandler() {}
 
 	/**
-	 * Determines whether a particular FileData can be parsed as CompressedImageData
-	 * by this handler.
+	 * Determines whether a particular FileData can be parsed as
+	 * CompressedImageData by this handler.
 	 * @param data The data to parse.
 	 **/
 	virtual bool canParse(const filesystem::FileData *data) = 0;
@@ -56,21 +53,18 @@ public:
 	 * a single block of memory containing all the images.
 	 *
 	 * @param[in] filedata The data to parse.
-	 * @param[out] images The list of sub-images generated. Byte data is a pointer
-	 *             to the returned data.
+	 * @param[out] images The list of sub-images generated. Byte data is a
+	 *             pointer to the returned 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.
 	 **/
-	virtual StrongRef<CompressedImageData::Memory> parse(filesystem::FileData *filedata,
-	               std::vector<StrongRef<CompressedImageData::Slice>> &images,
+	virtual StrongRef<CompressedMemory> parse(filesystem::FileData *filedata,
+	               std::vector<StrongRef<CompressedSlice>> &images,
 	               PixelFormat &format, bool &sRGB) = 0;
 
 }; // CompressedFormatHandler
 
-} // magpie
 } // image
 } // love
-
-#endif // LOVE_IMAGE_MAGPIE_COMPRESSED_HANDLER_H

+ 36 - 40
src/modules/image/CompressedImageData.cpp

@@ -27,58 +27,54 @@ namespace image
 
 love::Type CompressedImageData::type("CompressedImageData", &Data::type);
 
-CompressedImageData::Memory::Memory(size_t size)
-	: data(nullptr)
-	, size(size)
+CompressedImageData::CompressedImageData(const std::list<CompressedFormatHandler *> &formats, love::filesystem::FileData *filedata)
+	: format(PIXELFORMAT_UNKNOWN)
+	, sRGB(false)
 {
-	try
-	{
-		data = new uint8[size];
-	}
-	catch (std::exception &)
+	CompressedFormatHandler *parser = nullptr;
+
+	for (CompressedFormatHandler *handler : formats)
 	{
-		throw love::Exception("Out of memory.");
+		if (handler->canParse(filedata))
+		{
+			parser = handler;
+			break;
+		}
 	}
-}
 
-CompressedImageData::Memory::~Memory()
-{
-	delete[] data;
-}
+	if (parser == nullptr)
+		throw love::Exception("Could not parse compressed data: Unknown format.");
 
-CompressedImageData::Slice::Slice(PixelFormat format, int width, int height, Memory *memory, size_t offset, size_t size)
-	: memory(memory)
-	, offset(offset)
-	, dataSize(size)
-{
-	this->format = format;
-	this->width = width;
-	this->height = height;
-}
+	memory = parser->parse(filedata, dataImages, format, sRGB);
 
-CompressedImageData::Slice::Slice(const Slice &s)
-	: memory(s.memory)
-	, offset(s.offset)
-	, dataSize(s.dataSize)
-{
-	this->format = s.getFormat();
-	this->width = s.getWidth();
-	this->height = s.getHeight();
-}
+	if (memory == nullptr)
+		throw love::Exception("Could not parse compressed data.");
 
-CompressedImageData::Slice::~Slice()
-{
+	if (format == PIXELFORMAT_UNKNOWN)
+		throw love::Exception("Could not parse compressed data: Unknown format.");
+
+	if (dataImages.size() == 0 || memory->size == 0)
+		throw love::Exception("Could not parse compressed data: No valid data?");
 }
 
-CompressedImageData::Slice *CompressedImageData::Slice::clone() const
+CompressedImageData::CompressedImageData(const CompressedImageData &c)
+	: format(c.format)
+	, sRGB(c.sRGB)
 {
-	return new Slice(*this);
+	memory.set(new CompressedMemory(c.memory->size), Acquire::NORETAIN);
+	memcpy(memory->data, c.memory->data, memory->size);
+
+	for (const auto &i : c.dataImages)
+	{
+		auto slice = new CompressedSlice(i->getFormat(), i->getWidth(), i->getHeight(), memory, i->getOffset(), i->getSize());
+		dataImages.push_back(slice);
+		slice->release();
+	}
 }
 
-CompressedImageData::CompressedImageData()
-	: format(PIXELFORMAT_UNKNOWN)
-	, sRGB(false)
+CompressedImageData *CompressedImageData::clone() const
 {
+	return new CompressedImageData(*this);
 }
 
 CompressedImageData::~CompressedImageData()
@@ -143,7 +139,7 @@ bool CompressedImageData::isSRGB() const
 	return sRGB;
 }
 
-CompressedImageData::Slice *CompressedImageData::getSlice(int slice, int miplevel) const
+CompressedSlice *CompressedImageData::getSlice(int slice, int miplevel) const
 {
 	checkSliceExists(slice, miplevel);
 

+ 12 - 47
src/modules/image/CompressedImageData.h

@@ -25,10 +25,13 @@
 #include "common/StringMap.h"
 #include "common/int.h"
 #include "common/pixelformat.h"
-#include "ImageDataBase.h"
+#include "filesystem/FileData.h"
+#include "CompressedSlice.h"
+#include "CompressedFormatHandler.h"
 
 // STL
 #include <vector>
+#include <list>
 
 namespace love
 {
@@ -44,53 +47,16 @@ class CompressedImageData : public Data
 {
 public:
 
-	class Memory : public Object
-	{
-	public:
-
-		Memory(size_t size);
-		virtual ~Memory();
-
-		uint8 *data;
-		size_t size;
-
-	}; // Memory
-
-	// Compressed image data can have multiple mipmap levels, each represented
-	// by a sub-image.
-	class Slice : public ImageDataBase
-	{
-	public:
-
-		Slice(PixelFormat format, int width, int height, Memory *memory, size_t offset, size_t size);
-		Slice(const Slice &slice);
-		virtual ~Slice();
-
-		Slice *clone() const override;
-		void *getData() const override { return memory->data + offset; }
-		size_t getSize() const override { return dataSize; }
-		bool isSRGB() const override { return sRGB; }
-		size_t getOffset() const { return offset; }
-
-	private:
-
-		StrongRef<Memory> memory;
-
-		size_t offset;
-		size_t dataSize;
-		bool sRGB;
-
-	}; // Slice
-
 	static love::Type type;
 
-	CompressedImageData();
+	CompressedImageData(const std::list<CompressedFormatHandler *> &formats, love::filesystem::FileData *filedata);
+	CompressedImageData(const CompressedImageData &c);
 	virtual ~CompressedImageData();
 
 	// Implements Data.
-	virtual CompressedImageData *clone() const = 0;
-	virtual void *getData() const;
-	virtual size_t getSize() const;
+	CompressedImageData *clone() const override;
+	void *getData() const override;
+	size_t getSize() const override;
 
 	/**
 	 * Gets the number of mipmaps in this Compressed Image Data.
@@ -130,19 +96,18 @@ public:
 
 	bool isSRGB() const;
 
-	Slice *getSlice(int slice, int miplevel) const;
+	CompressedSlice *getSlice(int slice, int miplevel) const;
 
 protected:
 
 	PixelFormat format;
-
 	bool sRGB;
 
 	// Single block of memory containing all of the sub-images.
-	StrongRef<Memory> memory;
+	StrongRef<CompressedMemory> memory;
 
 	// Texture info for each mipmap level.
-	std::vector<StrongRef<Slice>> dataImages;
+	std::vector<StrongRef<CompressedSlice>> dataImages;
 
 	void checkSliceExists(int slice, int miplevel) const;
 

+ 45 - 21
src/modules/image/magpie/CompressedImageData.h → src/modules/image/CompressedSlice.cpp

@@ -18,37 +18,61 @@
  * 3. This notice may not be removed or altered from any source distribution.
  **/
 
-#ifndef LOVE_IMAGE_MAGPIE_COMPRESSED_IMAGE_DATA_H
-#define LOVE_IMAGE_MAGPIE_COMPRESSED_IMAGE_DATA_H
-
-// LOVE
-#include "CompressedFormatHandler.h"
-#include "filesystem/FileData.h"
-#include "image/CompressedImageData.h"
-
-// C++
-#include <list>
+#include "CompressedSlice.h"
+#include "common/Exception.h"
 
 namespace love
 {
 namespace image
 {
-namespace magpie
+
+CompressedMemory::CompressedMemory(size_t size)
+	: data(nullptr)
+	, size(size)
+{
+	try
+	{
+		data = new uint8[size];
+	}
+	catch (std::exception &)
+	{
+		throw love::Exception("Out of memory.");
+	}
+}
+
+CompressedMemory::~CompressedMemory()
+{
+	delete[] data;
+}
+
+CompressedSlice::CompressedSlice(PixelFormat format, int width, int height, CompressedMemory *memory, size_t offset, size_t size)
+	: memory(memory)
+	, offset(offset)
+	, dataSize(size)
 {
+	this->format = format;
+	this->width = width;
+	this->height = height;
+}
 
-class CompressedImageData : public love::image::CompressedImageData
+CompressedSlice::CompressedSlice(const CompressedSlice &s)
+	: memory(s.memory)
+	, offset(s.offset)
+	, dataSize(s.dataSize)
 {
-public:
+	this->format = s.getFormat();
+	this->width = s.getWidth();
+	this->height = s.getHeight();
+}
 
-	CompressedImageData(std::list<CompressedFormatHandler *> formats, love::filesystem::FileData *filedata);
-	CompressedImageData(const CompressedImageData &c);
-	virtual ~CompressedImageData();
+CompressedSlice::~CompressedSlice()
+{
+}
 
-	virtual CompressedImageData *clone() const;
-}; // CompressedImageData
+CompressedSlice *CompressedSlice::clone() const
+{
+	return new CompressedSlice(*this);
+}
 
-} // magpie
 } // image
 } // love
-
-#endif // LOVE_IMAGE_MAGPIE_COMPRESSED_IMAGE_DATA_H

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

@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2006-2017 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#pragma once
+
+// LOVE
+#include "common/int.h"
+#include "common/pixelformat.h"
+#include "common/Object.h"
+#include "ImageDataBase.h"
+
+namespace love
+{
+namespace image
+{
+
+class CompressedMemory : public Object
+{
+public:
+
+	CompressedMemory(size_t size);
+	virtual ~CompressedMemory();
+
+	uint8 *data;
+	size_t size;
+
+}; // CompressedMemory
+
+// Compressed image data can have multiple mipmap levels, each represented by a
+// sub-image.
+class CompressedSlice : public ImageDataBase
+{
+public:
+
+	CompressedSlice(PixelFormat format, int width, int height, CompressedMemory *memory, size_t offset, size_t size);
+	CompressedSlice(const CompressedSlice &slice);
+	virtual ~CompressedSlice();
+
+	CompressedSlice *clone() const override;
+	void *getData() const override { return memory->data + offset; }
+	size_t getSize() const override { return dataSize; }
+	bool isSRGB() const override { return sRGB; }
+	size_t getOffset() const { return offset; }
+
+private:
+
+	StrongRef<CompressedMemory> memory;
+	size_t offset;
+	size_t dataSize;
+	bool sRGB;
+
+}; // CompressedSlice
+
+} // image
+} // love

+ 2 - 5
src/modules/image/magpie/FormatHandler.cpp → src/modules/image/FormatHandler.cpp

@@ -26,8 +26,6 @@ namespace love
 {
 namespace image
 {
-namespace magpie
-{
 
 FormatHandler::FormatHandler()
 {
@@ -42,7 +40,7 @@ bool FormatHandler::canDecode(love::filesystem::FileData* /*data*/)
 	return false;
 }
 
-bool FormatHandler::canEncode(PixelFormat /*rawFormat*/, ImageData::EncodedFormat /*encodedFormat*/)
+bool FormatHandler::canEncode(PixelFormat /*rawFormat*/, EncodedFormat /*encodedFormat*/)
 {
 	return false;
 }
@@ -52,7 +50,7 @@ FormatHandler::DecodedImage FormatHandler::decode(love::filesystem::FileData* /*
 	throw love::Exception("Image decoding is not implemented for this format backend.");
 }
 
-FormatHandler::EncodedImage FormatHandler::encode(const DecodedImage& /*img*/, ImageData::EncodedFormat /*format*/)
+FormatHandler::EncodedImage FormatHandler::encode(const DecodedImage& /*img*/, EncodedFormat /*format*/)
 {
 	throw love::Exception("Image encoding is not implemented for this format backend.");
 }
@@ -62,6 +60,5 @@ void FormatHandler::free(unsigned char *mem)
 	delete[] mem;
 }
 
-} // magpie
 } // image
 } // love

+ 12 - 11
src/modules/image/magpie/FormatHandler.h → src/modules/image/FormatHandler.h

@@ -18,20 +18,17 @@
  * 3. This notice may not be removed or altered from any source distribution.
  **/
 
-#ifndef LOVE_IMAGE_MAGPIE_FORMAT_HANDLER_H
-#define LOVE_IMAGE_MAGPIE_FORMAT_HANDLER_H
+#pragma once
 
 // LOVE
-#include "image/ImageData.h"
-#include "filesystem/FileData.h"
 #include "common/Object.h"
+#include "common/pixelformat.h"
+#include "filesystem/FileData.h"
 
 namespace love
 {
 namespace image
 {
-namespace magpie
-{
 
 /**
  * Base class for all ImageData encoder/decoder library interfaces.
@@ -41,6 +38,13 @@ class FormatHandler : public love::Object
 {
 public:
 
+	enum EncodedFormat
+	{
+		ENCODED_TGA,
+		ENCODED_PNG,
+		ENCODED_MAX_ENUM
+	};
+
 	// Raw RGBA pixel data.
 	struct DecodedImage
 	{
@@ -76,7 +80,7 @@ public:
 	/**
 	 * Whether this format handler can encode to a particular format.
 	 **/
-	virtual bool canEncode(PixelFormat rawFormat, ImageData::EncodedFormat encodedFormat);
+	virtual bool canEncode(PixelFormat rawFormat, EncodedFormat encodedFormat);
 
 	/**
 	 * Decodes an image from its encoded form into raw pixel data.
@@ -91,7 +95,7 @@ public:
 	 * @param format The format to encode to.
 	 * @return The encoded image data.
 	 **/
-	virtual EncodedImage encode(const DecodedImage &img, ImageData::EncodedFormat format);
+	virtual EncodedImage encode(const DecodedImage &img, EncodedFormat format);
 
 	/**
 	 * Frees memory allocated by the format handler.
@@ -100,8 +104,5 @@ public:
 
 }; // FormatHandler
 
-} // magpie
 } // image
 } // love
-
-#endif // LOVE_IMAGE_MAGPIE_FORMAT_HANDLER_H

+ 84 - 0
src/modules/image/Image.cpp

@@ -20,6 +20,17 @@
 
 // LOVE
 #include "Image.h"
+#include "common/config.h"
+
+#include "magpie/PNGHandler.h"
+#include "magpie/STBHandler.h"
+#include "magpie/EXRHandler.h"
+
+#include "magpie/ddsHandler.h"
+#include "magpie/PVRHandler.h"
+#include "magpie/KTXHandler.h"
+#include "magpie/PKMHandler.h"
+#include "magpie/ASTCHandler.h"
 
 namespace love
 {
@@ -28,6 +39,79 @@ namespace image
 
 love::Type Image::type("image", &Module::type);
 
+Image::Image()
+{
+	using namespace magpie;
+
+	halfInit(); // Makes sure half-float conversions can be used.
+
+	formatHandlers = {
+		new PNGHandler,
+		new STBHandler,
+		new EXRHandler,
+	};
+
+	compressedFormatHandlers = {
+		new DDSHandler,
+		new PVRHandler,
+		new KTXHandler,
+		new PKMHandler,
+		new ASTCHandler,
+	};
+}
+
+Image::~Image()
+{
+	// ImageData objects reference the FormatHandlers in our list, so we should
+	// release them instead of deleting them completely here.
+	for (FormatHandler *handler : formatHandlers)
+		handler->release();
+
+	for (CompressedFormatHandler *handler : compressedFormatHandlers)
+		handler->release();
+}
+
+const char *Image::getName() const
+{
+	return "love.image.magpie";
+}
+
+love::image::ImageData *Image::newImageData(love::filesystem::FileData *data)
+{
+	return new ImageData(data);
+}
+
+love::image::ImageData *Image::newImageData(int width, int height, PixelFormat format)
+{
+	return new ImageData(width, height, format);
+}
+
+love::image::ImageData *Image::newImageData(int width, int height, PixelFormat format, void *data, bool own)
+{
+	return new ImageData(width, height, format, data, own);
+}
+
+love::image::CompressedImageData *Image::newCompressedData(love::filesystem::FileData *data)
+{
+	return new CompressedImageData(compressedFormatHandlers, data);
+}
+
+bool Image::isCompressed(love::filesystem::FileData *data)
+{
+	for (CompressedFormatHandler *handler : compressedFormatHandlers)
+	{
+		if (handler->canParse(data))
+			return true;
+	}
+
+	return false;
+}
+
+const std::list<FormatHandler *> &Image::getFormatHandlers() const
+{
+	return formatHandlers;
+}
+
 ImageData *Image::newPastedImageData(ImageData *src, int sx, int sy, int w, int h)
 {
 	ImageData *res = newImageData(w, h, src->getFormat());

+ 20 - 7
src/modules/image/Image.h

@@ -28,6 +28,9 @@
 #include "ImageData.h"
 #include "CompressedImageData.h"
 
+// C++
+#include <list>
+
 namespace love
 {
 namespace image
@@ -46,17 +49,19 @@ public:
 
 	static love::Type type;
 
-	virtual ~Image() {}
+	Image();
+	virtual ~Image();
 
 	// Implements Module.
-	virtual ModuleType getModuleType() const { return M_IMAGE; }
+	ModuleType getModuleType() const override { return M_IMAGE; }
+	const char *getName() const override;
 
 	/**
 	 * Creates new ImageData from FileData.
 	 * @param data The FileData containing the encoded image data.
 	 * @return The new ImageData.
 	 **/
-	virtual ImageData *newImageData(love::filesystem::FileData *data) = 0;
+	ImageData *newImageData(love::filesystem::FileData *data);
 
 	/**
 	 * Creates empty ImageData with the given size.
@@ -64,7 +69,7 @@ public:
 	 * @param height The height of the ImageData.
 	 * @return The new ImageData.
 	 **/
-	virtual ImageData *newImageData(int width, int height, PixelFormat format = PIXELFORMAT_RGBA8) = 0;
+	ImageData *newImageData(int width, int height, PixelFormat format = PIXELFORMAT_RGBA8);
 
 	/**
 	 * Creates empty ImageData with the given size.
@@ -75,28 +80,36 @@ public:
 	 *        copy it.
 	 * @return The new ImageData.
 	 **/
-	virtual ImageData *newImageData(int width, int height, PixelFormat format, void *data, bool own = false) = 0;
+	ImageData *newImageData(int width, int height, PixelFormat format, void *data, bool own = false);
 
 	/**
 	 * Creates new CompressedImageData from FileData.
 	 * @param data The FileData containing the compressed image data.
 	 * @return The new CompressedImageData.
 	 **/
-	virtual CompressedImageData *newCompressedData(love::filesystem::FileData *data) = 0;
+	CompressedImageData *newCompressedData(love::filesystem::FileData *data);
 
 	/**
 	 * Determines whether a FileData is Compressed image data or not.
 	 * @param data The FileData to test.
 	 **/
-	virtual bool isCompressed(love::filesystem::FileData *data) = 0;
+	bool isCompressed(love::filesystem::FileData *data);
 
 	std::vector<StrongRef<ImageData>> newCubeFaces(ImageData *src);
 	std::vector<StrongRef<ImageData>> newVolumeLayers(ImageData *src);
 
+	const std::list<FormatHandler *> &getFormatHandlers() const;
+
 private:
 
 	ImageData *newPastedImageData(ImageData *src, int sx, int sy, int w, int h);
 
+	// Image format handlers we can use for decoding and encoding ImageData.
+	std::list<FormatHandler *> formatHandlers;
+
+	// Compressed image format handers we can use for parsing CompressedImageData.
+	std::list<CompressedFormatHandler *> compressedFormatHandlers;
+
 }; // Image
 
 } // image

+ 204 - 8
src/modules/image/ImageData.cpp

@@ -19,6 +19,8 @@
  **/
 
 #include "ImageData.h"
+#include "Image.h"
+#include "filesystem/Filesystem.h"
 
 using love::thread::Lock;
 
@@ -29,13 +31,207 @@ namespace image
 
 love::Type ImageData::type("ImageData", &Data::type);
 
-ImageData::ImageData()
-	: data(nullptr)
+ImageData::ImageData(love::filesystem::FileData *data)
 {
+	decode(data);
+}
+
+ImageData::ImageData(int width, int height, PixelFormat format)
+{
+	if (!validPixelFormat(format))
+		throw love::Exception("Unsupported pixel format for ImageData");
+
+	this->width = width;
+	this->height = height;
+	this->format = format;
+
+	create(width, height, format);
+
+	// Set to black/transparency.
+	memset(data, 0, getSize());
+}
+
+ImageData::ImageData(int width, int height, PixelFormat format, void *data, bool own)
+{
+	if (!validPixelFormat(format))
+		throw love::Exception("Unsupported pixel format for ImageData");
+
+	this->width = width;
+	this->height = height;
+	this->format = format;
+
+	if (own)
+		this->data = (unsigned char *) data;
+	else
+		create(width, height, format, data);
+}
+
+ImageData::ImageData(const ImageData &c)
+{
+	width = c.width;
+	height = c.height;
+	format = c.format;
+
+	create(width, height, format, c.getData());
 }
 
 ImageData::~ImageData()
 {
+	if (decodeHandler.get())
+		decodeHandler->free(data);
+	else
+		delete[] data;
+}
+
+love::image::ImageData *ImageData::clone() const
+{
+	return new ImageData(*this);
+}
+
+void ImageData::create(int width, int height, PixelFormat format, void *data)
+{
+	size_t datasize = width * height * getPixelFormatSize(format);
+
+	try
+	{
+		this->data = new unsigned char[datasize];
+	}
+	catch(std::bad_alloc &)
+	{
+		throw love::Exception("Out of memory");
+	}
+
+	if (data)
+		memcpy(this->data, data, datasize);
+
+	decodeHandler = nullptr;
+	this->format = format;
+}
+
+void ImageData::decode(love::filesystem::FileData *data)
+{
+	FormatHandler *decoder = nullptr;
+	FormatHandler::DecodedImage decodedimage;
+
+	auto module = Module::getInstance<Image>(Module::M_IMAGE);
+
+	if (module == nullptr)
+		throw love::Exception("love.image must be loaded in order to decode an ImageData.");
+
+	for (FormatHandler *handler : module->getFormatHandlers())
+	{
+		if (handler->canDecode(data))
+		{
+			decoder = handler;
+			break;
+		}
+	}
+
+	if (decoder)
+		decodedimage = decoder->decode(data);
+
+	if (decodedimage.data == nullptr)
+	{
+		const std::string &name = data->getFilename();
+		throw love::Exception("Could not decode file '%s' to ImageData: unsupported file format", name.c_str());
+	}
+
+	if (decodedimage.size != decodedimage.width * decodedimage.height * getPixelFormatSize(decodedimage.format))
+	{
+		decoder->free(decodedimage.data);
+		throw love::Exception("Could not convert image!");
+	}
+
+	// Clean up any old data.
+	if (decodeHandler)
+		decodeHandler->free(this->data);
+	else
+		delete[] this->data;
+
+	this->width  = decodedimage.width;
+	this->height = decodedimage.height;
+	this->data   = decodedimage.data;
+	this->format = decodedimage.format;
+
+	decodeHandler = decoder;
+}
+
+love::filesystem::FileData *ImageData::encode(FormatHandler::EncodedFormat encodedFormat, const char *filename, bool writefile) const
+{
+	FormatHandler *encoder = nullptr;
+	FormatHandler::EncodedImage encodedimage;
+	FormatHandler::DecodedImage rawimage;
+
+	rawimage.width = width;
+	rawimage.height = height;
+	rawimage.size = getSize();
+	rawimage.data = data;
+	rawimage.format = format;
+
+	auto module = Module::getInstance<Image>(Module::M_IMAGE);
+
+	if (module == nullptr)
+		throw love::Exception("love.image must be loaded in order to encode an ImageData.");
+
+	for (FormatHandler *handler : module->getFormatHandlers())
+	{
+		if (handler->canEncode(format, encodedFormat))
+		{
+			encoder = handler;
+			break;
+		}
+	}
+
+	if (encoder != nullptr)
+	{
+		thread::Lock lock(mutex);
+		encodedimage = encoder->encode(rawimage, encodedFormat);
+	}
+
+	if (encoder == nullptr || encodedimage.data == nullptr)
+	{
+		const char *fname = "unknown";
+		love::getConstant(format, fname);
+		throw love::Exception("No suitable image encoder for %s format.", fname);
+	}
+
+	love::filesystem::FileData *filedata = nullptr;
+
+	try
+	{
+		filedata = new love::filesystem::FileData(encodedimage.size, filename);
+	}
+	catch (love::Exception &)
+	{
+		encoder->free(encodedimage.data);
+		throw;
+	}
+
+	memcpy(filedata->getData(), encodedimage.data, encodedimage.size);
+	encoder->free(encodedimage.data);
+
+	if (writefile)
+	{
+		auto fs = Module::getInstance<filesystem::Filesystem>(Module::M_FILESYSTEM);
+
+		if (fs == nullptr)
+		{
+			filedata->release();
+			throw love::Exception("love.filesystem must be loaded in order to write an encoded ImageData to a file.");
+		}
+
+		try
+		{
+			fs->write(filename, filedata->getData(), filedata->getSize());
+		}
+		catch (love::Exception &)
+		{
+			filedata->release();
+			throw;
+		}
+	}
+
+	return filedata;
 }
 
 size_t ImageData::getSize() const
@@ -289,23 +485,23 @@ bool ImageData::validPixelFormat(PixelFormat format)
 	}
 }
 
-bool ImageData::getConstant(const char *in, EncodedFormat &out)
+bool ImageData::getConstant(const char *in, FormatHandler::EncodedFormat &out)
 {
 	return encodedFormats.find(in, out);
 }
 
-bool ImageData::getConstant(EncodedFormat in, const char *&out)
+bool ImageData::getConstant(FormatHandler::EncodedFormat in, const char *&out)
 {
 	return encodedFormats.find(in, out);
 }
 
-StringMap<ImageData::EncodedFormat, ImageData::ENCODED_MAX_ENUM>::Entry ImageData::encodedFormatEntries[] =
+StringMap<FormatHandler::EncodedFormat, FormatHandler::ENCODED_MAX_ENUM>::Entry ImageData::encodedFormatEntries[] =
 {
-	{"tga", ENCODED_TGA},
-	{"png", ENCODED_PNG},
+	{"tga", FormatHandler::ENCODED_TGA},
+	{"png", FormatHandler::ENCODED_PNG},
 };
 
-StringMap<ImageData::EncodedFormat, ImageData::ENCODED_MAX_ENUM> ImageData::encodedFormats(ImageData::encodedFormatEntries, sizeof(ImageData::encodedFormatEntries));
+StringMap<FormatHandler::EncodedFormat, FormatHandler::ENCODED_MAX_ENUM> ImageData::encodedFormats(ImageData::encodedFormatEntries, sizeof(ImageData::encodedFormatEntries));
 
 } // image
 } // love

+ 26 - 31
src/modules/image/ImageData.h

@@ -29,6 +29,7 @@
 #include "filesystem/FileData.h"
 #include "thread/threads.h"
 #include "ImageDataBase.h"
+#include "FormatHandler.h"
 
 using love::thread::Mutex;
 
@@ -37,13 +38,6 @@ namespace love
 namespace image
 {
 
-// Pixel format structure.
-struct pixel
-{
-	// Red, green, blue, alpha.
-	unsigned char r, g, b, a;
-};
-
 union Pixel
 {
 	uint8  rgba8[4];
@@ -61,14 +55,10 @@ public:
 
 	static love::Type type;
 
-	enum EncodedFormat
-	{
-		ENCODED_TGA,
-		ENCODED_PNG,
-		ENCODED_MAX_ENUM
-	};
-
-	ImageData();
+	ImageData(love::filesystem::FileData *data);
+	ImageData(int width, int height, PixelFormat format = PIXELFORMAT_RGBA8);
+	ImageData(int width, int height, PixelFormat format, void *data, bool own);
+	ImageData(const ImageData &c);
 	virtual ~ImageData();
 
 	/**
@@ -111,12 +101,12 @@ public:
 	 * @param f The file to save the encoded image data to.
 	 * @param format The format of the encoded data.
 	 **/
-	virtual love::filesystem::FileData *encode(EncodedFormat format, const char *filename) = 0;
+	love::filesystem::FileData *encode(FormatHandler::EncodedFormat format, const char *filename, bool writefile) const;
 
 	love::thread::Mutex *getMutex() const;
 
 	// Implements ImageDataBase.
-	virtual ImageData *clone() const override = 0;
+	ImageData *clone() const override;
 	void *getData() const override;
 	size_t getSize() const override;
 	bool isSRGB() const override;
@@ -125,18 +115,8 @@ public:
 
 	static bool validPixelFormat(PixelFormat format);
 
-	static bool getConstant(const char *in, EncodedFormat &out);
-	static bool getConstant(EncodedFormat in, const char *&out);
-
-protected:
-
-	// The actual data.
-	unsigned char *data;
-
-	// We need to be thread-safe
-	// so we lock when we're accessing our
-	// data
-	love::thread::MutexRef mutex;
+	static bool getConstant(const char *in, FormatHandler::EncodedFormat &out);
+	static bool getConstant(FormatHandler::EncodedFormat in, const char *&out);
 
 private:
 
@@ -148,6 +128,21 @@ private:
 		float *f32;
 	};
 
+	// Create imagedata. Initialize with data if not null.
+	void create(int width, int height, PixelFormat format, void *data = nullptr);
+
+	// Decode and load an encoded format.
+	void decode(love::filesystem::FileData *data);
+
+	// The actual data.
+	unsigned char *data = nullptr;
+
+	love::thread::MutexRef mutex;
+
+	// The format handler that was used to decode the ImageData. We need to know
+	// this so we can properly delete memory allocated by the decoder.
+	StrongRef<FormatHandler> decodeHandler;
+
 	static void pasteRGBA8toRGBA16(Row src, Row dst, int w);
 	static void pasteRGBA8toRGBA16F(Row src, Row dst, int w);
 	static void pasteRGBA8toRGBA32F(Row src, Row dst, int w);
@@ -164,8 +159,8 @@ private:
 	static void pasteRGBA32FtoRGBA16(Row src, Row dst, int w);
 	static void pasteRGBA32FtoRGBA16F(Row src, Row dst, int w);
 
-	static StringMap<EncodedFormat, ENCODED_MAX_ENUM>::Entry encodedFormatEntries[];
-	static StringMap<EncodedFormat, ENCODED_MAX_ENUM> encodedFormats;
+	static StringMap<FormatHandler::EncodedFormat, FormatHandler::ENCODED_MAX_ENUM>::Entry encodedFormatEntries[];
+	static StringMap<FormatHandler::EncodedFormat, FormatHandler::ENCODED_MAX_ENUM> encodedFormats;
 
 }; // ImageData
 

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

@@ -104,7 +104,7 @@ bool ASTCHandler::canParse(const filesystem::FileData *data)
 	return true;
 }
 
-StrongRef<CompressedImageData::Memory> ASTCHandler::parse(filesystem::FileData *filedata, std::vector<StrongRef<CompressedImageData::Slice>> &images, PixelFormat &format, bool &sRGB)
+StrongRef<CompressedMemory> ASTCHandler::parse(filesystem::FileData *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB)
 {
 	if (!canParse(filedata))
 		throw love::Exception("Could not decode compressed data (not an .astc file?)");
@@ -129,12 +129,12 @@ StrongRef<CompressedImageData::Memory> ASTCHandler::parse(filesystem::FileData *
 	if (totalsize + sizeof(header) > filedata->getSize())
 		throw love::Exception("Could not parse .astc file: file is too small.");
 
-	StrongRef<CompressedImageData::Memory> memory(new CompressedImageData::Memory(totalsize), Acquire::NORETAIN);
+	StrongRef<CompressedMemory> memory(new CompressedMemory(totalsize), Acquire::NORETAIN);
 
 	// .astc files only store a single mipmap level.
 	memcpy(memory->data, (uint8 *) filedata->getData() + sizeof(ASTCHeader), totalsize);
 
-	images.emplace_back(new CompressedImageData::Slice(cformat, sizeX, sizeY, memory, 0, totalsize), Acquire::NORETAIN);
+	images.emplace_back(new CompressedSlice(cformat, sizeX, sizeY, memory, 0, totalsize), Acquire::NORETAIN);
 
 	format = cformat;
 	sRGB = false;

+ 4 - 7
src/modules/image/magpie/ASTCHandler.h

@@ -18,11 +18,10 @@
  * 3. This notice may not be removed or altered from any source distribution.
  **/
 
-#ifndef LOVE_IMAGE_MAGPIE_ASTC_HANDLER_H
-#define LOVE_IMAGE_MAGPIE_ASTC_HANDLER_H
+#pragma once
 
 #include "common/config.h"
-#include "CompressedFormatHandler.h"
+#include "image/CompressedFormatHandler.h"
 
 namespace love
 {
@@ -44,8 +43,8 @@ public:
 	// Implements CompressedFormatHandler.
 	bool canParse(const filesystem::FileData *data) override;
 
-	StrongRef<CompressedImageData::Memory> parse(filesystem::FileData *filedata,
-	        std::vector<StrongRef<CompressedImageData::Slice>> &images,
+	StrongRef<CompressedMemory> parse(filesystem::FileData *filedata,
+	        std::vector<StrongRef<CompressedSlice>> &images,
 	        PixelFormat &format, bool &sRGB) override;
 
 }; // ASTCHandler
@@ -53,5 +52,3 @@ public:
 } // magpie
 } // image
 } // love
-
-#endif // LOVE_IMAGE_MAGPIE_ASTC_HANDLER_H

+ 0 - 85
src/modules/image/magpie/CompressedImageData.cpp

@@ -1,85 +0,0 @@
-/**
- * Copyright (c) 2006-2017 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 "CompressedImageData.h"
-
-namespace love
-{
-namespace image
-{
-namespace magpie
-{
-
-CompressedImageData::CompressedImageData(std::list<CompressedFormatHandler *> formats, love::filesystem::FileData *filedata)
-{
-	CompressedFormatHandler *parser = nullptr;
-
-	for (CompressedFormatHandler *handler : formats)
-	{
-		if (handler->canParse(filedata))
-		{
-			parser = handler;
-			break;
-		}
-	}
-
-	if (parser == nullptr)
-		throw love::Exception("Could not parse compressed data: Unknown format.");
-
-	memory = parser->parse(filedata, dataImages, format, sRGB);
-
-	if (memory == nullptr)
-		throw love::Exception("Could not parse compressed data.");
-
-	if (format == PIXELFORMAT_UNKNOWN)
-		throw love::Exception("Could not parse compressed data: Unknown format.");
-
-	if (dataImages.size() == 0 || memory->size == 0)
-		throw love::Exception("Could not parse compressed data: No valid data?");
-}
-
-CompressedImageData::CompressedImageData(const CompressedImageData &c)
-{
-	format = c.format;
-	sRGB = c.sRGB;
-
-	memory.set(new Memory(c.memory->size), Acquire::NORETAIN);
-	memcpy(memory->data, c.memory->data, memory->size);
-
-	for (const auto &i : c.dataImages)
-	{
-		Slice *slice = new Slice(i->getFormat(), i->getWidth(), i->getHeight(), memory, i->getOffset(), i->getSize());
-		dataImages.push_back(slice);
-		slice->release();
-	}
-}
-
-CompressedImageData *CompressedImageData::clone() const
-{
-	return new CompressedImageData(*this);
-}
-
-CompressedImageData::~CompressedImageData()
-{
-}
-
-} // magpie
-} // image
-} // love

+ 3 - 2
src/modules/image/magpie/EXRHandler.cpp

@@ -20,6 +20,7 @@
 
 // LOVE
 #include "EXRHandler.h"
+#include "common/halffloat.h"
 
 // tinyexr
 #define TINYEXR_IMPLEMENTATION
@@ -41,7 +42,7 @@ bool EXRHandler::canDecode(love::filesystem::FileData *data)
 	return ParseEXRVersionFromMemory(&version, (const unsigned char *) data->getData(), data->getSize()) == TINYEXR_SUCCESS;
 }
 
-bool EXRHandler::canEncode(PixelFormat /*rawFormat*/, ImageData::EncodedFormat /*encodedFormat*/)
+bool EXRHandler::canEncode(PixelFormat /*rawFormat*/, EncodedFormat /*encodedFormat*/)
 {
 	return false;
 }
@@ -186,7 +187,7 @@ FormatHandler::DecodedImage EXRHandler::decode(love::filesystem::FileData *data)
 	return img;
 }
 
-FormatHandler::EncodedImage EXRHandler::encode(const DecodedImage & /*img*/, ImageData::EncodedFormat /*encodedFormat*/)
+FormatHandler::EncodedImage EXRHandler::encode(const DecodedImage & /*img*/, EncodedFormat /*encodedFormat*/)
 {
 	throw love::Exception("Invalid format.");
 }

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません