Browse Source

Implement Array, Cubemap, and Volume texture types (issue #1111).

- Add love.graphics.newArrayImage, newCubeImage, and newVolumeImage.
- Add love.graphics.newCanvas(w, h, layers) and newCanvas(w, h, layers, settings). Add ‘type’ field to the settings table of newCanvas.

- Add new love.graphics.setCanvas variants: setCanvas(canvas, slice), and setCanvas(canvastable) where canvastable is in the format: {{canvas1, layer=2}, {canvas2, face=5}}

- Add Texture:getTextureType, getDepth, getLayerCount, getMipmapCount, and getFormat.

- Remove Image:getData and Image:refresh.
- Add Image:replacePixels(imagedata [, slice] [, mipmap]).

- Update Canvas:newImageData to accept a slice argument.

- Add love.image.newCubeFaces(imagedata).

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
73fd45558b
91 changed files with 3119 additions and 1962 deletions
  1. 2 4
      CMakeLists.txt
  2. 10 20
      platform/xcode/liblove.xcodeproj/project.pbxproj
  3. 28 0
      src/common/runtime.cpp
  4. 7 0
      src/common/runtime.h
  5. 4 0
      src/libraries/glslang/glslang/Include/BaseTypes.h
  6. 8 0
      src/libraries/glslang/glslang/Include/ConstantUnion.h
  7. 1 1
      src/libraries/glslang/glslang/Include/PoolAlloc.h
  8. 103 50
      src/libraries/glslang/glslang/Include/Types.h
  9. 2 2
      src/libraries/glslang/glslang/Include/revision.h
  10. 78 15
      src/libraries/glslang/glslang/MachineIndependent/Initialize.cpp
  11. 38 10
      src/libraries/glslang/glslang/MachineIndependent/Intermediate.cpp
  12. 14 19
      src/libraries/glslang/glslang/MachineIndependent/ParseContextBase.cpp
  13. 26 17
      src/libraries/glslang/glslang/MachineIndependent/ParseHelper.cpp
  14. 2 8
      src/libraries/glslang/glslang/MachineIndependent/ParseHelper.h
  15. 2 2
      src/libraries/glslang/glslang/MachineIndependent/PoolAlloc.cpp
  16. 31 12
      src/libraries/glslang/glslang/MachineIndependent/Scan.cpp
  17. 10 0
      src/libraries/glslang/glslang/MachineIndependent/ShaderLang.cpp
  18. 22 1
      src/libraries/glslang/glslang/MachineIndependent/SymbolTable.h
  19. 6 0
      src/libraries/glslang/glslang/MachineIndependent/Versions.cpp
  20. 3 0
      src/libraries/glslang/glslang/MachineIndependent/Versions.h
  21. 3 0
      src/libraries/glslang/glslang/MachineIndependent/iomapper.cpp
  22. 3 1
      src/libraries/glslang/glslang/MachineIndependent/localintermediate.h
  23. 14 51
      src/libraries/glslang/glslang/MachineIndependent/preprocessor/Pp.cpp
  24. 22 11
      src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpContext.h
  25. 2 0
      src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpScanner.cpp
  26. 85 62
      src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpTokens.cpp
  27. 5 1
      src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpTokens.h
  28. 1 0
      src/libraries/glslang/glslang/Public/ShaderLang.h
  29. 2 1
      src/modules/graphics/Canvas.cpp
  30. 6 2
      src/modules/graphics/Canvas.h
  31. 81 2
      src/modules/graphics/Font.cpp
  32. 18 12
      src/modules/graphics/Font.h
  33. 50 32
      src/modules/graphics/Graphics.cpp
  34. 37 18
      src/modules/graphics/Graphics.h
  35. 162 14
      src/modules/graphics/Image.cpp
  36. 44 19
      src/modules/graphics/Image.h
  37. 8 0
      src/modules/graphics/Shader.cpp
  38. 9 7
      src/modules/graphics/Shader.h
  39. 111 4
      src/modules/graphics/Texture.cpp
  40. 52 5
      src/modules/graphics/Texture.h
  41. 51 5
      src/modules/graphics/Video.cpp
  42. 8 10
      src/modules/graphics/Video.h
  43. 152 66
      src/modules/graphics/opengl/Canvas.cpp
  44. 3 2
      src/modules/graphics/opengl/Canvas.h
  45. 0 190
      src/modules/graphics/opengl/Font.cpp
  46. 82 98
      src/modules/graphics/opengl/Graphics.cpp
  47. 5 11
      src/modules/graphics/opengl/Graphics.h
  48. 229 261
      src/modules/graphics/opengl/Image.cpp
  49. 13 28
      src/modules/graphics/opengl/Image.h
  50. 7 1
      src/modules/graphics/opengl/Mesh.cpp
  51. 273 42
      src/modules/graphics/opengl/OpenGL.cpp
  52. 25 8
      src/modules/graphics/opengl/OpenGL.h
  53. 3 0
      src/modules/graphics/opengl/ParticleSystem.cpp
  54. 141 138
      src/modules/graphics/opengl/Shader.cpp
  55. 11 8
      src/modules/graphics/opengl/Shader.h
  56. 9 10
      src/modules/graphics/opengl/SpriteBatch.cpp
  57. 12 13
      src/modules/graphics/opengl/Text.cpp
  58. 0 119
      src/modules/graphics/opengl/Video.cpp
  59. 42 28
      src/modules/graphics/wrap_Canvas.cpp
  60. 406 123
      src/modules/graphics/wrap_Graphics.cpp
  61. 38 12
      src/modules/graphics/wrap_Graphics.lua
  62. 19 93
      src/modules/graphics/wrap_Image.cpp
  63. 0 1
      src/modules/graphics/wrap_Image.h
  64. 1 1
      src/modules/graphics/wrap_Mesh.cpp
  65. 1 1
      src/modules/graphics/wrap_ParticleSystem.cpp
  66. 6 1
      src/modules/graphics/wrap_Shader.cpp
  67. 1 1
      src/modules/graphics/wrap_SpriteBatch.cpp
  68. 90 0
      src/modules/graphics/wrap_Texture.cpp
  69. 75 14
      src/modules/image/CompressedImageData.cpp
  70. 48 15
      src/modules/image/CompressedImageData.h
  71. 87 0
      src/modules/image/Image.cpp
  72. 6 0
      src/modules/image/Image.h
  73. 5 18
      src/modules/image/ImageData.cpp
  74. 8 29
      src/modules/image/ImageData.h
  75. 20 31
      src/modules/image/ImageDataBase.cpp
  76. 16 19
      src/modules/image/ImageDataBase.h
  77. 5 23
      src/modules/image/magpie/ASTCHandler.cpp
  78. 5 2
      src/modules/image/magpie/ASTCHandler.h
  79. 3 3
      src/modules/image/magpie/CompressedFormatHandler.h
  80. 9 21
      src/modules/image/magpie/CompressedImageData.cpp
  81. 1 1
      src/modules/image/magpie/ImageData.cpp
  82. 1 1
      src/modules/image/magpie/ImageData.h
  83. 11 20
      src/modules/image/magpie/KTXHandler.cpp
  84. 5 2
      src/modules/image/magpie/KTXHandler.h
  85. 8 21
      src/modules/image/magpie/PKMHandler.cpp
  86. 5 2
      src/modules/image/magpie/PKMHandler.h
  87. 10 18
      src/modules/image/magpie/PVRHandler.cpp
  88. 5 2
      src/modules/image/magpie/PVRHandler.h
  89. 30 44
      src/modules/image/magpie/ddsHandler.cpp
  90. 5 2
      src/modules/image/magpie/ddsHandler.h
  91. 11 0
      src/modules/image/wrap_Image.cpp

+ 2 - 4
CMakeLists.txt

@@ -539,8 +539,6 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL
 	src/modules/graphics/opengl/BufferSync.h
 	src/modules/graphics/opengl/Canvas.cpp
 	src/modules/graphics/opengl/Canvas.h
-	src/modules/graphics/opengl/Font.cpp
-	src/modules/graphics/opengl/Font.h
 	src/modules/graphics/opengl/Graphics.cpp
 	src/modules/graphics/opengl/Graphics.h
 	src/modules/graphics/opengl/Image.cpp
@@ -559,8 +557,6 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL
 	src/modules/graphics/opengl/StreamBuffer.h
 	src/modules/graphics/opengl/Text.cpp
 	src/modules/graphics/opengl/Text.h
-	src/modules/graphics/opengl/Video.cpp
-	src/modules/graphics/opengl/Video.h
 )
 
 set(LOVE_SRC_MODULE_GRAPHICS
@@ -582,6 +578,8 @@ set(LOVE_SRC_MODULE_IMAGE_ROOT
 	src/modules/image/Image.h
 	src/modules/image/ImageData.cpp
 	src/modules/image/ImageData.h
+	src/modules/image/ImageDataBase.cpp
+	src/modules/image/ImageDataBase.h
 	src/modules/image/wrap_CompressedImageData.cpp
 	src/modules/image/wrap_CompressedImageData.h
 	src/modules/image/wrap_Image.cpp

+ 10 - 20
platform/xcode/liblove.xcodeproj/project.pbxproj

@@ -423,9 +423,6 @@
 		FA0B7D331A95902C000E1D17 /* Canvas.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B8D1A95902C000E1D17 /* Canvas.cpp */; };
 		FA0B7D341A95902C000E1D17 /* Canvas.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B8D1A95902C000E1D17 /* Canvas.cpp */; };
 		FA0B7D351A95902C000E1D17 /* Canvas.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7B8E1A95902C000E1D17 /* Canvas.h */; };
-		FA0B7D361A95902C000E1D17 /* Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B8F1A95902C000E1D17 /* Font.cpp */; };
-		FA0B7D371A95902C000E1D17 /* Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B8F1A95902C000E1D17 /* Font.cpp */; };
-		FA0B7D381A95902C000E1D17 /* Font.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7B901A95902C000E1D17 /* Font.h */; };
 		FA0B7D391A95902C000E1D17 /* Graphics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B911A95902C000E1D17 /* Graphics.cpp */; };
 		FA0B7D3A1A95902C000E1D17 /* Graphics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7B911A95902C000E1D17 /* Graphics.cpp */; };
 		FA0B7D3B1A95902C000E1D17 /* Graphics.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7B921A95902C000E1D17 /* Graphics.h */; };
@@ -860,9 +857,6 @@
 		FA27B3C01B4985BF008A9DCE /* wrap_VideoStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA27B3B91B4985BF008A9DCE /* wrap_VideoStream.cpp */; };
 		FA27B3C11B4985BF008A9DCE /* wrap_VideoStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA27B3B91B4985BF008A9DCE /* wrap_VideoStream.cpp */; };
 		FA27B3C21B4985BF008A9DCE /* wrap_VideoStream.h in Headers */ = {isa = PBXBuildFile; fileRef = FA27B3BA1B4985BF008A9DCE /* wrap_VideoStream.h */; };
-		FA27B3C51B4985D8008A9DCE /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA27B3C31B4985D8008A9DCE /* Video.cpp */; };
-		FA27B3C61B4985D8008A9DCE /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA27B3C31B4985D8008A9DCE /* Video.cpp */; };
-		FA27B3C71B4985D8008A9DCE /* Video.h in Headers */ = {isa = PBXBuildFile; fileRef = FA27B3C41B4985D8008A9DCE /* Video.h */; };
 		FA27B3C91B498623008A9DCE /* Theora.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA27B3C81B498623008A9DCE /* Theora.framework */; };
 		FA28EBD51E352DB5003446F4 /* BufferSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA28EBD31E352DB5003446F4 /* BufferSync.cpp */; };
 		FA28EBD61E352DB5003446F4 /* BufferSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA28EBD31E352DB5003446F4 /* BufferSync.cpp */; };
@@ -996,6 +990,9 @@
 		FAC756FA1E4F99D200B91289 /* Effect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAC756F81E4F99D200B91289 /* Effect.cpp */; };
 		FAC756FB1E4F99D200B91289 /* Effect.h in Headers */ = {isa = PBXBuildFile; fileRef = FAC756F91E4F99D200B91289 /* Effect.h */; };
 		FAC756FC1E4F99DB00B91289 /* Effect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAC756F81E4F99D200B91289 /* Effect.cpp */; };
+		FAD19A171DFF8CA200D5398A /* ImageDataBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAD19A151DFF8CA200D5398A /* ImageDataBase.cpp */; };
+		FAD19A181DFF8CA200D5398A /* ImageDataBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAD19A151DFF8CA200D5398A /* ImageDataBase.cpp */; };
+		FAD19A191DFF8CA200D5398A /* ImageDataBase.h in Headers */ = {isa = PBXBuildFile; fileRef = FAD19A161DFF8CA200D5398A /* ImageDataBase.h */; };
 		FADF53F81E3C7ACD00012CC0 /* Buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FADF53F61E3C7ACD00012CC0 /* Buffer.cpp */; };
 		FADF53F91E3C7ACD00012CC0 /* Buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FADF53F61E3C7ACD00012CC0 /* Buffer.cpp */; };
 		FADF53FA1E3C7ACD00012CC0 /* Buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = FADF53F71E3C7ACD00012CC0 /* Buffer.h */; };
@@ -1456,8 +1453,6 @@
 		FA0B7B8B1A95902C000E1D17 /* Graphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Graphics.h; sourceTree = "<group>"; };
 		FA0B7B8D1A95902C000E1D17 /* Canvas.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Canvas.cpp; sourceTree = "<group>"; };
 		FA0B7B8E1A95902C000E1D17 /* Canvas.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Canvas.h; sourceTree = "<group>"; };
-		FA0B7B8F1A95902C000E1D17 /* Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Font.cpp; sourceTree = "<group>"; };
-		FA0B7B901A95902C000E1D17 /* Font.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Font.h; sourceTree = "<group>"; };
 		FA0B7B911A95902C000E1D17 /* Graphics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = Graphics.cpp; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.cpp; };
 		FA0B7B921A95902C000E1D17 /* Graphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Graphics.h; sourceTree = "<group>"; };
 		FA0B7B931A95902C000E1D17 /* Image.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Image.cpp; sourceTree = "<group>"; };
@@ -1753,8 +1748,6 @@
 		FA27B39C1B498151008A9DCE /* wrap_Video.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Video.h; sourceTree = "<group>"; };
 		FA27B3B91B4985BF008A9DCE /* wrap_VideoStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_VideoStream.cpp; sourceTree = "<group>"; };
 		FA27B3BA1B4985BF008A9DCE /* wrap_VideoStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_VideoStream.h; sourceTree = "<group>"; };
-		FA27B3C31B4985D8008A9DCE /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = "<group>"; };
-		FA27B3C41B4985D8008A9DCE /* Video.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Video.h; sourceTree = "<group>"; };
 		FA27B3C81B498623008A9DCE /* Theora.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Theora.framework; path = /Library/Frameworks/Theora.framework; sourceTree = "<absolute>"; };
 		FA283EDC1B27CFAA00C70067 /* nogame.lua */ = {isa = PBXFileReference; lastKnownFileType = text; path = nogame.lua; sourceTree = "<group>"; };
 		FA283EDD1B27CFAA00C70067 /* nogame.lua.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nogame.lua.h; sourceTree = "<group>"; };
@@ -1847,6 +1840,8 @@
 		FAC756F41E4F99B400B91289 /* Effect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Effect.h; sourceTree = "<group>"; };
 		FAC756F81E4F99D200B91289 /* Effect.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Effect.cpp; sourceTree = "<group>"; };
 		FAC756F91E4F99D200B91289 /* Effect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Effect.h; sourceTree = "<group>"; };
+		FAD19A151DFF8CA200D5398A /* ImageDataBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImageDataBase.cpp; sourceTree = "<group>"; };
+		FAD19A161DFF8CA200D5398A /* ImageDataBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageDataBase.h; sourceTree = "<group>"; };
 		FADF53F61E3C7ACD00012CC0 /* Buffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Buffer.cpp; sourceTree = "<group>"; };
 		FADF53F71E3C7ACD00012CC0 /* Buffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Buffer.h; sourceTree = "<group>"; };
 		FADF53FB1E3D74F200012CC0 /* Text.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Text.cpp; sourceTree = "<group>"; };
@@ -2738,8 +2733,6 @@
 				FA28EBD41E352DB5003446F4 /* BufferSync.h */,
 				FA0B7B8D1A95902C000E1D17 /* Canvas.cpp */,
 				FA0B7B8E1A95902C000E1D17 /* Canvas.h */,
-				FA0B7B8F1A95902C000E1D17 /* Font.cpp */,
-				FA0B7B901A95902C000E1D17 /* Font.h */,
 				FA0B7B911A95902C000E1D17 /* Graphics.cpp */,
 				FA0B7B921A95902C000E1D17 /* Graphics.h */,
 				FA0B7B931A95902C000E1D17 /* Image.cpp */,
@@ -2758,8 +2751,6 @@
 				FA7634491E28722A0066EF9E /* StreamBuffer.h */,
 				FA0B7BA11A95902C000E1D17 /* Text.cpp */,
 				FA0B7BA21A95902C000E1D17 /* Text.h */,
-				FA27B3C31B4985D8008A9DCE /* Video.cpp */,
-				FA27B3C41B4985D8008A9DCE /* Video.h */,
 			);
 			path = opengl;
 			sourceTree = "<group>";
@@ -2773,6 +2764,8 @@
 				FA0B7BC51A95902C000E1D17 /* Image.h */,
 				FA0B7BC61A95902C000E1D17 /* ImageData.cpp */,
 				FA0B7BC71A95902C000E1D17 /* ImageData.h */,
+				FAD19A151DFF8CA200D5398A /* ImageDataBase.cpp */,
+				FAD19A161DFF8CA200D5398A /* ImageDataBase.h */,
 				FA0B7BC81A95902C000E1D17 /* magpie */,
 				FA0B7BE21A95902C000E1D17 /* wrap_CompressedImageData.cpp */,
 				FA0B7BE31A95902C000E1D17 /* wrap_CompressedImageData.h */,
@@ -3533,7 +3526,6 @@
 				FAF1405C1E20934C00F898D2 /* InitializeGlobals.h in Headers */,
 				FA0B7DB31A95902C000E1D17 /* wrap_Image.h in Headers */,
 				FA0B7D531A95902C000E1D17 /* Text.h in Headers */,
-				FA0B7D381A95902C000E1D17 /* Font.h in Headers */,
 				FAF140591E20934C00F898D2 /* Common.h in Headers */,
 				FA0B7EC31A95902C000E1D17 /* threads.h in Headers */,
 				FA0B7AC21A958EA3000E1D17 /* enet.h in Headers */,
@@ -3594,7 +3586,6 @@
 				FA0B7E681A95902C000E1D17 /* wrap_PrismaticJoint.h in Headers */,
 				FA0B7D571A95902C000E1D17 /* Buffer.h in Headers */,
 				FAF140AB1E20934C00F898D2 /* SymbolTable.h in Headers */,
-				FA27B3C71B4985D8008A9DCE /* Video.h in Headers */,
 				FA0B7ED71A95902D000E1D17 /* Timer.h in Headers */,
 				FA0B7AC31A958EA3000E1D17 /* list.h in Headers */,
 				FA0B7B2D1A958EA3000E1D17 /* core.h in Headers */,
@@ -3790,6 +3781,7 @@
 				FA0B7D841A95902C000E1D17 /* CompressedImageData.h in Headers */,
 				FAF1407E1E20934C00F898D2 /* LiveTraverser.h in Headers */,
 				FA0B7D231A95902C000E1D17 /* Rasterizer.h in Headers */,
+				FAD19A191DFF8CA200D5398A /* ImageDataBase.h in Headers */,
 				FA0B7CDB1A95902C000E1D17 /* Pool.h in Headers */,
 				FA0B7D0B1A95902C000E1D17 /* wrap_FileData.h in Headers */,
 				FA0B7DF91A95902C000E1D17 /* Body.h in Headers */,
@@ -4048,7 +4040,6 @@
 				FA0B7DB81A95902C000E1D17 /* Joystick.cpp in Sources */,
 				FA0B794B1A958E3B000E1D17 /* wrap_Data.cpp in Sources */,
 				FAB17BEC1ABFAF1800F9BA27 /* CompressedData.cpp in Sources */,
-				FA0B7D371A95902C000E1D17 /* Font.cpp in Sources */,
 				FA0B7E401A95902C000E1D17 /* wrap_ChainShape.cpp in Sources */,
 				FA0B7DEC1A95902C000E1D17 /* Cursor.cpp in Sources */,
 				FA0B7D871A95902C000E1D17 /* ImageData.cpp in Sources */,
@@ -4126,7 +4117,6 @@
 				FA0B7A7B1A958EA3000E1D17 /* b2Contact.cpp in Sources */,
 				FA0B7A6F1A958EA3000E1D17 /* b2WorldCallbacks.cpp in Sources */,
 				FA0B793C1A958E3B000E1D17 /* runtime.cpp in Sources */,
-				FA27B3C61B4985D8008A9DCE /* Video.cpp in Sources */,
 				FAF140DC1E20934C00F898D2 /* InitializeDll.cpp in Sources */,
 				FA0B7DBC1A95902C000E1D17 /* Joystick.cpp in Sources */,
 				FA0B7DAF1A95902C000E1D17 /* wrap_CompressedImageData.cpp in Sources */,
@@ -4194,6 +4184,7 @@
 				FA0B7A2A1A958EA3000E1D17 /* b2BroadPhase.cpp in Sources */,
 				FA0B7A811A958EA3000E1D17 /* b2EdgeAndCircleContact.cpp in Sources */,
 				FA0B7D071A95902C000E1D17 /* wrap_File.cpp in Sources */,
+				FAD19A181DFF8CA200D5398A /* ImageDataBase.cpp in Sources */,
 				FA0B7AD01A958EA3000E1D17 /* peer.c in Sources */,
 				FA27B3C11B4985BF008A9DCE /* wrap_VideoStream.cpp in Sources */,
 				FADF54211E3DA52C00012CC0 /* wrap_ParticleSystem.cpp in Sources */,
@@ -4409,7 +4400,6 @@
 				FA0B7DB71A95902C000E1D17 /* Joystick.cpp in Sources */,
 				FA0B7A321A958EA3000E1D17 /* b2Collision.cpp in Sources */,
 				FAB17BEB1ABFAF1800F9BA27 /* CompressedData.cpp in Sources */,
-				FA0B7D361A95902C000E1D17 /* Font.cpp in Sources */,
 				FA0B7E3F1A95902C000E1D17 /* wrap_ChainShape.cpp in Sources */,
 				FA0B7DEB1A95902C000E1D17 /* Cursor.cpp in Sources */,
 				FA0B7D861A95902C000E1D17 /* ImageData.cpp in Sources */,
@@ -4486,7 +4476,6 @@
 				FAF140AC1E20934C00F898D2 /* Versions.cpp in Sources */,
 				FA0B7A441A958EA3000E1D17 /* b2EdgeShape.cpp in Sources */,
 				FA0B79461A958E3B000E1D17 /* Vector.cpp in Sources */,
-				FA27B3C51B4985D8008A9DCE /* Video.cpp in Sources */,
 				FA0B7DBB1A95902C000E1D17 /* Joystick.cpp in Sources */,
 				FAF140DB1E20934C00F898D2 /* InitializeDll.cpp in Sources */,
 				FA0B7DAE1A95902C000E1D17 /* wrap_CompressedImageData.cpp in Sources */,
@@ -4555,6 +4544,7 @@
 				FA0B7A951A958EA3000E1D17 /* b2Joint.cpp in Sources */,
 				FA0B7D061A95902C000E1D17 /* wrap_File.cpp in Sources */,
 				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 */,

+ 28 - 0
src/common/runtime.cpp

@@ -130,6 +130,17 @@ void luax_printstack(lua_State *L)
 		std::cout << i << " - " << luaL_typename(L, i) << std::endl;
 }
 
+bool luax_isarrayoftables(lua_State *L, int idx)
+{
+	if (!lua_istable(L, idx))
+		return false;
+
+	lua_rawgeti(L, idx, 1);
+	bool tableoftables = lua_istable(L, -1);
+	lua_pop(L, 1);
+	return tableoftables;
+}
+
 bool luax_toboolean(lua_State *L, int idx)
 {
 	return (lua_toboolean(L, idx) != 0);
@@ -208,6 +219,23 @@ double luax_numberflag(lua_State *L, int table_index, const char *key, double de
 	return retval;
 }
 
+int luax_checkintflag(lua_State *L, int table_index, const char *key)
+{
+	lua_getfield(L, table_index, key);
+
+	int retval;
+	if (!lua_isnumber(L, -1))
+	{
+		std::string err = "expected integer field " + std::string(key) + " in table";
+		return luaL_argerror(L, table_index, err.c_str());
+	}
+	else
+		retval = (int) luaL_checkinteger(L, -1);
+	lua_pop(L, 1);
+
+	return retval;
+}
+
 int luax_assert_argc(lua_State *L, int min)
 {
 	int argc = lua_gettop(L);

+ 7 - 0
src/common/runtime.h

@@ -106,6 +106,11 @@ Reference *luax_refif(lua_State *L, int type);
  **/
 void luax_printstack(lua_State *L);
 
+/**
+ * Gets whether the value at idx is an array of tables.
+ **/
+bool luax_isarrayoftables(lua_State *L, int idx);
+
 /**
  * Converts the value at idx to a bool. It follow the same rules
  * as lua_toboolean, but returns a bool instead of an int.
@@ -163,6 +168,8 @@ bool luax_boolflag(lua_State *L, int table_index, const char *key, bool defaultV
 int luax_intflag(lua_State *L, int table_index, const char *key, int defaultValue);
 double luax_numberflag(lua_State *L, int table_index, const char *key, double defaultValue);
 
+int luax_checkintflag(lua_State *L, int table_index, const char *key);
+
 /**
  * Convert the value at the specified index to an Lua number, and then
  * convert to a float.

+ 4 - 0
src/libraries/glslang/glslang/Include/BaseTypes.h

@@ -207,6 +207,8 @@ enum TBuiltInVariable {
     EbvViewportMaskNV,
     EbvSecondaryPositionNV,
     EbvSecondaryViewportMaskNV,
+    EbvPositionPerViewNV,
+    EbvViewportMaskPerViewNV,
 #endif 
     // HLSL built-ins that live only temporarily, until they get remapped
     // to one of the above.
@@ -325,6 +327,8 @@ __inline const char* GetBuiltInVariableString(TBuiltInVariable v)
     case EbvViewportMaskNV:             return "ViewportMaskNV";
     case EbvSecondaryPositionNV:        return "SecondaryPositionNV";
     case EbvSecondaryViewportMaskNV:    return "SecondaryViewportMaskNV";
+    case EbvPositionPerViewNV:          return "PositionPerViewNV";
+    case EbvViewportMaskPerViewNV:      return "ViewportMaskPerViewNV";
 #endif 
     default:                      return "unknown built-in variable";
     }

+ 8 - 0
src/libraries/glslang/glslang/Include/ConstantUnion.h

@@ -81,12 +81,19 @@ public:
         type = EbtBool;
     }
 
+    void setSConst(const TString* s)
+    {
+        sConst = s;
+        type = EbtString;
+    }
+
     int                getIConst() const   { return iConst; }
     unsigned int       getUConst() const   { return uConst; }
     long long          getI64Const() const { return i64Const; }
     unsigned long long getU64Const() const { return u64Const; }
     double             getDConst() const   { return dConst; }
     bool               getBConst() const   { return bConst; }
+    const TString*     getSConst() const   { return sConst; }
 
     bool operator==(const int i) const
     {
@@ -532,6 +539,7 @@ private:
         unsigned long long u64Const;    // used for u64vec, scalar uint64s
         bool               bConst;      // used for bvec, scalar bools
         double             dConst;      // used for vec, dvec, mat, dmat, scalar floats and doubles
+        const TString*     sConst;      // string constant
     };
 
     TBasicType type;

+ 1 - 1
src/libraries/glslang/glslang/Include/PoolAlloc.h

@@ -255,7 +255,7 @@ extern TPoolAllocator& GetThreadPoolAllocator();
 
 struct TThreadMemoryPools
 {
-        TPoolAllocator* threadPoolAllocator;
+    TPoolAllocator* threadPoolAllocator;
 };
 
 void SetThreadPoolAllocator(TPoolAllocator& poolAllocator);

+ 103 - 50
src/libraries/glslang/glslang/Include/Types.h

@@ -401,8 +401,23 @@ public:
     // drop qualifiers that don't belong in a temporary variable
     void makeTemporary()
     {
-        storage      = EvqTemporary;
-        builtIn      = EbvNone;
+        storage = EvqTemporary;
+        builtIn = EbvNone;
+        clearInterstage();
+        clearMemory();
+        specConstant = false;
+        clearLayout();
+    }
+
+    void clearInterstage()
+    {
+        clearInterpolation();
+        patch = false;
+        sample = false;
+    }
+
+    void clearInterpolation()
+    {
         centroid     = false;
         smooth       = false;
         flat         = false;
@@ -410,15 +425,15 @@ public:
 #ifdef AMD_EXTENSIONS
         explicitInterp = false;
 #endif
-        patch        = false;
-        sample       = false;
+    }
+
+    void clearMemory()
+    {
         coherent     = false;
         volatil      = false;
         restrict     = false;
         readonly     = false;
         writeonly    = false;
-        specConstant = false;
-        clearLayout();
     }
 
     // Drop just the storage qualification, which perhaps should
@@ -575,42 +590,47 @@ public:
     }
 
     // Implementing an embedded layout-qualifier class here, since C++ can't have a real class bitfield
-    void clearLayout()
+    void clearLayout()  // all layout
     {
-        layoutMatrix = ElmNone;
-        layoutPacking = ElpNone;
-        layoutOffset = layoutNotSet;
-        layoutAlign = layoutNotSet;
+        clearUniformLayout();
+
+        layoutPushConstant = false;
+#ifdef NV_EXTENSIONS
+        layoutPassthrough = false;
+        layoutViewportRelative = false;
+        // -2048 as the default value indicating layoutSecondaryViewportRelative is not set
+        layoutSecondaryViewportRelativeOffset = -2048;
+#endif
 
+        clearInterstageLayout();
+
+        layoutSpecConstantId = layoutSpecConstantIdEnd;
+
+        layoutFormat = ElfNone;
+    }
+    void clearInterstageLayout()
+    {
         layoutLocation = layoutLocationEnd;
         layoutComponent = layoutComponentEnd;
-        layoutSet = layoutSetEnd;
-        layoutBinding = layoutBindingEnd;
         layoutIndex = layoutIndexEnd;
-
+        clearStreamLayout();
+        clearXfbLayout();
+    }
+    void clearStreamLayout()
+    {
         layoutStream = layoutStreamEnd;
-
+    }
+    void clearXfbLayout()
+    {
         layoutXfbBuffer = layoutXfbBufferEnd;
         layoutXfbStride = layoutXfbStrideEnd;
         layoutXfbOffset = layoutXfbOffsetEnd;
-        layoutAttachment = layoutAttachmentEnd;
-        layoutSpecConstantId = layoutSpecConstantIdEnd;
-
-        layoutFormat = ElfNone;
-
-        layoutPushConstant = false;
-#ifdef NV_EXTENSIONS
-        layoutPassthrough = false;
-        layoutViewportRelative = false;
-        // -2048 as the default vaule indicating layoutSecondaryViewportRelative is not set
-        layoutSecondaryViewportRelativeOffset = -2048;
-#endif
     }
+
     bool hasLayout() const
     {
         return hasUniformLayout() ||
                hasAnyLocation() ||
-               hasBinding() ||
                hasStream() ||
                hasXfb() ||
                hasFormat() ||
@@ -670,8 +690,21 @@ public:
                hasPacking() ||
                hasOffset() ||
                hasBinding() ||
+               hasSet() ||
                hasAlign();
     }
+    void clearUniformLayout() // only uniform specific
+    {
+        layoutMatrix = ElmNone;
+        layoutPacking = ElpNone;
+        layoutOffset = layoutNotSet;
+        layoutAlign = layoutNotSet;
+
+        layoutSet = layoutSetEnd;
+        layoutBinding = layoutBindingEnd;
+        layoutAttachment = layoutAttachmentEnd;
+    }
+
     bool hasMatrix() const
     {
         return layoutMatrix != ElmNone;
@@ -1202,30 +1235,11 @@ public:
         typeName = copyOf.typeName;
     }
 
+    // Make complete copy of the whole type graph rooted at 'copyOf'.
     void deepCopy(const TType& copyOf)
     {
-        shallowCopy(copyOf);
-
-        if (copyOf.arraySizes) {
-            arraySizes = new TArraySizes;
-            *arraySizes = *copyOf.arraySizes;
-        }
-
-        if (copyOf.structure) {
-            structure = new TTypeList;
-            for (unsigned int i = 0; i < copyOf.structure->size(); ++i) {
-                TTypeLoc typeLoc;
-                typeLoc.loc = (*copyOf.structure)[i].loc;
-                typeLoc.type = new TType();
-                typeLoc.type->deepCopy(*(*copyOf.structure)[i].type);
-                structure->push_back(typeLoc);
-            }
-        }
-
-        if (copyOf.fieldName)
-            fieldName = NewPoolTString(copyOf.fieldName->c_str());
-        if (copyOf.typeName)
-            typeName = NewPoolTString(copyOf.typeName->c_str());
+        TMap<TTypeList*,TTypeList*> copied;  // to enable copying a type graph as a graph, not a tree
+        deepCopy(copyOf, copied);
     }
 
     // Recursively make temporary
@@ -1346,6 +1360,8 @@ public:
         case EbvViewportMaskNV:
         case EbvSecondaryPositionNV:
         case EbvSecondaryViewportMaskNV:
+        case EbvPositionPerViewNV:
+        case EbvViewportMaskPerViewNV:
 #endif
             return true;
         default:
@@ -1720,6 +1736,7 @@ public:
     const char* getBuiltInVariableString() const { return GetBuiltInVariableString(qualifier.builtIn); }
     const char* getPrecisionQualifierString() const { return GetPrecisionQualifierString(qualifier.precision); }
     const TTypeList* getStruct() const { return structure; }
+    void setStruct(TTypeList* s) { structure = s; }
     TTypeList* getWritableStruct() const { return structure; }  // This should only be used when known to not be sharing with other threads
 
     int computeNumComponents() const
@@ -1830,6 +1847,42 @@ protected:
     TType(const TType& type);
     TType& operator=(const TType& type);
 
+    // Recursively copy a type graph, while preserving the graph-like
+    // quality. That is, don't make more than one copy of a structure that
+    // gets reused multiple times in the type graph.
+    void deepCopy(const TType& copyOf, TMap<TTypeList*,TTypeList*>& copiedMap)
+    {
+        shallowCopy(copyOf);
+
+        if (copyOf.arraySizes) {
+            arraySizes = new TArraySizes;
+            *arraySizes = *copyOf.arraySizes;
+        }
+
+        if (copyOf.structure) {
+            auto prevCopy = copiedMap.find(copyOf.structure);
+            if (prevCopy != copiedMap.end())
+                structure = prevCopy->second;
+            else {
+                structure = new TTypeList;
+                copiedMap[copyOf.structure] = structure;
+                for (unsigned int i = 0; i < copyOf.structure->size(); ++i) {
+                    TTypeLoc typeLoc;
+                    typeLoc.loc = (*copyOf.structure)[i].loc;
+                    typeLoc.type = new TType();
+                    typeLoc.type->deepCopy(*(*copyOf.structure)[i].type, copiedMap);
+                    structure->push_back(typeLoc);
+                }
+            }
+        }
+
+        if (copyOf.fieldName)
+            fieldName = NewPoolTString(copyOf.fieldName->c_str());
+        if (copyOf.typeName)
+            typeName = NewPoolTString(copyOf.typeName->c_str());
+    }
+
+
     void buildMangledName(TString&);
 
     TBasicType basicType : 8;

+ 2 - 2
src/libraries/glslang/glslang/Include/revision.h

@@ -2,5 +2,5 @@
 // For the version, it uses the latest git tag followed by the number of commits.
 // For the date, it uses the current date (when then script is run).
 
-#define GLSLANG_REVISION "Overload400-PrecQual.1773"
-#define GLSLANG_DATE "19-Jan-2017"
+#define GLSLANG_REVISION "Overload400-PrecQual.1843"
+#define GLSLANG_DATE "18-Feb-2017"

+ 78 - 15
src/libraries/glslang/glslang/MachineIndependent/Initialize.cpp

@@ -1292,6 +1292,8 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV
 
                 "vec4 textureCube(samplerCube, vec3);"
 
+                "vec4 texture2DArray(sampler2DArray, vec3);" // GL_EXT_texture_array
+
                 "\n");
         }
     }
@@ -1317,6 +1319,10 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV
                 "vec4 shadow2DRect(sampler2DRectShadow, vec3);"     // GL_ARB_texture_rectangle, caught by keyword check
                 "vec4 shadow2DRectProj(sampler2DRectShadow, vec4);" // GL_ARB_texture_rectangle, caught by keyword check
 
+                "vec4 texture1DArray(sampler1DArray, vec2);"      // GL_EXT_texture_array
+                "vec4 shadow1DArray(sampler1DArrayShadow, vec3);" // GL_EXT_texture_array
+                "vec4 shadow2DArray(sampler2DArrayShadow, vec4);" // GL_EXT_texture_array
+
                 "\n");
         }
     }
@@ -2646,6 +2652,8 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV
                 "vec4 texture3DProjLod(sampler3D, vec4, float);"     // GL_ARB_shader_texture_lod  // OES_texture_3D, but caught by keyword check
                 "vec4 textureCubeLod(samplerCube, vec3, float);"     // GL_ARB_shader_texture_lod
 
+                "vec4 texture2DArrayLod(sampler2DArray, vec3, float);"      // GL_EXT_texture_array
+
                 "\n");
         }
     }
@@ -2681,6 +2689,10 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV
                 "vec4 shadow2DRectGradARB( sampler2DRectShadow, vec3, vec2, vec2);"    // GL_ARB_shader_texture_lod
                 "vec4 shadow2DRectProjGradARB(sampler2DRectShadow, vec4, vec2, vec2);" // GL_ARB_shader_texture_lod
 
+                "vec4 texture1DArrayLod(sampler1DArray, vec2, float);"                 // GL_EXT_texture_array
+                "vec4 shadow1DArrayLod(sampler1DArrayShadow, vec3, float);"            // GL_EXT_texture_array
+                "vec4 shadow2DArrayLod(sampler2DArrayShadow, vec4, float);"            // GL_EXT_texture_array
+
                 "\n");
         }
     }
@@ -2752,6 +2764,7 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV
             "vec4 texture3D(sampler3D, vec3, float);"        // OES_texture_3D
             "vec4 texture3DProj(sampler3D, vec4, float);"    // OES_texture_3D
             "vec4 textureCube(samplerCube, vec3, float);"
+            "vec4 texture2DArray(sampler2DArray, vec3, float);" // GL_EXT_texture_array
 
             "\n");
     }
@@ -2764,7 +2777,9 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV
             "vec4 shadow2D(sampler2DShadow, vec3, float);"
             "vec4 shadow1DProj(sampler1DShadow, vec4, float);"
             "vec4 shadow2DProj(sampler2DShadow, vec4, float);"
-
+            "vec4 texture1DArray(sampler1DArray, vec2, float);"      // GL_EXT_texture_array
+            "vec4 shadow1DArray(sampler1DArrayShadow, vec3, float);" // GL_EXT_texture_array
+            "vec4 shadow2DArray(sampler2DArrayShadow, vec4, float);" // GL_EXT_texture_array
             "\n");
     }
     if (spvVersion.spv == 0 && profile == EEsProfile) {
@@ -3249,6 +3264,8 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV
                 "out int gl_ViewportMask[];"
                 "out int gl_SecondaryViewportMaskNV[];"
                 "out vec4 gl_SecondaryPositionNV;"
+                "out vec4 gl_PositionPerViewNV[];"
+                "out int  gl_ViewportMaskPerViewNV[];"
                 );
 #endif
 
@@ -3313,6 +3330,7 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV
                 "float gl_CullDistance[];"
 #ifdef NV_EXTENSIONS
                 "vec4 gl_SecondaryPositionNV;"
+                "vec4 gl_PositionPerViewNV[];"
 #endif
                 );
         stageBuiltins[EShLangGeometry].append(
@@ -3362,9 +3380,11 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV
 #ifdef NV_EXTENSIONS
         if (version >= 450)
             stageBuiltins[EShLangGeometry].append(
-            "out int gl_ViewportMask[];"
-            "out int gl_SecondaryViewportMaskNV[];"
-            "out vec4 gl_SecondaryPositionNV;"
+                "out int gl_ViewportMask[];"
+                "out int gl_SecondaryViewportMaskNV[];"
+                "out vec4 gl_SecondaryPositionNV;"
+                "out vec4 gl_PositionPerViewNV[];"
+                "out int  gl_ViewportMaskPerViewNV[];"
             );
 #endif
 
@@ -3424,11 +3444,13 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV
             stageBuiltins[EShLangTessControl].append(
                 "float gl_CullDistance[];"
 #ifdef NV_EXTENSIONS
-                "int gl_ViewportIndex;"
-                "int gl_Layer;"
-                "int gl_ViewportMask[];"
+                "int  gl_ViewportIndex;"
+                "int  gl_Layer;"
+                "int  gl_ViewportMask[];"
                 "vec4 gl_SecondaryPositionNV;"
-                "int gl_SecondaryViewportMaskNV[];"
+                "int  gl_SecondaryViewportMaskNV[];"
+                "vec4 gl_PositionPerViewNV[];"
+                "int  gl_ViewportMaskPerViewNV[];"
 #endif
                 );
         stageBuiltins[EShLangTessControl].append(
@@ -3503,11 +3525,13 @@ void TBuiltIns::initialize(int version, EProfile profile, const SpvVersion& spvV
 #ifdef NV_EXTENSIONS
         if (version >= 450)
             stageBuiltins[EShLangTessEvaluation].append(
-                "out int gl_ViewportIndex;"
-                "out int gl_Layer;"
-                "out int gl_ViewportMask[];"
+                "out int  gl_ViewportIndex;"
+                "out int  gl_Layer;"
+                "out int  gl_ViewportMask[];"
                 "out vec4 gl_SecondaryPositionNV;"
-                "out int gl_SecondaryViewportMaskNV[];"
+                "out int  gl_SecondaryViewportMaskNV[];"
+                "out vec4 gl_PositionPerViewNV[];"
+                "out int  gl_ViewportMaskPerViewNV[];"
                 );
 #endif
 
@@ -4203,7 +4227,7 @@ void TBuiltIns::addSamplingFunctions(TSampler sampler, TString& typeName, int ve
 
                                         // Add to the per-language set of built-ins
 
-                                        if (bias)
+                                        if (bias || lodClamp)
                                             stageBuiltins[EShLangFragment].append(s);
                                         else
                                             commonBuiltins.append(s);
@@ -4446,6 +4470,7 @@ void TBuiltIns::initialize(const TBuiltInResource &resources, int version, EProf
                         "highp float gl_PointSize;"
 #ifdef NV_EXTENSIONS
                         "highp vec4 gl_SecondaryPositionNV;"
+                        "highp vec4 gl_PositionPerViewNV[];"
 #endif
                     "} gl_in[gl_MaxPatchVertices];"
                     "\n");
@@ -4635,6 +4660,7 @@ void TBuiltIns::initialize(const TBuiltInResource &resources, int version, EProf
                         "float gl_CullDistance[];"
 #ifdef NV_EXTENSIONS
                         "vec4 gl_SecondaryPositionNV;"
+                        "vec4 gl_PositionPerViewNV[];"
 #endif
                        );
                 s.append(
@@ -5033,19 +5059,26 @@ void TBuiltIns::identifyBuiltIns(int version, EProfile profile, const SpvVersion
         symbolTable.setVariableExtensions("gl_ViewportMask",            1, &E_GL_NV_viewport_array2);
         symbolTable.setVariableExtensions("gl_SecondaryPositionNV",     1, &E_GL_NV_stereo_view_rendering);
         symbolTable.setVariableExtensions("gl_SecondaryViewportMaskNV", 1, &E_GL_NV_stereo_view_rendering);
+        symbolTable.setVariableExtensions("gl_PositionPerViewNV",       1, &E_GL_NVX_multiview_per_view_attributes);
+        symbolTable.setVariableExtensions("gl_ViewportMaskPerViewNV",   1, &E_GL_NVX_multiview_per_view_attributes);
 
         BuiltInVariable("gl_ViewportMask",              EbvViewportMaskNV,          symbolTable);
         BuiltInVariable("gl_SecondaryPositionNV",       EbvSecondaryPositionNV,     symbolTable);
         BuiltInVariable("gl_SecondaryViewportMaskNV",   EbvSecondaryViewportMaskNV, symbolTable);
+        BuiltInVariable("gl_PositionPerViewNV",         EbvPositionPerViewNV,       symbolTable);
+        BuiltInVariable("gl_ViewportMaskPerViewNV",     EbvViewportMaskPerViewNV,   symbolTable);
 
-        if (language != EShLangVertex) 
+        if (language != EShLangVertex) {
             BuiltInVariable("gl_in", "gl_SecondaryPositionNV", EbvSecondaryPositionNV, symbolTable);
-
+            BuiltInVariable("gl_in", "gl_PositionPerViewNV",   EbvPositionPerViewNV,   symbolTable);
+        }
         BuiltInVariable("gl_out", "gl_Layer",                   EbvLayer,                   symbolTable);
         BuiltInVariable("gl_out", "gl_ViewportIndex",           EbvViewportIndex,           symbolTable);
         BuiltInVariable("gl_out", "gl_ViewportMask",            EbvViewportMaskNV,          symbolTable);
         BuiltInVariable("gl_out", "gl_SecondaryPositionNV",     EbvSecondaryPositionNV,     symbolTable);
         BuiltInVariable("gl_out", "gl_SecondaryViewportMaskNV", EbvSecondaryViewportMaskNV, symbolTable);
+        BuiltInVariable("gl_out", "gl_PositionPerViewNV",       EbvPositionPerViewNV,       symbolTable);
+        BuiltInVariable("gl_out", "gl_ViewportMaskPerViewNV",   EbvViewportMaskPerViewNV,   symbolTable);
 #endif
 
         BuiltInVariable("gl_PatchVerticesIn", EbvPatchVertices,  symbolTable);
@@ -5197,6 +5230,27 @@ void TBuiltIns::identifyBuiltIns(int version, EProfile profile, const SpvVersion
             symbolTable.setFunctionExtensions("shadow2DRectProjGradARB",  1, &E_GL_ARB_shader_texture_lod);
         }
 
+		// E_GL_EXT_texture_array
+        if (spvVersion.spv == 0) {
+            symbolTable.setFunctionExtensions("texture2DArray",    1, &E_GL_EXT_texture_array);
+        }
+        if (profile != EEsProfile && spvVersion.spv == 0) {
+            int nLodExtensions = 2;
+            const char *lodExtensions[] = {E_GL_EXT_texture_array, E_GL_ARB_shader_texture_lod};
+
+            if (version >= 130)
+                nLodExtensions = 1;
+
+            symbolTable.setFunctionExtensions("texture1DArray",    1, &E_GL_EXT_texture_array);
+            symbolTable.setFunctionExtensions("shadow1DArray",     1, &E_GL_EXT_texture_array);
+            symbolTable.setFunctionExtensions("shadow2DArray",     1, &E_GL_EXT_texture_array);
+
+            symbolTable.setFunctionExtensions("texture1DArrayLod", nLodExtensions, lodExtensions);
+            symbolTable.setFunctionExtensions("texture2DArrayLod", nLodExtensions, lodExtensions);
+            symbolTable.setFunctionExtensions("shadow1DArrayLod",  nLodExtensions, lodExtensions);
+            symbolTable.setFunctionExtensions("shadow2DArrayLod",  nLodExtensions, lodExtensions);
+        }
+
         // E_GL_ARB_shader_image_load_store
         if (profile != EEsProfile && version < 420)
             symbolTable.setFunctionExtensions("memoryBarrier", 1, &E_GL_ARB_shader_image_load_store);
@@ -5555,6 +5609,15 @@ void TBuiltIns::identifyBuiltIns(int version, EProfile profile, const SpvVersion
             symbolTable.relateToOperator("texture2DProjLod",         EOpTextureProjLod);
             symbolTable.relateToOperator("texture2DProjLodEXT",      EOpTextureProjLod);
 
+            symbolTable.relateToOperator("texture1DArray",           EOpTexture);
+            symbolTable.relateToOperator("texture2DArray",           EOpTexture);
+            symbolTable.relateToOperator("shadow1DArray",            EOpTexture);
+            symbolTable.relateToOperator("shadow2DArray",            EOpTexture);
+            symbolTable.relateToOperator("texture1DArrayLod",        EOpTextureLod);
+            symbolTable.relateToOperator("texture2DArrayLod",        EOpTextureLod);
+            symbolTable.relateToOperator("shadow1DArrayLod",         EOpTextureLod);
+            symbolTable.relateToOperator("shadow2DArrayLod",         EOpTextureLod);
+
             symbolTable.relateToOperator("texture3D",                EOpTexture);
             symbolTable.relateToOperator("texture3DGradARB",         EOpTextureGrad);
             symbolTable.relateToOperator("texture3DProj",            EOpTextureProj);

+ 38 - 10
src/libraries/glslang/glslang/MachineIndependent/Intermediate.cpp

@@ -155,14 +155,10 @@ TIntermTyped* TIntermediate::addBinaryMath(TOperator op, TIntermTyped* left, TIn
             return folded;
     }
 
-    // If either is a specialization constant, while the other is
-    // a constant (or specialization constant), the result is still
-    // a specialization constant, if the operation is an allowed
-    // specialization-constant operation.
-    if (( left->getType().getQualifier().isSpecConstant() && right->getType().getQualifier().isConstant()) ||
-        (right->getType().getQualifier().isSpecConstant() &&  left->getType().getQualifier().isConstant()))
-        if (isSpecializationOperation(*node))
-            node->getWritableType().getQualifier().makeSpecConstant();
+    // If can propagate spec-constantness and if the operation is an allowed
+    // specialization-constant operation, make a spec-constant.
+    if (specConstantPropagates(*left, *right) && isSpecializationOperation(*node))
+        node->getWritableType().getQualifier().makeSpecConstant();
 
     return node;
 }
@@ -1232,7 +1228,7 @@ TIntermAggregate* TIntermediate::makeAggregate(const TSourceLoc& loc)
 //
 // Returns the selection node created.
 //
-TIntermNode* TIntermediate::addSelection(TIntermTyped* cond, TIntermNodePair nodePair, const TSourceLoc& loc)
+TIntermTyped* TIntermediate::addSelection(TIntermTyped* cond, TIntermNodePair nodePair, const TSourceLoc& loc)
 {
     //
     // Don't prune the false path for compile-time constants; it's needed
@@ -1277,10 +1273,19 @@ TIntermTyped* TIntermediate::addMethod(TIntermTyped* object, const TType& type,
 // a true path, and a false path.  The two paths are specified
 // as separate parameters.
 //
+// Specialization constant operations include
+//     - The ternary operator ( ? : )
+//
 // Returns the selection node created, or nullptr if one could not be.
 //
 TIntermTyped* TIntermediate::addSelection(TIntermTyped* cond, TIntermTyped* trueBlock, TIntermTyped* falseBlock, const TSourceLoc& loc)
 {
+    // If it's void, go to the if-then-else selection()
+    if (trueBlock->getBasicType() == EbtVoid && falseBlock->getBasicType() == EbtVoid) {
+        TIntermNodePair pair = { trueBlock, falseBlock };
+        return addSelection(cond, pair, loc);
+    }
+
     //
     // Get compatible types.
     //
@@ -1314,10 +1319,16 @@ TIntermTyped* TIntermediate::addSelection(TIntermTyped* cond, TIntermTyped* true
     // Make a selection node.
     //
     TIntermSelection* node = new TIntermSelection(cond, trueBlock, falseBlock, trueBlock->getType());
-    node->getQualifier().makeTemporary();
     node->setLoc(loc);
     node->getQualifier().precision = std::max(trueBlock->getQualifier().precision, falseBlock->getQualifier().precision);
 
+    if ((cond->getQualifier().isConstant() && specConstantPropagates(*trueBlock, *falseBlock)) ||
+        (cond->getQualifier().isSpecConstant() && trueBlock->getQualifier().isConstant() &&
+                                                 falseBlock->getQualifier().isConstant()))
+        node->getQualifier().makeSpecConstant();
+    else
+        node->getQualifier().makeTemporary();
+
     return node;
 }
 
@@ -1392,6 +1403,14 @@ TIntermConstantUnion* TIntermediate::addConstantUnion(double d, TBasicType baseT
     return addConstantUnion(unionArray, TType(baseType, EvqConst), loc, literal);
 }
 
+TIntermConstantUnion* TIntermediate::addConstantUnion(const TString* s, const TSourceLoc& loc, bool literal) const
+{
+    TConstUnionArray unionArray(1);
+    unionArray[0].setSConst(s);
+
+    return addConstantUnion(unionArray, TType(EbtString, EvqConst), loc, literal);
+}
+
 // Put vector swizzle selectors onto the given sequence
 void TIntermediate::pushSelector(TIntermSequence& sequence, const TVectorSelector& selector, const TSourceLoc& loc)
 {
@@ -2639,4 +2658,13 @@ void TIntermAggregate::addToPragmaTable(const TPragmaTable& pTable)
     *pragmaTable = pTable;
 }
 
+// If either node is a specialization constant, while the other is
+// a constant (or specialization constant), the result is still
+// a specialization constant.
+bool TIntermediate::specConstantPropagates(const TIntermTyped& node1, const TIntermTyped& node2)
+{
+    return (node1.getType().getQualifier().isSpecConstant() && node2.getType().getQualifier().isConstant()) ||
+           (node2.getType().getQualifier().isSpecConstant() && node1.getType().getQualifier().isConstant());
+}
+
 } // end namespace glslang

+ 14 - 19
src/libraries/glslang/glslang/MachineIndependent/ParseContextBase.cpp

@@ -223,20 +223,11 @@ void TParseContextBase::rValueErrorCheck(const TSourceLoc& loc, const char* op,
         error(loc, "can't read from writeonly object: ", op, symNode->getName().c_str());
 }
 
-// Add a linkage symbol node for 'symbol', which
-// must have its type fully edited, as this will snapshot the type.
-// It is okay if symbol becomes invalid before finish().
-void TParseContextBase::trackLinkage(TSymbol& symbol)
-{
-    if (!parsingBuiltins)
-        intermediate.addSymbolLinkageNode(linkage, symbol);
-}
-
 // Add 'symbol' to the list of deferred linkage symbols, which
 // are later processed in finish(), at which point the symbol
 // must still be valid.
 // It is okay if the symbol's type will be subsequently edited.
-void TParseContextBase::trackLinkageDeferred(TSymbol& symbol)
+void TParseContextBase::trackLinkage(TSymbol& symbol)
 {
     if (!parsingBuiltins)
         linkageSymbols.push_back(&symbol);
@@ -253,7 +244,7 @@ void TParseContextBase::makeEditable(TSymbol*& symbol)
 
     // Save it (deferred, so it can be edited first) in the AST for linker use.
     if (symbol)
-        trackLinkageDeferred(*symbol);
+        trackLinkage(*symbol);
 }
 
 // Return a writable version of the variable 'name'.
@@ -539,7 +530,7 @@ void TParseContextBase::parseSwizzleSelector(const TSourceLoc& loc, const TStrin
 // Make the passed-in variable information become a member of the
 // global uniform block.  If this doesn't exist yet, make it.
 //
-void TParseContextBase::growGlobalUniformBlock(TSourceLoc& loc, TType& memberType, TString& memberName)
+void TParseContextBase::growGlobalUniformBlock(TSourceLoc& loc, TType& memberType, TString& memberName, TTypeList* typeList)
 {
     // make the global block, if not yet made
     if (globalUniformBlock == nullptr) {
@@ -557,6 +548,8 @@ void TParseContextBase::growGlobalUniformBlock(TSourceLoc& loc, TType& memberTyp
     TType* type = new TType;
     type->shallowCopy(memberType);
     type->setFieldName(memberName);
+    if (typeList)
+        type->setStruct(typeList);
     TTypeLoc typeLoc = {type, loc};
     globalUniformBlock->getType().getWritableStruct()->push_back(typeLoc);
 }
@@ -577,7 +570,7 @@ bool TParseContextBase::insertGlobalUniformBlock()
         // This is the first request; we need a normal symbol table insert
         inserted = symbolTable.insert(*globalUniformBlock);
         if (inserted)
-            trackLinkageDeferred(*globalUniformBlock);
+            trackLinkage(*globalUniformBlock);
     } else if (firstNewMember <= numMembers) {
         // This is a follow-on request; we need to amend the first insert
         inserted = symbolTable.amend(*globalUniformBlock, firstNewMember);
@@ -593,12 +586,14 @@ bool TParseContextBase::insertGlobalUniformBlock()
 
 void TParseContextBase::finish()
 {
-    if (!parsingBuiltins) {
-        // Transfer the linkage symbols to AST nodes
-        for (auto i = linkageSymbols.begin(); i != linkageSymbols.end(); ++i)
-            intermediate.addSymbolLinkageNode(linkage, **i);
-        intermediate.addSymbolLinkageNodes(linkage, getLanguage(), symbolTable);
-    }
+    if (parsingBuiltins)
+        return;
+
+    // Transfer the linkage symbols to AST nodes
+    TIntermAggregate* linkage = new TIntermAggregate;
+    for (auto i = linkageSymbols.begin(); i != linkageSymbols.end(); ++i)
+        intermediate.addSymbolLinkageNode(linkage, **i);
+    intermediate.addSymbolLinkageNodes(linkage, getLanguage(), symbolTable);
 }
 
 } // end namespace glslang

+ 26 - 17
src/libraries/glslang/glslang/MachineIndependent/ParseHelper.cpp

@@ -3009,7 +3009,7 @@ void TParseContext::declareArray(const TSourceLoc& loc, TString& identifier, con
             symbol = new TVariable(&identifier, type);
             symbolTable.insert(*symbol);
             if (symbolTable.atGlobalLevel())
-                trackLinkageDeferred(*symbol);
+                trackLinkage(*symbol);
 
             if (! symbolTable.atBuiltInLevel()) {
                 if (isIoResizeArray(type)) {
@@ -3431,9 +3431,9 @@ void TParseContext::redeclareBuiltinBlock(const TSourceLoc& loc, TTypeList& newT
                 oldType.getQualifier().layoutViewportRelative = newType.getQualifier().layoutViewportRelative;
                 oldType.getQualifier().layoutSecondaryViewportRelativeOffset = newType.getQualifier().layoutSecondaryViewportRelativeOffset;
             }
+#endif
             if (oldType.isImplicitlySizedArray() && newType.isExplicitlySizedArray())
                 oldType.changeOuterArraySize(newType.getOuterArraySize());
-#endif
 
             // go to next member
             ++member;
@@ -3476,7 +3476,7 @@ void TParseContext::redeclareBuiltinBlock(const TSourceLoc& loc, TTypeList& newT
         fixIoArraySize(loc, block->getWritableType());
 
     // Save it in the AST for linker use.
-    trackLinkageDeferred(*block);
+    trackLinkage(*block);
 }
 
 void TParseContext::paramCheckFix(const TSourceLoc& loc, const TStorageQualifier& qualifier, TType& type)
@@ -3958,9 +3958,7 @@ void TParseContext::setLayoutQualifier(const TSourceLoc& loc, TPublicType& publi
             publicType.shaderQualifiers.layoutOverrideCoverage = true;
             return;
         }
-#endif
     }
-#ifdef NV_EXTENSIONS
     if (language == EShLangVertex ||
         language == EShLangTessControl ||
         language == EShLangTessEvaluation ||
@@ -3971,6 +3969,8 @@ void TParseContext::setLayoutQualifier(const TSourceLoc& loc, TPublicType& publi
             return;
         }
     }
+#else
+    }
 #endif
     error(loc, "unrecognized layout identifier, or qualifier requires assignment (e.g., binding = 4)", id.c_str(), "");
 }
@@ -4008,16 +4008,20 @@ void TParseContext::setLayoutQualifier(const TSourceLoc& loc, TPublicType& publi
         //  - uniform offsets
         //  - atomic_uint offsets
         const char* feature = "offset";
-        requireProfile(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, feature);
-        const char* exts[2] = { E_GL_ARB_enhanced_layouts, E_GL_ARB_shader_atomic_counters };
-        profileRequires(loc, ECoreProfile | ECompatibilityProfile, 420, 2, exts, feature);
-        profileRequires(loc, EEsProfile, 310, nullptr, feature);
+        if (spvVersion.spv == 0) {
+            requireProfile(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, feature);
+            const char* exts[2] = { E_GL_ARB_enhanced_layouts, E_GL_ARB_shader_atomic_counters };
+            profileRequires(loc, ECoreProfile | ECompatibilityProfile, 420, 2, exts, feature);
+            profileRequires(loc, EEsProfile, 310, nullptr, feature);
+        }
         publicType.qualifier.layoutOffset = value;
         return;
     } else if (id == "align") {
         const char* feature = "uniform buffer-member align";
-        requireProfile(loc, ECoreProfile | ECompatibilityProfile, feature);
-        profileRequires(loc, ECoreProfile | ECompatibilityProfile, 440, E_GL_ARB_enhanced_layouts, feature);
+        if (spvVersion.spv == 0) {
+            requireProfile(loc, ECoreProfile | ECompatibilityProfile, feature);
+            profileRequires(loc, ECoreProfile | ECompatibilityProfile, 440, E_GL_ARB_enhanced_layouts, feature);
+        }
         // "The specified alignment must be a power of 2, or a compile-time error results."
         if (! IsPow2(value))
             error(loc, "must be a power of 2", "align", "");
@@ -4495,8 +4499,11 @@ void TParseContext::layoutTypeCheck(const TSourceLoc& loc, const TType& type)
                 }
             }
         }
-    } else if (type.isImage() && ! qualifier.writeonly)
-        error(loc, "image variables not declared 'writeonly' must have a format layout qualifier", "", "");
+    } else if (type.isImage() && ! qualifier.writeonly) {
+        const char *explanation = "image variables declared 'writeonly' without a format layout qualifier";
+        requireProfile(loc, ECoreProfile | ECompatibilityProfile, explanation);
+        profileRequires(loc, ECoreProfile | ECompatibilityProfile, 0, E_GL_EXT_shader_image_load_formatted, explanation);
+    }
 
     if (qualifier.layoutPushConstant && type.getBasicType() != EbtBlock)
         error(loc, "can only be used with a block", "push_constant", "");
@@ -5056,7 +5063,7 @@ TVariable* TParseContext::declareNonArray(const TSourceLoc& loc, TString& identi
     // add variable to symbol table
     if (symbolTable.insert(*variable)) {
         if (symbolTable.atGlobalLevel())
-            trackLinkageDeferred(*variable);
+            trackLinkage(*variable);
         return variable;
     }
 
@@ -5546,8 +5553,10 @@ void TParseContext::declareBlock(const TSourceLoc& loc, TTypeList& typeList, con
         if (memberType.isArray())
             arrayUnsizedCheck(memberLoc, currentBlockQualifier, &memberType.getArraySizes(), false, member == typeList.size() - 1);
         if (memberQualifier.hasOffset()) {
-            requireProfile(memberLoc, ~EEsProfile, "offset on block member");
-            profileRequires(memberLoc, ~EEsProfile, 440, E_GL_ARB_enhanced_layouts, "offset on block member");
+            if (spvVersion.spv == 0) {
+                requireProfile(memberLoc, ~EEsProfile, "offset on block member");
+                profileRequires(memberLoc, ~EEsProfile, 440, E_GL_ARB_enhanced_layouts, "offset on block member");
+            }
         }
 
         if (memberType.containsOpaque())
@@ -5718,7 +5727,7 @@ void TParseContext::declareBlock(const TSourceLoc& loc, TTypeList& typeList, con
         fixIoArraySize(loc, variable.getWritableType());
 
     // Save it in the AST for linker use.
-    trackLinkageDeferred(variable);
+    trackLinkage(variable);
 }
 
 // Do all block-declaration checking regarding the combination of in/out/uniform/buffer

+ 2 - 8
src/libraries/glslang/glslang/MachineIndependent/ParseHelper.h

@@ -79,9 +79,7 @@ public:
             symbolTable(symbolTable),
             parsingBuiltins(parsingBuiltins), scanContext(nullptr), ppContext(nullptr),
             globalUniformBlock(nullptr)
-    {
-        linkage = new TIntermAggregate;
-    }
+    { }
     virtual ~TParseContextBase() { }
 
     virtual void C_DECL   error(const TSourceLoc&, const char* szReason, const char* szToken,
@@ -141,7 +139,7 @@ public:
     // TODO: This could perhaps get its own object, but the current design doesn't work
     // yet when new uniform variables are declared between function definitions, so
     // this is pending getting a fully functional design.
-    virtual void growGlobalUniformBlock(TSourceLoc&, TType&, TString& memberName);
+    virtual void growGlobalUniformBlock(TSourceLoc&, TType&, TString& memberName, TTypeList* typeList = nullptr);
     virtual bool insertGlobalUniformBlock();
 
     virtual bool lValueErrorCheck(const TSourceLoc&, const char* op, TIntermTyped*);
@@ -183,13 +181,9 @@ protected:
                                const char* szExtraInfoFormat, TPrefixType prefix,
                                va_list args);
     virtual void trackLinkage(TSymbol& symbol);
-    virtual void trackLinkageDeferred(TSymbol& symbol);
     virtual void makeEditable(TSymbol*&);
     virtual TVariable* getEditableVariable(const char* name);
     virtual void finish();
-
-private:
-    TIntermAggregate* linkage;
 };
 
 //

+ 2 - 2
src/libraries/glslang/glslang/MachineIndependent/PoolAlloc.cpp

@@ -105,8 +105,8 @@ void SetThreadPoolAllocator(TPoolAllocator& poolAllocator)
 TPoolAllocator::TPoolAllocator(int growthIncrement, int allocationAlignment) :
     pageSize(growthIncrement),
     alignment(allocationAlignment),
-    freeList(0),
-    inUseList(0),
+    freeList(nullptr),
+    inUseList(nullptr),
     numCalls(0)
 {
     //

+ 31 - 12
src/libraries/glslang/glslang/MachineIndependent/Scan.cpp

@@ -1006,7 +1006,6 @@ int TScanContext::tokenizeIdentifier()
 
     case ISAMPLER1D:
     case ISAMPLER1DARRAY:
-    case SAMPLER1DARRAYSHADOW:
     case USAMPLER1D:
     case USAMPLER1DARRAY:
         afterType = true;
@@ -1017,8 +1016,6 @@ int TScanContext::tokenizeIdentifier()
     case UVEC3:
     case UVEC4:
     case SAMPLERCUBESHADOW:
-    case SAMPLER2DARRAY:
-    case SAMPLER2DARRAYSHADOW:
     case ISAMPLER2D:
     case ISAMPLER3D:
     case ISAMPLERCUBE:
@@ -1085,6 +1082,37 @@ int TScanContext::tokenizeIdentifier()
             reservedWord();
         return keyword;
 
+    case SAMPLER1DARRAY:
+    case SAMPLER1DARRAYSHADOW:
+        afterType = true;
+        if (parseContext.symbolTable.atBuiltInLevel())
+            return keyword;
+        else if (parseContext.profile == EEsProfile) {
+            if (parseContext.version >= 300)
+                reservedWord();
+            else
+                return identifierOrType();
+        } else if (parseContext.version < 130 && !parseContext.extensionTurnedOn(E_GL_EXT_texture_array)) {
+            if (parseContext.forwardCompatible)
+                parseContext.warn(loc, "using future keyword", tokenText, "");
+
+            return identifierOrType();
+        }
+        return keyword;
+
+    case SAMPLER2DARRAY:
+    case SAMPLER2DARRAYSHADOW:
+        afterType = true;
+        if (parseContext.symbolTable.atBuiltInLevel())
+            return keyword;
+        else if (parseContext.version < 130 && ((parseContext.profile == EEsProfile && keyword != SAMPLER2DARRAY) || !parseContext.extensionTurnedOn(E_GL_EXT_texture_array))) {
+            if (parseContext.forwardCompatible)
+                parseContext.warn(loc, "using future keyword", tokenText, "");
+
+            return identifierOrType();
+        }
+        return keyword;
+
     case SAMPLER2DRECT:
     case SAMPLER2DRECTSHADOW:
         afterType = true;
@@ -1098,15 +1126,6 @@ int TScanContext::tokenizeIdentifier()
         }
         return keyword;
 
-    case SAMPLER1DARRAY:
-        afterType = true;
-        if (parseContext.profile == EEsProfile && parseContext.version == 300)
-            reservedWord();
-        else if ((parseContext.profile == EEsProfile && parseContext.version < 300) ||
-                 (parseContext.profile != EEsProfile && parseContext.version < 130))
-            return identifierOrType();
-        return keyword;
-
     case SAMPLEREXTERNALOES:
         afterType = true;
         if (parseContext.symbolTable.atBuiltInLevel() || parseContext.extensionTurnedOn(E_GL_OES_EGL_image_external))

+ 10 - 0
src/libraries/glslang/glslang/MachineIndependent/ShaderLang.cpp

@@ -1632,6 +1632,7 @@ TProgram::TProgram() : pool(0), reflection(0), ioMapper(nullptr), linked(false)
 
 TProgram::~TProgram()
 {
+    delete ioMapper;
     delete infoSink;
     delete reflection;
 
@@ -1707,6 +1708,15 @@ bool TProgram::linkStage(EShLanguage stage, EShMessages messages)
         intermediate[stage] = new TIntermediate(stage,
                                                 firstIntermediate->getVersion(),
                                                 firstIntermediate->getProfile());
+
+
+        // The new TIntermediate must use the same origin as the original TIntermediates.
+        // Otherwise linking will fail due to different coordinate systems.
+        if (firstIntermediate->getOriginUpperLeft()) {
+            intermediate[stage]->setOriginUpperLeft();
+        }
+        intermediate[stage]->setSpv(firstIntermediate->getSpv());
+
         newedIntermediate[stage] = true;
     }
 

+ 22 - 1
src/libraries/glslang/glslang/MachineIndependent/SymbolTable.h

@@ -87,6 +87,12 @@ public:
 
     virtual const TString& getName() const { return *name; }
     virtual void changeName(const TString* newName) { name = newName; }
+    virtual void addPrefix(const char* prefix)
+    {
+        TString newName(prefix);
+        newName.append(*name);
+        changeName(NewPoolTString(newName.c_str()));
+    }
     virtual const TString& getMangledName() const { return getName(); }
     virtual TFunction* getAsFunction() { return 0; }
     virtual const TFunction* getAsFunction() const { return 0; }
@@ -192,6 +198,7 @@ struct TParameter {
     TString *name;
     TType* type;
     TIntermTyped* defaultValue;
+    TBuiltInVariable declaredBuiltIn;
     void copyParam(const TParameter& param)
     {
         if (param.name)
@@ -200,6 +207,7 @@ struct TParameter {
             name = 0;
         type = param.type->clone();
         defaultValue = param.defaultValue;
+        declaredBuiltIn = param.declaredBuiltIn;
     }
 };
 
@@ -216,7 +224,11 @@ public:
         TSymbol(name),
         mangledName(*name + '('),
         op(tOp),
-        defined(false), prototyped(false), defaultParamCount(0) { returnType.shallowCopy(retType); }
+        defined(false), prototyped(false), defaultParamCount(0)
+    {
+        returnType.shallowCopy(retType);
+        declaredBuiltIn = retType.getQualifier().builtIn;
+    }
     virtual TFunction* clone() const;
     virtual ~TFunction();
 
@@ -226,15 +238,22 @@ public:
     virtual void addParameter(TParameter& p)
     {
         assert(writable);
+        p.declaredBuiltIn = p.type->getQualifier().builtIn;
         parameters.push_back(p);
         p.type->appendMangledName(mangledName);
 
         if (p.defaultValue != nullptr)
             defaultParamCount++;
     }
+    virtual void addPrefix(const char* prefix) override
+    {
+        TSymbol::addPrefix(prefix);
+        mangledName.insert(0, prefix);
+    }
 
     virtual const TString& getMangledName() const { return mangledName; }
     virtual const TType& getType() const { return returnType; }
+    virtual TBuiltInVariable getDeclaredBuiltInType() const { return declaredBuiltIn; }
     virtual TType& getWritableType() { return returnType; }
     virtual void relateToOperator(TOperator o) { assert(writable); op = o; }
     virtual TOperator getBuiltInOp() const { return op; }
@@ -262,6 +281,8 @@ protected:
     typedef TVector<TParameter> TParamList;
     TParamList parameters;
     TType returnType;
+    TBuiltInVariable declaredBuiltIn;
+
     TString mangledName;
     TOperator op;
     bool defined;

+ 6 - 0
src/libraries/glslang/glslang/MachineIndependent/Versions.cpp

@@ -156,6 +156,7 @@ void TParseVersions::initializeExtensionBehavior()
     extensionBehavior[E_GL_OES_EGL_image_external]           = EBhDisable;
     extensionBehavior[E_GL_EXT_shader_texture_lod]           = EBhDisable;
 
+    extensionBehavior[E_GL_EXT_texture_array]                = EBhDisable;
     extensionBehavior[E_GL_ARB_texture_rectangle]            = EBhDisable;
     extensionBehavior[E_GL_3DL_array_objects]                = EBhDisable;
     extensionBehavior[E_GL_ARB_shading_language_420pack]     = EBhDisable;
@@ -182,6 +183,7 @@ void TParseVersions::initializeExtensionBehavior()
 //    extensionBehavior[E_GL_ARB_cull_distance]                = EBhDisable;    // present for 4.5, but need extension control over block members
 
     extensionBehavior[E_GL_EXT_shader_non_constant_global_initializers] = EBhDisable;
+    extensionBehavior[E_GL_EXT_shader_image_load_formatted]  = EBhDisable;
 
     // #line and #include
     extensionBehavior[E_GL_GOOGLE_cpp_style_line_directive]          = EBhDisable;
@@ -201,6 +203,7 @@ void TParseVersions::initializeExtensionBehavior()
     extensionBehavior[E_GL_ARB_shader_viewport_layer_array]          = EBhDisable;
     extensionBehavior[E_GL_NV_viewport_array2]                       = EBhDisable;
     extensionBehavior[E_GL_NV_stereo_view_rendering]                 = EBhDisable;
+    extensionBehavior[E_GL_NVX_multiview_per_view_attributes]        = EBhDisable;
 #endif
 
     // AEP
@@ -240,6 +243,7 @@ void TParseVersions::getPreamble(std::string& preamble)
         preamble =
             "#define GL_ES 1\n"
             "#define GL_FRAGMENT_PRECISION_HIGH 1\n"
+            "#define GL_EXT_texture_array 1\n"
             "#define GL_OES_texture_3D 1\n"
             "#define GL_OES_standard_derivatives 1\n"
             "#define GL_EXT_frag_depth 1\n"
@@ -278,6 +282,7 @@ void TParseVersions::getPreamble(std::string& preamble)
     } else {
         preamble =
             "#define GL_FRAGMENT_PRECISION_HIGH 1\n"
+            "#define GL_EXT_texture_array 1\n"
             "#define GL_ARB_texture_rectangle 1\n"
             "#define GL_ARB_shading_language_420pack 1\n"
             "#define GL_ARB_texture_gather 1\n"
@@ -302,6 +307,7 @@ void TParseVersions::getPreamble(std::string& preamble)
             "#define GL_ARB_sparse_texture_clamp 1\n"
 //            "#define GL_ARB_cull_distance 1\n"    // present for 4.5, but need extension control over block members
             "#define GL_EXT_shader_non_constant_global_initializers 1\n"
+            "#define GL_EXT_shader_image_load_formatted 1\n"
 
 #ifdef AMD_EXTENSIONS
             "#define GL_AMD_shader_ballot 1\n"

+ 3 - 0
src/libraries/glslang/glslang/MachineIndependent/Versions.h

@@ -104,6 +104,7 @@ const char* const E_GL_EXT_frag_depth                   = "GL_EXT_frag_depth";
 const char* const E_GL_OES_EGL_image_external           = "GL_OES_EGL_image_external";
 const char* const E_GL_EXT_shader_texture_lod           = "GL_EXT_shader_texture_lod";
 
+const char* const E_GL_EXT_texture_array                = "GL_EXT_texture_array";
 const char* const E_GL_ARB_texture_rectangle            = "GL_ARB_texture_rectangle";
 const char* const E_GL_3DL_array_objects                = "GL_3DL_array_objects";
 const char* const E_GL_ARB_shading_language_420pack     = "GL_ARB_shading_language_420pack";
@@ -130,6 +131,7 @@ const char* const E_GL_ARB_sparse_texture_clamp         = "GL_ARB_sparse_texture
 // const char* const E_GL_ARB_cull_distance            = "GL_ARB_cull_distance";  // present for 4.5, but need extension control over block members
 
 const char* const E_GL_EXT_shader_non_constant_global_initializers = "GL_EXT_shader_non_constant_global_initializers";
+const char* const E_GL_EXT_shader_image_load_formatted = "GL_EXT_shader_image_load_formatted";
 
 // #line and #include
 const char* const E_GL_GOOGLE_cpp_style_line_directive          = "GL_GOOGLE_cpp_style_line_directive";
@@ -149,6 +151,7 @@ const char* const E_SPV_NV_geometry_shader_passthrough          = "GL_NV_geometr
 const char* const E_GL_ARB_shader_viewport_layer_array          = "GL_ARB_shader_viewport_layer_array";
 const char* const E_GL_NV_viewport_array2                       = "GL_NV_viewport_array2";
 const char* const E_GL_NV_stereo_view_rendering                 = "GL_NV_stereo_view_rendering";
+const char* const E_GL_NVX_multiview_per_view_attributes        = "GL_NVX_multiview_per_view_attributes";
 
 // Arrays of extensions for the above viewportEXTs duplications
 

+ 3 - 0
src/libraries/glslang/glslang/MachineIndependent/iomapper.cpp

@@ -209,6 +209,9 @@ struct TResolverAdaptor
     TIoMapResolver& resolver;
     TInfoSink&      infoSink;
     bool&           error;
+
+private:
+    TResolverAdaptor& operator=(TResolverAdaptor&);
 };
 
 /*

+ 3 - 1
src/libraries/glslang/glslang/MachineIndependent/localintermediate.h

@@ -252,7 +252,7 @@ public:
     TIntermAggregate* makeAggregate(const TSourceLoc&);
     TIntermTyped* setAggregateOperator(TIntermNode*, TOperator, const TType& type, TSourceLoc);
     bool areAllChildConst(TIntermAggregate* aggrNode);
-    TIntermNode*  addSelection(TIntermTyped* cond, TIntermNodePair code, const TSourceLoc&);
+    TIntermTyped* addSelection(TIntermTyped* cond, TIntermNodePair code, const TSourceLoc&);
     TIntermTyped* addSelection(TIntermTyped* cond, TIntermTyped* trueBlock, TIntermTyped* falseBlock, const TSourceLoc&);
     TIntermTyped* addComma(TIntermTyped* left, TIntermTyped* right, const TSourceLoc&);
     TIntermTyped* addMethod(TIntermTyped*, const TType&, const TString*, const TSourceLoc&);
@@ -263,6 +263,7 @@ public:
     TIntermConstantUnion* addConstantUnion(unsigned long long, const TSourceLoc&, bool literal = false) const;
     TIntermConstantUnion* addConstantUnion(bool, const TSourceLoc&, bool literal = false) const;
     TIntermConstantUnion* addConstantUnion(double, TBasicType, const TSourceLoc&, bool literal = false) const;
+    TIntermConstantUnion* addConstantUnion(const TString*, const TSourceLoc&, bool literal = false) const;
     TIntermTyped* promoteConstantUnion(TBasicType, TIntermConstantUnion*) const;
     bool parseConstTree(TIntermNode*, TConstUnionArray, TOperator, const TType&, bool singleConstantParam = false);
     TIntermLoop* addLoop(TIntermNode*, TIntermTyped*, TIntermTyped*, bool testFirst, const TSourceLoc&);
@@ -440,6 +441,7 @@ protected:
     bool promoteAggregate(TIntermAggregate&);
     void pushSelector(TIntermSequence&, const TVectorSelector&, const TSourceLoc&);
     void pushSelector(TIntermSequence&, const TMatrixSelector&, const TSourceLoc&);
+    bool specConstantPropagates(const TIntermTyped&, const TIntermTyped&);
 
     const EShLanguage language;  // stage, known at construction time
     EShSource source;            // source language, known a bit later

+ 14 - 51
src/libraries/glslang/glslang/MachineIndependent/preprocessor/Pp.cpp

@@ -148,10 +148,10 @@ int TPpContext::CPPdefine(TPpToken* ppToken)
     // record the definition of the macro
     TSourceLoc defineLoc = ppToken->loc; // because ppToken is going to go to the next line before we report errors
     while (token != '\n' && token != EndOfInput) {
-        RecordToken(mac.body, token, ppToken);
+        mac.body.putToken(token, ppToken);
         token = scanToken(ppToken);
         if (token != '\n' && ppToken->space)
-            RecordToken(mac.body, ' ', ppToken);
+            mac.body.putToken(' ', ppToken);
     }
 
     // check for duplicate definition
@@ -166,15 +166,15 @@ int TPpContext::CPPdefine(TPpToken* ppToken)
             else {
                 if (existing->args != mac.args)
                     parseContext.ppError(defineLoc, "Macro redefined; different argument names:", "#define", atomStrings.getString(defAtom));
-                RewindTokenStream(existing->body);
-                RewindTokenStream(mac.body);
+                existing->body.reset();
+                mac.body.reset();
                 int newToken;
                 do {
                     int oldToken;
                     TPpToken oldPpToken;
                     TPpToken newPpToken;
-                    oldToken = ReadToken(existing->body, &oldPpToken);
-                    newToken = ReadToken(mac.body, &newPpToken);
+                    oldToken = existing->body.getToken(parseContext, &oldPpToken);
+                    newToken = mac.body.getToken(parseContext, &newPpToken);
                     if (oldToken != newToken || oldPpToken != newPpToken) {
                         parseContext.ppError(defineLoc, "Macro redefined; different substitutions:", "#define", atomStrings.getString(defAtom));
                         break;
@@ -979,28 +979,16 @@ int TPpContext::scanHeaderName(TPpToken* ppToken, char delimit)
 // Returns nullptr if no expanded argument is created.
 TPpContext::TokenStream* TPpContext::PrescanMacroArg(TokenStream& arg, TPpToken* ppToken, bool newLineOkay)
 {
-    // pre-check, to see if anything in the argument needs to be expanded,
-    // to see if we can kick out early
-    int token;
-    RewindTokenStream(arg);
-    do {
-        token = ReadToken(arg, ppToken);
-        if (token == PpAtomIdentifier && lookupMacroDef(atomStrings.getAtom(ppToken->name)) != nullptr)
-            break;
-    } while (token != EndOfInput);
-
-    // if nothing needs to be expanded, kick out early
-    if (token == EndOfInput)
-        return nullptr;
-
     // expand the argument
     TokenStream* expandedArg = new TokenStream;
     pushInput(new tMarkerInput(this));
     pushTokenStreamInput(arg);
+    int token;
     while ((token = scanToken(ppToken)) != tMarkerInput::marker && token != EndOfInput) {
+        token = tokenPaste(token, *ppToken);
         if (token == PpAtomIdentifier && MacroExpand(ppToken, false, newLineOkay) != 0)
             continue;
-        RecordToken(*expandedArg, token, ppToken);
+        expandedArg->putToken(token, ppToken);
     }
 
     if (token == EndOfInput) {
@@ -1023,7 +1011,7 @@ int TPpContext::tMacroInput::scan(TPpToken* ppToken)
 {
     int token;
     do {
-        token = pp->ReadToken(mac->body, ppToken);
+        token = mac->body.getToken(pp->parseContext, ppToken);
     } while (token == ' ');  // handle white space in macro
 
     // Hash operators basically turn off a round of macro substitution
@@ -1054,7 +1042,7 @@ int TPpContext::tMacroInput::scan(TPpToken* ppToken)
     }
 
     // see if are preceding a ##
-    if (peekMacPasting()) {
+    if (mac->body.peekUntokenizedPasting()) {
         prepaste = true;
         pasting = true;
     }
@@ -1081,31 +1069,6 @@ int TPpContext::tMacroInput::scan(TPpToken* ppToken)
     return token;
 }
 
-// See if the next non-white-space token in the macro is ##
-bool TPpContext::tMacroInput::peekMacPasting()
-{
-    // don't return early, have to restore this
-    size_t savePos = mac->body.current;
-
-    // skip white-space
-    int ltoken;
-    do {
-        ltoken = pp->lReadByte(mac->body);
-    } while (ltoken == ' ');
-
-    // check for ##
-    bool pasting = false;
-    if (ltoken == '#') {
-        ltoken = pp->lReadByte(mac->body);
-        if (ltoken == '#')
-            pasting = true;
-    }
-
-    mac->body.current = savePos;
-
-    return pasting;
-}
-
 // return a textual zero, for scanning a macro that was never defined
 int TPpContext::tZeroInput::scan(TPpToken* ppToken)
 {
@@ -1230,7 +1193,7 @@ int TPpContext::MacroExpand(TPpToken* ppToken, bool expandUndef, bool newLineOka
                     depth++;
                 if (token == ')')
                     depth--;
-                RecordToken(*in->args[arg], token, ppToken);
+                in->args[arg]->putToken(token, ppToken);
                 tokenRecorded = true;
             }
             if (token == ')') {
@@ -1263,14 +1226,14 @@ int TPpContext::MacroExpand(TPpToken* ppToken, bool expandUndef, bool newLineOka
         }
 
         // We need both expanded and non-expanded forms of the argument, for whether or
-        // not token pasting is in play.
+        // not token pasting will be applied later when the argument is consumed next to ##.
         for (size_t i = 0; i < in->mac->args.size(); i++)
             in->expandedArgs[i] = PrescanMacroArg(*in->args[i], ppToken, newLineOkay);
     }
 
     pushInput(in);
     macro->busy = 1;
-    RewindTokenStream(macro->body);
+    macro->body.reset();
 
     return 1;
 }

+ 22 - 11
src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpContext.h

@@ -220,8 +220,26 @@ public:
         inputStack.pop_back();
     }
 
-    struct TokenStream {
+    //
+    // From PpTokens.cpp
+    //
+
+    class TokenStream {
+    public:
         TokenStream() : current(0) { }
+
+        void putToken(int token, TPpToken* ppToken);
+        int getToken(TParseContextBase&, TPpToken*);
+        bool atEnd() { return current >= data.size(); }
+        bool peekTokenizedPasting(bool lastTokenPastes);
+        bool peekUntokenizedPasting();
+        void reset() { current = 0; }
+
+    protected:
+        void putSubtoken(int);
+        int getSubtoken();
+        void ungetSubtoken();
+
         TVector<unsigned char> data;
         size_t current;
     };
@@ -306,14 +324,13 @@ protected:
         virtual int getch() override { assert(0); return EndOfInput; }
         virtual void ungetch() override { assert(0); }
         bool peekPasting() override { return prepaste; }
-        bool endOfReplacementList() override { return mac->body.current >= mac->body.data.size(); }
+        bool endOfReplacementList() override { return mac->body.atEnd(); }
 
         MacroSymbol *mac;
         TVector<TokenStream*> args;
         TVector<TokenStream*> expandedArgs;
 
     protected:
-        bool peekMacPasting();
         bool prepaste;         // true if we are just before ##
         bool postpaste;        // true if we are right after ##
     };
@@ -375,22 +392,16 @@ protected:
     //
     // From PpTokens.cpp
     //
-    void lAddByte(TokenStream&, unsigned char fVal);
-    int lReadByte(TokenStream&);
-    void lUnreadByte(TokenStream&);
-    void RecordToken(TokenStream&, int token, TPpToken* ppToken);
-    void RewindTokenStream(TokenStream&);
-    int ReadToken(TokenStream&, TPpToken*);
     void pushTokenStreamInput(TokenStream&, bool pasting = false);
     void UngetToken(int token, TPpToken*);
 
     class tTokenInput : public tInput {
     public:
         tTokenInput(TPpContext* pp, TokenStream* t, bool prepasting) : tInput(pp), tokens(t), lastTokenPastes(prepasting) { }
-        virtual int scan(TPpToken *) override;
+        virtual int scan(TPpToken *ppToken) override { return tokens->getToken(pp->parseContext, ppToken); }
         virtual int getch() override { assert(0); return EndOfInput; }
         virtual void ungetch() override { assert(0); }
-        virtual bool peekPasting() override;
+        virtual bool peekPasting() override { return tokens->peekTokenizedPasting(lastTokenPastes); }
     protected:
         TokenStream* tokens;
         bool lastTokenPastes;     // true if the last token in the input is to be pasted, rather than consumed as a token

+ 2 - 0
src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpScanner.cpp

@@ -232,6 +232,8 @@ int TPpContext::tStringInput::scan(TPpToken* ppToken)
         switch (ch) {
         default:
             // Single character token, including EndOfInput, '#' and '\' (escaped newlines are handled at a lower level, so this is just a '\' token)
+            if (ch > PpAtomMaxSingle)
+                ch = PpAtomBadToken;
             return ch;
 
         case 'A': case 'B': case 'C': case 'D': case 'E':

+ 85 - 62
src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpTokens.cpp

@@ -95,48 +95,45 @@ NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 namespace glslang {
 
-void TPpContext::lAddByte(TokenStream& fTok, unsigned char fVal)
+// push onto back of stream
+void TPpContext::TokenStream::putSubtoken(int subtoken)
 {
-    fTok.data.push_back(fVal);
+    assert((subtoken & ~0xff) == 0);
+    data.push_back(static_cast<unsigned char>(subtoken));
 }
 
-/*
-* Get the next byte from a stream.
-*/
-int TPpContext::lReadByte(TokenStream& pTok)
+// get the next token in stream
+int TPpContext::TokenStream::getSubtoken()
 {
-    if (pTok.current < pTok.data.size())
-        return pTok.data[pTok.current++];
+    if (current < data.size())
+        return data[current++];
     else
         return EndOfInput;
 }
 
-void TPpContext::lUnreadByte(TokenStream& pTok)
+// back up one position in the stream
+void TPpContext::TokenStream::ungetSubtoken()
 {
-    if (pTok.current > 0)
-        --pTok.current;
+    if (current > 0)
+        --current;
 }
 
-/*
-* Add a token to the end of a list for later playback.
-*/
-void TPpContext::RecordToken(TokenStream& pTok, int token, TPpToken* ppToken)
+// Add a complete token (including backing string) to the end of a list
+// for later playback.
+void TPpContext::TokenStream::putToken(int token, TPpToken* ppToken)
 {
     const char* s;
     char* str = NULL;
 
-    if (token > PpAtomMaxSingle)
-        lAddByte(pTok, (unsigned char)((token & 0x7f) + 0x80));
-    else
-        lAddByte(pTok, (unsigned char)(token & 0x7f));
+    putSubtoken(token);
 
     switch (token) {
     case PpAtomIdentifier:
     case PpAtomConstString:
         s = ppToken->name;
         while (*s)
-            lAddByte(pTok, (unsigned char) *s++);
-        lAddByte(pTok, 0);
+            putSubtoken(*s++);
+        putSubtoken(0);
         break;
     case PpAtomConstInt:
     case PpAtomConstUint:
@@ -149,46 +146,35 @@ void TPpContext::RecordToken(TokenStream& pTok, int token, TPpToken* ppToken)
 #endif
         str = ppToken->name;
         while (*str) {
-            lAddByte(pTok, (unsigned char) *str);
+            putSubtoken(*str);
             str++;
         }
-        lAddByte(pTok, 0);
+        putSubtoken(0);
         break;
     default:
         break;
     }
 }
 
-/*
-* Reset a token stream in preparation for reading.
-*/
-void TPpContext::RewindTokenStream(TokenStream& pTok)
+// Read the next token from a token stream.
+// (Not the source stream, but a stream used to hold a tokenized macro).
+int TPpContext::TokenStream::getToken(TParseContextBase& parseContext, TPpToken *ppToken)
 {
-    pTok.current = 0;
-}
-
-/*
-* Read the next token from a token stream (not the source stream, but stream used to hold a tokenized macro).
-*/
-int TPpContext::ReadToken(TokenStream& pTok, TPpToken *ppToken)
-{
-    int ltoken, len;
+    int len;
     int ch;
 
-    ltoken = lReadByte(pTok);
+    int subtoken = getSubtoken();
     ppToken->loc = parseContext.getCurrentLoc();
-    if (ltoken > 127)
-        ltoken += 128;
-    switch (ltoken) {
+    switch (subtoken) {
     case '#':
         // Check for ##, unless the current # is the last character
-        if (pTok.current < pTok.data.size()) {
-            if (lReadByte(pTok) == '#') {
+        if (current < data.size()) {
+            if (getSubtoken() == '#') {
                 parseContext.requireProfile(ppToken->loc, ~EEsProfile, "token pasting (##)");
                 parseContext.profileRequires(ppToken->loc, ~EEsProfile, 130, 0, "token pasting (##)");
-                ltoken = PpAtomPaste;
+                subtoken = PpAtomPaste;
             } else
-                lUnreadByte(pTok);
+                ungetSubtoken();
         }
         break;
     case PpAtomConstString:
@@ -203,12 +189,12 @@ int TPpContext::ReadToken(TokenStream& pTok, TPpToken *ppToken)
     case PpAtomConstInt64:
     case PpAtomConstUint64:
         len = 0;
-        ch = lReadByte(pTok);
+        ch = getSubtoken();
         while (ch != 0 && ch != EndOfInput) {
             if (len < MaxTokenLength) {
                 ppToken->name[len] = (char)ch;
                 len++;
-                ch = lReadByte(pTok);
+                ch = getSubtoken();
             } else {
                 parseContext.error(ppToken->loc, "token too long", "", "");
                 break;
@@ -216,7 +202,7 @@ int TPpContext::ReadToken(TokenStream& pTok, TPpToken *ppToken)
         }
         ppToken->name[len] = 0;
 
-        switch (ltoken) {
+        switch (subtoken) {
         case PpAtomIdentifier:
             break;
         case PpAtomConstString:
@@ -267,43 +253,80 @@ int TPpContext::ReadToken(TokenStream& pTok, TPpToken *ppToken)
         }
     }
 
-    return ltoken;
+    return subtoken;
 }
 
-int TPpContext::tTokenInput::scan(TPpToken* ppToken)
+// We are pasting if
+//   1. we are preceding a pasting operator within this stream
+// or
+//   2. the entire macro is preceding a pasting operator (lastTokenPastes)
+//      and we are also on the last token
+bool TPpContext::TokenStream::peekTokenizedPasting(bool lastTokenPastes)
 {
-    return pp->ReadToken(*tokens, ppToken);
-}
+    // 1. preceding ##?
+
+    size_t savePos = current;
+    int subtoken;
+    // skip white space
+    do {
+        subtoken = getSubtoken();
+    } while (subtoken == ' ');
+    current = savePos;
+    if (subtoken == PpAtomPaste)
+        return true;
+
+    // 2. last token and we've been told after this there will be a ##
 
-// We are pasting if the entire macro is preceding a pasting operator
-// (lastTokenPastes) and we are also on the last token.
-bool TPpContext::tTokenInput::peekPasting()
-{
     if (! lastTokenPastes)
         return false;
-    // Getting here means the last token will be pasted.
+    // Getting here means the last token will be pasted, after this
 
     // Are we at the last non-whitespace token?
-    size_t savePos = tokens->current;
+    savePos = current;
     bool moreTokens = false;
     do {
-        int byte = pp->lReadByte(*tokens);
-        if (byte == EndOfInput)
+        subtoken = getSubtoken();
+        if (subtoken == EndOfInput)
             break;
-        if (byte != ' ') {
+        if (subtoken != ' ') {
             moreTokens = true;
             break;
         }
     } while (true);
-    tokens->current = savePos;
+    current = savePos;
 
     return !moreTokens;
 }
 
+// See if the next non-white-space tokens are two consecutive #
+bool TPpContext::TokenStream::peekUntokenizedPasting()
+{
+    // don't return early, have to restore this
+    size_t savePos = current;
+
+    // skip white-space
+    int subtoken;
+    do {
+        subtoken = getSubtoken();
+    } while (subtoken == ' ');
+
+    // check for ##
+    bool pasting = false;
+    if (subtoken == '#') {
+        subtoken = getSubtoken();
+        if (subtoken == '#')
+            pasting = true;
+    }
+
+    current = savePos;
+
+    return pasting;
+}
+
 void TPpContext::pushTokenStreamInput(TokenStream& ts, bool prepasting)
 {
     pushInput(new tTokenInput(this, &ts, prepasting));
-    RewindTokenStream(ts);
+    ts.reset();
 }
 
 int TPpContext::tUngotTokenInput::scan(TPpToken* ppToken)

+ 5 - 1
src/libraries/glslang/glslang/MachineIndependent/preprocessor/PpTokens.h

@@ -82,7 +82,11 @@ namespace glslang {
 
 // Multi-character tokens
 enum EFixedAtoms {
-    PpAtomMaxSingle = 256, // single character tokens get their own char value as their token, skip them
+    // single character tokens get their own char value as their token; start here for multi-character tokens
+    PpAtomMaxSingle = 127,
+
+    // replace bad character tokens with this, to avoid accidental aliasing with the below
+    PpAtomBadToken,
 
     // Operators
 

+ 1 - 0
src/libraries/glslang/glslang/Public/ShaderLang.h

@@ -540,6 +540,7 @@ protected:
     bool linked;
 
 private:
+    TProgram(TProgram&);
     TProgram& operator=(TProgram&);
 };
 

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

@@ -29,7 +29,8 @@ namespace graphics
 love::Type Canvas::type("Canvas", &Texture::type);
 int Canvas::canvasCount = 0;
 
-Canvas::Canvas()
+Canvas::Canvas(TextureType textype)
+	: Texture(textype)
 {
 	canvasCount++;
 }

+ 6 - 2
src/modules/graphics/Canvas.h

@@ -39,15 +39,19 @@ public:
 
 	struct Settings
 	{
+		int width  = 1;
+		int height = 1;
+		int layers = 1; // depth for 3D textures
 		PixelFormat format = PIXELFORMAT_NORMAL;
+		TextureType type = TEXTURE_2D;
 		float pixeldensity = 1.0f;
 		int msaa = 0;
 	};
 
-	Canvas();
+	Canvas(TextureType textype);
 	virtual ~Canvas();
 
-	virtual love::image::ImageData *newImageData(love::image::Image *module, int x, int y, int w, int h) = 0;
+	virtual love::image::ImageData *newImageData(love::image::Image *module, int slice, int x, int y, int w, int h) = 0;
 
 	virtual int getMSAA() const = 0;
 	virtual int getRequestedMSAA() const = 0;

+ 81 - 2
src/modules/graphics/Font.cpp

@@ -81,6 +81,7 @@ Font::Font(love::font::Rasterizer *r, const Texture::Filter &f)
 	if (!r->hasGlyph(9)) // No tab character in the Rasterizer.
 		useSpacesAsTab = true;
 
+	loadVolatile();
 	++fontCount;
 }
 
@@ -113,6 +114,72 @@ Font::TextureSize Font::getNextTextureSize() const
 	return size;
 }
 
+bool Font::loadVolatile()
+{
+	textureCacheID++;
+	createTexture();
+	return true;
+}
+
+void Font::createTexture()
+{
+	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
+	gfx->flushStreamDraws();
+
+	Image *image = nullptr;
+	TextureSize size = {textureWidth, textureHeight};
+	TextureSize nextsize = getNextTextureSize();
+	bool recreatetexture = false;
+
+	// If we have an existing texture already, we'll try replacing it with a
+	// larger-sized one rather than creating a second one. Having a single
+	// texture reduces texture switches and draw calls when rendering.
+	if ((nextsize.width > size.width || nextsize.height > size.height) && !images.empty())
+	{
+		recreatetexture = true;
+		size = nextsize;
+		images.pop_back();
+	}
+
+	Image::Settings settings;
+	image = gfx->newImage(TEXTURE_2D, pixelFormat, size.width, size.height, 1, settings);
+	image->setFilter(filter);
+
+	// Initialize the texture with transparent black.
+	size_t bpp = getPixelFormatSize(pixelFormat);
+	std::vector<uint8> emptydata(size.width * size.height * bpp, 0);
+
+	Rect rect = {0, 0, size.width, size.height};
+	image->replacePixels(emptydata.data(), emptydata.size(), rect, 0, 0, false);
+
+	images.emplace_back(image, Acquire::NORETAIN);
+
+	textureWidth  = size.width;
+	textureHeight = size.height;
+
+	rowHeight = textureX = textureY = TEXTURE_PADDING;
+
+	// Re-add the old glyphs if we re-created the existing texture object.
+	if (recreatetexture)
+	{
+		textureCacheID++;
+
+		std::vector<uint32> glyphstoadd;
+
+		for (const auto &glyphpair : glyphs)
+			glyphstoadd.push_back(glyphpair.first);
+
+		glyphs.clear();
+		
+		for (uint32 g : glyphstoadd)
+			addGlyph(g);
+	}
+}
+
+void Font::unloadVolatile()
+{
+}
+
 love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph)
 {
 	// Use spaces for the tab 'glyph'.
@@ -178,7 +245,11 @@ const Font::Glyph &Font::addGlyph(uint32 glyph)
 	// Don't waste space for empty glyphs.
 	if (w > 0 && h > 0)
 	{
-		uploadGlyphToTexture(gd, g);
+		Image *image = images.back();
+		g.texture = image;
+
+		Rect rect = {textureX, textureY, gd->getWidth(), gd->getHeight()};
+		image->replacePixels(gd->getData(), gd->getSize(), rect, 0, 0, false);
 
 		double tX     = (double) textureX,     tY      = (double) textureY;
 		double tWidth = (double) textureWidth, tHeight = (double) textureHeight;
@@ -535,7 +606,7 @@ void Font::printv(graphics::Graphics *gfx, const Matrix4 &t, const std::vector<D
 		req.formats[0] = vertex::CommonFormat::XYf_STus_RGBAub;
 		req.indexMode = vertex::TriangleIndexMode::QUADS;
 		req.vertexCount = cmd.vertexcount;
-		req.textureHandle = cmd.texture;
+		req.texture = cmd.texture;
 
 		Graphics::StreamVertexData data = gfx->requestStreamDraw(req);
 		GlyphVertex *vertexdata = (GlyphVertex *) data.stream[0];
@@ -809,6 +880,14 @@ float Font::getLineHeight() const
 	return lineHeight;
 }
 
+void Font::setFilter(const Texture::Filter &f)
+{
+	for (const auto &image : images)
+		image->setFilter(f);
+
+	filter = f;
+}
+
 const Texture::Filter &Font::getFilter() const
 {
 	return filter;

+ 18 - 12
src/modules/graphics/Font.h

@@ -33,8 +33,9 @@
 #include "common/Vector.h"
 
 #include "font/Rasterizer.h"
-#include "Texture.h"
+#include "Image.h"
 #include "vertex.h"
+#include "Volatile.h"
 
 namespace love
 {
@@ -43,7 +44,7 @@ namespace graphics
 
 class Graphics;
 
-class Font : public Object
+class Font : public Object, public Volatile
 {
 public:
 
@@ -88,7 +89,7 @@ public:
 	// Used to determine when to change textures in the generated vertex array.
 	struct DrawCommand
 	{
-		ptrdiff_t texture;
+		Texture *texture;
 		int startvertex;
 		int vertexcount;
 	};
@@ -155,7 +156,7 @@ public:
 	 **/
 	float getLineHeight() const;
 
-	virtual void setFilter(const Texture::Filter &f) = 0;
+	void setFilter(const Texture::Filter &f);
 	const Texture::Filter &getFilter() const;
 
 	// Extra font metrics
@@ -172,16 +173,20 @@ public:
 
 	uint32 getTextureCacheID() const;
 
+	// Implements Volatile.
+	bool loadVolatile() override;
+	void unloadVolatile() override;
+
 	static bool getConstant(const char *in, AlignMode &out);
 	static bool getConstant(AlignMode in, const char *&out);
 
 	static int fontCount;
 
-protected:
+private:
 
 	struct Glyph
 	{
-		ptrdiff_t texture;
+		Texture *texture;
 		int spacing;
 		GlyphVertex vertices[4];
 	};
@@ -192,8 +197,7 @@ protected:
 		int height;
 	};
 
-	virtual void createTexture() = 0;
-	virtual void uploadGlyphToTexture(font::GlyphData *data, Glyph &glyph) = 0;
+	void createTexture();
 
 	TextureSize getNextTextureSize() const;
 	love::font::GlyphData *getRasterizerGlyphData(uint32 glyph);
@@ -210,6 +214,8 @@ protected:
 	int textureWidth;
 	int textureHeight;
 
+	std::vector<StrongRef<love::graphics::Image>> images;
+
 	// maps glyphs to glyph texture information
 	std::unordered_map<uint32, Glyph> glyphs;
 
@@ -226,15 +232,15 @@ protected:
 	int rowHeight;
 
 	bool useSpacesAsTab;
-	
+
 	// ID which is incremented when the texture cache is invalidated.
 	uint32 textureCacheID;
-	
+
 	static const int TEXTURE_PADDING = 1;
-	
+
 	// This will be used if the Rasterizer doesn't have a tab character itself.
 	static const int SPACES_PER_TAB = 4;
-	
+
 	static StringMap<AlignMode, ALIGN_MAX_ENUM>::Entry alignModeEntries[];
 	static StringMap<AlignMode, ALIGN_MAX_ENUM> alignModes;
 	

+ 50 - 32
src/modules/graphics/Graphics.cpp

@@ -25,6 +25,8 @@
 #include "Polyline.h"
 #include "font/Font.h"
 #include "window/Window.h"
+#include "Font.h"
+#include "Video.h"
 
 // C++
 #include <algorithm>
@@ -152,6 +154,16 @@ Quad *Graphics::newQuad(Quad::Viewport v, double sw, double sh)
 	return new Quad(v, sw, sh);
 }
 
+Font *Graphics::newFont(love::font::Rasterizer *data, const Texture::Filter &filter)
+{
+	return new Font(data, filter);
+}
+
+Video *Graphics::newVideo(love::video::VideoStream *stream, float pixeldensity)
+{
+	return new Video(this, stream, pixeldensity);
+}
+
 bool Graphics::validateShader(bool gles, const Shader::ShaderSource &source, std::string &err)
 {
 	return Shader::validate(this, gles, source, true, err);
@@ -179,9 +191,9 @@ int Graphics::getPixelHeight() const
 
 double Graphics::getCurrentPixelDensity() const
 {
-	if (states.back().canvases.size() > 0)
+	if (states.back().renderTargets.size() > 0)
 	{
-		love::graphics::Canvas *c = states.back().canvases[0];
+		love::graphics::Canvas *c = states.back().renderTargets[0].canvas;
 		return (double) c->getPixelHeight() / (double) c->getHeight();
 	}
 
@@ -240,7 +252,7 @@ void Graphics::restoreState(const DisplayState &s)
 
 	setFont(s.font.get());
 	setShader(s.shader.get());
-	setCanvas(s.canvases);
+	setCanvas(s.renderTargets);
 
 	setColorMask(s.colorMask);
 	setWireframe(s.wireframe);
@@ -283,12 +295,14 @@ void Graphics::restoreStateChecked(const DisplayState &s)
 	setFont(s.font.get());
 	setShader(s.shader.get());
 
-	bool canvaseschanged = s.canvases.size() != cur.canvases.size();
+	bool canvaseschanged = s.renderTargets.size() != cur.renderTargets.size();
 	if (!canvaseschanged)
 	{
-		for (size_t i = 0; i < s.canvases.size() && i < cur.canvases.size(); i++)
+		for (size_t i = 0; i < s.renderTargets.size() && i < cur.renderTargets.size(); i++)
 		{
-			if (s.canvases[i].get() != cur.canvases[i].get())
+			const auto &rt1 = s.renderTargets[i];
+			const auto &rt2 = cur.renderTargets[i];
+			if (rt1.canvas.get() != rt2.canvas.get() || rt1.slice != rt2.slice)
 			{
 				canvaseschanged = true;
 				break;
@@ -297,7 +311,7 @@ void Graphics::restoreStateChecked(const DisplayState &s)
 	}
 
 	if (canvaseschanged)
-		setCanvas(s.canvases);
+		setCanvas(s.renderTargets);
 
 	if (s.colorMask != cur.colorMask)
 		setColorMask(s.colorMask);
@@ -386,47 +400,47 @@ love::graphics::Shader *Graphics::getShader() const
 	return states.back().shader.get();
 }
 
-void Graphics::setCanvas(Canvas *canvas)
+void Graphics::setCanvas(RenderTarget rt)
 {
-	if (canvas == nullptr)
+	if (rt.canvas == nullptr)
 		return setCanvas();
 
-	std::vector<Canvas *> canvases = {canvas};
-	setCanvas(canvases);
+	std::vector<RenderTarget> rts = {rt};
+	setCanvas(rts);
 }
 
-void Graphics::setCanvas(const std::vector<StrongRef<Canvas>> &canvases)
+void Graphics::setCanvas(const std::vector<RenderTargetStrongRef> &rts)
 {
-	std::vector<Canvas *> canvaslist;
-	canvaslist.reserve(canvases.size());
+	std::vector<RenderTarget> canvaslist;
+	canvaslist.reserve(rts.size());
 
-	for (const StrongRef<Canvas> &c : canvases)
-		canvaslist.push_back(c.get());
+	for (const auto &rt : rts)
+		canvaslist.emplace_back(rt.canvas.get(), rt.slice);
 
 	return setCanvas(canvaslist);
 }
 
-std::vector<Canvas *> Graphics::getCanvas() const
+std::vector<Graphics::RenderTarget> Graphics::getCanvas() const
 {
-	std::vector<Canvas *> canvases;
-	canvases.reserve(states.back().canvases.size());
+	std::vector<RenderTarget> rts;
+	rts.reserve(states.back().renderTargets.size());
 
-	for (const StrongRef<Canvas> &c : states.back().canvases)
-		canvases.push_back(c.get());
+	for (const auto &rt : states.back().renderTargets)
+		rts.emplace_back(rt.canvas.get(), rt.slice);
 
-	return canvases;
+	return rts;
 }
 
 bool Graphics::isCanvasActive() const
 {
-	return !states.back().canvases.empty();
+	return !states.back().renderTargets.empty();
 }
 
 bool Graphics::isCanvasActive(love::graphics::Canvas *canvas) const
 {
-	for (const auto &c : states.back().canvases)
+	for (const auto &rt : states.back().renderTargets)
 	{
-		if (c.get() == canvas)
+		if (rt.canvas.get() == canvas)
 			return true;
 	}
 
@@ -563,7 +577,7 @@ Graphics::StreamVertexData Graphics::requestStreamDraw(const StreamDrawRequest &
 	if (req.primitiveMode != state.primitiveMode
 		|| req.formats[0] != state.formats[0] || req.formats[1] != state.formats[1]
 		|| ((req.indexMode != TriangleIndexMode::NONE) != (state.indexCount > 0))
-		|| req.texture != state.texture || req.textureHandle != state.textureHandle)
+		|| req.texture != state.texture)
 	{
 		shouldflush = true;
 	}
@@ -622,7 +636,6 @@ Graphics::StreamVertexData Graphics::requestStreamDraw(const StreamDrawRequest &
 		state.formats[0] = req.formats[0];
 		state.formats[1] = req.formats[1];
 		state.texture = req.texture;
-		state.textureHandle = req.textureHandle;
 	}
 
 	if (shouldresize)
@@ -1358,6 +1371,8 @@ StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM>::Entry Graphics::featur
 	{ "lighten",            FEATURE_LIGHTEN              },
 	{ "fullnpot",           FEATURE_FULL_NPOT            },
 	{ "pixelshaderhighp",   FEATURE_PIXEL_SHADER_HIGHP   },
+	{ "arraytexture",       FEATURE_ARRAY_TEXTURE        },
+	{ "volumetexture",      FEATURE_VOLUME_TEXTURE       },
 	{ "glsl3",              FEATURE_GLSL3                },
 	{ "instancing",         FEATURE_INSTANCING           },
 };
@@ -1366,11 +1381,14 @@ StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM> Graphics::features(Grap
 
 StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM>::Entry Graphics::systemLimitEntries[] =
 {
-	{ "pointsize",   LIMIT_POINT_SIZE   },
-	{ "texturesize", LIMIT_TEXTURE_SIZE },
-	{ "multicanvas", LIMIT_MULTI_CANVAS },
-	{ "canvasmsaa",  LIMIT_CANVAS_MSAA  },
-	{ "anisotropy",  LIMIT_ANISOTROPY   },
+	{ "pointsize",         LIMIT_POINT_SIZE          },
+	{ "texturesize",       LIMIT_TEXTURE_SIZE        },
+	{ "texturelayers",     LIMIT_TEXTURE_LAYERS      },
+	{ "volumetexturesize", LIMIT_VOLUME_TEXTURE_SIZE },
+	{ "cubetexturesize",   LIMIT_CUBE_TEXTURE_SIZE   },
+	{ "multicanvas",       LIMIT_MULTI_CANVAS        },
+	{ "canvasmsaa",        LIMIT_CANVAS_MSAA         },
+	{ "anisotropy",        LIMIT_ANISOTROPY          },
 };
 
 StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM> Graphics::systemLimits(Graphics::systemLimitEntries, sizeof(Graphics::systemLimitEntries));

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

@@ -177,6 +177,8 @@ public:
 		FEATURE_LIGHTEN,
 		FEATURE_FULL_NPOT,
 		FEATURE_PIXEL_SHADER_HIGHP,
+		FEATURE_ARRAY_TEXTURE,
+		FEATURE_VOLUME_TEXTURE,
 		FEATURE_GLSL3,
 		FEATURE_INSTANCING,
 		FEATURE_MAX_ENUM
@@ -193,6 +195,9 @@ public:
 	{
 		LIMIT_POINT_SIZE,
 		LIMIT_TEXTURE_SIZE,
+		LIMIT_VOLUME_TEXTURE_SIZE,
+		LIMIT_CUBE_TEXTURE_SIZE,
+		LIMIT_TEXTURE_LAYERS,
 		LIMIT_MULTI_CANVAS,
 		LIMIT_CANVAS_MSAA,
 		LIMIT_ANISOTROPY,
@@ -262,10 +267,6 @@ public:
 		int vertexCount = 0;
 		Texture *texture = nullptr;
 
-		// FIXME: This is only needed for fonts. We should just change fonts to
-		// use love.graphics Images instead of raw OpenGL textures.
-		ptrdiff_t textureHandle = 0;
-
 		StreamDrawRequest()
 		{
 			// VS2013 can't initialize arrays in the above manner...
@@ -312,29 +313,48 @@ public:
 		Reference *ref;
 	};
 
+	struct RenderTarget
+	{
+		Canvas *canvas = nullptr;
+		int slice = 0;
+
+		RenderTarget(Canvas *canvas, int slice = 0)
+			: canvas(canvas)
+			, slice(slice)
+		{}
+	};
+
+	struct RenderTargetStrongRef
+	{
+		StrongRef<Canvas> canvas;
+		int slice = 0;
+
+		RenderTargetStrongRef(Canvas *canvas, int slice = 0)
+			: canvas(canvas)
+			, slice(slice)
+		{}
+	};
+
 	Graphics();
 	virtual ~Graphics();
 
 	// Implements Module.
 	virtual ModuleType getModuleType() const { return M_GRAPHICS; }
 
-	virtual Image *newImage(const std::vector<love::image::ImageData *> &data, const Image::Settings &settings) = 0;
-	virtual Image *newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Settings &settings) = 0;
+	virtual Image *newImage(const Image::Slices &data, const Image::Settings &settings) = 0;
+	virtual Image *newImage(TextureType textype, PixelFormat format, int width, int height, int slices, const Image::Settings &settings) = 0;
 
 	Quad *newQuad(Quad::Viewport v, double sw, double sh);
+	Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::defaultFilter);
+	Video *newVideo(love::video::VideoStream *stream, float pixeldensity);
 
 	virtual SpriteBatch *newSpriteBatch(Texture *texture, int size, vertex::Usage usage) = 0;
 
 	virtual ParticleSystem *newParticleSystem(Texture *texture, int size) = 0;
 
-	virtual Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::defaultFilter) = 0;
-
-	virtual Canvas *newCanvas(int width, int height, const Canvas::Settings &settings) = 0;
-
+	virtual Canvas *newCanvas(const Canvas::Settings &settings) = 0;
 	virtual Shader *newShader(const Shader::ShaderSource &source) = 0;
 
-	virtual Video *newVideo(love::video::VideoStream *stream, float pixeldensity) = 0;
-
 	virtual Buffer *newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags) = 0;
 
 	virtual Mesh *newMesh(const std::vector<Vertex> &vertices, Mesh::DrawMode drawmode, vertex::Usage usage) = 0;
@@ -435,12 +455,12 @@ public:
 
 	Shader *getShader() const;
 
-	void setCanvas(Canvas *canvas);
-	virtual void setCanvas(const std::vector<Canvas *> &canvases) = 0;
-	void setCanvas(const std::vector<StrongRef<Canvas>> &canvases);
+	void setCanvas(RenderTarget rt);
+	virtual void setCanvas(const std::vector<RenderTarget> &rts) = 0;
+	void setCanvas(const std::vector<RenderTargetStrongRef> &rts);
 	virtual void setCanvas() = 0;
 
-	std::vector<Canvas *> getCanvas() const;
+	std::vector<RenderTarget> getCanvas() const;
 	bool isCanvasActive() const;
 	bool isCanvasActive(Canvas *canvas) const;
 
@@ -790,7 +810,7 @@ protected:
 		StrongRef<Font> font;
 		StrongRef<Shader> shader;
 
-		std::vector<StrongRef<Canvas>> canvases;
+		std::vector<RenderTargetStrongRef> renderTargets;
 
 		ColorMask colorMask = ColorMask(true, true, true, true);
 
@@ -810,7 +830,6 @@ protected:
 		vertex::PrimitiveMode primitiveMode = vertex::PrimitiveMode::TRIANGLES;
 		vertex::CommonFormat formats[2];
 		StrongRef<Texture> texture;
-		ptrdiff_t textureHandle = 0;
 		int vertexCount = 0;
 		int indexCount = 0;
 

+ 162 - 14
src/modules/graphics/Image.cpp

@@ -19,6 +19,10 @@
  **/
 
 #include "Image.h"
+#include "Graphics.h"
+
+// C++
+#include <algorithm>
 
 namespace love
 {
@@ -29,9 +33,16 @@ love::Type Image::type("Image", &Texture::type);
 
 int Image::imageCount = 0;
 
-Image::Image(const Settings &settings)
-	: settings(settings)
+Image::Image(const Slices &data, const Settings &settings, bool validatedata)
+	: Texture(data.getTextureType())
+	, settings(settings)
+	, data(data)
+	, mipmapsType(settings.mipmaps ? MIPMAPS_GENERATED : MIPMAPS_NONE)
+	, sRGB(isGammaCorrect() && !settings.linear)
 {
+	if (validatedata && data.validate() == MIPMAPS_DATA)
+		mipmapsType = MIPMAPS_DATA;
+
 	++imageCount;
 }
 
@@ -40,29 +51,166 @@ Image::~Image()
 	--imageCount;
 }
 
-const Image::Settings &Image::getFlags() const
+Image::Slices::Slices(TextureType textype)
+	: textureType(textype)
+{
+}
+
+void Image::Slices::clear()
 {
-	return settings;
+	data.clear();
+}
+
+void Image::Slices::set(int slice, int mipmap, love::image::ImageDataBase *d)
+{
+	if (textureType == TEXTURE_VOLUME)
+	{
+		if (mipmap >= (int) data.size())
+			data.resize(mipmap + 1);
+
+		if (slice >= (int) data[mipmap].size())
+			data[mipmap].resize(slice + 1);
+
+		data[mipmap][slice].set(d);
+	}
+	else
+	{
+		if (slice >= (int) data.size())
+			data.resize(slice + 1);
+
+		if (mipmap >= (int) data[slice].size())
+			data[slice].resize(mipmap + 1);
+
+		data[slice][mipmap].set(d);
+	}
+}
+
+love::image::ImageDataBase *Image::Slices::get(int slice, int mipmap) const
+{
+	if (slice < 0 || slice >= getSliceCount(mipmap))
+		return nullptr;
+
+	if (mipmap < 0 || mipmap >= getMipmapCount(slice))
+		return nullptr;
+
+	if (textureType == TEXTURE_VOLUME)
+		return data[mipmap][slice].get();
+	else
+		return data[slice][mipmap].get();
+}
+
+void Image::Slices::add(love::image::CompressedImageData *cdata, int startslice, int startmip, bool addallslices, bool addallmips)
+{
+	int slicecount = addallslices ? cdata->getSliceCount() : 1;
+	int mipcount = addallmips ? cdata->getMipmapCount() : 1;
+
+	for (int mip = 0; mip < mipcount; mip++)
+	{
+		for (int slice = 0; slice < slicecount; slice++)
+			set(startslice + slice, startmip + mip, cdata->getSlice(slice, mip));
+	}
 }
 
-bool Image::getConstant(const char *in, SettingType &out)
+int Image::Slices::getSliceCount(int mip) const
 {
-	return settingTypes.find(in, out);
+	if (textureType == TEXTURE_VOLUME)
+	{
+		if (mip < 0 || mip >= (int) data.size())
+			return 0;
+
+		return (int) data[mip].size();
+	}
+	else
+		return (int) data.size();
 }
 
-bool Image::getConstant(SettingType in, const char *&out)
+int Image::Slices::getMipmapCount(int slice) const
 {
-	return settingTypes.find(in, out);
+	if (textureType == TEXTURE_VOLUME)
+		return (int) data.size();
+	else
+	{
+		if (slice < 0 || slice >= (int) data.size())
+			return 0;
+
+		return data[slice].size();
+	}
 }
 
-StringMap<Image::SettingType, Image::SETTING_MAX_ENUM>::Entry Image::settingTypeEntries[] =
+Image::MipmapsType Image::Slices::validate() const
 {
-	{ "mipmaps",      SETTING_MIPMAPS      },
-	{ "linear",       SETTING_LINEAR       },
-	{ "pixeldensity", SETTING_PIXELDENSITY },
-};
+	int slicecount = getSliceCount();
+	int mipcount = getMipmapCount(0);
+
+	if (slicecount == 0 || mipcount == 0)
+		throw love::Exception("At least one ImageData or CompressedImageData is required!");
+
+	if (textureType == TEXTURE_CUBE && slicecount != 6)
+		throw love::Exception("Cube textures must have exactly 6 sides.");
+
+	image::ImageDataBase *firstdata = get(0, 0);
+
+	int w = firstdata->getWidth();
+	int h = firstdata->getHeight();
+	PixelFormat format = firstdata->getFormat();
+
+	int expectedmips = Texture::getMipmapCount(w, h);
+
+	if (mipcount != expectedmips && mipcount != 1)
+		throw love::Exception("Image does not have all required mipmap levels (expected %d, got %d)", expectedmips, mipcount);
+
+	if (textureType == TEXTURE_CUBE && w != h)
+		throw love::Exception("Cube images must have equal widths and heights for each cube face.");
+
+	int mipw = w;
+	int miph = h;
+	int mipslices = slicecount;
+
+	for (int mip = 0; mip < mipcount; mip++)
+	{
+		if (textureType == TEXTURE_VOLUME)
+		{
+			slicecount = getSliceCount(mip);
 
-StringMap<Image::SettingType, Image::SETTING_MAX_ENUM> Image::settingTypes(Image::settingTypeEntries, sizeof(Image::settingTypeEntries));
+			if (slicecount != mipslices)
+				throw love::Exception("Invalid number of image data layers in mipmap level %d (expected %d, got %d)", mip+1, mipslices, slicecount);
+		}
+
+		for (int slice = 0; slice < slicecount; slice++)
+		{
+			auto slicedata = get(slice, mip);
+
+			if (slicedata == nullptr)
+				throw love::Exception("Missing image data (slice %d, mipmap level %d)", slice+1, mip+1);
+
+			int realw = slicedata->getWidth();
+			int realh = slicedata->getHeight();
+
+			if (getMipmapCount(slice) != mipcount)
+				throw love::Exception("All Image layers must have the same mipmap count.");
+
+			if (mipw != realw)
+				throw love::Exception("Width of image data (slice %d, mipmap level %d) is incorrect (expected %d, got %d)", slice+1, mip+1, mipw, realw);
+
+			if (miph != realh)
+				throw love::Exception("Height of image data (slice %d, mipmap level %d) is incorrect (expected %d, got %d)", slice+1, mip+1, miph, realh);
+
+			if (format != slicedata->getFormat())
+				throw love::Exception("All Image slices and mipmaps must have the same pixel format.");
+		}
+
+		mipw = std::max(mipw / 2, 1);
+		miph = std::max(miph / 2, 1);
+
+		if (textureType == TEXTURE_VOLUME)
+			mipslices = std::max(mipslices / 2, 1);
+	}
+	
+	if (mipcount > 1)
+		return MIPMAPS_DATA;
+	else
+		return MIPMAPS_NONE;
+}
 
 } // graphics
 } // love

+ 44 - 19
src/modules/graphics/Image.h

@@ -39,12 +39,11 @@ public:
 
 	static love::Type type;
 
-	enum SettingType
+	enum MipmapsType
 	{
-		SETTING_MIPMAPS,
-		SETTING_LINEAR,
-		SETTING_PIXELDENSITY,
-		SETTING_MAX_ENUM
+		MIPMAPS_NONE,
+		MIPMAPS_DATA,
+		MIPMAPS_GENERATED,
 	};
 
 	struct Settings
@@ -54,33 +53,59 @@ public:
 		float pixeldensity = 1.0f;
 	};
 
-	Image(const Settings &settings);
-	virtual ~Image();
+	struct Slices
+	{
+	public:
 
-	virtual const std::vector<StrongRef<love::image::ImageData>> &getImageData() const = 0;
-	virtual const std::vector<StrongRef<love::image::CompressedImageData>> &getCompressedData() const = 0;
+		Slices(TextureType textype);
 
-	virtual void setMipmapSharpness(float sharpness) = 0;
-	virtual float getMipmapSharpness() const = 0;
+		void clear();
+		void set(int slice, int mipmap, love::image::ImageDataBase *data);
+		love::image::ImageDataBase *get(int slice, int mipmap) const;
 
-	virtual bool isCompressed() const = 0;
+		void add(love::image::CompressedImageData *cdata, int startslice, int startmip, bool addallslices, bool addallmips);
+
+		int getSliceCount(int mip = 0) const;
+		int getMipmapCount(int slice = 0) const;
+
+		MipmapsType validate() const;
 
-	virtual bool refresh(int xoffset, int yoffset, int w, int h) = 0;
+		TextureType getTextureType() const { return textureType; }
 
-	const Settings &getFlags() const;
+	private:
 
-	static bool getConstant(const char *in, SettingType &out);
-	static bool getConstant(SettingType in, const char *&out);
+		TextureType textureType;
+
+		// For 2D/Cube/2DArray texture types, each element in the data array has
+		// an array of mipmap levels. For 3D texture types, each mipmap level
+		// has an array of layers.
+		std::vector<std::vector<StrongRef<love::image::ImageDataBase>>> data;
+
+	}; // Slices
+
+	virtual ~Image();
+
+	virtual void replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, bool reloadmipmaps) = 0;
+	virtual void replacePixels(const void *data, size_t size, const Rect &rect, int slice, int mipmap, bool reloadmipmaps) = 0;
+
+	virtual bool isFormatLinear() const = 0;
+	virtual bool isCompressed() const = 0;
+
+	virtual MipmapsType getMipmapsType() const = 0;
 
 	static int imageCount;
 
 protected:
 
+	Image(const Slices &data, const Settings &settings, bool validatedata);
+
 	// The settings used to initialize this Image.
 	Settings settings;
-	
-	static StringMap<SettingType, SETTING_MAX_ENUM>::Entry settingTypeEntries[];
-	static StringMap<SettingType, SETTING_MAX_ENUM> settingTypes;
+
+	Slices data;
+
+	MipmapsType mipmapsType;
+	bool sRGB;
 	
 }; // Image
 

+ 8 - 0
src/modules/graphics/Shader.cpp

@@ -174,6 +174,14 @@ void Shader::attachDefault()
 	current = nullptr;
 }
 
+void Shader::checkMainTextureType(TextureType textype) const
+{
+	const UniformInfo *info = getUniformInfo(BUILTIN_TEXTURE_MAIN);
+
+	if (info != nullptr && info->textureType != TEXTURE_MAX_ENUM && info->textureType != textype)
+		throw love::Exception("Texture's type must match the type of the shader's main texture.");
+}
+
 bool Shader::validate(Graphics *gfx, bool gles, const ShaderSource &source, bool checkWithDefaults, std::string &err)
 {
 	if (source.vertex.empty() && source.pixel.empty())

+ 9 - 7
src/modules/graphics/Shader.h

@@ -119,6 +119,7 @@ public:
 		};
 
 		UniformType baseType;
+		TextureType textureType;
 		std::string name;
 
 		union
@@ -144,11 +145,8 @@ public:
 
 	/**
 	 * Binds this Shader's program to be used when rendering.
-	 *
-	 * @param temporary True if we just want to send values to the shader with
-	 * no intention of rendering.
 	 **/
-	virtual void attach(bool temporary = false) = 0;
+	virtual void attach() = 0;
 
 	/**
 	 * Attach the default shader.
@@ -161,9 +159,11 @@ public:
 	virtual std::string getWarnings() const = 0;
 
 	virtual const UniformInfo *getUniformInfo(const std::string &name) const = 0;
-	virtual void updateUniform(const UniformInfo *info, int count, bool internalUpdate = false) = 0;
+	virtual const UniformInfo *getUniformInfo(BuiltinUniform builtin) const = 0;
 
-	virtual void sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalUpdate = false) = 0;
+	virtual void updateUniform(const UniformInfo *info, int count) = 0;
+
+	virtual void sendTextures(const UniformInfo *info, Texture **textures, int count) = 0;
 
 	/**
 	 * Gets whether a uniform with the specified name exists and is actively
@@ -174,7 +174,9 @@ public:
 	/**
 	 * Sets the textures used when rendering a video. For internal use only.
 	 **/
-	virtual void setVideoTextures(ptrdiff_t ytexture, ptrdiff_t cbtexture, ptrdiff_t crtexture) = 0;
+	virtual void setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture) = 0;
+
+	void checkMainTextureType(TextureType textype) const;
 
 	virtual ptrdiff_t getHandle() const = 0;
 

+ 111 - 4
src/modules/graphics/Texture.cpp

@@ -18,9 +18,25 @@
  * 3. This notice may not be removed or altered from any source distribution.
  **/
 
+// LOVE
+#include "common/config.h"
 #include "Texture.h"
 #include "Graphics.h"
 
+// C
+#include <cmath>
+#include <algorithm>
+
+#ifdef LOVE_ANDROID
+// log2 is not declared in the math.h shipped with the Android NDK
+static inline double log2(double n)
+{
+	// log(n)/log(2) is log2.
+	return std::log(n) / std::log(2);
+}
+#endif
+
+
 namespace love
 {
 namespace graphics
@@ -32,14 +48,19 @@ Texture::Filter Texture::defaultFilter;
 Texture::FilterMode Texture::defaultMipmapFilter = Texture::FILTER_LINEAR;
 float Texture::defaultMipmapSharpness = 0.0f;
 
-Texture::Texture()
-	: format(PIXELFORMAT_UNKNOWN)
+Texture::Texture(TextureType texType)
+	: texType(texType)
+	, format(PIXELFORMAT_UNKNOWN)
 	, width(0)
 	, height(0)
+	, depth(1)
+	, layers(1)
+	, mipmapCount(1)
 	, pixelWidth(0)
 	, pixelHeight(0)
 	, filter(defaultFilter)
 	, wrap()
+	, mipmapSharpness(defaultMipmapSharpness)
 	, vertices()
 {
 }
@@ -48,6 +69,39 @@ Texture::~Texture()
 {
 }
 
+void Texture::initVertices()
+{
+	for (int i = 0; i < 4; i++)
+		vertices[i].color = Color(255, 255, 255, 255);
+
+	// Vertices are ordered for use with triangle strips:
+	// 0---2
+	// | / |
+	// 1---3
+	vertices[0].x = 0.0f;
+	vertices[0].y = 0.0f;
+	vertices[1].x = 0.0f;
+	vertices[1].y = (float) height;
+	vertices[2].x = (float) width;
+	vertices[2].y = 0.0f;
+	vertices[3].x = (float) width;
+	vertices[3].y = (float) height;
+
+	vertices[0].s = 0.0f;
+	vertices[0].t = 0.0f;
+	vertices[1].s = 0.0f;
+	vertices[1].t = 1.0f;
+	vertices[2].s = 1.0f;
+	vertices[2].t = 0.0f;
+	vertices[3].s = 1.0f;
+	vertices[3].t = 1.0f;
+}
+
+TextureType Texture::getTextureType() const
+{
+	return texType;
+}
+
 PixelFormat Texture::getPixelFormat() const
 {
 	return format;
@@ -65,6 +119,9 @@ void Texture::drawq(Graphics *gfx, Quad *quad, const Matrix4 &m)
 
 void Texture::drawv(Graphics *gfx, const Matrix4 &localTransform, const Vertex *v)
 {
+	if (Shader::current)
+		Shader::current->checkMainTextureType(texType);
+
 	Matrix4 t(gfx->getTransform(), localTransform);
 
 	Vertex verts[4] = {v[0], v[1], v[2], v[3]};
@@ -95,6 +152,21 @@ int Texture::getHeight() const
 	return height;
 }
 
+int Texture::getDepth() const
+{
+	return depth;
+}
+
+int Texture::getLayerCount() const
+{
+	return layers;
+}
+
+int Texture::getMipmapCount() const
+{
+	return mipmapCount;
+}
+
 int Texture::getPixelWidth() const
 {
 	return pixelWidth;
@@ -120,6 +192,11 @@ const Texture::Wrap &Texture::getWrap() const
 	return wrap;
 }
 
+float Texture::getMipmapSharpness() const
+{
+	return mipmapSharpness;
+}
+
 const Vertex *Texture::getVertices() const
 {
 	return vertices;
@@ -142,12 +219,32 @@ bool Texture::validateFilter(const Filter &f, bool mipmapsAllowed)
 	return true;
 }
 
+int Texture::getMipmapCount(int w, int h)
+{
+	return (int) log2(std::max(w, h)) + 1;
+}
+
+int Texture::getMipmapCount(int w, int h, int d)
+{
+	return (int) log2(std::max(std::max(w, h), d)) + 1;
+}
+
+bool Texture::getConstant(const char *in, TextureType &out)
+{
+	return texTypes.find(in, out);
+}
+
+bool Texture::getConstant(TextureType in, const char *&out)
+{
+	return texTypes.find(in, out);
+}
+
 bool Texture::getConstant(const char *in, FilterMode &out)
 {
 	return filterModes.find(in, out);
 }
 
-bool Texture::getConstant(FilterMode in, const char  *&out)
+bool Texture::getConstant(FilterMode in, const char *&out)
 {
 	return filterModes.find(in, out);
 }
@@ -157,11 +254,21 @@ bool Texture::getConstant(const char *in, WrapMode &out)
 	return wrapModes.find(in, out);
 }
 
-bool Texture::getConstant(WrapMode in, const char  *&out)
+bool Texture::getConstant(WrapMode in, const char *&out)
 {
 	return wrapModes.find(in, out);
 }
 
+StringMap<TextureType, TEXTURE_MAX_ENUM>::Entry Texture::texTypeEntries[] =
+{
+	{ "2d", TEXTURE_2D },
+	{ "volume", TEXTURE_VOLUME },
+	{ "array", TEXTURE_2D_ARRAY },
+	{ "cube", TEXTURE_CUBE },
+};
+
+StringMap<TextureType, TEXTURE_MAX_ENUM> Texture::texTypes(Texture::texTypeEntries, sizeof(Texture::texTypeEntries));
+
 StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM>::Entry Texture::filterModeEntries[] =
 {
 	{ "linear", FILTER_LINEAR },

+ 52 - 5
src/modules/graphics/Texture.h

@@ -25,6 +25,7 @@
 #include "common/StringMap.h"
 #include "common/math.h"
 #include "common/pixelformat.h"
+#include "common/Exception.h"
 #include "Drawable.h"
 #include "Quad.h"
 #include "vertex.h"
@@ -37,6 +38,25 @@ namespace love
 namespace graphics
 {
 
+class Graphics;
+
+enum TextureType
+{
+	TEXTURE_2D,
+	TEXTURE_VOLUME,
+	TEXTURE_2D_ARRAY,
+	TEXTURE_CUBE,
+	TEXTURE_MAX_ENUM
+};
+
+class TextureTooLargeException : public love::Exception
+{
+public:
+	TextureTooLargeException(const char *dimname, int pix)
+		: Exception("Cannot create texture: %s of %d pixels is too large for this system.", dimname, pix)
+	{}
+};
+
 /**
  * Base class for 2D textures. All textures can be drawn with Quads, have a
  * width and height, and have filter and wrap modes.
@@ -76,9 +96,10 @@ public:
 	{
 		WrapMode s = WRAP_CLAMP;
 		WrapMode t = WRAP_CLAMP;
+		WrapMode r = WRAP_CLAMP;
 	};
 
-	Texture();
+	Texture(TextureType texType);
 	virtual ~Texture();
 
 	static Filter defaultFilter;
@@ -93,10 +114,14 @@ public:
 	 **/
 	void drawq(Graphics *gfx, Quad *quad, const Matrix4 &m);
 
+	TextureType getTextureType() const;
 	PixelFormat getPixelFormat() const;
 
-	virtual int getWidth() const;
-	virtual int getHeight() const;
+	int getWidth() const;
+	int getHeight() const;
+	int getDepth() const;
+	int getLayerCount() const;
+	int getMipmapCount() const;
 
 	virtual int getPixelWidth() const;
 	virtual int getPixelHeight() const;
@@ -109,37 +134,59 @@ public:
 	virtual bool setWrap(const Wrap &w) = 0;
 	virtual const Wrap &getWrap() const;
 
+	// Sets the mipmap texture LOD bias (sharpness) value.
+	virtual bool setMipmapSharpness(float sharpness) = 0;
+	float getMipmapSharpness() const;
+
 	virtual const Vertex *getVertices() const;
 
 	virtual ptrdiff_t getHandle() const = 0;
 
 	static bool validateFilter(const Filter &f, bool mipmapsAllowed);
 
+	static int getMipmapCount(int w, int h);
+	static int getMipmapCount(int w, int h, int d);
+
+	static bool getConstant(const char *in, TextureType &out);
+	static bool getConstant(TextureType in, const char *&out);
+
 	static bool getConstant(const char *in, FilterMode &out);
-	static bool getConstant(FilterMode in, const char  *&out);
+	static bool getConstant(FilterMode in, const char *&out);
 
 	static bool getConstant(const char *in, WrapMode &out);
-	static bool getConstant(WrapMode in, const char  *&out);
+	static bool getConstant(WrapMode in, const char *&out);
 
 protected:
 
+	void initVertices();
 	virtual void drawv(Graphics *gfx, const Matrix4 &localTransform, const Vertex *v);
 
+	TextureType texType;
+
 	PixelFormat format;
 
 	int width;
 	int height;
+	int depth;
+	int layers;
+	int mipmapCount;
 
 	int pixelWidth;
 	int pixelHeight;
 
+
 	Filter filter;
 	Wrap wrap;
 
+	float mipmapSharpness;
+
 	Vertex vertices[4];
 
 private:
 
+	static StringMap<TextureType, TEXTURE_MAX_ENUM>::Entry texTypeEntries[];
+	static StringMap<TextureType, TEXTURE_MAX_ENUM> texTypes;
+
 	static StringMap<FilterMode, FILTER_MAX_ENUM>::Entry filterModeEntries[];
 	static StringMap<FilterMode, FILTER_MAX_ENUM> filterModes;
 

+ 51 - 5
src/modules/graphics/Video.cpp

@@ -31,14 +31,12 @@ namespace graphics
 
 love::Type Video::type("Video", &Drawable::type);
 
-Video::Video(love::video::VideoStream *stream, float pixeldensity)
+Video::Video(Graphics *gfx, love::video::VideoStream *stream, float pixeldensity)
 	: stream(stream)
 	, width(stream->getWidth() / pixeldensity)
 	, height(stream->getHeight() / pixeldensity)
 	, filter(Texture::defaultFilter)
 {
-	textureHandles[2] = textureHandles[1] = textureHandles[0] = 0;
-
 	filter.mipmap = Texture::FILTER_NONE;
 
 	stream->fillBackBuffer();
@@ -67,6 +65,33 @@ Video::Video(love::video::VideoStream *stream, float pixeldensity)
 	vertices[2].t = 0.0f;
 	vertices[3].s = 1.0f;
 	vertices[3].t = 1.0f;
+
+	// Create the textures using the initial frame data.
+	auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer();
+
+	int widths[3]  = {frame->yw, frame->cw, frame->cw};
+	int heights[3] = {frame->yh, frame->ch, frame->ch};
+
+	const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane};
+
+	Texture::Wrap wrap; // Clamp wrap mode.
+	Image::Settings settings;
+
+	for (int i = 0; i < 3; i++)
+	{
+		Image *img = gfx->newImage(TEXTURE_2D, PIXELFORMAT_R8, widths[i], heights[i], 1, settings);
+
+		img->setFilter(filter);
+		img->setWrap(wrap);
+
+		size_t bpp = getPixelFormatSize(PIXELFORMAT_R8);
+		size_t size = bpp * widths[i] * heights[i];
+
+		Rect rect = {0, 0, widths[i], heights[i]};
+		img->replacePixels(data[i], size, rect, 0, 0, false);
+
+		images[i].set(img, Acquire::NORETAIN);
+	}
 }
 
 Video::~Video()
@@ -93,7 +118,7 @@ void Video::draw(Graphics *gfx, const Matrix4 &m)
 		shader = Shader::defaultVideoShader;
 	}
 
-	shader->setVideoTextures(textureHandles[0], textureHandles[1], textureHandles[2]);
+	shader->setVideoTextures(images[0], images[1], images[2]);
 
 	Graphics::StreamDrawRequest req;
 	req.formats[0] = vertex::CommonFormat::XYf_STf_RGBAub;
@@ -129,7 +154,20 @@ void Video::update()
 	if (bufferschanged)
 	{
 		auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer();
-		uploadFrame(frame);
+
+		int widths[3]  = {frame->yw, frame->cw, frame->cw};
+		int heights[3] = {frame->yh, frame->ch, frame->ch};
+
+		const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane};
+
+		for (int i = 0; i < 3; i++)
+		{
+			size_t bpp = getPixelFormatSize(PIXELFORMAT_R8);
+			size_t size = bpp * widths[i] * heights[i];
+
+			Rect rect = {0, 0, widths[i], heights[i]};
+			images[i]->replacePixels(data[i], size, rect, 0, 0, false);
+		}
 	}
 }
 
@@ -163,6 +201,14 @@ int Video::getPixelHeight() const
 	return stream->getHeight();
 }
 
+void Video::setFilter(const Texture::Filter &f)
+{
+	for (const auto &image : images)
+		image->setFilter(f);
+
+	filter = f;
+}
+
 const Texture::Filter &Video::getFilter() const
 {
 	return filter;

+ 8 - 10
src/modules/graphics/Video.h

@@ -23,7 +23,7 @@
 // LOVE
 #include "common/math.h"
 #include "Drawable.h"
-#include "Texture.h"
+#include "Image.h"
 #include "vertex.h"
 #include "video/VideoStream.h"
 #include "audio/Source.h"
@@ -33,13 +33,15 @@ namespace love
 namespace graphics
 {
 
+class Graphics;
+
 class Video : public Drawable
 {
 public:
 
 	static love::Type type;
 
-	Video(love::video::VideoStream *stream, float pixeldensity = 1.0f);
+	Video(Graphics *gfx, love::video::VideoStream *stream, float pixeldensity = 1.0f);
 	virtual ~Video();
 
 	// Drawable
@@ -56,12 +58,12 @@ public:
 	int getPixelWidth() const;
 	int getPixelHeight() const;
 
-	virtual void setFilter(const Texture::Filter &f) = 0;
+	void setFilter(const Texture::Filter &f);
 	const Texture::Filter &getFilter() const;
 
-protected:
+private:
 
-	virtual void uploadFrame(const love::video::VideoStream::Frame *frame) = 0;
+	void update();
 
 	StrongRef<love::video::VideoStream> stream;
 
@@ -72,11 +74,7 @@ protected:
 
 	Vertex vertices[4];
 
-	ptrdiff_t textureHandles[3];
-
-private:
-
-	void update();
+	StrongRef<Image> images[3];
 	StrongRef<love::audio::Source> source;
 	
 }; // Video

+ 152 - 66
src/modules/graphics/opengl/Canvas.cpp

@@ -30,7 +30,7 @@ namespace graphics
 namespace opengl
 {
 
-static GLenum createFBO(GLuint &framebuffer, GLuint texture)
+static GLenum createFBO(GLuint &framebuffer, TextureType texType, GLuint texture, int layers, bool initialize)
 {
 	// get currently bound fbo to reset to it later
 	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
@@ -40,11 +40,27 @@ static GLenum createFBO(GLuint &framebuffer, GLuint texture)
 
 	if (texture != 0)
 	{
-		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
-
-		// Initialize the texture to transparent black.
-		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-		glClear(GL_COLOR_BUFFER_BIT);
+		if (initialize)
+		{
+			int faces = texType == TEXTURE_CUBE ? 6 : 1;
+
+			// Make sure all faces and layers of the texture are initialized to
+			// transparent black. This is unfortunately probably pretty slow for
+			// 2D-array and 3D textures with a lot of layers...
+			for (int layer = layers - 1; layer >= 0; layer--)
+			{
+				for (int face = faces - 1; face >= 0; face--)
+				{
+					gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, layer, face);
+					glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+					glClear(GL_COLOR_BUFFER_BIT);
+				}
+			}
+		}
+		else
+		{
+			gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
+		}
 	}
 
 	GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
@@ -96,46 +112,39 @@ static bool createMSAABuffer(int width, int height, int &samples, PixelFormat pi
 	return status == GL_FRAMEBUFFER_COMPLETE && samples > 1;
 }
 
-Canvas::Canvas(int width, int height, const Settings &settings)
-	: settings(settings)
+Canvas::Canvas(const Settings &settings)
+	: love::graphics::Canvas(settings.type)
 	, fbo(0)
 	, texture(0)
     , msaa_buffer(0)
 	, actual_samples(0)
 	, texture_memory(0)
 {
-	this->width = width;
-	this->height = height;
+	this->width = settings.width;
+	this->height = settings.height;
 	this->pixelWidth = (int) ((width * settings.pixeldensity) + 0.5);
 	this->pixelHeight = (int) ((height * settings.pixeldensity) + 0.5);
 
-	// Vertices are ordered for use with triangle strips:
-	// 0---2
-	// | / |
-	// 1---3
-	// world coordinates
-	vertices[0].x = 0;
-	vertices[0].y = 0;
-	vertices[1].x = 0;
-	vertices[1].y = (float) height;
-	vertices[2].x = (float) width;
-	vertices[2].y = 0;
-	vertices[3].x = (float) width;
-	vertices[3].y = (float) height;
-
-	// texture coordinates
-	vertices[0].s = 0;
-	vertices[0].t = 0;
-	vertices[1].s = 0;
-	vertices[1].t = 1;
-	vertices[2].s = 1;
-	vertices[2].t = 0;
-	vertices[3].s = 1;
-	vertices[3].t = 1;
+	if (texType == TEXTURE_VOLUME)
+		this->depth = settings.layers;
+	else if (texType == TEXTURE_2D_ARRAY)
+		this->layers = settings.layers;
+	else
+		this->layers = 1;
+
+	if (width <= 0 || height <= 0 || layers <= 0)
+		throw love::Exception("Canvas dimensions must be greater than 0.");
+
+	if (texType != TEXTURE_2D && settings.msaa > 1)
+		throw love::Exception("MSAA is only supported for Canvases with the 2D texture type.");
 
 	this->format = getSizedFormat(settings.format);
 
+	initVertices();
 	loadVolatile();
+
+	if (status != GL_FRAMEBUFFER_COMPLETE)
+		throw love::Exception("Cannot create Canvas: %s", OpenGL::framebufferStatusString(status));
 }
 
 Canvas::~Canvas()
@@ -148,41 +157,91 @@ bool Canvas::loadVolatile()
 	if (texture != 0)
 		return true;
 
+	if (!Canvas::isSupported())
+		throw love::Exception("Canvases are not supported by your OpenGL drivers!");
+
+	if (!Canvas::isFormatSupported(format))
+	{
+		const char *fstr = "rgba8";
+		love::getConstant(Canvas::getSizedFormat(format), fstr);
+		throw love::Exception("The %s canvas format is not supported by your OpenGL drivers.", fstr);
+	}
+
+	if (settings.msaa > 1 && texType != TEXTURE_2D)
+		throw love::Exception("MSAA is only supported for 2D texture types.");
+
+	if (!gl.isTextureTypeSupported(texType))
+	{
+		const char *textypestr = "unknown";
+		getConstant(texType, textypestr);
+		throw love::Exception("%s textures are not supported on this system!", textypestr);
+	}
+
+	switch (texType)
+	{
+	case TEXTURE_2D:
+		if (pixelWidth > gl.getMax2DTextureSize())
+			throw TextureTooLargeException("width", pixelWidth);
+		else if (pixelHeight > gl.getMax2DTextureSize())
+			throw TextureTooLargeException("height", pixelHeight);
+		break;
+	case TEXTURE_VOLUME:
+		if (pixelWidth > gl.getMax3DTextureSize())
+			throw TextureTooLargeException("width", pixelWidth);
+		else if (pixelHeight > gl.getMax3DTextureSize())
+			throw TextureTooLargeException("height", pixelHeight);
+		else if (depth > gl.getMax3DTextureSize())
+			throw TextureTooLargeException("depth", depth);
+		break;
+	case TEXTURE_2D_ARRAY:
+		if (pixelWidth > gl.getMax2DTextureSize())
+			throw TextureTooLargeException("width", pixelWidth);
+		else if (pixelHeight > gl.getMax2DTextureSize())
+			throw TextureTooLargeException("height", pixelHeight);
+		else if (layers > gl.getMaxTextureLayers())
+			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 > gl.getMaxCubeTextureSize())
+			throw TextureTooLargeException("width", pixelWidth);
+		break;
+	default:
+		break;
+	}
+
 	OpenGL::TempDebugGroup debuggroup("Canvas load");
 
 	fbo = texture = 0;
 	msaa_buffer = 0;
 	status = GL_FRAMEBUFFER_COMPLETE;
 
-	// glTexImage2D is guaranteed to error in this case.
-	if (pixelWidth > gl.getMaxTextureSize() || pixelHeight > gl.getMaxTextureSize())
-	{
-		status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
-		return false;
-	}
-
 	// getMaxRenderbufferSamples will be 0 on systems that don't support
 	// multisampled renderbuffers / don't export FBO multisample extensions.
 	settings.msaa = std::min(settings.msaa, gl.getMaxRenderbufferSamples());
 	settings.msaa = std::max(settings.msaa, 0);
 
 	glGenTextures(1, &texture);
-	gl.bindTextureToUnit(texture, 0, false);
+	gl.bindTextureToUnit(this, 0, false);
+
+	GLenum gltype = OpenGL::getGLTextureType(texType);
 
 	if (GLAD_ANGLE_texture_usage)
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
+		glTexParameteri(gltype, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
 
 	setFilter(filter);
 	setWrap(wrap);
 
-	bool unusedSRGB = false;
-	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, unusedSRGB);
-
 	while (glGetError() != GL_NO_ERROR)
 		/* Clear the error buffer. */;
 
-	glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, pixelWidth, pixelHeight,
-	             0, fmt.externalformat, fmt.type, nullptr);
+	bool isSRGB = format == PIXELFORMAT_sRGBA8;
+	if (!gl.rawTexStorage(texType, 1, format, isSRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers))
+	{
+		status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
+		return false;
+	}
 
 	if (glGetError() != GL_NO_ERROR)
 	{
@@ -193,7 +252,7 @@ bool Canvas::loadVolatile()
 	}
 
 	// Create a canvas-local FBO used for glReadPixels as well as MSAA blitting.
-	status = createFBO(fbo, texture);
+	status = createFBO(fbo, texType, texture, texType == TEXTURE_VOLUME ? depth : layers, true);
 
 	if (status != GL_FRAMEBUFFER_COMPLETE)
 	{
@@ -207,7 +266,7 @@ bool Canvas::loadVolatile()
 
 	actual_samples = settings.msaa == 1 ? 0 : settings.msaa;
 
-	if (actual_samples > 0 && !createMSAABuffer(width, height, actual_samples, format, msaa_buffer))
+	if (actual_samples > 0 && !createMSAABuffer(pixelWidth, pixelHeight, actual_samples, format, msaa_buffer))
 		actual_samples = 0;
 
 	size_t prevmemsize = texture_memory;
@@ -246,49 +305,66 @@ void Canvas::setFilter(const Texture::Filter &f)
 		throw love::Exception("Invalid texture filter.");
 
 	filter = f;
-	gl.bindTextureToUnit(texture, 0, false);
-	gl.setTextureFilter(filter);
+	gl.bindTextureToUnit(this, 0, false);
+	gl.setTextureFilter(texType, filter);
 }
 
 bool Canvas::setWrap(const Texture::Wrap &w)
 {
 	bool success = true;
+	bool forceclamp = texType == TEXTURE_CUBE;
 	wrap = w;
 
+	// If we only have limited NPOT support then the wrap mode must be CLAMP.
 	if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
-		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight)))
+		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight) || depth != nextP2(depth)))
+	{
+		forceclamp = true;
+	}
+
+	if (forceclamp)
 	{
-		if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP)
+		if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP || wrap.r != WRAP_CLAMP)
 			success = false;
 
-		// If we only have limited NPOT support then the wrap mode must be CLAMP.
-		wrap.s = wrap.t = WRAP_CLAMP;
+		wrap.s = wrap.t = wrap.r = WRAP_CLAMP;
 	}
 
 	if (!gl.isClampZeroTextureWrapSupported())
 	{
-		if (wrap.s == WRAP_CLAMP_ZERO)
-			wrap.s = WRAP_CLAMP;
-		if (wrap.t == WRAP_CLAMP_ZERO)
-			wrap.t = WRAP_CLAMP;
+		if (wrap.s == WRAP_CLAMP_ZERO) wrap.s = WRAP_CLAMP;
+		if (wrap.t == WRAP_CLAMP_ZERO) wrap.t = WRAP_CLAMP;
+		if (wrap.r == WRAP_CLAMP_ZERO) wrap.r = WRAP_CLAMP;
 	}
 
-	gl.bindTextureToUnit(texture, 0, false);
-	gl.setTextureWrap(wrap);
+	gl.bindTextureToUnit(this, 0, false);
+	gl.setTextureWrap(texType, wrap);
 
 	return success;
 }
 
+bool Canvas::setMipmapSharpness(float /*sharpness*/)
+{
+	return false;
+}
+
 ptrdiff_t Canvas::getHandle() const
 {
 	return texture;
 }
 
-love::image::ImageData *Canvas::newImageData(love::image::Image *module, int x, int y, int w, int h)
+love::image::ImageData *Canvas::newImageData(love::image::Image *module, int slice, int x, int y, int w, int h)
 {
 	if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > getPixelWidth() || (y + h) > getPixelHeight())
 		throw love::Exception("Invalid rectangle dimensions.");
 
+	if (slice < 0 || (texType == TEXTURE_VOLUME && slice >= depth)
+		|| (texType == TEXTURE_2D_ARRAY && slice >= layers)
+		|| (texType == TEXTURE_CUBE && slice >= 6))
+	{
+		throw love::Exception("Invalid slice index.");
+	}
+
 	Graphics *gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 	if (gfx != nullptr && gfx->isCanvasActive(this))
 		throw love::Exception("Canvas:newImageData cannot be called while that Canvas is currently active.");
@@ -323,8 +399,18 @@ love::image::ImageData *Canvas::newImageData(love::image::Image *module, int x,
 	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO());
 
+	if (slice > 0)
+	{
+		int layer = texType == TEXTURE_CUBE ? 0 : slice;
+		int face = texType == TEXTURE_CUBE ? slice : 0;
+		gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, layer, face);
+	}
+
 	glReadPixels(x, y, w, h, fmt.externalformat, fmt.type, imagedata->getData());
 
+	if (slice > 0)
+		gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
+
 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
 
 	return imagedata;
@@ -383,14 +469,14 @@ bool Canvas::isFormatSupported(PixelFormat format)
 
 	GLuint texture = 0;
 	glGenTextures(1, &texture);
-	gl.bindTextureToUnit(texture, 0, false);
+	gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false);
 
 	Texture::Filter f;
 	f.min = f.mag = Texture::FILTER_NEAREST;
-	gl.setTextureFilter(f);
+	gl.setTextureFilter(TEXTURE_2D, f);
 
 	Texture::Wrap w;
-	gl.setTextureWrap(w);
+	gl.setTextureWrap(TEXTURE_2D, w);
 
 	bool unusedSRGB = false;
 	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, unusedSRGB);
@@ -398,7 +484,7 @@ bool Canvas::isFormatSupported(PixelFormat format)
 	glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, 2, 2, 0, fmt.externalformat, fmt.type, nullptr);
 
 	GLuint fbo = 0;
-	supported = (createFBO(fbo, texture) == GL_FRAMEBUFFER_COMPLETE);
+	supported = (createFBO(fbo, TEXTURE_2D, texture, 1, false) == GL_FRAMEBUFFER_COMPLETE);
 	gl.deleteFramebuffer(fbo);
 
 	gl.deleteTexture(texture);

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

@@ -39,7 +39,7 @@ class Canvas final : public love::graphics::Canvas, public Volatile
 {
 public:
 
-	Canvas(int width, int height, const Settings &settings);
+	Canvas(const Settings &settings);
 	virtual ~Canvas();
 
 	// Implements Volatile.
@@ -49,9 +49,10 @@ public:
 	// Implements Texture.
 	void setFilter(const Texture::Filter &f) override;
 	bool setWrap(const Texture::Wrap &w) override;
+	bool setMipmapSharpness(float sharpness) override;
 	ptrdiff_t getHandle() const override;
 
-	love::image::ImageData *newImageData(love::image::Image *module, int x, int y, int w, int h) override;
+	love::image::ImageData *newImageData(love::image::Image *module, int slice, int x, int y, int w, int h) override;
 
 	int getMSAA() const override
 	{

+ 0 - 190
src/modules/graphics/opengl/Font.cpp

@@ -1,190 +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.
- **/
-
-// LOVE
-#include "Font.h"
-#include "graphics/Graphics.h"
-
-namespace love
-{
-namespace graphics
-{
-namespace opengl
-{
-
-Font::Font(love::font::Rasterizer *r, const Texture::Filter &f)
-	: love::graphics::Font(r, f)
-	, textureMemorySize(0)
-{
-	loadVolatile();
-}
-
-Font::~Font()
-{
-	unloadVolatile();
-}
-
-void Font::createTexture()
-{
-	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-	gfx->flushStreamDraws();
-
-	OpenGL::TempDebugGroup debuggroup("Font create texture");
-
-	size_t bpp = getPixelFormatSize(pixelFormat);
-
-	size_t prevmemsize = textureMemorySize;
-	if (prevmemsize > 0)
-	{
-		textureMemorySize -= (textureWidth * textureHeight * bpp);
-		gl.updateTextureMemorySize(prevmemsize, textureMemorySize);
-	}
-
-	GLuint t = 0;
-	TextureSize size = {textureWidth, textureHeight};
-	TextureSize nextsize = getNextTextureSize();
-	bool recreatetexture = false;
-
-	// If we have an existing texture already, we'll try replacing it with a
-	// larger-sized one rather than creating a second one. Having a single
-	// texture reduces texture switches and draw calls when rendering.
-	if ((nextsize.width > size.width || nextsize.height > size.height)
-		&& !textures.empty())
-	{
-		recreatetexture = true;
-		size = nextsize;
-		t = textures.back();
-	}
-	else
-		glGenTextures(1, &t);
-
-	gl.bindTextureToUnit(t, 0, false);
-
-	gl.setTextureFilter(filter);
-
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
-	bool sRGB = isGammaCorrect();
-	OpenGL::TextureFormat fmt = gl.convertPixelFormat(pixelFormat, false, sRGB);
-
-	if (fmt.swizzled)
-	{
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, fmt.swizzle[0]);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, fmt.swizzle[1]);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, fmt.swizzle[2]);
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, fmt.swizzle[3]);
-	}
-
-	// Initialize the texture with transparent black.
-	std::vector<GLubyte> emptydata(size.width * size.height * bpp, 0);
-
-	// Clear errors before initializing.
-	while (glGetError() != GL_NO_ERROR);
-
-	glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, size.width, size.height,
-	             0, fmt.externalformat, fmt.type, &emptydata[0]);
-
-	if (glGetError() != GL_NO_ERROR)
-	{
-		if (!recreatetexture)
-			gl.deleteTexture(t);
-		throw love::Exception("Could not create font texture!");
-	}
-
-	textureWidth  = size.width;
-	textureHeight = size.height;
-
-	rowHeight = textureX = textureY = TEXTURE_PADDING;
-
-	prevmemsize = textureMemorySize;
-	textureMemorySize += emptydata.size();
-	gl.updateTextureMemorySize(prevmemsize, textureMemorySize);
-
-	// Re-add the old glyphs if we re-created the existing texture object.
-	if (recreatetexture)
-	{
-		textureCacheID++;
-
-		std::vector<uint32> glyphstoadd;
-
-		for (const auto &glyphpair : glyphs)
-			glyphstoadd.push_back(glyphpair.first);
-
-		glyphs.clear();
-
-		for (uint32 g : glyphstoadd)
-			addGlyph(g);
-	}
-	else
-		textures.push_back(t);
-}
-
-void Font::uploadGlyphToTexture(font::GlyphData *gd, Glyph &glyph)
-{
-	bool isSRGB = isGammaCorrect();
-	OpenGL::TextureFormat fmt = gl.convertPixelFormat(pixelFormat, false, isSRGB);
-
-	glyph.texture = textures.back();
-
-	gl.bindTextureToUnit(glyph.texture, 0, false);
-	glTexSubImage2D(GL_TEXTURE_2D, 0, textureX, textureY, gd->getWidth(), gd->getHeight(),
-	                fmt.externalformat, fmt.type, gd->getData());
-}
-
-void Font::setFilter(const Texture::Filter &f)
-{
-	if (!Texture::validateFilter(f, false))
-		throw love::Exception("Invalid texture filter.");
-
-	filter = f;
-
-	for (GLuint texture : textures)
-	{
-		gl.bindTextureToUnit(texture, 0, false);
-		gl.setTextureFilter(filter);
-	}
-}
-
-bool Font::loadVolatile()
-{
-	createTexture();
-	textureCacheID++;
-	return true;
-}
-
-void Font::unloadVolatile()
-{
-	// nuke everything from orbit
-
-	glyphs.clear();
-
-	for (GLuint texture : textures)
-		gl.deleteTexture(texture);
-
-	textures.clear();
-
-	gl.updateTextureMemorySize(textureMemorySize, 0);
-	textureMemorySize = 0;
-}
-
-} // opengl
-} // graphics
-} // love

+ 82 - 98
src/modules/graphics/opengl/Graphics.cpp

@@ -25,12 +25,10 @@
 
 #include "Graphics.h"
 #include "font/Font.h"
-#include "Font.h"
 #include "StreamBuffer.h"
 #include "math/MathModule.h"
 #include "window/Window.h"
 #include "Buffer.h"
-#include "Video.h"
 #include "Text.h"
 
 #include "libraries/xxHash/xxhash.h"
@@ -103,19 +101,14 @@ love::graphics::StreamBuffer *Graphics::newStreamBuffer(BufferType type, size_t
 	return CreateStreamBuffer(type, size);
 }
 
-love::graphics::Image *Graphics::newImage(const std::vector<love::image::ImageData *> &data, const Image::Settings &settings)
+love::graphics::Image *Graphics::newImage(const Image::Slices &data, const Image::Settings &settings)
 {
 	return new Image(data, settings);
 }
 
-love::graphics::Image *Graphics::newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Settings &settings)
+love::graphics::Image *Graphics::newImage(TextureType textype, PixelFormat format, int width, int height, int slices, const Image::Settings &settings)
 {
-	return new Image(cdata, settings);
-}
-
-graphics::Font *Graphics::newFont(love::font::Rasterizer *r, const Texture::Filter &filter)
-{
-	return new Font(r, filter);
+	return new Image(textype, format, width, height, slices, settings);
 }
 
 love::graphics::SpriteBatch *Graphics::newSpriteBatch(Texture *texture, int size, vertex::Usage usage)
@@ -128,35 +121,12 @@ love::graphics::ParticleSystem *Graphics::newParticleSystem(Texture *texture, in
 	return new ParticleSystem(this, texture, size);
 }
 
-love::graphics::Canvas *Graphics::newCanvas(int width, int height, const Canvas::Settings &settings)
+love::graphics::Canvas *Graphics::newCanvas(const Canvas::Settings &settings)
 {
-	if (!Canvas::isSupported())
-		throw love::Exception("Canvases are not supported by your OpenGL drivers!");
-
-	if (!Canvas::isFormatSupported(settings.format))
-	{
-		const char *fstr = "rgba8";
-		love::getConstant(Canvas::getSizedFormat(settings.format), fstr);
-		throw love::Exception("The %s canvas format is not supported by your OpenGL drivers.", fstr);
-	}
-
-	if (width > gl.getMaxTextureSize())
-		throw Exception("Cannot create canvas: width of %d pixels is too large for this system.", width);
-	else if (height > gl.getMaxTextureSize())
-		throw Exception("Cannot create canvas: height of %d pixels is too large for this system.", height);
-
-	Canvas *canvas = new Canvas(width, height, settings);
-	GLenum err = canvas->getStatus();
-
-	// everything ok, return canvas (early out)
-	if (err == GL_FRAMEBUFFER_COMPLETE)
-		return canvas;
-
-	canvas->release();
-	throw love::Exception("Cannot create Canvas: %s", OpenGL::framebufferStatusString(err));
-	return nullptr; // never reached
+	return new Canvas(settings);
 }
 
+
 love::graphics::Shader *Graphics::newShader(const Shader::ShaderSource &source)
 {
 	return new Shader(source);
@@ -192,11 +162,6 @@ love::graphics::Text *Graphics::newText(graphics::Font *font, const std::vector<
 	return new Text(this, font, text);
 }
 
-love::graphics::Video *Graphics::newVideo(love::video::VideoStream *stream, float pixeldensity)
-{
-	return new Video(stream, pixeldensity);
-}
-
 void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelheight)
 {
 	this->width = width;
@@ -204,7 +169,7 @@ void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelh
 	this->pixelWidth = pixelwidth;
 	this->pixelHeight = pixelheight;
 
-	if (states.back().canvases.empty())
+	if (states.back().renderTargets.empty())
 	{
 		// Set the viewport to top-left corner.
 		gl.setViewport({0, 0, pixelwidth, pixelheight});
@@ -262,6 +227,10 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b
 	// Set pixel row alignment
 	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 
+	// Always enable seamless cubemap filtering when possible.
+	if (GLAD_VERSION_3_2 || GLAD_ARB_seamless_cube_map)
+		glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
+
 	// Set whether drawing converts input from linear -> sRGB colorspace.
 	if (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB
 		|| GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB)
@@ -374,6 +343,9 @@ void Graphics::flushStreamDraws()
 	if (sbstate.vertexCount == 0 && sbstate.indexCount == 0)
 		return;
 
+	if (Shader::current && sbstate.texture.get())
+		Shader::current->checkMainTextureType(sbstate.texture->getTextureType());
+
 	OpenGL::TempDebugGroup debuggroup("Stream vertices flush and draw");
 
 	uint32 attribs = 0;
@@ -448,11 +420,7 @@ void Graphics::flushStreamDraws()
 	pushIdentityTransform();
 
 	gl.prepareDraw();
-
-	if (sbstate.textureHandle != 0)
-		gl.bindTextureToUnit((GLuint) sbstate.textureHandle, 0, false);
-	else
-		gl.bindTextureToUnit(sbstate.texture, 0, false);
+	gl.bindTextureToUnit(sbstate.texture, 0, false);
 
 	gl.useVertexAttribArrays(attribs);
 
@@ -547,21 +515,22 @@ void Graphics::setDebug(bool enable)
 	::printf("OpenGL debug output enabled (LOVE_GRAPHICS_DEBUG=1)\n");
 }
 
-void Graphics::setCanvas(const std::vector<love::graphics::Canvas *> &canvases)
+void Graphics::setCanvas(const std::vector<RenderTarget> &rts)
 {
 	DisplayState &state = states.back();
-	int ncanvases = (int) canvases.size();
+	int ncanvases = (int) rts.size();
 
 	if (ncanvases == 0)
 		return setCanvas();
 
-	if (ncanvases == (int) state.canvases.size())
+	if (ncanvases == (int) state.renderTargets.size())
 	{
 		bool modified = false;
 
 		for (int i = 0; i < ncanvases; i++)
 		{
-			if (canvases[i] != state.canvases[i].get())
+			if (rts[i].canvas != state.renderTargets[i].canvas.get()
+				|| rts[i].slice != state.renderTargets[i].slice)
 			{
 				modified = true;
 				break;
@@ -575,7 +544,7 @@ void Graphics::setCanvas(const std::vector<love::graphics::Canvas *> &canvases)
 	if (ncanvases > gl.getMaxRenderTargets())
 		throw love::Exception("This system can't simultaneously render to %d canvases.", ncanvases);
 
-	love::graphics::Canvas *firstcanvas = canvases[0];
+	love::graphics::Canvas *firstcanvas = rts[0].canvas;
 
 	bool multiformatsupported = Canvas::isMultiFormatMultiCanvasSupported();
 	PixelFormat firstformat = firstcanvas->getPixelFormat();
@@ -586,7 +555,7 @@ void Graphics::setCanvas(const std::vector<love::graphics::Canvas *> &canvases)
 
 	for (int i = 1; i < ncanvases; i++)
 	{
-		love::graphics::Canvas *c = canvases[i];
+		love::graphics::Canvas *c = rts[i].canvas;
 
 		if (c->getPixelWidth() != pixelwidth || c->getPixelHeight() != pixelheight)
 			throw love::Exception("All canvases in must have the same pixel dimensions.");
@@ -595,7 +564,7 @@ void Graphics::setCanvas(const std::vector<love::graphics::Canvas *> &canvases)
 			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 in must have the same requested MSAA value.");
+			throw love::Exception("All Canvases in must have the same MSAA value.");
 
 		if (c->getPixelFormat() == PIXELFORMAT_sRGBA8)
 			hasSRGBcanvas = true;
@@ -605,7 +574,7 @@ void Graphics::setCanvas(const std::vector<love::graphics::Canvas *> &canvases)
 
 	endPass();
 
-	bindCachedFBO(canvases);
+	bindCachedFBO(rts);
 
 	gl.setViewport({0, 0, pixelwidth, pixelheight});
 
@@ -627,13 +596,13 @@ void Graphics::setCanvas(const std::vector<love::graphics::Canvas *> &canvases)
 			gl.setFramebufferSRGB(false);
 	}
 
-	std::vector<StrongRef<love::graphics::Canvas>> canvasrefs;
-	canvasrefs.reserve(canvases.size());
+	std::vector<RenderTargetStrongRef> canvasrefs;
+	canvasrefs.reserve(rts.size());
 
-	for (love::graphics::Canvas *c : canvases)
-		canvasrefs.push_back(c);
+	for (auto c : rts)
+		canvasrefs.emplace_back(c.canvas, c.slice);
 
-	std::swap(state.canvases, canvasrefs);
+	std::swap(state.renderTargets, canvasrefs);
 
 	canvasSwitchCount++;
 }
@@ -642,14 +611,14 @@ void Graphics::setCanvas()
 {
 	DisplayState &state = states.back();
 
-	if (state.canvases.empty())
+	if (state.renderTargets.empty())
 		return;
 
 	OpenGL::TempDebugGroup debuggroup("setCanvas()");
 
 	endPass();
 
-	state.canvases.clear();
+	state.renderTargets.clear();
 
 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO());
 
@@ -682,17 +651,18 @@ void Graphics::endPass()
 	// Discard the stencil buffer.
 	discard({}, true);
 
-	auto &canvases = states.back().canvases;
+	auto &canvases = states.back().renderTargets;
 
-	// Resolve MSAA buffers.
-	if (canvases.size() > 0 && canvases[0]->getMSAA() > 1)
+	// Resolve MSAA buffers. MSAA is only supported for 2D render targets so we
+	// don't have to worry about resolving to slices.
+	if (canvases.size() > 0 && canvases[0].canvas->getMSAA() > 1)
 	{
-		int w = canvases[0]->getPixelWidth();
-		int h = canvases[0]->getPixelHeight();
+		int w = canvases[0].canvas->getPixelWidth();
+		int h = canvases[0].canvas->getPixelHeight();
 
 		for (int i = 0; i < (int) canvases.size(); i++)
 		{
-			Canvas *c = (Canvas *) canvases[i].get();
+			Canvas *c = (Canvas *) canvases[i].canvas.get();
 
 			glReadBuffer(GL_COLOR_ATTACHMENT0 + i);
 
@@ -728,7 +698,7 @@ void Graphics::clear(const std::vector<OptionalColorf> &colors)
 	if (colors.size() == 0)
 		return;
 
-	int ncanvases = (int) states.back().canvases.size();
+	int ncanvases = (int) states.back().renderTargets.size();
 	int ncolors = std::min((int) colors.size(), ncanvases);
 
 	if (ncolors <= 1 && ncanvases <= 1)
@@ -810,7 +780,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector<bool>
 	attachments.reserve(colorbuffers.size());
 
 	// glDiscardFramebuffer uses different attachment enums for the default FBO.
-	if (states.back().canvases.empty() && gl.getDefaultFBO() == 0)
+	if (states.back().renderTargets.empty() && gl.getDefaultFBO() == 0)
 	{
 		if (colorbuffers.size() > 0 && colorbuffers[0])
 			attachments.push_back(GL_COLOR);
@@ -823,7 +793,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector<bool>
 	}
 	else
 	{
-		int rendertargetcount = std::max((int) states.back().canvases.size(), 1);
+		int rendertargetcount = std::max((int) states.back().renderTargets.size(), 1);
 
 		for (int i = 0; i < (int) colorbuffers.size(); i++)
 		{
@@ -845,11 +815,10 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector<bool>
 		glDiscardFramebufferEXT(gltarget, (GLint) attachments.size(), &attachments[0]);
 }
 
-void Graphics::bindCachedFBO(const std::vector<love::graphics::Canvas *> &canvases)
+void Graphics::bindCachedFBO(const std::vector<RenderTarget> &targets)
 {
-	int ncanvases = (int) canvases.size();
-
-	uint32 hash = XXH32(&canvases[0], sizeof(love::graphics::Canvas *) * ncanvases, 0);
+	int ntargets = (int) targets.size();
+	uint32 hash = XXH32(&targets[0], sizeof(RenderTarget) * ntargets, 0);
 
 	GLuint fbo = framebufferObjects[hash];
 
@@ -859,35 +828,40 @@ void Graphics::bindCachedFBO(const std::vector<love::graphics::Canvas *> &canvas
 	}
 	else
 	{
-		int w = canvases[0]->getPixelWidth();
-		int h = canvases[0]->getPixelHeight();
-		int msaa = std::max(canvases[0]->getMSAA(), 1);
+		int w = targets[0].canvas->getPixelWidth();
+		int h = targets[0].canvas->getPixelHeight();
+		int msaa = std::max(targets[0].canvas->getMSAA(), 1);
 
 		glGenFramebuffers(1, &fbo);
 		gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo);
 
 		GLenum drawbuffers[MAX_COLOR_RENDER_TARGETS];
 
-		for (int i = 0; i < ncanvases; i++)
+		for (int i = 0; i < ntargets; i++)
 		{
 			drawbuffers[i] = GL_COLOR_ATTACHMENT0 + i;
 
 			if (msaa > 1)
 			{
-				GLuint rbo = (GLuint) canvases[i]->getMSAAHandle();
+				GLuint rbo = (GLuint) targets[i].canvas->getMSAAHandle();
 				glFramebufferRenderbuffer(GL_FRAMEBUFFER, drawbuffers[i], GL_RENDERBUFFER, rbo);
 			}
 			else
 			{
-				GLuint tex = (GLuint) canvases[i]->getHandle();
-				glFramebufferTexture2D(GL_FRAMEBUFFER, drawbuffers[i], GL_TEXTURE_2D, tex, 0);
+				GLuint tex = (GLuint) targets[i].canvas->getHandle();
+				TextureType textype = targets[i].canvas->getTextureType();
+
+				int layer = textype == TEXTURE_CUBE ? 0 : targets[i].slice;
+				int face = textype == TEXTURE_CUBE ? targets[i].slice : 0;
+
+				gl.framebufferTexture(drawbuffers[i], textype, tex, 0, layer, face);
 			}
 		}
 
-		if (ncanvases > 1)
-			glDrawBuffers(ncanvases, drawbuffers);
+		if (ntargets > 1)
+			glDrawBuffers(ntargets, drawbuffers);
 
-		GLuint stencil = attachCachedStencilBuffer(w, h, canvases[0]->getRequestedMSAA());
+		GLuint stencil = attachCachedStencilBuffer(w, h, targets[0].canvas->getRequestedMSAA());
 
 		if (stencil == 0)
 		{
@@ -904,7 +878,7 @@ void Graphics::bindCachedFBO(const std::vector<love::graphics::Canvas *> &canvas
 			const char *sstr = OpenGL::framebufferStatusString(status);
 			throw love::Exception("Could not create Framebuffer Object! %s", sstr);
 		}
-
+		
 		framebufferObjects[hash] = fbo;
 	}
 }
@@ -990,7 +964,7 @@ void Graphics::present(void *screenshotCallbackData)
 	if (!isActive())
 		return;
 
-	if (!states.back().canvases.empty())
+	if (!states.back().renderTargets.empty())
 		throw love::Exception("present cannot be called while a Canvas is active.");
 
 	endPass();
@@ -1029,8 +1003,8 @@ void Graphics::present(void *screenshotCallbackData)
 		{
 			gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, info.info.uikit.resolveFramebuffer);
 
-			// We need to do an explicit MSAA resolve on iOS, because it uses GLES
-			// FBOs rather than a system framebuffer.
+			// We need to do an explicit MSAA resolve on iOS, because it uses
+			// GLES FBOs rather than a system framebuffer.
 			if (GLAD_ES_VERSION_3_0)
 				glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
 			else if (GLAD_APPLE_framebuffer_multisample)
@@ -1122,7 +1096,7 @@ void Graphics::setScissor(const Rect &rect)
 	glrect.h = (int) (rect.h * density);
 
 	// OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor.
-	gl.setScissor(glrect, !state.canvases.empty());
+	gl.setScissor(glrect, !state.renderTargets.empty());
 
 	state.scissor = true;
 	state.scissorRect = rect;
@@ -1139,7 +1113,7 @@ void Graphics::setScissor()
 
 void Graphics::drawToStencilBuffer(StencilAction action, int value)
 {
-	if (states.back().canvases.empty() && !windowHasStencil)
+	if (states.back().renderTargets.empty() && !windowHasStencil)
 		throw love::Exception("The window must have stenciling enabled to draw to the main screen's stencil buffer.");
 
 	flushStreamDraws();
@@ -1200,7 +1174,7 @@ void Graphics::stopDrawToStencilBuffer()
 
 void Graphics::setStencilTest(CompareMode compare, int value)
 {
-	if (compare != COMPARE_ALWAYS && states.back().canvases.empty() && !windowHasStencil)
+	if (compare != COMPARE_ALWAYS && states.back().renderTargets.empty() && !windowHasStencil)
 		throw love::Exception("The window must have stenciling enabled to use setStencilTest on the main screen.");
 
 	DisplayState &state = states.back();
@@ -1454,15 +1428,21 @@ double Graphics::getSystemLimit(SystemLimit limittype) const
 {
 	switch (limittype)
 	{
-	case Graphics::LIMIT_POINT_SIZE:
+	case LIMIT_POINT_SIZE:
 		return (double) gl.getMaxPointSize();
-	case Graphics::LIMIT_TEXTURE_SIZE:
-		return (double) gl.getMaxTextureSize();
-	case Graphics::LIMIT_MULTI_CANVAS:
+	case LIMIT_TEXTURE_SIZE:
+		return (double) gl.getMax2DTextureSize();
+	case LIMIT_TEXTURE_LAYERS:
+		return (double) gl.getMaxTextureLayers();
+	case LIMIT_VOLUME_TEXTURE_SIZE:
+		return (double) gl.getMax3DTextureSize();
+	case LIMIT_CUBE_TEXTURE_SIZE:
+		return (double) gl.getMaxCubeTextureSize();
+	case LIMIT_MULTI_CANVAS:
 		return (double) gl.getMaxRenderTargets();
-	case Graphics::LIMIT_CANVAS_MSAA:
+	case LIMIT_CANVAS_MSAA:
 		return (double) gl.getMaxRenderbufferSamples();
-	case Graphics::LIMIT_ANISOTROPY:
+	case LIMIT_ANISOTROPY:
 		return (double) gl.getMaxAnisotropy();
 	default:
 		return 0.0;
@@ -1483,6 +1463,10 @@ bool Graphics::isSupported(Feature feature) const
 		return GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot;
 	case FEATURE_PIXEL_SHADER_HIGHP:
 		return gl.isPixelShaderHighpSupported();
+	case FEATURE_ARRAY_TEXTURE:
+		return gl.isTextureTypeSupported(TEXTURE_2D_ARRAY);
+	case FEATURE_VOLUME_TEXTURE:
+		return gl.isTextureTypeSupported(TEXTURE_VOLUME);
 	case FEATURE_GLSL3:
 		return GLAD_ES_VERSION_3_0 || gl.isCoreProfile();
 	case FEATURE_INSTANCING:

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

@@ -61,17 +61,13 @@ public:
 	// Implements Module.
 	const char *getName() const override;
 
-	love::graphics::Image *newImage(const std::vector<love::image::ImageData *> &data, const Image::Settings &settings) override;
-	love::graphics::Image *newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Settings &settings) override;
-
-	love::graphics::Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::defaultFilter) override;
+	love::graphics::Image *newImage(const Image::Slices &data, const Image::Settings &settings) override;
+	love::graphics::Image *newImage(TextureType textype, PixelFormat format, int width, int height, int slices, const Image::Settings &settings) override;
 
 	love::graphics::SpriteBatch *newSpriteBatch(Texture *texture, int size, vertex::Usage usage) override;
-
 	love::graphics::ParticleSystem *newParticleSystem(Texture *texture, int size) override;
 
-	love::graphics::Canvas *newCanvas(int width, int height, const Canvas::Settings &settings) override;
-
+	love::graphics::Canvas *newCanvas(const Canvas::Settings &settings) override;
 	love::graphics::Shader *newShader(const Shader::ShaderSource &source) override;
 
 	love::graphics::Buffer *newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags) override;
@@ -84,8 +80,6 @@ public:
 
 	love::graphics::Text *newText(love::graphics::Font *font, const std::vector<Font::ColoredString> &text = {}) override;
 
-	love::graphics::Video *newVideo(love::video::VideoStream *stream, float pixeldensity) override;
-
 	void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override;
 	bool setMode(int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil) override;
 	void unSetMode() override;
@@ -103,7 +97,7 @@ public:
 
 	void setColor(Colorf c) override;
 
-	void setCanvas(const std::vector<love::graphics::Canvas *> &canvases) override;
+	void setCanvas(const std::vector<RenderTarget> &rts) override;
 	void setCanvas() override;
 
 	void setScissor(const Rect &rect) override;
@@ -149,7 +143,7 @@ private:
 	love::graphics::StreamBuffer *newStreamBuffer(BufferType type, size_t size) override;
 
 	void endPass();
-	void bindCachedFBO(const std::vector<love::graphics::Canvas *> &canvases);
+	void bindCachedFBO(const std::vector<RenderTarget> &targets);
 	void discard(OpenGL::FramebufferTarget target, const std::vector<bool> &colorbuffers, bool depthstencil);
 	GLuint attachCachedStencilBuffer(int w, int h, int samples);
 

+ 229 - 261
src/modules/graphics/opengl/Image.cpp

@@ -26,16 +26,6 @@
 // STD
 #include <algorithm> // for min/max
 
-#ifdef LOVE_ANDROID
-// log2 is not declared in the math.h shipped with the Android NDK
-#include <cmath>
-inline double log2(double n)
-{ 
-	// log(n)/log(2) is log2.  
-	return std::log(n) / std::log(2);
-}
-#endif
-
 namespace love
 {
 namespace graphics
@@ -45,114 +35,38 @@ namespace opengl
 
 float Image::maxMipmapSharpness = 0.0f;
 
-static int getMipmapCount(int basewidth, int baseheight)
-{
-	return (int) log2(std::max(basewidth, baseheight)) + 1;
-}
-
-template <typename T>
-static bool verifyMipmapLevels(const std::vector<T> &miplevels)
-{
-	int numlevels = (int) miplevels.size();
-
-	if (numlevels == 1)
-		return false;
-
-	int width  = miplevels[0]->getWidth();
-	int height = miplevels[0]->getHeight();
-
-	auto format = miplevels[0]->getFormat();
-
-	int expectedlevels = getMipmapCount(width, height);
-
-	// All mip levels must be present when not using auto-generated mipmaps.
-	if (numlevels != expectedlevels)
-		throw love::Exception("Image does not have all required mipmap levels (expected %d, got %d)", expectedlevels, numlevels);
-
-	// Verify the size of each mip level.
-	for (int i = 1; i < numlevels; i++)
-	{
-		width  = std::max(width / 2, 1);
-		height = std::max(height / 2, 1);
-
-		if (miplevels[i]->getWidth() != width)
-			throw love::Exception("Width of image mipmap level %d is incorrect (expected %d, got %d)", i+1, width, miplevels[i]->getWidth());
-		if (miplevels[i]->getHeight() != height)
-			throw love::Exception("Height of image mipmap level %d is incorrect (expected %d, got %d)", i+1, height, miplevels[i]->getHeight());
-
-		if (miplevels[i]->getFormat() != format)
-			throw love::Exception("All image mipmap levels must have the same format.");
-	}
-
-	return true;
-}
-
-Image::Image(const std::vector<love::image::ImageData *> &imagedata, const Settings &settings)
-	: love::graphics::Image(settings)
+Image::Image(TextureType textype, PixelFormat format, int width, int height, int slices, const Settings &settings)
+	: love::graphics::Image(Slices(textype), settings, false)
 	, texture(0)
-	, mipmapSharpness(defaultMipmapSharpness)
 	, compressed(false)
-	, sRGB(false)
 	, usingDefaultTexture(false)
 	, textureMemorySize(0)
 {
-	if (imagedata.empty())
-		throw love::Exception("");
-
-	pixelWidth  = imagedata[0]->getWidth();
-	pixelHeight = imagedata[0]->getHeight();
+	if (isPixelFormatCompressed(format))
+		throw love::Exception("This constructor is only supported for non-compressed pixel formats.");
 
-	width  = (int) (pixelWidth / settings.pixeldensity + 0.5);
-	height = (int) (pixelHeight / settings.pixeldensity + 0.5);
+	if (textype == TEXTURE_VOLUME)
+		depth = slices;
+	else if (textype == TEXTURE_2D_ARRAY)
+		layers = slices;
 
-	if (verifyMipmapLevels(imagedata))
-		this->settings.mipmaps = true;
-
-	for (const auto &id : imagedata)
-		data.push_back(id);
-
-	format = data[0]->getFormat();
-
-	preload();
-	loadVolatile();
+	init(format, width, height, settings);
 }
 
-Image::Image(const std::vector<love::image::CompressedImageData *> &compresseddata, const Settings &settings)
-	: love::graphics::Image(settings)
+Image::Image(const Slices &slices, const Settings &settings)
+	: love::graphics::Image(slices, settings, true)
 	, texture(0)
-	, mipmapSharpness(defaultMipmapSharpness)
-	, compressed(true)
-	, sRGB(false)
+	, compressed(false)
 	, usingDefaultTexture(false)
 	, textureMemorySize(0)
 {
-	pixelWidth  = compresseddata[0]->getWidth(0);
-	pixelHeight = compresseddata[0]->getHeight(0);
-
-	width  = (int) (pixelWidth / settings.pixeldensity + 0.5);
-	height = (int) (pixelHeight / settings.pixeldensity + 0.5);
-
-	if (verifyMipmapLevels(compresseddata))
-		this->settings.mipmaps = true;
-	else if (settings.mipmaps && getMipmapCount(pixelWidth, pixelHeight) != compresseddata[0]->getMipmapCount())
-	{
-		if (compresseddata[0]->getMipmapCount() == 1)
-			this->settings.mipmaps = false;
-		else
-		{
-			throw love::Exception("Image cannot have mipmaps: compressed image data does not have all required mipmap levels (expected %d, got %d)",
-			                      getMipmapCount(width, height),
-			                      compresseddata[0]->getMipmapCount());
-		}
-	}
-
-	for (image::CompressedImageData *cd : compresseddata)
-		cdata.push_back(cd);
+	if (texType == TEXTURE_2D_ARRAY)
+		this->layers = data.getSliceCount();
+	else if (texType == TEXTURE_VOLUME)
+		this->depth = data.getSliceCount();
 
-	format = cdata[0]->getFormat();
-
-	preload();
-	loadVolatile();
+	love::image::ImageDataBase *slice = data.get(0, 0);
+	init(slice->getFormat(), slice->getWidth(), slice->getHeight(), settings);
 }
 
 Image::~Image()
@@ -160,56 +74,41 @@ Image::~Image()
 	unloadVolatile();
 }
 
-void Image::preload()
+void Image::init(PixelFormat fmt, int w, int h, const Settings &settings)
 {
-	for (int i = 0; i < 4; i++)
-		vertices[i].color = Color(255, 255, 255, 255);
-
-	// Vertices are ordered for use with triangle strips:
-	// 0---2
-	// | / |
-	// 1---3
-	vertices[0].x = 0.0f;
-	vertices[0].y = 0.0f;
-	vertices[1].x = 0.0f;
-	vertices[1].y = (float) height;
-	vertices[2].x = (float) width;
-	vertices[2].y = 0.0f;
-	vertices[3].x = (float) width;
-	vertices[3].y = (float) height;
-
-	vertices[0].s = 0.0f;
-	vertices[0].t = 0.0f;
-	vertices[1].s = 0.0f;
-	vertices[1].t = 1.0f;
-	vertices[2].s = 1.0f;
-	vertices[2].t = 0.0f;
-	vertices[3].s = 1.0f;
-	vertices[3].t = 1.0f;
-
-	if (settings.mipmaps)
-		filter.mipmap = defaultMipmapFilter;
+	pixelWidth = w;
+	pixelHeight = h;
+
+	width  = (int) (pixelWidth / settings.pixeldensity + 0.5);
+	height = (int) (pixelHeight / settings.pixeldensity + 0.5);
 
-	if (!isGammaCorrect())
-		settings.linear = false;
+	mipmapCount = mipmapsType == MIPMAPS_NONE ? 1 : getMipmapCount(w, h);
+	format = fmt;
+	compressed = isPixelFormatCompressed(format);
 
-	if (isGammaCorrect() && !settings.linear)
-		sRGB = true;
-	else
-		sRGB = false;
+	if (compressed && mipmapsType == MIPMAPS_GENERATED)
+		mipmapsType = MIPMAPS_NONE;
+
+	if (getMipmapCount() > 1)
+		filter.mipmap = defaultMipmapFilter;
+
+	loadVolatile();
+	initVertices();
 }
 
 void Image::generateMipmaps()
 {
 	// The GL_GENERATE_MIPMAP texparameter is set in loadVolatile if we don't
 	// have support for glGenerateMipmap.
-	if (settings.mipmaps && !isCompressed() &&
-		(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object))
+	if (getMipmapCount() > 1 && !isCompressed() &&
+		(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object || GLAD_EXT_framebuffer_object))
 	{
+		GLenum gltextype = OpenGL::getGLTextureType(texType);
+
 		if (gl.bugs.generateMipmapsRequiresTexture2DEnable)
-			glEnable(GL_TEXTURE_2D);
+			glEnable(gltextype);
 
-		glGenerateMipmap(GL_TEXTURE_2D);
+		glGenerateMipmap(gltextype);
 	}
 }
 
@@ -217,63 +116,118 @@ void Image::loadDefaultTexture()
 {
 	usingDefaultTexture = true;
 
-	gl.bindTextureToUnit(texture, 0, false);
+	gl.bindTextureToUnit(this, 0, false);
 	setFilter(filter);
 
+	bool isSRGB = false;
+	gl.rawTexStorage(texType, 1, PIXELFORMAT_RGBA8, isSRGB, 2, 2, 1);
+
 	// A nice friendly checkerboard to signify invalid textures...
 	GLubyte px[] = {0xFF,0xFF,0xFF,0xFF, 0xFF,0xA0,0xA0,0xFF,
 	                0xFF,0xA0,0xA0,0xFF, 0xFF,0xFF,0xFF,0xFF};
 
-	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, px);
+	int slices = texType == TEXTURE_CUBE ? 6 : 1;
+	Rect rect = {0, 0, 2, 2};
+	for (int slice = 0; slice < slices; slice++)
+		uploadByteData(PIXELFORMAT_RGBA8, px, sizeof(px), rect, 0, slice);
 }
 
-void Image::loadFromCompressedData()
+void Image::loadData()
 {
-	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB);
+	int mipcount = getMipmapCount();
+	int slicecount = 1;
+
+	if (texType == TEXTURE_VOLUME)
+		slicecount = getDepth();
+	else if (texType == TEXTURE_2D_ARRAY)
+		slicecount = getLayerCount();
+	else if (texType == TEXTURE_CUBE)
+		slicecount = 6;
 
-	if (isGammaCorrect() && !sRGB)
-		settings.linear = true;
+	if (!isCompressed())
+		gl.rawTexStorage(texType, mipcount, format, sRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers);
 
-	int count = 1;
+	if (mipmapsType == MIPMAPS_GENERATED)
+		mipcount = 1;
 
-	if (settings.mipmaps && cdata.size() > 1)
-		count = (int) cdata.size();
-	else if (settings.mipmaps)
-		count = cdata[0]->getMipmapCount();
+	int w = pixelWidth;
+	int h = pixelHeight;
+	int d = depth;
 
-	for (int i = 0; i < count; i++)
+	OpenGL::TextureFormat fmt = gl.convertPixelFormat(format, false, sRGB);
+
+	for (int mip = 0; mip < mipcount; mip++)
 	{
-		// Compressed image mipmaps can come from separate CompressedImageData
-		// objects, or all from a single object.
-		auto cd = cdata.size() > 1 ? cdata[i].get() : cdata[0].get();
-		int datamip = cdata.size() > 1 ? 0 : i;
-
-		glCompressedTexImage2D(GL_TEXTURE_2D, i, fmt.internalformat,
-		                       cd->getWidth(datamip), cd->getHeight(datamip), 0,
-		                       (GLsizei) cd->getSize(datamip), cd->getData(datamip));
+		if (isCompressed() && (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME))
+		{
+			size_t mipsize = 0;
+
+			if (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME)
+			{
+				for (int slice = 0; slice < data.getSliceCount(mip); slice++)
+					mipsize += data.get(slice, mip)->getSize();
+			}
+
+			GLenum gltarget = OpenGL::getGLTextureType(texType);
+			glCompressedTexImage3D(gltarget, mip, fmt.internalformat, w, h, d, 0, mipsize, nullptr);
+		}
+
+		for (int slice = 0; slice < slicecount; slice++)
+		{
+			love::image::ImageDataBase *id = data.get(slice, mip);
+
+			if (id != nullptr)
+				uploadImageData(id, mip, slice);
+		}
+
+		w = std::max(w / 2, 1);
+		h = std::max(h / 2, 1);
+
+		if (texType == TEXTURE_VOLUME)
+			d = std::max(d / 2, 1);
 	}
+
+	if (mipmapsType == MIPMAPS_GENERATED)
+		generateMipmaps();
 }
 
-void Image::loadFromImageData()
+void Image::uploadByteData(PixelFormat pixelformat, const void *data, size_t size, const Rect &r, int level, int slice)
 {
-	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB);
+	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(pixelformat, false, sRGB);
+	GLenum gltarget = OpenGL::getGLTextureType(texType);
 
-	if (isGammaCorrect() && !sRGB)
-		settings.linear = true;
+	if (texType == TEXTURE_CUBE)
+		gltarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice;
 
-	int mipcount = settings.mipmaps ? (int) data.size() : 1;
-
-	for (int i = 0; i < mipcount; i++)
+	if (isPixelFormatCompressed(pixelformat))
 	{
-		love::image::ImageData *id = data[i].get();
-		love::thread::Lock lock(id->getMutex());
+		if (r.x != 0 || r.y != 0)
+			throw love::Exception("x and y parameters must be 0 for compressed images.");
 
-		glTexImage2D(GL_TEXTURE_2D, i, fmt.internalformat, id->getWidth(), id->getHeight(),
-		             0, fmt.externalformat, fmt.type, id->getData());
+		if (texType == TEXTURE_2D || texType == TEXTURE_CUBE)
+			glCompressedTexImage2D(gltarget, level, fmt.internalformat, r.w, r.h, 0, size, data);
+		else if (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME)
+			glCompressedTexSubImage3D(gltarget, level, 0, 0, slice, r.w, r.h, 1, fmt.internalformat, size, data);
+	}
+	else
+	{
+		if (texType == TEXTURE_2D || texType == TEXTURE_CUBE)
+			glTexSubImage2D(gltarget, level, r.x, r.y, r.w, r.h, fmt.externalformat, fmt.type, data);
+		else if (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME)
+			glTexSubImage3D(gltarget, level, r.x, r.y, slice, r.w, r.h, 1, fmt.externalformat, fmt.type, data);
 	}
+}
 
-	if (data.size() <= 1)
-		generateMipmaps();
+void Image::uploadImageData(love::image::ImageDataBase *d, int level, int slice)
+{
+	love::image::ImageData *id = dynamic_cast<love::image::ImageData *>(d);
+
+	love::thread::EmptyLock lock;
+	if (id != nullptr)
+		lock.setLock(id->getMutex());
+
+	Rect rect = {0, 0, d->getWidth(), d->getHeight()};
+	uploadByteData(d->getFormat(), d->getData(), d->getSize(), rect, level, slice);
 }
 
 bool Image::loadVolatile()
@@ -301,9 +255,9 @@ bool Image::loadVolatile()
 
 		// GL_EXT_sRGB doesn't support glGenerateMipmap for sRGB textures.
 		if (sRGB && (GLAD_ES_VERSION_2_0 && GLAD_EXT_sRGB && !GLAD_ES_VERSION_3_0)
-			&& data.size() <= 1)
+			&& mipmapsType != MIPMAPS_DATA)
 		{
-			settings.mipmaps = false;
+			mipmapsType = MIPMAPS_NONE;
 			filter.mipmap = FILTER_NONE;
 		}
 	}
@@ -312,7 +266,7 @@ bool Image::loadVolatile()
 	if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
 		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight)))
 	{
-		settings.mipmaps = false;
+		mipmapsType = MIPMAPS_NONE;
 		filter.mipmap = FILTER_NONE;
 	}
 
@@ -320,38 +274,43 @@ bool Image::loadVolatile()
 		glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &maxMipmapSharpness);
 
 	glGenTextures(1, &texture);
-	gl.bindTextureToUnit(texture, 0, false);
+	gl.bindTextureToUnit(this, 0, false);
 
-	setFilter(filter);
-	setWrap(wrap);
-	setMipmapSharpness(mipmapSharpness);
+	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 (pixelWidth > gl.getMaxTextureSize() || pixelHeight > gl.getMaxTextureSize())
+	if (loaddefault)
 	{
 		loadDefaultTexture();
 		return true;
 	}
 
-	if (!settings.mipmaps && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0))
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+	setFilter(filter);
+	setWrap(wrap);
+	setMipmapSharpness(mipmapSharpness);
 
-	if (settings.mipmaps && !isCompressed() && data.size() <= 1 &&
-		!(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object))
-	{
-		// Auto-generate mipmaps every time the texture is modified, if
-		// glGenerateMipmap isn't supported.
-		glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
-	}
+	GLenum gltextype = OpenGL::getGLTextureType(texType);
+
+	if (mipmapsType == MIPMAPS_NONE && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0))
+		glTexParameteri(gltextype, GL_TEXTURE_MAX_LEVEL, 0);
 
 	while (glGetError() != GL_NO_ERROR); // Clear errors.
 
 	try
 	{
-		if (isCompressed())
-			loadFromCompressedData();
-		else
-			loadFromImageData();
+		loadData();
 
 		GLenum glerr = glGetError();
 		if (glerr != GL_NO_ERROR)
@@ -365,13 +324,12 @@ bool Image::loadVolatile()
 	}
 
 	size_t prevmemsize = textureMemorySize;
+	textureMemorySize = 0;
 
-	if (isCompressed())
-		textureMemorySize = cdata[0]->getSize();
-	else
-		textureMemorySize = data[0]->getSize();
+	for (int slice = 0; slice < data.getSliceCount(0); slice++)
+		textureMemorySize += data.get(slice, 0)->getSize();
 
-	if (settings.mipmaps)
+	if (getMipmapCount() > 1)
 		textureMemorySize *= 1.33334;
 
 	gl.updateTextureMemorySize(prevmemsize, textureMemorySize);
@@ -392,75 +350,73 @@ void Image::unloadVolatile()
 	textureMemorySize = 0;
 }
 
-bool Image::refresh(int xoffset, int yoffset, int w, int h)
+void Image::replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, bool reloadmipmaps)
 {
 	// No effect if the texture hasn't been created yet.
 	if (texture == 0 || usingDefaultTexture)
-		return false;
+		return;
 
-	if (xoffset < 0 || yoffset < 0 || w <= 0 || h <= 0 ||
-		(xoffset + w) > pixelWidth || (yoffset + h) > pixelHeight)
+	if (d->getFormat() != getPixelFormat())
+		throw love::Exception("Pixel formats must match.");
+
+	if (mipmap < 0 || (mipmapsType != MIPMAPS_DATA && mipmap > 0) || mipmap >= getMipmapCount())
+		throw love::Exception("Invalid image mipmap index.");
+
+	if (slice < 0 || (texType == TEXTURE_CUBE && slice >= 6)
+		|| (texType == TEXTURE_VOLUME && slice >= std::max(getDepth() >> mipmap, 1))
+		|| (texType == TEXTURE_2D_ARRAY && slice >= getLayerCount()))
 	{
-		throw love::Exception("Invalid rectangle dimensions.");
+		throw love::Exception("Invalid image slice index.");
 	}
 
-	OpenGL::TempDebugGroup debuggroup("Image refresh");
+	love::image::ImageDataBase *oldd = data.get(slice, mipmap);
 
-	gl.bindTextureToUnit(texture, 0, false);
+	if (oldd == nullptr)
+		throw love::Exception("Image does not store ImageData!");
 
-	if (isCompressed())
-	{
-		loadFromCompressedData();
-		return true;
-	}
+	int w = d->getWidth();
+	int h = d->getHeight();
 
-	bool isSRGB = sRGB;
-	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, isSRGB);
+	if (w != oldd->getWidth() || h != oldd->getHeight())
+		throw love::Exception("Dimensions must match the texture's dimensions for the specified mipmap level.");
 
-	int mipcount = settings.mipmaps ? (int) data.size() : 1;
+	d->retain();
+	oldd->release();
 
-	// Reupload the sub-rectangle of each mip level (if we have custom mipmaps.)
-	for (int i = 0; i < mipcount; i++)
-	{
-		const image::pixel *pdata = (const image::pixel *) data[i]->getData();
-		pdata += yoffset * data[i]->getWidth() + xoffset;
+	data.set(slice, mipmap, d);
 
-		thread::Lock lock(data[i]->getMutex());
-		glTexSubImage2D(GL_TEXTURE_2D, i, xoffset, yoffset, w, h,
-		                fmt.externalformat, fmt.type, pdata);
+	OpenGL::TempDebugGroup debuggroup("Image replace pixels");
 
-		xoffset /= 2;
-		yoffset /= 2;
-		w = std::max(w / 2, 1);
-		h = std::max(h / 2, 1);
-	}
+	gl.bindTextureToUnit(this, 0, false);
 
-	if (data.size() <= 1)
-		generateMipmaps();
+	uploadImageData(d, mipmap, slice);
 
-	return true;
+	if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1)
+		generateMipmaps();
 }
 
-ptrdiff_t Image::getHandle() const
+void Image::replacePixels(const void *data, size_t size, const Rect &rect, int slice, int mipmap, bool reloadmipmaps)
 {
-	return texture;
-}
+	OpenGL::TempDebugGroup debuggroup("Image replace pixels");
 
-const std::vector<StrongRef<love::image::ImageData>> &Image::getImageData() const
-{
-	return data;
+	gl.bindTextureToUnit(this, 0, false);
+
+	uploadByteData(format, data, size, rect, mipmap, slice);
+
+	if (reloadmipmaps && mipmap == 0 && getMipmapCount() > 1)
+		generateMipmaps();
 }
 
-const std::vector<StrongRef<love::image::CompressedImageData>> &Image::getCompressedData() const
+ptrdiff_t Image::getHandle() const
 {
-	return cdata;
+	return texture;
 }
 
 void Image::setFilter(const Texture::Filter &f)
 {
-	if (!validateFilter(f, settings.mipmaps))
+	if (!validateFilter(f, getMipmapCount() > 1))
 	{
-		if (f.mipmap != FILTER_NONE && !settings.mipmaps)
+		if (f.mipmap != FILTER_NONE && getMipmapCount() == 1)
 			throw love::Exception("Non-mipmapped image cannot have mipmap filtering.");
 		else
 			throw love::Exception("Invalid texture filter.");
@@ -468,7 +424,7 @@ void Image::setFilter(const Texture::Filter &f)
 
 	filter = f;
 
-	if (!data.empty() && !OpenGL::hasTextureFilteringSupport(data[0]->getFormat()))
+	if (!OpenGL::hasTextureFilteringSupport(getPixelFormat()))
 	{
 		filter.mag = filter.min = FILTER_NEAREST;
 
@@ -483,57 +439,64 @@ void Image::setFilter(const Texture::Filter &f)
 		filter.min = filter.mag = FILTER_NEAREST;
 	}
 
-	gl.bindTextureToUnit(texture, 0, false);
-	gl.setTextureFilter(filter);
+	gl.bindTextureToUnit(this, 0, false);
+	gl.setTextureFilter(texType, filter);
 }
 
 bool Image::setWrap(const Texture::Wrap &w)
 {
 	bool success = true;
+	bool forceclamp = texType == TEXTURE_CUBE;
 	wrap = w;
 
+	// If we only have limited NPOT support then the wrap mode must be CLAMP.
 	if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
-		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight)))
+		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight) || depth != nextP2(depth)))
 	{
-		if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP)
+		forceclamp = true;
+	}
+
+	if (forceclamp)
+	{
+		if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP || wrap.r != WRAP_CLAMP)
 			success = false;
 
-		// If we only have limited NPOT support then the wrap mode must be CLAMP.
-		wrap.s = wrap.t = WRAP_CLAMP;
+		wrap.s = wrap.t = wrap.r = WRAP_CLAMP;
 	}
 
 	if (!gl.isClampZeroTextureWrapSupported())
 	{
-		if (wrap.s == WRAP_CLAMP_ZERO)
-			wrap.s = WRAP_CLAMP;
-		if (wrap.t == WRAP_CLAMP_ZERO)
-			wrap.t = WRAP_CLAMP;
+		if (wrap.s == WRAP_CLAMP_ZERO) wrap.s = WRAP_CLAMP;
+		if (wrap.t == WRAP_CLAMP_ZERO) wrap.t = WRAP_CLAMP;
+		if (wrap.r == WRAP_CLAMP_ZERO) wrap.r = WRAP_CLAMP;
 	}
 
-	gl.bindTextureToUnit(texture, 0, false);
-	gl.setTextureWrap(wrap);
+	gl.bindTextureToUnit(this, 0, false);
+	gl.setTextureWrap(texType, wrap);
 
 	return success;
 }
 
-void Image::setMipmapSharpness(float sharpness)
+bool Image::setMipmapSharpness(float sharpness)
 {
 	// OpenGL ES doesn't support LOD bias via glTexParameter.
 	if (!GLAD_VERSION_1_4)
-		return;
+		return false;
 
 	// LOD bias has the range (-maxbias, maxbias)
 	mipmapSharpness = std::min(std::max(sharpness, -maxMipmapSharpness + 0.01f), maxMipmapSharpness - 0.01f);
 
-	gl.bindTextureToUnit(texture, 0, false);
+	gl.bindTextureToUnit(this, 0, false);
 
 	// negative bias is sharper
-	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -mipmapSharpness);
+	GLenum gltextype = OpenGL::getGLTextureType(texType);
+	glTexParameterf(gltextype, GL_TEXTURE_LOD_BIAS, -mipmapSharpness);
+	return true;
 }
 
-float Image::getMipmapSharpness() const
+bool Image::isFormatLinear() const
 {
-	return mipmapSharpness;
+	return isGammaCorrect() && !sRGB;
 }
 
 bool Image::isCompressed() const
@@ -541,6 +504,11 @@ bool Image::isCompressed() const
 	return compressed;
 }
 
+Image::MipmapsType Image::getMipmapsType() const
+{
+	return mipmapsType;
+}
+
 bool Image::isFormatSupported(PixelFormat pixelformat)
 {
 	return OpenGL::isPixelFormatSupported(pixelformat, false, false);

+ 13 - 28
src/modules/graphics/opengl/Image.h

@@ -38,8 +38,8 @@ class Image final : public love::graphics::Image, public Volatile
 {
 public:
 
-	Image(const std::vector<love::image::ImageData *> &data, const Settings &settings);
-	Image(const std::vector<love::image::CompressedImageData *> &cdata, const Settings &settings);
+	Image(const Slices &data, const Settings &settings);
+	Image(TextureType textype, PixelFormat format, int width, int height, int slices, const Settings &settings);
 
 	virtual ~Image();
 
@@ -49,52 +49,37 @@ public:
 
 	ptrdiff_t getHandle() const override;
 
-	const std::vector<StrongRef<love::image::ImageData>> &getImageData() const override;
-	const std::vector<StrongRef<love::image::CompressedImageData>> &getCompressedData() const override;
-
 	void setFilter(const Texture::Filter &f) override;
 	bool setWrap(const Texture::Wrap &w) override;
 
-	void setMipmapSharpness(float sharpness) override;
-	float getMipmapSharpness() const override;
+	bool setMipmapSharpness(float sharpness) override;
+
+	void replacePixels(love::image::ImageDataBase *d, int slice, int mipmap, bool reloadmipmaps) override;
+	void replacePixels(const void *data, size_t size, const Rect &rect, int slice, int mipmap, bool reloadmipmaps) override;
+
+	bool isFormatLinear() const override;
 	bool isCompressed() const override;
-	bool refresh(int xoffset, int yoffset, int w, int h) override;
+	MipmapsType getMipmapsType() const override;
 
 	static bool isFormatSupported(PixelFormat pixelformat);
 	static bool hasSRGBSupport();
 
-	static bool getConstant(const char *in, SettingType &out);
-	static bool getConstant(SettingType in, const char *&out);
-
 private:
 
-	void preload();
+	void init(PixelFormat fmt, int w, int h, const Settings &settings);
 
 	void generateMipmaps();
 	void loadDefaultTexture();
-	void loadFromCompressedData();
-	void loadFromImageData();
-
-	// The ImageData from which the texture is created. May be empty if
-	// Compressed image data was used to create the texture.
-	// Each element in the array is a mipmap level.
-	std::vector<StrongRef<love::image::ImageData>> data;
-
-	// Or the Compressed Image Data from which the texture is created. May be
-	// empty if raw ImageData was used to create the texture.
-	std::vector<StrongRef<love::image::CompressedImageData>> cdata;
+	void loadData();
+	void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, const Rect &rect, int level, int slice);
+	void uploadImageData(love::image::ImageDataBase *d, int level, int slice);
 
 	// OpenGL texture identifier.
 	GLuint texture;
 
-	// Mipmap texture LOD bias (sharpness) value.
-	float mipmapSharpness;
-
 	// Whether this Image is using a compressed texture.
 	bool compressed;
 
-	bool sRGB;
-
 	// True if the image wasn't able to be properly created and it had to fall
 	// back to a default texture.
 	bool usingDefaultTexture;

+ 7 - 1
src/modules/graphics/opengl/Mesh.cpp

@@ -99,6 +99,9 @@ void Mesh::drawInstanced(love::graphics::Graphics *gfx, const love::Matrix4 &m,
 	if (instancecount > 1 && !gl.isInstancingSupported())
 		throw love::Exception("Instancing is not supported on this system.");
 
+	if (Shader::current && texture.get())
+		Shader::current->checkMainTextureType(texture->getTextureType());
+
 	gfx->flushStreamDraws();
 
 	OpenGL::TempDebugGroup debuggroup("Mesh draw");
@@ -131,7 +134,10 @@ void Mesh::drawInstanced(love::graphics::Graphics *gfx, const love::Matrix4 &m,
 
 	gl.useVertexAttribArrays(enabledattribs, instancedattribs);
 
-	gl.bindTextureToUnit(texture, 0, false);
+	if (texture.get())
+		gl.bindTextureToUnit(texture, 0, false);
+	else
+		gl.bindTextureToUnit(TEXTURE_2D, gl.getDefaultTexture(TEXTURE_2D), 0, false);
 
 	Graphics::TempTransform transform(gfx, m);
 

+ 273 - 42
src/modules/graphics/opengl/OpenGL.cpp

@@ -95,7 +95,10 @@ OpenGL::OpenGL()
 	, contextInitialized(false)
 	, pixelShaderHighpSupported(false)
 	, maxAnisotropy(1.0f)
-	, maxTextureSize(0)
+	, max2DTextureSize(0)
+	, max3DTextureSize(0)
+	, maxCubeTextureSize(0)
+	, maxTextureArrayLayers(0)
 	, maxRenderTargets(1)
 	, maxRenderbufferSamples(0)
 	, maxTextureUnits(1)
@@ -202,13 +205,18 @@ void OpenGL::setupContext()
 	}
 
 	// Initialize multiple texture unit support for shaders.
-	state.boundTextures.clear();
-	state.boundTextures.resize(maxTextureUnits, 0);
+	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
+	{
+		state.boundTextures[i].clear();
+		state.boundTextures[i].resize(maxTextureUnits, 0);
+	}
 
-	for (int i = 0; i < (int) state.boundTextures.size(); i++)
+	for (int i = 0; i < maxTextureUnits; i++)
 	{
 		glActiveTexture(GL_TEXTURE0 + i);
-		glBindTexture(GL_TEXTURE_2D, 0);
+
+		for (int j = 0; j < TEXTURE_MAX_ENUM; j++)
+			glBindTexture(getGLTextureType((TextureType) j), 0);
 	}
 
 	glActiveTexture(GL_TEXTURE0);
@@ -224,8 +232,14 @@ void OpenGL::deInitContext()
 	if (!contextInitialized)
 		return;
 
-	glDeleteTextures(1, &state.defaultTexture);
-	state.defaultTexture = 0;
+	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
+	{
+		if (state.defaultTexture[i] != 0)
+		{
+			gl.deleteTexture(state.defaultTexture[i]);
+			state.defaultTexture[i] = 0;
+		}
+	}
 
 	contextInitialized = false;
 }
@@ -285,11 +299,15 @@ void OpenGL::initOpenGLFunctions()
 			fp_glGenFramebuffers = fp_glGenFramebuffersEXT;
 			fp_glCheckFramebufferStatus = fp_glCheckFramebufferStatusEXT;
 			fp_glFramebufferTexture2D = fp_glFramebufferTexture2DEXT;
+			fp_glFramebufferTexture3D = fp_glFramebufferTexture3DEXT;
 			fp_glFramebufferRenderbuffer = fp_glFramebufferRenderbufferEXT;
 			fp_glGetFramebufferAttachmentParameteriv = fp_glGetFramebufferAttachmentParameterivEXT;
 			fp_glGenerateMipmap = fp_glGenerateMipmapEXT;
 		}
 
+		if (GLAD_VERSION_1_0 && GLAD_EXT_texture_array)
+			fp_glFramebufferTextureLayer = fp_glFramebufferTextureLayerEXT;
+
 		if (GLAD_EXT_framebuffer_blit)
 			fp_glBlitFramebuffer = fp_glBlitFramebufferEXT;
 		else if (GLAD_ANGLE_framebuffer_blit)
@@ -328,6 +346,17 @@ void OpenGL::initOpenGLFunctions()
 			fp_glVertexAttribDivisor = fp_glVertexAttribDivisorANGLE;
 		}
 	}
+
+	if (GLAD_ES_VERSION_2_0 && GLAD_OES_texture_3D && !GLAD_ES_VERSION_3_0)
+	{
+		// Function signatures don't match, we'll have to conditionally call it
+		//fp_glTexImage3D = fp_glTexImage3DOES;
+		fp_glTexSubImage3D = fp_glTexSubImage3DOES;
+		fp_glCopyTexSubImage3D = fp_glCopyTexSubImage3DOES;
+		fp_glCompressedTexImage3D = fp_glCompressedTexImage3DOES;
+		fp_glCompressedTexSubImage3D = fp_glCompressedTexSubImage3DOES;
+		fp_glFramebufferTexture3D = fp_glFramebufferTexture3DOES;
+	}
 }
 
 void OpenGL::initMaxValues()
@@ -348,7 +377,18 @@ void OpenGL::initMaxValues()
 	else
 		maxAnisotropy = 1.0f;
 
-	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max2DTextureSize);
+	glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &maxCubeTextureSize);
+
+	if (isTextureTypeSupported(TEXTURE_VOLUME))
+		glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &max3DTextureSize);
+	else
+		max3DTextureSize = 0;
+
+	if (isTextureTypeSupported(TEXTURE_2D_ARRAY))
+		glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxTextureArrayLayers);
+	else
+		maxTextureArrayLayers = 0;
 
 	int maxattachments = 1;
 	int maxdrawbuffers = 1;
@@ -382,26 +422,57 @@ void OpenGL::initMaxValues()
 
 void OpenGL::createDefaultTexture()
 {
-	// Set the 'default' texture (id 0) as a repeating white pixel. Otherwise,
-	// texture2D calls inside a shader would return black when drawing graphics
-	// primitives, which would create the need to use different "passthrough"
-	// shaders for untextured primitives vs images.
+	// Set the 'default' texture as a repeating white pixel. Otherwise, texture
+	// calls inside a shader would return black when drawing graphics primitives
+	// which would create the need to use different "passthrough" shaders for
+	// untextured primitives vs images.
+	const GLubyte pix[] = {255, 255, 255, 255};
+
+	Texture::Filter filter;
+	filter.min = filter.mag = Texture::FILTER_NEAREST;
+
+	Texture::Wrap wrap;
+	wrap.s = wrap.t = wrap.r = Texture::WRAP_CLAMP;
+
+	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
+	{
+		state.defaultTexture[i] = 0;
 
-	GLuint curtexture = state.boundTextures[state.curTextureUnit];
+		TextureType type = (TextureType) i;
 
-	glGenTextures(1, &state.defaultTexture);
-	bindTextureToUnit(state.defaultTexture, 0, false);
+		if (!isTextureTypeSupported(type))
+			continue;
 
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		GLuint curtexture = state.boundTextures[type][0];
 
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+		glGenTextures(1, &state.defaultTexture[type]);
+		bindTextureToUnit(type, state.defaultTexture[type], 0, false);
+
+		setTextureWrap(type, wrap);
+		setTextureFilter(type, filter);
+
+		bool isSRGB = false;
+		rawTexStorage(type, 1, PIXELFORMAT_RGBA8, isSRGB, 1, 1);
+
+		TextureFormat fmt = convertPixelFormat(PIXELFORMAT_RGBA8, false, isSRGB);
+
+		int slices = type == TEXTURE_CUBE ? 6 : 1;
+
+		for (int slice = 0; slice < slices; slice++)
+		{
+			GLenum gltarget = getGLTextureType(type);
 
-	GLubyte pix[] = {255, 255, 255, 255};
-	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, pix);
+			if (type == TEXTURE_CUBE)
+				gltarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice;
 
-	bindTextureToUnit(curtexture, 0, false);
+			if (type == TEXTURE_2D || type == TEXTURE_CUBE)
+				glTexSubImage2D(gltarget, 0, 0, 0, 1, 1, fmt.externalformat, fmt.type, pix);
+			else if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME)
+				glTexSubImage3D(gltarget, 0, 0, 0, slice, 1, 1, 1, fmt.externalformat, fmt.type, pix);
+		}
+
+		bindTextureToUnit(type, curtexture, 0, false);
+	}
 }
 
 void OpenGL::prepareDraw()
@@ -432,6 +503,27 @@ GLenum OpenGL::getGLBufferType(BufferType type)
 	case BUFFER_MAX_ENUM:
 		return GL_ZERO;
 	}
+
+	return GL_ZERO;
+}
+
+GLenum OpenGL::getGLTextureType(TextureType type)
+{
+	switch (type)
+	{
+	case TEXTURE_2D:
+		return GL_TEXTURE_2D;
+	case TEXTURE_VOLUME:
+		return GL_TEXTURE_3D;
+	case TEXTURE_2D_ARRAY:
+		return GL_TEXTURE_2D_ARRAY;
+	case TEXTURE_CUBE:
+		return GL_TEXTURE_CUBE_MAP;
+	case TEXTURE_MAX_ENUM:
+		return GL_ZERO;
+	}
+
+	return GL_ZERO;
 }
 
 GLenum OpenGL::getGLIndexDataType(IndexDataType type)
@@ -651,6 +743,29 @@ void OpenGL::deleteFramebuffer(GLuint framebuffer)
 	}
 }
 
+void OpenGL::framebufferTexture(GLenum attachment, TextureType texType, GLuint texture, int level, int layer, int face)
+{
+	GLenum textarget = getGLTextureType(texType);
+
+	switch (texType)
+	{
+	case TEXTURE_2D:
+		glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, textarget, texture, level);
+		break;
+	case TEXTURE_VOLUME:
+		glFramebufferTexture3D(GL_FRAMEBUFFER, attachment, textarget, texture, level, layer);
+		break;
+	case TEXTURE_2D_ARRAY:
+		glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment, texture, level, layer);
+		break;
+	case TEXTURE_CUBE:
+		glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture, level);
+		break;
+	default:
+		break;
+	}
+}
+
 void OpenGL::useProgram(GLuint program)
 {
 	glUseProgram(program);
@@ -670,9 +785,9 @@ GLuint OpenGL::getDefaultFBO() const
 #endif
 }
 
-GLuint OpenGL::getDefaultTexture() const
+GLuint OpenGL::getDefaultTexture(TextureType type) const
 {
-	return state.defaultTexture;
+	return state.defaultTexture[type];
 }
 
 void OpenGL::setTextureUnit(int textureunit)
@@ -683,16 +798,16 @@ void OpenGL::setTextureUnit(int textureunit)
 	state.curTextureUnit = textureunit;
 }
 
-void OpenGL::bindTextureToUnit(GLuint texture, int textureunit, bool restoreprev)
+void OpenGL::bindTextureToUnit(TextureType target, GLuint texture, int textureunit, bool restoreprev)
 {
-	if (texture != state.boundTextures[textureunit])
+	if (texture != state.boundTextures[target][textureunit])
 	{
 		int oldtextureunit = state.curTextureUnit;
 		if (oldtextureunit != textureunit)
 			glActiveTexture(GL_TEXTURE0 + textureunit);
 
-		state.boundTextures[textureunit] = texture;
-		glBindTexture(GL_TEXTURE_2D, texture);
+		state.boundTextures[target][textureunit] = texture;
+		glBindTexture(getGLTextureType(target), texture);
 
 		if (restoreprev && oldtextureunit != textureunit)
 			glActiveTexture(GL_TEXTURE0 + oldtextureunit);
@@ -703,24 +818,29 @@ void OpenGL::bindTextureToUnit(GLuint texture, int textureunit, bool restoreprev
 
 void OpenGL::bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev)
 {
-	GLuint handle = texture != nullptr ? (GLuint) texture->getHandle() : getDefaultTexture();
-	bindTextureToUnit(handle, textureunit, restoreprev);
+	GLuint handle = texture != nullptr ? (GLuint) texture->getHandle() : getDefaultTexture(TEXTURE_2D);
+	TextureType textype = texture != nullptr ? texture->getTextureType() : TEXTURE_2D;
+
+	bindTextureToUnit(textype, handle, textureunit, restoreprev);
 }
 
 void OpenGL::deleteTexture(GLuint texture)
 {
 	// glDeleteTextures binds texture 0 to all texture units the deleted texture
 	// was bound to before deletion.
-	for (GLuint &texid : state.boundTextures)
+	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
 	{
-		if (texid == texture)
-			texid = 0;
+		for (GLuint &texid : state.boundTextures[i])
+		{
+			if (texid == texture)
+				texid = 0;
+		}
 	}
 
 	glDeleteTextures(1, &texture);
 }
 
-void OpenGL::setTextureFilter(graphics::Texture::Filter &f)
+void OpenGL::setTextureFilter(TextureType target, graphics::Texture::Filter &f)
 {
 	GLint gmin, gmag;
 
@@ -756,13 +876,15 @@ void OpenGL::setTextureFilter(graphics::Texture::Filter &f)
 		break;
 	}
 
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gmin);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gmag);
+	GLenum gltarget = getGLTextureType(target);
+
+	glTexParameteri(gltarget, GL_TEXTURE_MIN_FILTER, gmin);
+	glTexParameteri(gltarget, GL_TEXTURE_MAG_FILTER, gmag);
 
 	if (GLAD_EXT_texture_filter_anisotropic)
 	{
 		f.anisotropy = std::min(std::max(f.anisotropy, 1.0f), maxAnisotropy);
-		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, f.anisotropy);
+		glTexParameterf(gltarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, f.anisotropy);
 	}
 	else
 		f.anisotropy = 1.0f;
@@ -785,10 +907,104 @@ GLint OpenGL::getGLWrapMode(Texture::WrapMode wmode)
 
 }
 
-void OpenGL::setTextureWrap(const graphics::Texture::Wrap &w)
+void OpenGL::setTextureWrap(TextureType target, const graphics::Texture::Wrap &w)
+{
+	glTexParameteri(getGLTextureType(target), GL_TEXTURE_WRAP_S, getGLWrapMode(w.s));
+	glTexParameteri(getGLTextureType(target), GL_TEXTURE_WRAP_T, getGLWrapMode(w.t));
+
+	if (target == TEXTURE_VOLUME)
+		glTexParameteri(getGLTextureType(target), GL_TEXTURE_WRAP_R, getGLWrapMode(w.r));
+}
+
+bool OpenGL::rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth)
+{
+	GLenum gltarget = getGLTextureType(target);
+	TextureFormat fmt = convertPixelFormat(pixelformat, false, isSRGB);
+
+	if (fmt.swizzled)
+	{
+		glTexParameteri(gltarget, GL_TEXTURE_SWIZZLE_R, fmt.swizzle[0]);
+		glTexParameteri(gltarget, GL_TEXTURE_SWIZZLE_G, fmt.swizzle[1]);
+		glTexParameteri(gltarget, GL_TEXTURE_SWIZZLE_B, fmt.swizzle[2]);
+		glTexParameteri(gltarget, GL_TEXTURE_SWIZZLE_A, fmt.swizzle[3]);
+	}
+
+	bool supportsTexStorage = GLAD_VERSION_4_2 || GLAD_ARB_texture_storage;
+
+	// Apparently there are bugs with glTexStorage on some Android drivers. I'd
+	// rather not find out the hard way, so we'll avoid it for now...
+#ifndef LOVE_ANDROID
+	if (GLAD_ES_VERSION_3_0)
+		supportsTexStorage = true;
+#endif
+
+	if (supportsTexStorage)
+	{
+		if (target == TEXTURE_2D || target == TEXTURE_CUBE)
+			glTexStorage2D(gltarget, levels, fmt.internalformat, width, height);
+		else if (target == TEXTURE_VOLUME || target == TEXTURE_2D_ARRAY)
+			glTexStorage3D(gltarget, levels, fmt.internalformat, width, height, depth);
+	}
+	else
+	{
+		int w = width;
+		int h = height;
+		int d = depth;
+
+		for (int level = 0; level < levels; level++)
+		{
+			if (target == TEXTURE_2D || target == TEXTURE_CUBE)
+			{
+				int faces = target == TEXTURE_CUBE ? 6 : 1;
+				for (int face = 0; face < faces; face++)
+				{
+					if (target == TEXTURE_CUBE)
+						gltarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
+
+					glTexImage2D(gltarget, level, fmt.internalformat, w, h, 0,
+					             fmt.externalformat, fmt.type, nullptr);
+				}
+			}
+			else if (target == TEXTURE_2D_ARRAY || target == TEXTURE_VOLUME)
+			{
+				if (target == TEXTURE_VOLUME && GLAD_ES_VERSION_2_0 && GLAD_OES_texture_3D && !GLAD_ES_VERSION_3_0)
+				{
+					glTexImage3DOES(gltarget, level, fmt.internalformat, w, h,
+					                d, 0, fmt.externalformat, fmt.type, nullptr);
+				}
+				else
+				{
+					glTexImage3D(gltarget, level, fmt.internalformat, w, h, d,
+					             0, fmt.externalformat, fmt.type, nullptr);
+				}
+			}
+
+			w = std::max(w / 2, 1);
+			h = std::max(h / 2, 1);
+
+			if (target == TEXTURE_VOLUME)
+				d = std::max(d / 2, 1);
+		}
+	}
+
+	return gltarget != GL_ZERO;
+}
+
+bool OpenGL::isTextureTypeSupported(TextureType type) const
 {
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, getGLWrapMode(w.s));
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, getGLWrapMode(w.t));
+	switch (type)
+	{
+	case TEXTURE_2D:
+		return true;
+	case TEXTURE_VOLUME:
+		return GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_3D;
+	case TEXTURE_2D_ARRAY:
+		return GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_EXT_texture_array;
+	case TEXTURE_CUBE:
+		return GLAD_VERSION_1_3 || GLAD_ES_VERSION_2_0;
+	default:
+		return false;
+	}
 }
 
 bool OpenGL::isClampZeroTextureWrapSupported() const
@@ -807,9 +1023,24 @@ bool OpenGL::isInstancingSupported() const
 		|| GLAD_ARB_instanced_arrays || GLAD_EXT_instanced_arrays || GLAD_ANGLE_instanced_arrays;
 }
 
-int OpenGL::getMaxTextureSize() const
+int OpenGL::getMax2DTextureSize() const
+{
+	return std::max(max2DTextureSize, 1);
+}
+
+int OpenGL::getMax3DTextureSize() const
+{
+	return std::max(max3DTextureSize, 1);
+}
+
+int OpenGL::getMaxCubeTextureSize() const
+{
+	return std::max(maxCubeTextureSize, 1);
+}
+
+int OpenGL::getMaxTextureLayers() const
 {
-	return maxTextureSize;
+	return std::max(maxTextureArrayLayers, 1);
 }
 
 int OpenGL::getMaxRenderTargets() const

+ 25 - 8
src/modules/graphics/opengl/OpenGL.h

@@ -248,6 +248,8 @@ public:
 	GLuint getFramebuffer(FramebufferTarget target) const;
 	void deleteFramebuffer(GLuint framebuffer);
 
+	void framebufferTexture(GLenum attachment, TextureType texType, GLuint texture, int level, int layer = 0, int face = 0);
+
 	/**
 	 * Calls glUseProgram.
 	 **/
@@ -262,7 +264,7 @@ public:
 	/**
 	 * Gets the ID for love's default texture (used for "untextured" primitives.)
 	 **/
-	GLuint getDefaultTexture() const;
+	GLuint getDefaultTexture(TextureType type) const;
 
 	/**
 	 * Helper for setting the active texture unit.
@@ -277,7 +279,7 @@ public:
 	 * @param textureunit Index in the range of [0, maxtextureunits-1]
 	 * @param restoreprev Restore previously bound texture unit when done.
 	 **/
-	void bindTextureToUnit(GLuint texture, int textureunit, bool restoreprev);
+	void bindTextureToUnit(TextureType target, GLuint texture, int textureunit, bool restoreprev);
 	void bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev);
 
 	/**
@@ -291,13 +293,21 @@ public:
 	 * The anisotropy parameter of the argument is set to the actual amount of
 	 * anisotropy that was used.
 	 **/
-	void setTextureFilter(graphics::Texture::Filter &f);
+	void setTextureFilter(TextureType target, graphics::Texture::Filter &f);
 
 	/**
 	 * Sets the texture wrap mode for the currently bound texture.
 	 **/
-	void setTextureWrap(const graphics::Texture::Wrap &w);
+	void setTextureWrap(TextureType target, const graphics::Texture::Wrap &w);
+
+	/**
+	 * Equivalent to glTexStorage2D/3D on platforms that support it. Equivalent
+	 * to glTexImage2D/3D for all levels and slices of a texture otherwise.
+	 * NOTE: this does not handle compressed texture formats.
+	 **/
+	bool rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth = 1);
 
+	bool isTextureTypeSupported(TextureType type) const;
 	bool isClampZeroTextureWrapSupported() const;
 	bool isPixelShaderHighpSupported() const;
 	bool isInstancingSupported() const;
@@ -305,7 +315,10 @@ public:
 	/**
 	 * Returns the maximum supported width or height of a texture.
 	 **/
-	int getMaxTextureSize() const;
+	int getMax2DTextureSize() const;
+	int getMax3DTextureSize() const;
+	int getMaxCubeTextureSize() const;
+	int getMaxTextureLayers() const;
 
 	/**
 	 * Returns the maximum supported number of simultaneous render targets.
@@ -349,6 +362,7 @@ public:
 	static GLenum getGLBufferType(BufferType type);
 	static GLenum getGLIndexDataType(IndexDataType type);
 	static GLenum getGLBufferUsage(vertex::Usage usage);
+	static GLenum getGLTextureType(TextureType type);
 	static GLint getGLWrapMode(Texture::WrapMode wmode);
 
 	static TextureFormat convertPixelFormat(PixelFormat pixelformat, bool renderbuffer, bool &isSRGB);
@@ -374,7 +388,10 @@ private:
 
 	bool pixelShaderHighpSupported;
 	float maxAnisotropy;
-	int maxTextureSize;
+	int max2DTextureSize;
+	int max3DTextureSize;
+	int maxCubeTextureSize;
+	int maxTextureArrayLayers;
 	int maxRenderTargets;
 	int maxRenderbufferSamples;
 	int maxTextureUnits;
@@ -390,7 +407,7 @@ private:
 		GLuint boundBuffers[BUFFER_MAX_ENUM];
 
 		// Texture unit state (currently bound texture for each texture unit.)
-		std::vector<GLuint> boundTextures;
+		std::vector<GLuint> boundTextures[TEXTURE_MAX_ENUM];
 
 		// Currently active texture unit.
 		int curTextureUnit;
@@ -410,7 +427,7 @@ private:
 
 		bool framebufferSRGBEnabled;
 
-		GLuint defaultTexture;
+		GLuint defaultTexture[TEXTURE_MAX_ENUM];
 
 	} state;
 

+ 3 - 0
src/modules/graphics/opengl/ParticleSystem.cpp

@@ -55,6 +55,9 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
 	if (!prepareDraw(gfx, m))
 		return;
 
+	if (Shader::current && texture.get())
+		Shader::current->checkMainTextureType(texture->getTextureType());
+
 	OpenGL::TempDebugGroup debuggroup("ParticleSystem draw");
 
 	gl.bindTextureToUnit(texture, 0, false);

+ 141 - 138
src/modules/graphics/opengl/Shader.cpp

@@ -40,11 +40,11 @@ Shader::Shader(const ShaderSource &source)
 	: love::graphics::Shader(source)
 	, program(0)
 	, builtinUniforms()
+	, builtinUniformInfo()
 	, builtinAttributes()
 	, canvasWasActive(false)
 	, lastViewport()
 	, lastPointSize(0.0f)
-	, videoTextureUnits()
 {
 	// load shader source and create program object
 	loadVolatile();
@@ -142,7 +142,10 @@ void Shader::mapActiveUniforms()
 {
 	// Built-in uniform locations default to -1 (nonexistent.)
 	for (int i = 0; i < int(BUILTIN_MAX_ENUM); i++)
+	{
 		builtinUniforms[i] = -1;
+		builtinUniformInfo[i] = nullptr;
+	}
 
 	GLint activeprogram = 0;
 	glGetIntegerv(GL_CURRENT_PROGRAM, &activeprogram);
@@ -158,17 +161,18 @@ void Shader::mapActiveUniforms()
 	std::map<std::string, UniformInfo> olduniforms = uniforms;
 	uniforms.clear();
 
-	for (int i = 0; i < numuniforms; i++)
+	for (int uindex = 0; uindex < numuniforms; uindex++)
 	{
 		GLsizei namelen = 0;
 		GLenum gltype = 0;
 		UniformInfo u = {};
 
-		glGetActiveUniform(program, (GLuint) i, bufsize, &namelen, &u.count, &gltype, cname);
+		glGetActiveUniform(program, (GLuint) uindex, bufsize, &namelen, &u.count, &gltype, cname);
 
 		u.name = std::string(cname, (size_t) namelen);
 		u.location = glGetUniformLocation(program, u.name.c_str());
 		u.baseType = getUniformBaseType(gltype);
+		u.textureType = getUniformTextureType(gltype);
 
 		if (u.baseType == UNIFORM_MATRIX)
 			u.matrix = getMatrixSize(gltype);
@@ -184,13 +188,24 @@ void Shader::mapActiveUniforms()
 		}
 
 		// If this is a built-in (LOVE-created) uniform, store the location.
-		BuiltinUniform builtin;
+		BuiltinUniform builtin = BUILTIN_MAX_ENUM;
 		if (getConstant(u.name.c_str(), builtin))
 			builtinUniforms[int(builtin)] = u.location;
 
 		if (u.location == -1)
 			continue;
 
+		if (u.baseType == UNIFORM_SAMPLER && builtin != BUILTIN_TEXTURE_MAIN)
+		{
+			TextureUnit unit;
+			unit.type = u.textureType;
+			unit.active = true;
+			unit.texture = gl.getDefaultTexture(u.textureType);
+
+			for (int i = 0; i < u.count; i++)
+				textureUnits.push_back(unit);
+		}
+
 		// Make sure previously set uniform data is preserved, and shader-
 		// initialized values are retrieved.
 		auto oldu = olduniforms.find(u.name);
@@ -200,23 +215,6 @@ void Shader::mapActiveUniforms()
 			u.textures = oldu->second.textures;
 
 			updateUniform(&u, u.count, true);
-
-			if (u.baseType == UNIFORM_SAMPLER)
-			{
-				// Make sure all stored textures have their Volatiles loaded
-				// before the sendTextures call, since it calls getHandle().
-				for (int i = 0; i < u.count; i++)
-				{
-					if (u.textures[i] == nullptr)
-						continue;
-
-					Volatile *v = dynamic_cast<Volatile *>(u.textures[i]);
-					if (v != nullptr)
-						v->loadVolatile();
-				}
-
-				sendTextures(&u, u.textures, u.count, true);
-			}
 		}
 		else
 		{
@@ -252,9 +250,14 @@ void Shader::mapActiveUniforms()
 
 				if (u.baseType == UNIFORM_SAMPLER)
 				{
-					// Initialize all samplers to 0. Both GLSL and GLSL ES are
-					// supposed to do this themselves, but some Android devices
-					// (galaxy tab 3 and 4) don't seem to do it...
+					int startunit = (int) textureUnits.size() - u.count;
+
+					if (builtin == BUILTIN_TEXTURE_MAIN)
+						startunit = 0;
+
+					for (int i = 0; i < u.count; i++)
+						u.ints[i] = startunit + i;
+
 					glUniform1iv(u.location, u.count, u.ints);
 
 					u.textures = new Texture*[u.count];
@@ -307,6 +310,26 @@ void Shader::mapActiveUniforms()
 		}
 
 		uniforms[u.name] = u;
+
+		if (builtin != BUILTIN_MAX_ENUM)
+			builtinUniformInfo[(int)builtin] = &uniforms[u.name];
+
+		if (u.baseType == UNIFORM_SAMPLER)
+		{
+			// Make sure all stored textures have their Volatiles loaded before
+			// the sendTextures call, since it calls getHandle().
+			for (int i = 0; i < u.count; i++)
+			{
+				if (u.textures[i] == nullptr)
+					continue;
+
+				Volatile *v = dynamic_cast<Volatile *>(u.textures[i]);
+				if (v != nullptr)
+					v->loadVolatile();
+			}
+
+			sendTextures(&u, u.textures, u.count, true);
+		}
 	}
 
 	// Make sure uniforms that existed before but don't exist anymore are
@@ -348,12 +371,9 @@ bool Shader::loadVolatile()
 	lastProjectionMatrix.setTranslation(nan, nan);
 	lastTransformMatrix.setTranslation(nan, nan);
 
-	for (int i = 0; i < 3; i++)
-		videoTextureUnits[i] = 0;
-
 	// zero out active texture list
 	textureUnits.clear();
-	textureUnits.resize(gl.getMaxTextureUnits(), TextureUnit());
+	textureUnits.push_back(TextureUnit());
 
 	std::vector<GLuint> shaderids;
 
@@ -449,7 +469,7 @@ void Shader::unloadVolatile()
 
 	// active texture list is probably invalid, clear it
 	textureUnits.clear();
-	textureUnits.resize(gl.getMaxTextureUnits(), TextureUnit());
+	textureUnits.push_back(TextureUnit());
 
 	attributes.clear();
 
@@ -497,7 +517,7 @@ std::string Shader::getWarnings() const
 	return warnings;
 }
 
-void Shader::attach(bool temporary)
+void Shader::attach()
 {
 	if (current != this)
 	{
@@ -505,22 +525,19 @@ void Shader::attach(bool temporary)
 		current = this;
 		// retain/release happens in Graphics::setShader.
 
-		if (!temporary)
+		// Make sure all textures are bound to their respective texture units.
+		for (int i = 0; i < (int) textureUnits.size(); ++i)
 		{
-			// Make sure all textures are properly bound to their respective
-			// texture units.
-			for (int i = 1; i < (int) textureUnits.size(); ++i)
-			{
-				if (textureUnits[i].active)
-					gl.bindTextureToUnit(textureUnits[i].texture, i, false);
-			}
+			const TextureUnit &unit = textureUnits[i];
+			if (unit.active)
+				gl.bindTextureToUnit(unit.type, unit.texture, i, false);
+		}
 
-			// send any pending uniforms to the shader program.
-			for (const auto &p : pendingUniformUpdates)
-				updateUniform(p.first, p.second);
+		// send any pending uniforms to the shader program.
+		for (const auto &p : pendingUniformUpdates)
+			updateUniform(p.first, p.second, true);
 
-			pendingUniformUpdates.clear();
-		}
+		pendingUniformUpdates.clear();
 	}
 }
 
@@ -534,15 +551,25 @@ const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
 	return &(it->second);
 }
 
-void Shader::updateUniform(const UniformInfo *info, int count, bool internalUpdate)
+const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
 {
-	if (current != this)
+	return builtinUniformInfo[(int)builtin];
+}
+
+void Shader::updateUniform(const UniformInfo *info, int count)
+{
+	updateUniform(info, count, false);
+}
+
+void Shader::updateUniform(const UniformInfo *info, int count, bool internalupdate)
+{
+	if (current != this && !internalupdate)
 	{
 		pendingUniformUpdates.push_back(std::make_pair(info, count));
 		return;
 	}
 
-	if (!internalUpdate)
+	if (!internalupdate)
 		flushStreamDraws();
 
 	int location = info->location;
@@ -628,24 +655,9 @@ void Shader::updateUniform(const UniformInfo *info, int count, bool internalUpda
 	}
 }
 
-int Shader::getFreeTextureUnits(int count)
+void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count)
 {
-	int startunit = -1;
-
-	// Ignore the first texture unit for Shader-local texture bindings.
-	for (int i = 1; i < (int) textureUnits.size(); i++)
-	{
-		if (!textureUnits[i].active && i + count <= (int) textureUnits.size())
-		{
-			startunit = i;
-			break;
-		}
-	}
-
-	if (startunit == -1)
-		throw love::Exception("No more texture units available for shader.");
-
-	return startunit;
+	Shader::sendTextures(info, textures, count, false);
 }
 
 void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalUpdate)
@@ -659,55 +671,36 @@ void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count
 		flushStreamDraws();
 
 	count = std::min(count, info->count);
-	bool updateuniform = false;
-
-	// Make sure the shader's samplers are associated with texture units.
-	for (int i = 0; i < count; i++)
-	{
-		if (info->ints[i] == 0 && textures[i] != nullptr)
-		{
-			int texunit = getFreeTextureUnits(1);
-			textureUnits[texunit].active = true;
-
-			info->ints[i] = texunit;
-			updateuniform = true;
-		}
-	}
-
-	if (updateuniform)
-		updateUniform(info, count, internalUpdate);
 
 	// Bind the textures to the texture units.
 	for (int i = 0; i < count; i++)
 	{
 		if (textures[i] != nullptr)
+		{
+			if (textures[i]->getTextureType() != info->textureType)
+				continue;
+
 			textures[i]->retain();
+		}
 
 		if (info->textures[i] != nullptr)
 			info->textures[i]->release();
 
 		info->textures[i] = textures[i];
 
-		int texunit = info->ints[i];
-
+		GLuint gltex = 0;
 		if (textures[i] != nullptr)
-		{
-			GLuint gltex = (GLuint) textures[i]->getHandle();
+			gltex = (GLuint) textures[i]->getHandle();
+		else
+			gltex = gl.getDefaultTexture(info->textureType);
 
-			if (shaderactive)
-				gl.bindTextureToUnit(gltex, texunit, false);
+		int texunit = info->ints[i];
 
-			// Store texture id so it can be re-bound to the texture unit later.
-			textureUnits[texunit].texture = gltex;
-		}
-		else
-		{
-			if (shaderactive)
-				gl.bindTextureToUnit((GLuint) 0, texunit, false);
+		if (shaderactive)
+			gl.bindTextureToUnit(info->textureType, gltex, texunit, false);
 
-			textureUnits[texunit].texture = 0;
-			textureUnits[texunit].active = false;
-		}
+		// Store texture id so it can be re-bound to the texture unit later.
+		textureUnits[texunit].texture = gltex;
 	}
 }
 
@@ -743,50 +736,22 @@ GLint Shader::getAttribLocation(const std::string &name)
 	return location;
 }
 
-void Shader::setVideoTextures(ptrdiff_t ytexture, ptrdiff_t cbtexture, ptrdiff_t crtexture)
+void Shader::setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture)
 {
-	// Set up the texture units that will be used by the shader to sample from
-	// the textures, if they haven't been set up yet.
-	if (videoTextureUnits[0] == 0)
-	{
-		const BuiltinUniform builtins[3] = {
-			BUILTIN_TEXTURE_VIDEO_Y,
-			BUILTIN_TEXTURE_VIDEO_CB,
-			BUILTIN_TEXTURE_VIDEO_CR,
-		};
-
-		for (int i = 0; i < 3; i++)
-		{
-			GLint loc = builtinUniforms[builtins[i]];
-			const char *name = nullptr;;
-
-			if (loc >= 0 && getConstant(builtins[i], name) && name != nullptr)
-			{
-				const UniformInfo *info = getUniformInfo(name);
-				if (info == nullptr)
-					continue;
-
-				videoTextureUnits[i] = getFreeTextureUnits(1);
-				textureUnits[videoTextureUnits[i]].active = true;
-
-				info->ints[0] = videoTextureUnits[i];
-				updateUniform(info, 1);
-			}
-		}
-	}
+	const BuiltinUniform builtins[3] = {
+		BUILTIN_TEXTURE_VIDEO_Y,
+		BUILTIN_TEXTURE_VIDEO_CB,
+		BUILTIN_TEXTURE_VIDEO_CR,
+	};
 
-	const GLuint textures[3] = {(GLuint) ytexture, (GLuint) cbtexture, (GLuint) crtexture};
+	Texture *textures[3] = {ytexture, cbtexture, crtexture};
 
-	// Bind the textures to their respective texture units.
 	for (int i = 0; i < 3; i++)
 	{
-		if (videoTextureUnits[i] != 0)
-		{
-			// Store texture id so it can be re-bound later.
-			textureUnits[videoTextureUnits[i]].texture = textures[i];
-			if (current == this)
-				gl.bindTextureToUnit(textures[i], videoTextureUnits[i], false);
-		}
+		const UniformInfo *info = builtinUniformInfo[builtins[i]];
+
+		if (info != nullptr)
+			sendTextures(info, &textures[i], 1);
 	}
 }
 
@@ -925,15 +890,15 @@ bool Shader::isSupported()
 
 int Shader::getUniformTypeComponents(GLenum type) const
 {
+	if (getUniformBaseType(type) == UNIFORM_SAMPLER)
+		return 1;
+
 	switch (type)
 	{
 	case GL_INT:
 	case GL_UNSIGNED_INT:
 	case GL_FLOAT:
 	case GL_BOOL:
-	case GL_SAMPLER_1D:
-	case GL_SAMPLER_2D:
-	case GL_SAMPLER_3D:
 		return 1;
 	case GL_INT_VEC2:
 	case GL_UNSIGNED_INT_VEC2:
@@ -1059,6 +1024,44 @@ Shader::UniformType Shader::getUniformBaseType(GLenum type) const
 	}
 }
 
+TextureType Shader::getUniformTextureType(GLenum type) const
+{
+	switch (type)
+	{
+	case GL_SAMPLER_1D:
+	case GL_SAMPLER_1D_SHADOW:
+	case GL_SAMPLER_1D_ARRAY:
+	case GL_SAMPLER_1D_ARRAY_SHADOW:
+		// 1D-typed textures are not supported.
+		return TEXTURE_MAX_ENUM;
+	case GL_SAMPLER_2D:
+	//case GL_SAMPLER_2D_SHADOW:
+		return TEXTURE_2D;
+	case GL_SAMPLER_2D_MULTISAMPLE:
+	case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
+		// Multisample textures are not supported.
+		return TEXTURE_MAX_ENUM;
+	case GL_SAMPLER_2D_RECT:
+	case GL_SAMPLER_2D_RECT_SHADOW:
+		// Rectangle textures are not supported.
+		return TEXTURE_MAX_ENUM;
+	case GL_SAMPLER_2D_ARRAY:
+	//case GL_SAMPLER_2D_ARRAY_SHADOW:
+		return TEXTURE_2D_ARRAY;
+	case GL_SAMPLER_3D:
+		return TEXTURE_VOLUME;
+	case GL_SAMPLER_CUBE:
+	//case GL_SAMPLER_CUBE_SHADOW:
+		return TEXTURE_CUBE;
+	case GL_SAMPLER_CUBE_MAP_ARRAY:
+	case GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW:
+		// Cubemap array textures are not supported.
+		return TEXTURE_MAX_ENUM;
+	default:
+		return TEXTURE_MAX_ENUM;
+	}
+}
+
 } // opengl
 } // graphics
 } // love

+ 11 - 8
src/modules/graphics/opengl/Shader.h

@@ -56,14 +56,15 @@ public:
 	void unloadVolatile() override;
 
 	// Implements Shader.
-	void attach(bool temporary = false) override;
+	void attach() override;
 	std::string getWarnings() const override;
 	const UniformInfo *getUniformInfo(const std::string &name) const override;
-	void updateUniform(const UniformInfo *info, int count, bool internalUpdate = false) override;
-	void sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalUpdate = false) override;
+	const UniformInfo *getUniformInfo(BuiltinUniform builtin) const override;
+	void updateUniform(const UniformInfo *info, int count) override;
+	void sendTextures(const UniformInfo *info, Texture **textures, int count) override;
 	bool hasUniform(const std::string &name) const override;
 	ptrdiff_t getHandle() const override;
-	void setVideoTextures(ptrdiff_t ytexture, ptrdiff_t cbtexture, ptrdiff_t crtexture) override;
+	void setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture) override;
 
 	GLint getAttribLocation(const std::string &name);
 
@@ -79,20 +80,23 @@ private:
 	struct TextureUnit
 	{
 		GLuint texture = 0;
+		TextureType type = TEXTURE_2D;
 		bool active = false;
 	};
 
 	// Map active uniform names to their locations.
 	void mapActiveUniforms();
 
+	void updateUniform(const UniformInfo *info, int count, bool internalupdate);
+	void sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalupdate);
+
 	int getUniformTypeComponents(GLenum type) const;
 	MatrixSize getMatrixSize(GLenum type) const;
 	UniformType getUniformBaseType(GLenum type) const;
+	TextureType getUniformTextureType(GLenum type) const;
 
 	GLuint compileCode(ShaderStage stage, const std::string &code);
 
-	int getFreeTextureUnits(int count);
-
 	void flushStreamDraws() const;
 
 	// Get any warnings or errors generated only by the shader program object.
@@ -106,6 +110,7 @@ private:
 
 	// Location values for any built-in uniform variables.
 	GLint builtinUniforms[BUILTIN_MAX_ENUM];
+	UniformInfo *builtinUniformInfo[BUILTIN_MAX_ENUM];
 
 	// Location values for any generic vertex attribute variables.
 	GLint builtinAttributes[ATTRIB_MAX_ENUM];
@@ -128,8 +133,6 @@ private:
 	Matrix4 lastTransformMatrix;
 	Matrix4 lastProjectionMatrix;
 
-	GLuint videoTextureUnits[3];
-
 }; // Shader
 
 } // opengl

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

@@ -53,38 +53,37 @@ SpriteBatch::~SpriteBatch()
 
 void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 {
-	const size_t pos_offset   = offsetof(Vertex, x);
-	const size_t texel_offset = offsetof(Vertex, s);
-	const size_t color_offset = offsetof(Vertex, color.r);
-
 	if (next == 0)
 		return;
 
 	gfx->flushStreamDraws();
 
+	if (Shader::current && texture.get())
+		Shader::current->checkMainTextureType(texture->getTextureType());
+
 	OpenGL::TempDebugGroup debuggroup("SpriteBatch draw");
 
 	Graphics::TempTransform transform(gfx, m);
 
 	gl.bindTextureToUnit(texture, 0, false);
 
-	uint32 enabledattribs = ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
-
 	// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
 	array_buf->unmap();
 
 	gl.bindBuffer(BUFFER_VERTEX, (GLuint) array_buf->getHandle());
 
+	uint32 enabledattribs = ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
+
+	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(offsetof(Vertex, x)));
+	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(offsetof(Vertex, s)));
+
 	// Apply per-sprite color, if a color is set.
 	if (color)
 	{
 		enabledattribs |= ATTRIBFLAG_COLOR;
-		glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), BUFFER_OFFSET(color_offset));
+		glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), BUFFER_OFFSET(offsetof(Vertex, color.r)));
 	}
 
-	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(pos_offset));
-	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(texel_offset));
-
 	for (const auto &it : attached_attributes)
 	{
 		Mesh *mesh = it.second.mesh.get();

+ 12 - 13
src/modules/graphics/opengl/Text.cpp

@@ -45,6 +45,9 @@ void Text::draw(Graphics *gfx, const Matrix4 &m)
 	if (vbo == nullptr || draw_commands.empty())
 		return;
 
+	if (Shader::current)
+		Shader::current->checkMainTextureType(TEXTURE_2D);
+
 	gfx->flushStreamDraws();
 
 	OpenGL::TempDebugGroup debuggroup("Text object draw");
@@ -60,28 +63,24 @@ void Text::draw(Graphics *gfx, const Matrix4 &m)
 	if ((size_t) totalverts / 4 > quadIndices.getSize())
 		quadIndices = QuadIndices(gfx, (size_t) totalverts / 4);
 
-	const size_t pos_offset   = offsetof(Font::GlyphVertex, x);
-	const size_t tex_offset   = offsetof(Font::GlyphVertex, s);
-	const size_t color_offset = offsetof(Font::GlyphVertex, color.r);
-	const size_t stride = sizeof(Font::GlyphVertex);
-
-	const GLenum gltype = OpenGL::getGLIndexDataType(quadIndices.getType());
-	const size_t elemsize = quadIndices.getElementSize();
+	vbo->unmap(); // Make sure all pending data is flushed to the GPU.
 
 	Graphics::TempTransform transform(gfx, m);
 
 	gl.prepareDraw();
 
-	vbo->unmap(); // Make sure all pending data is flushed to the GPU.
+	size_t stride = sizeof(Font::GlyphVertex);
 
 	gl.bindBuffer(BUFFER_VERTEX, (GLuint) vbo->getHandle());
-
-	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(pos_offset));
-	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, BUFFER_OFFSET(tex_offset));
-	glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, BUFFER_OFFSET(color_offset));
+	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offsetof(Font::GlyphVertex, x)));
+	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, BUFFER_OFFSET(offsetof(Font::GlyphVertex, s)));
+	glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, BUFFER_OFFSET(offsetof(Font::GlyphVertex, color.r)));
 
 	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR);
 
+	const GLenum gltype = OpenGL::getGLIndexDataType(quadIndices.getType());
+	const size_t elemsize = quadIndices.getElementSize();
+
 	gl.bindBuffer(BUFFER_INDEX, (GLuint) quadIndices.getBuffer()->getHandle());
 
 	// We need a separate draw call for every section of the text which uses a
@@ -92,7 +91,7 @@ void Text::draw(Graphics *gfx, const Matrix4 &m)
 		size_t offset = (cmd.startvertex / 4) * 6 * elemsize;
 
 		// TODO: Use glDrawElementsBaseVertex when supported?
-		gl.bindTextureToUnit((GLuint) cmd.texture, 0, false);
+		gl.bindTextureToUnit(cmd.texture, 0, false);
 
 		gl.drawElements(GL_TRIANGLES, count, gltype, BUFFER_OFFSET(offset));
 	}

+ 0 - 119
src/modules/graphics/opengl/Video.cpp

@@ -1,119 +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 "Video.h"
-
-namespace love
-{
-namespace graphics
-{
-namespace opengl
-{
-
-Video::Video(love::video::VideoStream *stream, float pixeldensity)
-	: love::graphics::Video(stream, pixeldensity)
-{
-	loadVolatile();
-}
-
-Video::~Video()
-{
-	unloadVolatile();
-}
-
-bool Video::loadVolatile()
-{
-	GLuint textures[3];
-	glGenTextures(3, textures);
-
-	for (int i = 0; i < 3; i++)
-		textureHandles[i] = textures[i];
-
-	// Create the textures using the initial frame data.
-	auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer();
-
-	int widths[3]  = {frame->yw, frame->cw, frame->cw};
-	int heights[3] = {frame->yh, frame->ch, frame->ch};
-
-	const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane};
-
-	Texture::Wrap wrap; // Clamp wrap mode.
-
-	bool srgb = false;
-	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(PIXELFORMAT_R8, false, srgb);
-
-	for (int i = 0; i < 3; i++)
-	{
-		gl.bindTextureToUnit(textures[i], 0, false);
-
-		gl.setTextureFilter(filter);
-		gl.setTextureWrap(wrap);
-
-		glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, widths[i], heights[i],
-		             0, fmt.externalformat, fmt.type, data[i]);
-	}
-
-	return true;
-}
-
-void Video::unloadVolatile()
-{
-	for (int i = 0; i < 3; i++)
-	{
-		gl.deleteTexture((GLuint) textureHandles[i]);
-		textureHandles[i] = 0;
-	}
-}
-
-void Video::uploadFrame(const love::video::VideoStream::Frame *frame)
-{
-	int widths[3]  = {frame->yw, frame->cw, frame->cw};
-	int heights[3] = {frame->yh, frame->ch, frame->ch};
-
-	const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane};
-
-	bool srgb = false;
-	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(PIXELFORMAT_R8, false, srgb);
-
-	for (int i = 0; i < 3; i++)
-	{
-		gl.bindTextureToUnit((GLuint) textureHandles[i], 0, false);
-		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, widths[i], heights[i],
-		                fmt.externalformat, fmt.type, data[i]);
-	}
-}
-
-void Video::setFilter(const Texture::Filter &f)
-{
-	if (!Texture::validateFilter(f, false))
-		throw love::Exception("Invalid texture filter.");
-
-	filter = f;
-
-	for (int i = 0; i < 3; i++)
-	{
-		gl.bindTextureToUnit((GLuint) textureHandles[i], 0, false);
-		gl.setTextureFilter(filter);
-	}
-}
-
-} // opengl
-} // graphics
-} // love

+ 42 - 28
src/modules/graphics/wrap_Canvas.cpp

@@ -31,18 +31,6 @@ Canvas *luax_checkcanvas(lua_State *L, int idx)
 	return luax_checktype<Canvas>(L, idx);
 }
 
-int w_Canvas_getFormat(lua_State *L)
-{
-	Canvas *canvas = luax_checkcanvas(L, 1);
-	PixelFormat format = canvas->getPixelFormat();
-	const char *str;
-	if (!getConstant(format, str))
-		return luaL_error(L, "Unknown pixel format.");
-
-	lua_pushstring(L, str);
-	return 1;
-}
-
 int w_Canvas_getMSAA(lua_State *L)
 {
 	Canvas *canvas = luax_checkcanvas(L, 1);
@@ -52,28 +40,37 @@ int w_Canvas_getMSAA(lua_State *L)
 
 int w_Canvas_renderTo(lua_State *L)
 {
-	Canvas *canvas = luax_checkcanvas(L, 1);
-	luaL_checktype(L, 2, LUA_TFUNCTION);
+	Graphics::RenderTarget rt(luax_checkcanvas(L, 1));
+
+	int startidx = 2;
+
+	if (rt.canvas->getTextureType() != TEXTURE_2D)
+	{
+		rt.slice = (int) luaL_checknumber(L, 2) - 1;
+		startidx++;
+	}
+
+	luaL_checktype(L, startidx, LUA_TFUNCTION);
 
 	auto graphics = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 
 	if (graphics)
 	{
-		// Save the current Canvas so we can restore it when we're done.
-		std::vector<Canvas *> oldcanvases = graphics->getCanvas();
+		// Save the current render targets so we can restore them when we're done.
+		std::vector<Graphics::RenderTarget> oldtargets = graphics->getCanvas();
 
-		for (Canvas *c : oldcanvases)
-			c->retain();
+		for (auto c : oldtargets)
+			c.canvas->retain();
 
-		luax_catchexcept(L, [&](){ graphics->setCanvas(canvas); });
+		luax_catchexcept(L, [&](){ graphics->setCanvas(rt); });
 
 		lua_settop(L, 2); // make sure the function is on top of the stack
 		int status = lua_pcall(L, 0, 0, 0);
 
-		graphics->setCanvas(oldcanvases);
+		graphics->setCanvas(oldtargets);
 
-		for (Canvas *c : oldcanvases)
-			c->release();
+		for (auto c : oldtargets)
+			c.canvas->release();
 
 		if (status != 0)
 			return lua_error(L);
@@ -86,13 +83,31 @@ int w_Canvas_newImageData(lua_State *L)
 {
 	Canvas *canvas = luax_checkcanvas(L, 1);
 	love::image::Image *image = luax_getmodule<love::image::Image>(L, love::image::Image::type);
-	int x = (int) luaL_optnumber(L, 2, 0);
-	int y = (int) luaL_optnumber(L, 3, 0);
-	int w = (int) luaL_optnumber(L, 4, canvas->getPixelWidth());
-	int h = (int) luaL_optnumber(L, 5, canvas->getPixelHeight());
+
+	int slice = 0;
+	int x = 0;
+	int y = 0;
+	int w = canvas->getPixelWidth();
+	int h = canvas->getPixelHeight();
+
+	int startidx = 2;
+
+	if (canvas->getTextureType() != TEXTURE_2D)
+	{
+		slice = (int) luaL_checknumber(L, startidx);
+		startidx++;
+	}
+
+	if (!lua_isnoneornil(L, startidx))
+	{
+		x = (int) luaL_checknumber(L, startidx + 0);
+		y = (int) luaL_checknumber(L, startidx + 1);
+		w = (int) luaL_checknumber(L, startidx + 2);
+		h = (int) luaL_checknumber(L, startidx + 3);
+	}
 
 	love::image::ImageData *img = nullptr;
-	luax_catchexcept(L, [&](){ img = canvas->newImageData(image, x, y, w, h); });
+	luax_catchexcept(L, [&](){ img = canvas->newImageData(image, slice, x, y, w, h); });
 
 	luax_pushtype(L, img);
 	img->release();
@@ -101,7 +116,6 @@ int w_Canvas_newImageData(lua_State *L)
 
 static const luaL_Reg w_Canvas_functions[] =
 {
-	{ "getFormat", w_Canvas_getFormat },
 	{ "getMSAA", w_Canvas_getMSAA },
 	{ "renderTo", w_Canvas_renderTo },
 	{ "newImageData", w_Canvas_newImageData },

+ 406 - 123
src/modules/graphics/wrap_Graphics.cpp

@@ -216,26 +216,67 @@ int w_setCanvas(lua_State *L)
 	}
 
 	bool is_table = lua_istable(L, 1);
-	std::vector<Canvas *> canvases;
+	std::vector<Graphics::RenderTarget> targets;
 
 	if (is_table)
 	{
+		lua_rawgeti(L, 1, 1);
+		bool table_of_tables = lua_istable(L, -1);
+		lua_pop(L, 1);
+
 		for (int i = 1; i <= (int) luax_objlen(L, 1); i++)
 		{
 			lua_rawgeti(L, 1, i);
-			canvases.push_back(luax_checkcanvas(L, -1));
+
+			if (table_of_tables)
+			{
+				lua_rawgeti(L, -1, 1);
+				Graphics::RenderTarget target(luax_checkcanvas(L, -1), 0);
+				lua_pop(L, 1);
+
+				TextureType type = target.canvas->getTextureType();
+				if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME)
+					target.slice = luax_checkintflag(L, -1, "layer") - 1;
+				else if (type == TEXTURE_CUBE)
+					target.slice = luax_checkintflag(L, -1, "face") - 1;
+
+				targets.push_back(target);
+			}
+			else
+			{
+				targets.emplace_back(luax_checkcanvas(L, -1), 0);
+
+				if (targets.back().canvas->getTextureType() != TEXTURE_2D)
+					return luaL_error(L, "The table-of-tables variant of setCanvas must be used with non-2D Canvases.");
+			}
+
 			lua_pop(L, 1);
 		}
 	}
 	else
 	{
 		for (int i = 1; i <= lua_gettop(L); i++)
-			canvases.push_back(luax_checkcanvas(L, i));
+		{
+			Graphics::RenderTarget target(luax_checkcanvas(L, i), 0);
+			TextureType type = target.canvas->getTextureType();
+
+			if (i == 1 && type != TEXTURE_2D)
+			{
+				target.slice = (int) luaL_checknumber(L, 2) - 1;
+				targets.push_back(target);
+				break;
+			}
+
+			if (i > 1 && type != TEXTURE_2D)
+				return luaL_error(L, "This variant of setCanvas only supports 2D texture types.");
+
+			targets.push_back(target);
+		}
 	}
 
 	luax_catchexcept(L, [&]() {
-		if (canvases.size() > 0)
-			instance()->setCanvas(canvases);
+		if (targets.size() > 0)
+			instance()->setCanvas(targets);
 		else
 			instance()->setCanvas();
 	});
@@ -245,22 +286,63 @@ int w_setCanvas(lua_State *L)
 
 int w_getCanvas(lua_State *L)
 {
-	const std::vector<Canvas *> canvases = instance()->getCanvas();
-	int n = 0;
+	const std::vector<Graphics::RenderTarget> targets = instance()->getCanvas();
+	int ntargets = (int) targets.size();
 
-	for (Canvas *c : canvases)
+	if (ntargets == 0)
 	{
-		luax_pushtype(L, c);
-		n++;
+		lua_pushnil(L);
+		return 1;
 	}
 
-	if (n == 0)
+	bool hasNon2DTextureType = false;
+	for (const auto &rt : targets)
 	{
-		lua_pushnil(L);
-		n = 1;
+		if (rt.canvas->getTextureType() != TEXTURE_2D)
+		{
+			hasNon2DTextureType = true;
+			break;
+		}
+	}
+
+	if (hasNon2DTextureType)
+	{
+		lua_createtable(L, ntargets, 0);
+
+		for (int i = 0; i < ntargets; i++)
+		{
+			const auto &rt = targets[i];
+
+			lua_createtable(L, 1, 1);
+
+			luax_pushtype(L, rt.canvas);
+			lua_rawseti(L, -2, 1);
+
+			TextureType type = rt.canvas->getTextureType();
+
+			if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME)
+			{
+				lua_pushnumber(L, rt.slice + 1);
+				lua_setfield(L, -2, "layer");
+			}
+			else if (type == TEXTURE_VOLUME)
+			{
+				lua_pushnumber(L, rt.slice + 1);
+				lua_setfield(L, -2, "face");
+			}
+
+			lua_rawseti(L, -2, i + 1);
+		}
+
+		return 1;
+	}
+	else
+	{
+		for (const auto &rt : targets)
+			luax_pushtype(L, rt.canvas);
+
+		return ntargets;
 	}
-	
-	return n;
 }
 
 static void screenshotCallback(love::image::ImageData *i, Reference *ref, void *gd)
@@ -413,130 +495,325 @@ int w_getStencilTest(lua_State *L)
 	return 2;
 }
 
-int w_newImage(lua_State *L)
+static void parsePixelDensity(love::filesystem::FileData *d, float *pixeldensity)
 {
-	luax_checkgraphicscreated(L);
+	// Parse a density scale of 2.0 from "[email protected]".
+	const std::string &fname = d->getName();
 
-	std::vector<love::image::ImageData *> data;
-	std::vector<love::image::CompressedImageData *> cdata;
+	size_t namelen = fname.length();
+	size_t atpos = fname.rfind('@');
 
-	Image::Settings settings;
+	if (atpos != std::string::npos && atpos + 2 < namelen
+		&& (fname[namelen - 1] == 'x' || fname[namelen - 1] == 'X'))
+	{
+		char *end = nullptr;
+		long density = strtol(fname.c_str() + atpos + 1, &end, 10);
+		if (end != nullptr && density > 0 && pixeldensity != nullptr)
+			*pixeldensity = (float) density;
+	}
+}
 
-	bool releasedata = false;
+static Image::Settings w__optImageSettings(lua_State *L, int idx, const Image::Settings &s)
+{
+	Image::Settings settings = s;
+
+	if (!lua_isnoneornil(L, idx))
+	{
+		luaL_checktype(L, idx, LUA_TTABLE);
+		settings.mipmaps = luax_boolflag(L, idx, "mipmaps", s.mipmaps);
+		settings.linear = luax_boolflag(L, idx, "linear", s.linear);
+		settings.pixeldensity = (float) luax_numberflag(L, idx, "pixeldensity", s.pixeldensity);
+	}
+
+	return settings;
+}
+
+static std::pair<StrongRef<love::image::ImageData>, StrongRef<love::image::CompressedImageData>>
+getImageData(lua_State *L, int idx, bool allowcompressed, float *density)
+{
+	StrongRef<love::image::ImageData> idata;
+	StrongRef<love::image::CompressedImageData> cdata;
 
 	// Convert to ImageData / CompressedImageData, if necessary.
-	if (lua_isstring(L, 1) || luax_istype(L, 1, love::filesystem::File::type) || luax_istype(L, 1, love::filesystem::FileData::type))
+	if (lua_isstring(L, idx) || luax_istype(L, idx, love::filesystem::File::type) || luax_istype(L, idx, love::filesystem::FileData::type))
 	{
 		auto imagemodule = Module::getInstance<love::image::Image>(Module::M_IMAGE);
 		if (imagemodule == nullptr)
-			return luaL_error(L, "Cannot load images without the love.image module.");
+			luaL_error(L, "Cannot load images without the love.image module.");
 
-		love::filesystem::FileData *fdata = love::filesystem::luax_getfiledata(L, 1);
+		StrongRef<love::filesystem::FileData> fdata(love::filesystem::luax_getfiledata(L, idx), Acquire::NORETAIN);
 
-		// Parse a density scale of 2.0 from "[email protected]".
-		const std::string &fname = fdata->getName();
-		size_t namelen = fname.length();
-		size_t atpos = fname.rfind('@');
+		if (density != nullptr)
+			parsePixelDensity(fdata, density);
 
-		if (atpos != std::string::npos && atpos + 2 < namelen
-			&& (fname[namelen - 1] == 'x' || fname[namelen - 1] == 'X'))
-		{
-			char *end = nullptr;
-			long density = strtol(fname.c_str() + atpos + 1, &end, 10);
-			if (end != nullptr && density > 0)
-				settings.pixeldensity = (float) density;
-		}
+		if (allowcompressed && imagemodule->isCompressed(fdata))
+			luax_catchexcept(L, [&]() { cdata.set(imagemodule->newCompressedData(fdata), Acquire::NORETAIN); });
+		else
+			luax_catchexcept(L, [&]() { idata.set(imagemodule->newImageData(fdata), Acquire::NORETAIN); });
+
+	}
+	else if (luax_istype(L, idx, love::image::CompressedImageData::type))
+		cdata.set(love::image::luax_checkcompressedimagedata(L, idx));
+	else
+		idata.set(love::image::luax_checkimagedata(L, idx));
+
+	return std::make_pair(idata, cdata);
+}
+
+static int w__pushNewImage(lua_State *L, Image::Slices &slices, const Image::Settings &settings)
+{
+	StrongRef<Image> i;
+	luax_catchexcept(L,
+		[&]() { i.set(instance()->newImage(slices, settings), Acquire::NORETAIN); },
+		[&](bool) { slices.clear(); }
+	);
+
+	luax_pushtype(L, i);
+	return 1;
+}
+
+int w_newCubeImage(lua_State *L)
+{
+	luax_checkgraphicscreated(L);
+
+	Image::Slices slices(TEXTURE_CUBE);
+	Image::Settings settings;
+
+	auto imagemodule = Module::getInstance<love::image::Image>(Module::M_IMAGE);
 
-		if (imagemodule->isCompressed(fdata))
+	if (!lua_istable(L, 1))
+	{
+		auto imagedata = getImageData(L, 1, false, &settings.pixeldensity);
+
+		std::vector<StrongRef<love::image::ImageData>> faces;
+		luax_catchexcept(L, [&](){ faces = imagemodule->newCubeFaces(imagedata.first); });
+
+		for (int i = 0; i < (int) faces.size(); i++)
+			slices.set(i, 0, faces[i]);
+	}
+	else
+	{
+		int tlen = (int) luax_objlen(L, 1);
+
+		if (luax_isarrayoftables(L, 1))
 		{
-			luax_catchexcept(L,
-				[&]() { cdata.push_back(imagemodule->newCompressedData(fdata)); },
-				[&](bool) { fdata->release(); }
-			);
+			if (tlen != 6)
+				return luaL_error(L, "Cubemap images must have 6 faces.");
+
+			for (int face = 0; face < tlen; face++)
+			{
+				lua_rawgeti(L, 1, face + 1);
+				luaL_checktype(L, -1, LUA_TTABLE);
+
+				int miplen = std::max(1, (int) luax_objlen(L, -1));
+
+				for (int mip = 0; mip < miplen; mip++)
+				{
+					lua_rawgeti(L, -1, mip + 1);
+
+					auto data = getImageData(L, -1, true, face == 0 && mip == 0 ? &settings.pixeldensity : nullptr);
+					if (data.first.get())
+						slices.set(face, mip, data.first);
+					else
+						slices.set(face, mip, data.second->getSlice(0, 0));
+
+					lua_pop(L, 1);
+				}
+			}
 		}
 		else
 		{
-			luax_catchexcept(L,
-				[&]() { data.push_back(imagemodule->newImageData(fdata)); },
-				[&](bool) { fdata->release(); }
-			);
+			bool usemipmaps = false;
+
+			for (int i = 0; i < tlen; i++)
+			{
+				lua_rawgeti(L, 1, i + 1);
+
+				auto data = getImageData(L, -1, true, i == 0 ? &settings.pixeldensity : nullptr);
+
+				if (data.first.get())
+				{
+					if (usemipmaps || data.first->getWidth() != data.first->getHeight())
+					{
+						usemipmaps = true;
+
+						std::vector<StrongRef<love::image::ImageData>> faces;
+						luax_catchexcept(L, [&](){ faces = imagemodule->newCubeFaces(data.first); });
+
+						for (int face = 0; face < (int) faces.size(); face++)
+							slices.set(face, i, faces[i]);
+					}
+					else
+						slices.set(i, 0, data.first);
+				}
+				else
+					slices.add(data.second, i, 0, false, true);
+			}
 		}
 
-		// Lua's GC won't release the image data, so we should do it ourselves.
-		releasedata = true;
+		lua_pop(L, tlen);
 	}
-	else if (luax_istype(L, 1, love::image::CompressedImageData::type))
-		cdata.push_back(love::image::luax_checkcompressedimagedata(L, 1));
-	else
-		data.push_back(love::image::luax_checkimagedata(L, 1));
 
-	if (!lua_isnoneornil(L, 2))
-	{
-		luaL_checktype(L, 2, LUA_TTABLE);
+	settings = w__optImageSettings(L, 2, settings);
+	return w__pushNewImage(L, slices, settings);
+}
 
-		settings.mipmaps = luax_boolflag(L, 2, luax_imageSettingName(Image::SETTING_MIPMAPS), settings.mipmaps);
-		settings.linear = luax_boolflag(L, 2, luax_imageSettingName(Image::SETTING_LINEAR), settings.linear);
-		settings.pixeldensity = (float) luax_numberflag(L, 2, luax_imageSettingName(Image::SETTING_PIXELDENSITY), settings.pixeldensity);
+int w_newArrayImage(lua_State *L)
+{
+	luax_checkgraphicscreated(L);
 
-		lua_getfield(L, 2, luax_imageSettingName(Image::SETTING_MIPMAPS));
+	Image::Slices slices(TEXTURE_2D_ARRAY);
+	Image::Settings settings;
 
-		// Add all manually specified mipmap images to the array of imagedata.
-		// i.e. settings = {mipmaps = {mip1, mip2, ...}}.
-		if (lua_istable(L, -1))
+	if (lua_istable(L, 1))
+	{
+		int tlen = std::max(1, (int) luax_objlen(L, 1));
+
+		if (luax_isarrayoftables(L, 1))
 		{
-			for (size_t i = 1; i <= luax_objlen(L, -1); i++)
+			for (int slice = 0; slice < tlen; slice++)
 			{
-				lua_rawgeti(L, -1, i);
+				lua_rawgeti(L, 1, slice + 1);
+				luaL_checktype(L, -1, LUA_TTABLE);
 
-				if (!data.empty())
-				{
-					if (!luax_istype(L, -1, love::image::ImageData::type))
-						luax_convobj(L, -1, "image", "newImageData");
+				int miplen = std::max(1, (int) luax_objlen(L, -1));
 
-					data.push_back(love::image::luax_checkimagedata(L, -1));
-				}
-				else if (!cdata.empty())
+				for (int mip = 0; mip < miplen; mip++)
 				{
-					if (!luax_istype(L, -1, love::image::CompressedImageData::type))
-						luax_convobj(L, -1, "image", "newCompressedData");
+					lua_rawgeti(L, -1, mip + 1);
 
-					cdata.push_back(love::image::luax_checkcompressedimagedata(L, -1));
-				}
+					auto data = getImageData(L, -1, true, slice == 0 && mip == 0 ? &settings.pixeldensity : nullptr);
+					if (data.first.get())
+						slices.set(slice, mip, data.first);
+					else
+						slices.set(slice, mip, data.second->getSlice(0, 0));
 
-				lua_pop(L, 1);
+					lua_pop(L, 1);
+				}
+			}
+		}
+		else
+		{
+			for (int slice = 0; slice < tlen; slice++)
+			{
+				lua_rawgeti(L, 1, slice + 1);
+				auto data = getImageData(L, -1, true, slice == 0 ? &settings.pixeldensity : nullptr);
+				if (data.first.get())
+					slices.set(slice, 0, data.first);
+				else
+					slices.add(data.second, slice, 0, false, true);
 			}
 		}
 
-		lua_pop(L, 1);
+		lua_pop(L, tlen);
+	}
+	else
+	{
+		auto data = getImageData(L, 1, true, &settings.pixeldensity);
+		if (data.first.get())
+			slices.set(0, 0, data.first);
+		else
+			slices.add(data.second, 0, 0, true, true);
 	}
 
-	// Create the image.
-	Image *image = nullptr;
-	luax_catchexcept(L,
-		[&]() {
-			if (!cdata.empty())
-				image = instance()->newImage(cdata, settings);
-			else if (!data.empty())
-				image = instance()->newImage(data, settings);
-		},
-		[&](bool) {
-			if (releasedata)
+	settings = w__optImageSettings(L, 2, settings);
+	return w__pushNewImage(L, slices, settings);
+}
+
+int w_newVolumeImage(lua_State *L)
+{
+	luax_checkgraphicscreated(L);
+
+	Image::Slices slices(TEXTURE_VOLUME);
+	Image::Settings settings;
+
+	if (lua_istable(L, 1))
+	{
+		int tlen = std::max(1, (int) luax_objlen(L, 1));
+
+		if (luax_isarrayoftables(L, 1))
+		{
+			for (int mip = 0; mip < tlen; mip++)
 			{
-				for (auto d : data)
-					d->release();
-				for (auto d : cdata)
-					d->release();
+				lua_rawgeti(L, 1, mip + 1);
+				luaL_checktype(L, -1, LUA_TTABLE);
+
+				int slicelen = std::max(1, (int) luax_objlen(L, -1));
+
+				for (int slice = 0; slice < slicelen; slice++)
+				{
+					lua_rawgeti(L, -1, mip + 1);
+
+					auto data = getImageData(L, -1, true, slice == 0 && mip == 0 ? &settings.pixeldensity : nullptr);
+					if (data.first.get())
+						slices.set(slice, mip, data.first);
+					else
+						slices.set(slice, mip, data.second->getSlice(0, 0));
+
+					lua_pop(L, 1);
+				}
+			}
+		}
+		else
+		{
+			for (int layer = 0; layer < tlen; layer++)
+			{
+				lua_rawgeti(L, 1, layer + 1);
+				auto data = getImageData(L, -1, true, layer == 0 ? &settings.pixeldensity : nullptr);
+				if (data.first.get())
+					slices.set(layer, 0, data.first);
+				else
+					slices.add(data.second, layer, 0, false, true);
 			}
 		}
-	);
 
-	if (image == nullptr)
-		return luaL_error(L, "Could not load image.");
+		lua_pop(L, tlen);
+	}
+	else
+	{
+		auto data = getImageData(L, 1, true, &settings.pixeldensity);
+		if (data.first.get())
+			slices.set(0, 0, data.first);
+		else
+			slices.add(data.second, 0, 0, true, true);
+	}
 
-	// Push the type.
-	luax_pushtype(L, image);
-	image->release();
-	return 1;
+	settings = w__optImageSettings(L, 2, settings);
+	return w__pushNewImage(L, slices, settings);
+}
+
+int w_newImage(lua_State *L)
+{
+	luax_checkgraphicscreated(L);
+
+	Image::Slices slices(TEXTURE_2D);
+	Image::Settings settings;
+
+	if (lua_istable(L, 1))
+	{
+		int n = std::max(1, (int) luax_objlen(L, 1));
+		for (int i = 0; i < n; i++)
+		{
+			lua_rawgeti(L, 1, i + 1);
+			auto data = getImageData(L, -1, true, i == 0 ? &settings.pixeldensity : nullptr);
+			if (data.first.get())
+				slices.set(0, i, data.first);
+			else
+				slices.set(0, i, data.second->getSlice(0, 0));
+		}
+		lua_pop(L, n);
+	}
+	else
+	{
+		auto data = getImageData(L, 1, true, &settings.pixeldensity);
+		if (data.first.get())
+			slices.set(0, 0, data.first);
+		else
+			slices.add(data.second, 0, 0, false, true);
+	}
+
+	settings = w__optImageSettings(L, 2, settings);
+	return w__pushNewImage(L, slices, settings);
 }
 
 int w_newQuad(lua_State *L)
@@ -605,18 +882,6 @@ int w_newImageFont(lua_State *L)
 	// filter for glyphs
 	Texture::Filter filter = instance()->getDefaultFilter();
 
-	// Convert to ImageData if necessary.
-	if (luax_istype(L, 1, Image::type))
-	{
-		Image *i = luax_checktype<Image>(L, 1);
-		filter = i->getFilter();
-		const auto &idlevels = i->getImageData();
-		if (idlevels.empty())
-			return luaL_argerror(L, 1, "Image must not be compressed.");
-		luax_pushtype(L, idlevels[0].get());
-		lua_replace(L, 1);
-	}
-
 	// Convert to Rasterizer if necessary.
 	if (!luax_istype(L, 1, love::font::Rasterizer::type))
 	{
@@ -687,35 +952,50 @@ int w_newCanvas(lua_State *L)
 {
 	luax_checkgraphicscreated(L);
 
-	// check if width and height are given. else default to screen dimensions.
-	int width  = (int) luaL_optnumber(L, 1, instance()->getWidth());
-	int height = (int) luaL_optnumber(L, 2, instance()->getHeight());
-
 	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());
+
 	// Default to the screen's current pixel density scale.
 	settings.pixeldensity = instance()->getScreenPixelDensity();
 
-	if (!lua_isnoneornil(L, 3))
+	int startidx = 3;
+
+	if (lua_isnumber(L, 3))
+	{
+		settings.layers = (int) luaL_checknumber(L, 3);
+		settings.type = TEXTURE_2D_ARRAY;
+		startidx = 4;
+	}
+
+	if (!lua_isnoneornil(L, startidx))
 	{
-		lua_getfield(L, 3, "format");
+		settings.pixeldensity = (float) luax_numberflag(L, startidx, "pixeldensity", settings.pixeldensity);
+		settings.msaa = luax_intflag(L, startidx, "msaa", 0);
+
+		lua_getfield(L, startidx, "format");
 		if (!lua_isnoneornil(L, -1))
 		{
 			const char *str = luaL_checkstring(L, -1);
 			if (!getConstant(str, settings.format))
-				return luaL_error(L, "Invalid Canvas format: %s", str);
+				return luaL_error(L, "Invalid pixel format: %s", str);
 		}
 		lua_pop(L, 1);
 
-		settings.pixeldensity = (float) luax_numberflag(L, 3, "pixeldensity", settings.pixeldensity);
-		settings.msaa = luax_intflag(L, 3, "msaa", settings.msaa);
+		lua_getfield(L, startidx, "type");
+		if (!lua_isnoneornil(L, -1))
+		{
+			const char *str = luaL_checkstring(L, -1);
+			if (!Texture::getConstant(str, settings.type))
+				return luaL_error(L, "Invalid texture type: %s", str);
+		}
+		lua_pop(L, 1);
 	}
 
 	Canvas *canvas = nullptr;
-	luax_catchexcept(L, [&](){ canvas = instance()->newCanvas(width, height, settings); });
-
-	if (canvas == nullptr)
-		return luaL_error(L, "Canvas not created, but no error thrown. I don't even...");
+	luax_catchexcept(L, [&](){ canvas = instance()->newCanvas(settings); });
 
 	luax_pushtype(L, canvas);
 	canvas->release();
@@ -2178,6 +2458,9 @@ static const luaL_Reg functions[] =
 	{ "present", w_present },
 
 	{ "newImage", w_newImage },
+	{ "newArrayImage", w_newArrayImage },
+	{ "newVolumeImage", w_newVolumeImage },
+	{ "newCubeImage", w_newCubeImage },
 	{ "newQuad", w_newQuad },
 	{ "newFont", w_newFont },
 	{ "newImageFont", w_newImageFont },

+ 38 - 12
src/modules/graphics/wrap_Graphics.lua

@@ -42,7 +42,17 @@ GLSL.SYNTAX = [[
 #endif
 #define number float
 #define Image sampler2D
-#define extern uniform]]
+#define ArrayImage sampler2DArray
+#define CubeImage samplerCube
+#define VolumeImage sampler3D
+#define extern uniform
+#ifdef GL_EXT_texture_array
+#extension GL_EXT_texture_array : enable
+#endif
+#ifdef GL_OES_texture_3D
+#extension GL_OES_texture_3D : enable
+#endif
+]]
 
 -- Uniforms shared by the vertex and pixel shader stages.
 GLSL.UNIFORMS = [[
@@ -66,13 +76,36 @@ GLSL.FUNCTIONS = [[
 #else
 	#if __VERSION__ >= 130
 		#define texture2D Texel
+		#define texture3D Texel
+		#define textureCube Texel
+		#define texture2DArray Texel
 		#define love_texture2D texture
+		#define love_texture3D texture
+		#define love_textureCube texture
+		#define love_texture2DArray texture
 	#else
 		#define love_texture2D texture2D
+		#define love_texture3D texture3D
+		#define love_textureCube textureCube
+		#define love_texture2DArray texture2DArray
 	#endif
 	vec4 Texel(sampler2D s, vec2 c) { return love_texture2D(s, c); }
+	vec4 Texel(samplerCube s, vec3 c) { return love_textureCube(s, c); }
+	#if __VERSION__ > 100 || defined(GL_OES_texture_3D)
+		vec4 Texel(sampler3D s, vec3 c) { return love_texture3D(s, c); }
+	#endif
+	#if __VERSION__ >= 130 || defined(GL_EXT_texture_array)
+		vec4 Texel(sampler2DArray s, vec3 c) { return love_texture2DArray(s, c); }
+	#endif
 	#ifdef PIXEL
 		vec4 Texel(sampler2D s, vec2 c, float b) { return love_texture2D(s, c, b); }
+		vec4 Texel(samplerCube s, vec3 c, float b) { return love_textureCube(s, c, b); }
+		#if __VERSION__ > 100 || defined(GL_OES_texture_3D)
+			vec4 Texel(sampler3D s, vec3 c, float b) { return love_texture3D(s, c, b); }
+		#endif
+		#if __VERSION__ >= 130 || defined(GL_EXT_texture_array)
+			vec4 Texel(sampler2DArray s, vec3 c, float b) { return love_texture2DArray(s, c, b); }
+		#endif
 	#endif
 	#define texture love_texture
 #endif
@@ -187,17 +220,11 @@ GLSL.PIXEL = {
 
 #if __VERSION__ >= 130
 	#define varying in
-	#ifdef LOVE_MULTI_CANVAS
-		layout(location = 0) out vec4 love_Canvases[love_MaxCanvases];
-	#else
-		layout(location = 0) out vec4 love_PixelColor;
-	#endif
+	layout(location = 0) out vec4 love_Canvases[love_MaxCanvases];
+	#define love_PixelColor love_Canvases[0]
 #else
-	#ifdef LOVE_MULTI_CANVAS
-		#define love_Canvases gl_FragData
-	#else
-		#define love_PixelColor gl_FragColor
-	#endif
+	#define love_Canvases gl_FragData
+	#define love_PixelColor gl_FragColor
 #endif
 
 // See Shader::updateScreenParams in Shader.cpp.
@@ -259,7 +286,6 @@ local function createShaderStageCode(stage, code, lang, gles, glsl1on3, gammacor
 		"#define "..stage,
 		glsl1on3 and "#define LOVE_GLSL1_ON_GLSL3 1" or "",
 		gammacorrect and "#define LOVE_GAMMA_CORRECT 1" or "",
-		multicanvas and "#define LOVE_MULTI_CANVAS 1" or "",
 		GLSL.SYNTAX,
 		GLSL[stage].HEADER,
 		GLSL.UNIFORMS,

+ 19 - 93
src/modules/graphics/wrap_Image.cpp

@@ -31,40 +31,11 @@ Image *luax_checkimage(lua_State *L, int idx)
 	return luax_checktype<Image>(L, idx);
 }
 
-int w_Image_setMipmapFilter(lua_State *L)
+int w_Image_isFormatLinear(lua_State *L)
 {
-	Image *t = luax_checkimage(L, 1);
-	Texture::Filter f = t->getFilter();
-
-	if (lua_isnoneornil(L, 2))
-		f.mipmap = Texture::FILTER_NONE; // mipmapping is disabled if no argument is given
-	else
-	{
-		const char *mipmapstr = luaL_checkstring(L, 2);
-		if (!Texture::getConstant(mipmapstr, f.mipmap))
-			return luaL_error(L, "Invalid filter mode: %s", mipmapstr);
-	}
-
-	luax_catchexcept(L, [&](){ t->setFilter(f); });
-	t->setMipmapSharpness((float) luaL_optnumber(L, 3, 0.0));
-
-	return 0;
-}
-
-int w_Image_getMipmapFilter(lua_State *L)
-{
-	Image *t = luax_checkimage(L, 1);
-
-	const Texture::Filter &f = t->getFilter();
-
-	const char *mipmapstr;
-	if (Texture::getConstant(f.mipmap, mipmapstr))
-		lua_pushstring(L, mipmapstr);
-	else
-		lua_pushnil(L); // only return a mipmap filter if mipmapping is enabled
-
-	lua_pushnumber(L, t->getMipmapSharpness());
-	return 2;
+	Image *i = luax_checkimage(L, 1);
+	luax_pushboolean(L, i->isFormatLinear());
+	return 1;
 }
 
 int w_Image_isCompressed(lua_State *L)
@@ -74,78 +45,33 @@ int w_Image_isCompressed(lua_State *L)
 	return 1;
 }
 
-int w_Image_refresh(lua_State *L)
+int w_Image_replacePixels(lua_State *L)
 {
 	Image *i = luax_checkimage(L, 1);
+	love::image::ImageData *id = luax_checktype<love::image::ImageData>(L, 2);
 
-	int xoffset = (int) luaL_optnumber(L, 2, 0);
-	int yoffset = (int) luaL_optnumber(L, 3, 0);
-	int w = (int) luaL_optnumber(L, 4, i->getWidth());
-	int h = (int) luaL_optnumber(L, 5, i->getHeight());
-
-	luax_catchexcept(L, [&](){ i->refresh(xoffset, yoffset, w, h); });
-	return 0;
-}
-
-int w_Image_getData(lua_State *L)
-{
-	Image *i = luax_checkimage(L, 1);
-	int n = 0;
+	int slice = 0;
+	int mipmap = 0;
+	bool reloadmipmaps = i->getMipmapsType() == Image::MIPMAPS_GENERATED;
 
-	if (i->isCompressed())
+	if (i->getTextureType() != TEXTURE_2D)
 	{
-		for (const auto &cdata : i->getCompressedData())
-		{
-			luax_pushtype(L, cdata.get());
-			n++;
-		}
+		slice = (int) luaL_checknumber(L, 3) - 1;
+		if (!reloadmipmaps)
+			mipmap = (int) luaL_optnumber(L, 4, 1) - 1;
 	}
-	else
-	{
-		for (const auto &data : i->getImageData())
-		{
-			luax_pushtype(L, data.get());
-			n++;
-		}
-	}
-
-	return n;
-}
-
-const char *luax_imageSettingName(Image::SettingType settingtype)
-{
-	const char *name = nullptr;
-	Image::getConstant(settingtype, name);
-	return name;
-}
+	else if (!reloadmipmaps)
+		mipmap = (int) luaL_optnumber(L, 3, 1) - 1;
 
-int w_Image_getFlags(lua_State *L)
-{
-	Image *i = luax_checkimage(L, 1);
-	Image::Settings settings = i->getFlags();
-
-	lua_createtable(L, 0, 2);
-
-	lua_pushboolean(L, settings.mipmaps);
-	lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_MIPMAPS));
-
-	lua_pushboolean(L, settings.linear);
-	lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_LINEAR));
-
-	lua_pushnumber(L, settings.pixeldensity);
-	lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_PIXELDENSITY));
-
-	return 1;
+	luax_catchexcept(L, [&](){ i->replacePixels(id, slice, mipmap, reloadmipmaps); });
+	return 0;
 }
 
 static const luaL_Reg w_Image_functions[] =
 {
-	{ "setMipmapFilter", w_Image_setMipmapFilter },
-	{ "getMipmapFilter", w_Image_getMipmapFilter },
+	{ "isFormatLinear", w_Image_isFormatLinear },
 	{ "isCompressed", w_Image_isCompressed },
-	{ "refresh", w_Image_refresh },
-	{ "getData", w_Image_getData },
-	{ "getFlags", w_Image_getFlags },
+	{ "replacePixels", w_Image_replacePixels },
 	{ 0, 0 }
 };
 

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

@@ -30,7 +30,6 @@ namespace love
 namespace graphics
 {
 
-const char *luax_imageSettingName(Image::SettingType settingtype);
 Image *luax_checkimage(lua_State *L, int idx);
 extern "C" int luaopen_image(lua_State *L);
 

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

@@ -456,7 +456,7 @@ int w_Mesh_setTexture(lua_State *L)
 	else
 	{
 		Texture *tex = luax_checktexture(L, 2);
-		t->setTexture(tex);
+		luax_catchexcept(L, [&](){ t->setTexture(tex); });
 	}
 
 	return 0;

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

@@ -55,7 +55,7 @@ int w_ParticleSystem_setTexture(lua_State *L)
 {
 	ParticleSystem *t = luax_checkparticlesystem(L, 1);
 	Texture *tex = luax_checktexture(L, 2);
-	t->setTexture(tex);
+	luax_catchexcept(L, [&](){ t->setTexture(tex); });
 	return 0;
 }
 

+ 6 - 1
src/modules/graphics/wrap_Shader.cpp

@@ -267,7 +267,12 @@ int w_Shader_sendTextures(lua_State *L, int startidx, Shader *shader, const Shad
 	textures.reserve(count);
 
 	for (int i = 0; i < count; i++)
-		textures.push_back(luax_checktexture(L, startidx + i));
+	{
+		Texture *tex = luax_checktexture(L, startidx + i);
+		if (tex->getTextureType() != info->textureType)
+			return luaL_argerror(L, startidx + i, "invalid texture type for uniform");
+		textures.push_back(tex);
+	}
 
 	luax_catchexcept(L, [&]() { shader->sendTextures(info, textures.data(), count); });
 	return 0;

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

@@ -120,7 +120,7 @@ int w_SpriteBatch_setTexture(lua_State *L)
 {
 	SpriteBatch *t = luax_checkspritebatch(L, 1);
 	Texture *tex = luax_checktexture(L, 2);
-	t->setTexture(tex);
+	luax_catchexcept(L, [&](){ t->setTexture(tex); });
 	return 0;
 }
 

+ 90 - 0
src/modules/graphics/wrap_Texture.cpp

@@ -30,6 +30,16 @@ Texture *luax_checktexture(lua_State *L, int idx)
 	return luax_checktype<Texture>(L, idx);
 }
 
+int w_Texture_getTextureType(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+	const char *tstr;
+	if (!Texture::getConstant(t->getTextureType(), tstr))
+		return luaL_error(L, "unknown texture type");
+	lua_pushstring(L, tstr);
+	return 1;
+}
+
 int w_Texture_getWidth(lua_State *L)
 {
 	Texture *t = luax_checktexture(L, 1);
@@ -52,6 +62,27 @@ int w_Texture_getDimensions(lua_State *L)
 	return 2;
 }
 
+int w_Texture_getDepth(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+	lua_pushnumber(L, t->getDepth());
+	return 1;
+}
+
+int w_Texture_getLayerCount(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+	lua_pushnumber(L, t->getLayerCount());
+	return 1;
+}
+
+int w_Texture_getMipmapCount(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+	lua_pushnumber(L, t->getMipmapCount());
+	return 1;
+}
+
 int w_Texture_getPixelWidth(lua_State *L)
 {
 	Texture *t = luax_checktexture(L, 1);
@@ -119,6 +150,42 @@ int w_Texture_getFilter(lua_State *L)
 	return 3;
 }
 
+int w_Texture_setMipmapFilter(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+	Texture::Filter f = t->getFilter();
+
+	if (lua_isnoneornil(L, 2))
+		f.mipmap = Texture::FILTER_NONE; // mipmapping is disabled if no argument is given
+	else
+	{
+		const char *mipmapstr = luaL_checkstring(L, 2);
+		if (!Texture::getConstant(mipmapstr, f.mipmap))
+			return luaL_error(L, "Invalid filter mode: %s", mipmapstr);
+	}
+
+	luax_catchexcept(L, [&](){ t->setFilter(f); });
+	t->setMipmapSharpness((float) luaL_optnumber(L, 3, 0.0));
+
+	return 0;
+}
+
+int w_Texture_getMipmapFilter(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+
+	const Texture::Filter &f = t->getFilter();
+
+	const char *mipmapstr;
+	if (Texture::getConstant(f.mipmap, mipmapstr))
+		lua_pushstring(L, mipmapstr);
+	else
+		lua_pushnil(L); // only return a mipmap filter if mipmapping is enabled
+
+	lua_pushnumber(L, t->getMipmapSharpness());
+	return 2;
+}
+
 int w_Texture_setWrap(lua_State *L)
 {
 	Texture *t = luax_checktexture(L, 1);
@@ -143,30 +210,53 @@ int w_Texture_getWrap(lua_State *L)
 
 	const char *sstr = nullptr;
 	const char *tstr = nullptr;
+	const char *rstr = nullptr;
 
 	if (!Texture::getConstant(w.s, sstr))
 		return luaL_error(L, "Unknown wrap mode.");
 	if (!Texture::getConstant(w.t, tstr))
 		return luaL_error(L, "Unknown wrap mode.");
+	if (!Texture::getConstant(w.r, rstr))
+		return luaL_error(L, "Unknown wrap mode.");
 
 	lua_pushstring(L, sstr);
 	lua_pushstring(L, tstr);
+	lua_pushstring(L, rstr);
 	return 2;
 }
 
+int w_Texture_getFormat(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+	PixelFormat format = t->getPixelFormat();
+	const char *str;
+	if (!getConstant(format, str))
+		return luaL_error(L, "Unknown pixel format.");
+
+	lua_pushstring(L, str);
+	return 1;
+}
+
 const luaL_Reg w_Texture_functions[] =
 {
+	{ "getTextureType", w_Texture_getTextureType },
 	{ "getWidth", w_Texture_getWidth },
 	{ "getHeight", w_Texture_getHeight },
 	{ "getDimensions", w_Texture_getDimensions },
+	{ "getDepth", w_Texture_getDepth },
+	{ "getLayerCount", w_Texture_getLayerCount },
+	{ "getMipmapCount", w_Texture_getMipmapCount },
 	{ "getPixelWidth", w_Texture_getPixelWidth },
 	{ "getPixelHeight", w_Texture_getPixelHeight },
 	{ "getPixelDimensions", w_Texture_getPixelDimensions },
 	{ "getPixelDensity", w_Texture_getPixelDensity },
 	{ "setFilter", w_Texture_setFilter },
 	{ "getFilter", w_Texture_getFilter },
+	{ "setMipmapFilter", w_Texture_setMipmapFilter },
+	{ "getMipmapFilter", w_Texture_getMipmapFilter },
 	{ "setWrap", w_Texture_setWrap },
 	{ "getWrap", w_Texture_getWrap },
+	{ "getFormat", w_Texture_getFormat },
 	{ 0, 0 }
 };
 

+ 75 - 14
src/modules/image/CompressedImageData.cpp

@@ -27,11 +27,57 @@ namespace image
 
 love::Type CompressedImageData::type("CompressedImageData", &Data::type);
 
+CompressedImageData::Memory::Memory(size_t size)
+	: data(nullptr)
+	, size(size)
+{
+	try
+	{
+		data = new uint8[size];
+	}
+	catch (std::exception &)
+	{
+		throw love::Exception("Out of memory.");
+	}
+}
+
+CompressedImageData::Memory::~Memory()
+{
+	delete[] data;
+}
+
+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;
+}
+
+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();
+}
+
+CompressedImageData::Slice::~Slice()
+{
+}
+
+CompressedImageData::Slice *CompressedImageData::Slice::clone() const
+{
+	return new Slice(*this);
+}
+
 CompressedImageData::CompressedImageData()
 	: format(PIXELFORMAT_UNKNOWN)
 	, sRGB(false)
-	, data(nullptr)
-	, dataSize(0)
 {
 }
 
@@ -41,45 +87,50 @@ CompressedImageData::~CompressedImageData()
 
 size_t CompressedImageData::getSize() const
 {
-	return dataSize;
+	return memory->size;
 }
 
 void *CompressedImageData::getData() const
 {
-	return data;
+	return memory->data;
 }
 
-int CompressedImageData::getMipmapCount() const
+int CompressedImageData::getMipmapCount(int /*slice*/) const
 {
 	return (int) dataImages.size();
 }
 
+int CompressedImageData::getSliceCount(int /*mip*/) const
+{
+	return 1;
+}
+
 size_t CompressedImageData::getSize(int miplevel) const
 {
-	checkMipmapLevelExists(miplevel);
+	checkSliceExists(0, miplevel);
 
-	return dataImages[miplevel].size;
+	return dataImages[miplevel]->getSize();
 }
 
 void *CompressedImageData::getData(int miplevel) const
 {
-	checkMipmapLevelExists(miplevel);
+	checkSliceExists(0, miplevel);
 
-	return &dataImages[miplevel].data[0];
+	return dataImages[miplevel]->getData();
 }
 
 int CompressedImageData::getWidth(int miplevel) const
 {
-	checkMipmapLevelExists(miplevel);
+	checkSliceExists(0, miplevel);
 
-	return dataImages[miplevel].width;
+	return dataImages[miplevel]->getWidth();
 }
 
 int CompressedImageData::getHeight(int miplevel) const
 {
-	checkMipmapLevelExists(miplevel);
+	checkSliceExists(0, miplevel);
 
-	return dataImages[miplevel].height;
+	return dataImages[miplevel]->getHeight();
 }
 
 PixelFormat CompressedImageData::getFormat() const
@@ -92,8 +143,18 @@ bool CompressedImageData::isSRGB() const
 	return sRGB;
 }
 
-void CompressedImageData::checkMipmapLevelExists(int miplevel) const
+CompressedImageData::Slice *CompressedImageData::getSlice(int slice, int miplevel) const
+{
+	checkSliceExists(slice, miplevel);
+
+	return dataImages[miplevel].get();
+}
+
+void CompressedImageData::checkSliceExists(int slice, int miplevel) const
 {
+	if (slice != 0)
+		throw love::Exception("Slice index %d does not exists", slice + 1);
+
 	if (miplevel < 0 || miplevel >= (int) dataImages.size())
 		throw love::Exception("Mipmap level %d does not exist", miplevel + 1);
 }

+ 48 - 15
src/modules/image/CompressedImageData.h

@@ -18,14 +18,14 @@
  * 3. This notice may not be removed or altered from any source distribution.
  **/
 
-#ifndef LOVE_IMAGE_COMPRESSED_IMAGE_DATA_H
-#define LOVE_IMAGE_COMPRESSED_IMAGE_DATA_H
+#pragma once
 
 // LOVE
 #include "common/Data.h"
 #include "common/StringMap.h"
 #include "common/int.h"
 #include "common/pixelformat.h"
+#include "ImageDataBase.h"
 
 // STL
 #include <vector>
@@ -44,16 +44,45 @@ class CompressedImageData : public Data
 {
 public:
 
-	static love::Type type;
+	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.
-	struct SubImage
+	class Slice : public ImageDataBase
 	{
-		int width, height;
-		size_t size;
-		uint8 *data; // Should not have ownership of the data.
-	};
+	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();
 	virtual ~CompressedImageData();
@@ -67,7 +96,12 @@ public:
 	 * Gets the number of mipmaps in this Compressed Image Data.
 	 * Includes the base image level.
 	 **/
-	int getMipmapCount() const;
+	int getMipmapCount(int slice = 0) const;
+
+	/**
+	 * Gets the number of slices (array layers, cube faces, 3D layers, etc.)
+	 **/
+	int getSliceCount(int mip = 0) const;
 
 	/**
 	 * Gets the size in bytes of a sub-image at the specified mipmap level.
@@ -96,6 +130,8 @@ public:
 
 	bool isSRGB() const;
 
+	Slice *getSlice(int slice, int miplevel) const;
+
 protected:
 
 	PixelFormat format;
@@ -103,17 +139,14 @@ protected:
 	bool sRGB;
 
 	// Single block of memory containing all of the sub-images.
-	uint8 *data;
-	size_t dataSize;
+	StrongRef<Memory> memory;
 
 	// Texture info for each mipmap level.
-	std::vector<SubImage> dataImages;
+	std::vector<StrongRef<Slice>> dataImages;
 
-	void checkMipmapLevelExists(int miplevel) const;
+	void checkSliceExists(int slice, int miplevel) const;
 
 }; // CompressedImageData
 
 } // image
 } // love
-
-#endif // LOVE_IMAGE_COMPRESSED_IMAGE_DATA_H

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

@@ -28,5 +28,92 @@ namespace image
 
 love::Type Image::type("image", &Module::type);
 
+ImageData *Image::newPastedImageData(ImageData *src, int sx, int sy, int w, int h)
+{
+	ImageData *res = newImageData(w, h, src->getFormat());
+	try
+	{
+		res->paste(src, 0, 0, sx, sy, w, h);
+	}
+	catch (love::Exception &)
+	{
+		res->release();
+		throw;
+	}
+	return res;
+}
+
+std::vector<StrongRef<ImageData>> Image::newCubeFaces(love::image::ImageData *src)
+{
+	// The faces array is always ordered +x, -x, +y, -y, +z, -z.
+	std::vector<StrongRef<ImageData>> faces;
+
+	int totalW = src->getWidth();
+	int totalH = src->getHeight();
+
+	if (totalW % 3 == 0 && totalH % 4 == 0 && totalW / 3 == totalH / 4)
+	{
+		//    +y
+		// +z +x -z
+		//    -y
+		//    -x
+
+		int w = totalW / 3;
+		int h = totalH / 4;
+
+		faces.emplace_back(newPastedImageData(src, 1*w, 1*h, w, h), Acquire::NORETAIN);
+		faces.emplace_back(newPastedImageData(src, 1*w, 3*h, w, h), Acquire::NORETAIN);
+		faces.emplace_back(newPastedImageData(src, 1*w, 0*h, w, h), Acquire::NORETAIN);
+		faces.emplace_back(newPastedImageData(src, 1*w, 2*h, w, h), Acquire::NORETAIN);
+		faces.emplace_back(newPastedImageData(src, 0*w, 1*h, w, h), Acquire::NORETAIN);
+		faces.emplace_back(newPastedImageData(src, 2*w, 1*h, w, h), Acquire::NORETAIN);
+	}
+	else if (totalW % 4 == 0 && totalH % 3 == 0 && totalW / 4 == totalH / 3)
+	{
+		//    +y
+		// -x +z +x -z
+		//    -y
+
+		int w = totalW / 4;
+		int h = totalH / 3;
+
+		faces.emplace_back(newPastedImageData(src, 2*w, 1*h, w, h), Acquire::NORETAIN);
+		faces.emplace_back(newPastedImageData(src, 0*w, 1*h, w, h), Acquire::NORETAIN);
+		faces.emplace_back(newPastedImageData(src, 1*w, 0*h, w, h), Acquire::NORETAIN);
+		faces.emplace_back(newPastedImageData(src, 1*w, 2*h, w, h), Acquire::NORETAIN);
+		faces.emplace_back(newPastedImageData(src, 1*w, 1*h, w, h), Acquire::NORETAIN);
+		faces.emplace_back(newPastedImageData(src, 3*w, 1*h, w, h), Acquire::NORETAIN);
+	}
+	else if (totalH % 6 == 0 && totalW == totalH / 6)
+	{
+		// +x
+		// -x
+		// +y
+		// -y
+		// +z
+		// -z
+
+		int w = totalW;
+		int h = totalH / 6;
+
+		for (int i = 0; i < 6; i++)
+			faces.emplace_back(newPastedImageData(src, 0, i * h, w, h), Acquire::NORETAIN);
+	}
+	else if (totalW % 6 == 0 && totalW / 6 == totalH)
+	{
+		// +x -x +y -y +z -z
+
+		int w = totalW / 6;
+		int h = totalH;
+
+		for (int i = 0; i < 6; i++)
+			faces.emplace_back(newPastedImageData(src, i * w, 0, w, h), Acquire::NORETAIN);
+	}
+	else
+		throw love::Exception("Unknown cubemap image dimensions!");
+
+	return faces;
+}
+
 } // image
 } // love

+ 6 - 0
src/modules/image/Image.h

@@ -90,6 +90,12 @@ public:
 	 **/
 	virtual bool isCompressed(love::filesystem::FileData *data) = 0;
 
+	std::vector<StrongRef<ImageData>> newCubeFaces(ImageData *layoutData);
+
+private:
+
+	ImageData *newPastedImageData(ImageData *src, int sx, int sy, int w, int h);
+
 }; // Image
 
 } // image

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

@@ -30,10 +30,7 @@ namespace image
 love::Type ImageData::type("ImageData", &Data::type);
 
 ImageData::ImageData()
-	: format(PIXELFORMAT_UNKNOWN)
-	, width(0)
-	, height(0)
-	, data(nullptr)
+	: data(nullptr)
 {
 }
 
@@ -51,24 +48,14 @@ void *ImageData::getData() const
 	return data;
 }
 
-bool ImageData::inside(int x, int y) const
-{
-	return x >= 0 && x < getWidth() && y >= 0 && y < getHeight();
-}
-
-PixelFormat ImageData::getFormat() const
+bool ImageData::isSRGB() const
 {
-	return format;
+	return false;
 }
 
-int ImageData::getWidth() const
-{
-	return width;
-}
-
-int ImageData::getHeight() const
+bool ImageData::inside(int x, int y) const
 {
-	return height;
+	return x >= 0 && x < getWidth() && y >= 0 && y < getHeight();
 }
 
 void ImageData::setPixel(int x, int y, const Pixel &p)

+ 8 - 29
src/modules/image/ImageData.h

@@ -18,8 +18,7 @@
  * 3. This notice may not be removed or altered from any source distribution.
  **/
 
-#ifndef LOVE_IMAGE_IMAGE_DATA_H
-#define LOVE_IMAGE_IMAGE_DATA_H
+#pragma once
 
 // LOVE
 #include "common/Data.h"
@@ -29,6 +28,7 @@
 #include "common/halffloat.h"
 #include "filesystem/FileData.h"
 #include "thread/threads.h"
+#include "ImageDataBase.h"
 
 using love::thread::Mutex;
 
@@ -55,7 +55,7 @@ union Pixel
 /**
  * Represents raw pixel data.
  **/
-class ImageData : public Data
+class ImageData : public ImageDataBase
 {
 public:
 
@@ -71,8 +71,6 @@ public:
 	ImageData();
 	virtual ~ImageData();
 
-	PixelFormat getFormat() const;
-
 	/**
 	 * Paste part of one ImageData onto another. The subregion defined by the top-left
 	 * corner (sx, sy) and the size (sw,sh) will be pasted to (dx,dy) in this ImageData.
@@ -92,16 +90,6 @@ public:
 	 **/
 	bool inside(int x, int y) const;
 
-	/**
-	 * Gets the width of this ImageData.
-	 **/
-	int getWidth() const;
-
-	/**
-	 * Gets the height of this ImageData.
-	 **/
-	int getHeight() const;
-
 	/**
 	 * Sets the pixel at location (x,y).
 	 * @param x The location along the x-axis.
@@ -127,10 +115,11 @@ public:
 
 	love::thread::Mutex *getMutex() const;
 
-	// Implements Data.
-	virtual ImageData *clone() const = 0;
-	virtual void *getData() const;
-	virtual size_t getSize() const;
+	// Implements ImageDataBase.
+	virtual ImageData *clone() const override = 0;
+	void *getData() const override;
+	size_t getSize() const override;
+	bool isSRGB() const override;
 
 	size_t getPixelSize() const;
 
@@ -141,14 +130,6 @@ public:
 
 protected:
 
-	PixelFormat format;
-
-	// The width of the image data.
-	int width;
-
-	// The height of the image data.
-	int height;
-
 	// The actual data.
 	unsigned char *data;
 
@@ -166,5 +147,3 @@ private:
 
 } // image
 } // love
-
-#endif // LOVE_IMAGE_IMAGE_DATA_H

+ 20 - 31
src/modules/graphics/opengl/Font.h → src/modules/image/ImageDataBase.cpp

@@ -18,45 +18,34 @@
  * 3. This notice may not be removed or altered from any source distribution.
  **/
 
-#pragma once
-
-// LOVE
-#include "graphics/Font.h"
-#include "graphics/Volatile.h"
-#include "OpenGL.h"
+#include "ImageDataBase.h"
 
 namespace love
 {
-namespace graphics
-{
-namespace opengl
+namespace image
 {
 
-class Font final : public love::graphics::Font, public Volatile
+ImageDataBase::ImageDataBase()
+	: format(PIXELFORMAT_UNKNOWN)
+	, width(0)
+	, height(0)
 {
-public:
-
-	Font(love::font::Rasterizer *r, const Texture::Filter &filter);
-	virtual ~Font();
-
-	void setFilter(const Texture::Filter &f) override;
-
-	// Implements Volatile.
-	bool loadVolatile() override;
-	void unloadVolatile() override;
+}
 
-private:
-
-	void createTexture() override;
-	void uploadGlyphToTexture(font::GlyphData *data, Glyph &glyph) override;
-
-	// vector of packed textures
-	std::vector<GLuint> textures;
+PixelFormat ImageDataBase::getFormat() const
+{
+	return format;
+}
 
-	size_t textureMemorySize;
+int ImageDataBase::getWidth() const
+{
+	return width;
+}
 
-}; // Font
+int ImageDataBase::getHeight() const
+{
+	return height;
+}
 
-} // opengl
-} // graphics
+} // image
 } // love

+ 16 - 19
src/modules/graphics/opengl/Video.h → src/modules/image/ImageDataBase.h

@@ -21,38 +21,35 @@
 #pragma once
 
 // LOVE
-#include "graphics/Video.h"
-#include "graphics/Volatile.h"
-#include "OpenGL.h"
+#include "common/Data.h"
+#include "common/pixelformat.h"
 
 namespace love
 {
-namespace graphics
-{
-namespace opengl
+namespace image
 {
 
-class Video : public love::graphics::Video, public Volatile
+class ImageDataBase : public Data
 {
 public:
 
-	Video(love::video::VideoStream *stream, float pixeldensity = 1.0f);
-	virtual ~Video();
+	ImageDataBase();
+	virtual ~ImageDataBase() {}
 
-	// Volatile
-	bool loadVolatile() override;
-	void unloadVolatile() override;
+	PixelFormat getFormat() const;
 
-	void setFilter(const Texture::Filter &f) override;
+	int getWidth() const;
+	int getHeight() const;
 
-private:
+	virtual bool isSRGB() const = 0;
 
-	void uploadFrame(const love::video::VideoStream::Frame *frame) override;
+protected:
 
-	Texture::Filter filter;
+	PixelFormat format;
+	int width;
+	int height;
 
-}; // Video
+}; // ImageDataBase
 
-} // opengl
-} // graphics
+} // image
 } // love

+ 5 - 23
src/modules/image/magpie/ASTCHandler.cpp

@@ -104,7 +104,7 @@ bool ASTCHandler::canParse(const filesystem::FileData *data)
 	return true;
 }
 
-uint8 *ASTCHandler::parse(filesystem::FileData *filedata, std::vector<CompressedImageData::SubImage> &images, size_t &dataSize, PixelFormat &format, bool &sRGB)
+StrongRef<CompressedImageData::Memory> ASTCHandler::parse(filesystem::FileData *filedata, std::vector<StrongRef<CompressedImageData::Slice>> &images, PixelFormat &format, bool &sRGB)
 {
 	if (!canParse(filedata))
 		throw love::Exception("Could not decode compressed data (not an .astc file?)");
@@ -129,35 +129,17 @@ uint8 *ASTCHandler::parse(filesystem::FileData *filedata, std::vector<Compressed
 	if (totalsize + sizeof(header) > filedata->getSize())
 		throw love::Exception("Could not parse .astc file: file is too small.");
 
-	uint8 *data = nullptr;
-
-	try
-	{
-		data = new uint8[totalsize];
-	}
-	catch (std::bad_alloc &)
-	{
-		throw love::Exception("Out of memory.");
-	}
+	StrongRef<CompressedImageData::Memory> memory(new CompressedImageData::Memory(totalsize), Acquire::NORETAIN);
 
 	// .astc files only store a single mipmap level.
-	memcpy(data, (uint8 *) filedata->getData() + sizeof(ASTCHeader), totalsize);
-
-	CompressedImageData::SubImage mip;
-
-	mip.width = sizeX;
-	mip.height = sizeY;
-
-	mip.size = totalsize;
-	mip.data = data;
+	memcpy(memory->data, (uint8 *) filedata->getData() + sizeof(ASTCHeader), totalsize);
 
-	images.push_back(mip);
+	images.emplace_back(new CompressedImageData::Slice(cformat, sizeX, sizeY, memory, 0, totalsize), Acquire::NORETAIN);
 
-	dataSize = totalsize;
 	format = cformat;
 	sRGB = false;
 
-	return data;
+	return memory;
 }
 
 } // magpie

+ 5 - 2
src/modules/image/magpie/ASTCHandler.h

@@ -42,8 +42,11 @@ public:
 	virtual ~ASTCHandler() {}
 
 	// Implements CompressedFormatHandler.
-	virtual bool canParse(const filesystem::FileData *data);
-	virtual uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedImageData::SubImage> &images, size_t &dataSize, PixelFormat &format, bool &sRGB);
+	bool canParse(const filesystem::FileData *data) override;
+
+	StrongRef<CompressedImageData::Memory> parse(filesystem::FileData *filedata,
+	        std::vector<StrongRef<CompressedImageData::Slice>> &images,
+	        PixelFormat &format, bool &sRGB) override;
 
 }; // ASTCHandler
 

+ 3 - 3
src/modules/image/magpie/CompressedFormatHandler.h

@@ -58,14 +58,14 @@ public:
 	 * @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] dataSize The total size in bytes of 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 uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedImageData::SubImage> &images,
-	                     size_t &dataSize, PixelFormat &format, bool &sRGB) = 0;
+	virtual StrongRef<CompressedImageData::Memory> parse(filesystem::FileData *filedata,
+	               std::vector<StrongRef<CompressedImageData::Slice>> &images,
+	               PixelFormat &format, bool &sRGB) = 0;
 
 }; // CompressedFormatHandler
 

+ 9 - 21
src/modules/image/magpie/CompressedImageData.cpp

@@ -43,42 +43,31 @@ CompressedImageData::CompressedImageData(std::list<CompressedFormatHandler *> fo
 	if (parser == nullptr)
 		throw love::Exception("Could not parse compressed data: Unknown format.");
 
-	data = parser->parse(filedata, dataImages, dataSize, format, sRGB);
+	memory = parser->parse(filedata, dataImages, format, sRGB);
 
-	if (data == nullptr)
+	if (memory == nullptr)
 		throw love::Exception("Could not parse compressed data.");
 
 	if (format == PIXELFORMAT_UNKNOWN)
-	{
-		delete[] data;
 		throw love::Exception("Could not parse compressed data: Unknown format.");
-	}
 
-	if (dataImages.size() == 0 || dataSize == 0)
-	{
-		delete[] data;
+	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;
-	dataSize = c.dataSize;
 
-	data = new uint8[dataSize];
-	memcpy(data, c.data, dataSize);
+	memory.set(new Memory(c.memory->size), Acquire::NORETAIN);
+	memcpy(memory->data, c.memory->data, memory->size);
 
-	for (auto i : c.dataImages )
+	for (const auto &i : c.dataImages)
 	{
-		struct SubImage s;
-		s.width = i.width;
-		s.height = i.height;
-		s.size = i.size;
-		s.data = (uint8*)((intptr_t)data + (intptr_t)i.data - (intptr_t)c.data);
-
-		dataImages.push_back(s);
+		Slice *slice = new Slice(i->getFormat(), i->getWidth(), i->getHeight(), memory, i->getOffset(), i->getSize());
+		dataImages.push_back(slice);
+		slice->release();
 	}
 }
 
@@ -89,7 +78,6 @@ CompressedImageData *CompressedImageData::clone() const
 
 CompressedImageData::~CompressedImageData()
 {
-	delete[] data;
 }
 
 } // magpie

+ 1 - 1
src/modules/image/magpie/ImageData.cpp

@@ -103,7 +103,7 @@ ImageData::~ImageData()
 		handler->release();
 }
 
-ImageData *ImageData::clone() const
+love::image::ImageData *ImageData::clone() const
 {
 	return new ImageData(*this);
 }

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

@@ -45,8 +45,8 @@ public:
 	ImageData(const ImageData &c);
 	virtual ~ImageData();
 
-	virtual ImageData *clone() const;
 	// Implements image::ImageData.
+	virtual love::image::ImageData *clone() const;
 	virtual love::filesystem::FileData *encode(EncodedFormat encodedFormat, const char *filename);
 
 private:

+ 11 - 20
src/modules/image/magpie/KTXHandler.cpp

@@ -297,7 +297,7 @@ bool KTXHandler::canParse(const filesystem::FileData *data)
 	return true;
 }
 
-uint8 *KTXHandler::parse(filesystem::FileData *filedata, std::vector<CompressedImageData::SubImage> &images, size_t &dataSize, PixelFormat &format, bool &sRGB)
+StrongRef<CompressedImageData::Memory> KTXHandler::parse(filesystem::FileData *filedata, std::vector<StrongRef<CompressedImageData::Slice>> &images, PixelFormat &format, bool &sRGB)
 {
 	if (!canParse(filedata))
 		throw love::Exception("Could not decode compressed data (not a KTX file?)");
@@ -353,15 +353,8 @@ uint8 *KTXHandler::parse(filesystem::FileData *filedata, std::vector<CompressedI
 		fileoffset += mipsizepadded;
 	}
 
-	uint8 *data = nullptr;
-	try
-	{
-		data = new uint8[totalsize];
-	}
-	catch (std::bad_alloc &)
-	{
-		throw love::Exception("Out of memory.");
-	}
+	StrongRef<CompressedImageData::Memory> memory;
+	memory.set(new CompressedImageData::Memory(totalsize), Acquire::NORETAIN);
 
 	// Reset the file offset to the start of the file's image data.
 	fileoffset = sizeof(KTXHeader) + header.bytesOfKeyValueData;
@@ -379,25 +372,23 @@ uint8 *KTXHandler::parse(filesystem::FileData *filedata, std::vector<CompressedI
 
 		uint32 mipsizepadded = (mipsize + 3) & ~uint32(3);
 
-		CompressedImageData::SubImage mip;
-		mip.width = (int) std::max(header.pixelWidth >> i, 1u);
-		mip.height = (int) std::max(header.pixelHeight >> i, 1u);
-		mip.size = mipsize;
+		int width = (int) std::max(header.pixelWidth >> i, 1u);
+		int height = (int) std::max(header.pixelHeight >> i, 1u);
+
+		memcpy(memory->data + dataoffset, filebytes + fileoffset, mipsize);
 
-		memcpy(data + dataoffset, filebytes + fileoffset, mipsize);
-		mip.data = data + dataoffset;
+		auto slice = new CompressedImageData::Slice(cformat, width, height, memory, dataoffset, mipsize);
+		images.push_back(slice);
+		slice->release();
 
 		fileoffset += mipsizepadded;
 		dataoffset += mipsizepadded;
-
-		images.push_back(mip);
 	}
 
-	dataSize = totalsize;
 	format = cformat;
 	sRGB = isSRGB;
 
-	return data;
+	return memory;
 }
 
 } // magpie

+ 5 - 2
src/modules/image/magpie/KTXHandler.h

@@ -41,8 +41,11 @@ public:
 	virtual ~KTXHandler() {}
 
 	// Implements CompressedFormatHandler.
-	virtual bool canParse(const filesystem::FileData *data);
-	virtual uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedImageData::SubImage> &images, size_t &dataSize, PixelFormat &format, bool &sRGB);
+	bool canParse(const filesystem::FileData *data) override;
+
+	StrongRef<CompressedImageData::Memory> parse(filesystem::FileData *filedata,
+	        std::vector<StrongRef<CompressedImageData::Slice>> &images,
+	        PixelFormat &format, bool &sRGB) override;
 
 }; // KTXHandler
 

+ 8 - 21
src/modules/image/magpie/PKMHandler.cpp

@@ -113,7 +113,7 @@ bool PKMHandler::canParse(const filesystem::FileData *data)
 	return true;
 }
 
-uint8 *PKMHandler::parse(filesystem::FileData *filedata, std::vector<CompressedImageData::SubImage> &images, size_t &dataSize, PixelFormat &format, bool &sRGB)
+StrongRef<CompressedImageData::Memory> PKMHandler::parse(filesystem::FileData *filedata, std::vector<StrongRef<CompressedImageData::Slice>> &images, PixelFormat &format, bool &sRGB)
 {
 	if (!canParse(filedata))
 		throw love::Exception("Could not decode compressed data (not a PKM file?)");
@@ -133,37 +133,24 @@ uint8 *PKMHandler::parse(filesystem::FileData *filedata, std::vector<CompressedI
 
 	// The rest of the file after the header is all texture data.
 	size_t totalsize = filedata->getSize() - sizeof(PKMHeader);
-	uint8 *data = nullptr;
 
-	try
-	{
-		data = new uint8[totalsize];
-	}
-	catch (std::bad_alloc &)
-	{
-		throw love::Exception("Out of memory.");
-	}
+	StrongRef<CompressedImageData::Memory> memory;
+	memory.set(new CompressedImageData::Memory(totalsize), Acquire::NORETAIN);
 
 	// PKM files only store a single mipmap level.
-	memcpy(data, (uint8 *) filedata->getData() + sizeof(PKMHeader), totalsize);
-
-	CompressedImageData::SubImage mip;
+	memcpy(memory->data, (uint8 *) filedata->getData() + sizeof(PKMHeader), totalsize);
 
 	// TODO: verify whether glCompressedTexImage works properly with the unpadded
 	// width and height values (extended == padded.)
-	mip.width = header.widthBig;
-	mip.height = header.heightBig;
-
-	mip.size = totalsize;
-	mip.data = data;
+	int width = header.widthBig;
+	int height = header.heightBig;
 
-	images.push_back(mip);
+	images.emplace_back(new CompressedImageData::Slice(cformat, width, height, memory, 0, totalsize), Acquire::NORETAIN);
 
-	dataSize = totalsize;
 	format = cformat;
 	sRGB = false;
 
-	return data;
+	return memory;
 }
 
 } // magpie

+ 5 - 2
src/modules/image/magpie/PKMHandler.h

@@ -41,8 +41,11 @@ public:
 	virtual ~PKMHandler() {}
 
 	// Implements CompressedFormatHandler.
-	virtual bool canParse(const filesystem::FileData *data);
-	virtual uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedImageData::SubImage> &images, size_t &dataSize, PixelFormat &format, bool &sRGB);
+	bool canParse(const filesystem::FileData *data) override;
+
+	StrongRef<CompressedImageData::Memory> parse(filesystem::FileData *filedata,
+	        std::vector<StrongRef<CompressedImageData::Slice>> &images,
+	        PixelFormat &format, bool &sRGB) override;
 
 }; // PKMHandler
 

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

@@ -474,7 +474,7 @@ bool PVRHandler::canParse(const filesystem::FileData *data)
 	return false;
 }
 
-uint8 *PVRHandler::parse(filesystem::FileData *filedata, std::vector<CompressedImageData::SubImage> &images, size_t &dataSize, PixelFormat &format, bool &sRGB)
+StrongRef<CompressedImageData::Memory> PVRHandler::parse(filesystem::FileData *filedata, std::vector<StrongRef<CompressedImageData::Slice>> &images, PixelFormat &format, bool &sRGB)
 {
 	if (!canParse(filedata))
 		throw love::Exception("Could not decode compressed data (not a PVR file?)");
@@ -525,14 +525,8 @@ uint8 *PVRHandler::parse(filesystem::FileData *filedata, std::vector<CompressedI
 	if (filedata->getSize() < fileoffset + totalsize)
 		throw love::Exception("Could not parse PVR file: invalid size calculation.");
 
-	try
-	{
-		data = new uint8[totalsize];
-	}
-	catch (std::bad_alloc &)
-	{
-		throw love::Exception("Out of memory.");
-	}
+	StrongRef<CompressedImageData::Memory> memory;
+	memory.set(new CompressedImageData::Memory(totalsize), Acquire::NORETAIN);
 
 	size_t curoffset = 0;
 	const uint8 *filebytes = (uint8 *) filedata->getData() + fileoffset;
@@ -544,24 +538,22 @@ uint8 *PVRHandler::parse(filesystem::FileData *filedata, std::vector<CompressedI
 		if (curoffset + mipsize > totalsize)
 			break; // Just in case.
 
-		CompressedImageData::SubImage mip;
-		mip.width = std::max((int) header3.width >> i, 1);
-		mip.height = std::max((int) header3.height >> i, 1);
-		mip.size = mipsize;
+		int width = std::max((int) header3.width >> i, 1);
+		int height = std::max((int) header3.height >> i, 1);
 
 		memcpy(data + curoffset, filebytes + curoffset, mipsize);
-		mip.data = data + curoffset;
 
-		curoffset += mipsize;
+		auto slice = new CompressedImageData::Slice(cformat, width, height, memory, curoffset, mipsize);
+		images.push_back(slice);
+		slice->release();
 
-		images.push_back(mip);
+		curoffset += mipsize;
 	}
 
-	dataSize = totalsize;
 	format = cformat;
 	sRGB = (header3.colorSpace == 1);
 
-	return data;
+	return memory;
 }
 
 } // magpie

+ 5 - 2
src/modules/image/magpie/PVRHandler.h

@@ -39,8 +39,11 @@ public:
 	virtual ~PVRHandler() {}
 
 	// Implements CompressedFormatHandler.
-	virtual bool canParse(const filesystem::FileData *data);
-	virtual uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedImageData::SubImage> &images, size_t &dataSize, PixelFormat &format, bool &sRGB);
+	bool canParse(const filesystem::FileData *data) override;
+
+	StrongRef<CompressedImageData::Memory> parse(filesystem::FileData *filedata,
+	        std::vector<StrongRef<CompressedImageData::Slice>> &images,
+	        PixelFormat &format, bool &sRGB) override;
 
 }; // PVRHandler
 

+ 30 - 44
src/modules/image/magpie/ddsHandler.cpp

@@ -32,7 +32,7 @@ bool DDSHandler::canParse(const filesystem::FileData *data)
 	return dds::isCompressedDDS(data->getData(), data->getSize());
 }
 
-uint8 *DDSHandler::parse(filesystem::FileData *filedata, std::vector<CompressedImageData::SubImage> &images, size_t &dataSize, PixelFormat &format, bool &sRGB)
+StrongRef<CompressedImageData::Memory> DDSHandler::parse(filesystem::FileData *filedata, std::vector<StrongRef<CompressedImageData::Slice>> &images, PixelFormat &format, bool &sRGB)
 {
 	if (!dds::isDDS(filedata->getData(), filedata->getSize()))
 		throw love::Exception("Could not decode compressed data (not a DDS file?)");
@@ -40,65 +40,51 @@ uint8 *DDSHandler::parse(filesystem::FileData *filedata, std::vector<CompressedI
 	PixelFormat texformat = PIXELFORMAT_UNKNOWN;
 	bool isSRGB = false;
 
-	uint8 *data = nullptr;
-	dataSize = 0;
-	images.clear();
-
-	try
-	{
-		// Attempt to parse the dds file.
-		dds::Parser parser(filedata->getData(), filedata->getSize());
+	StrongRef<CompressedImageData::Memory> memory;
+	size_t dataSize = 0;
 
-		texformat = convertFormat(parser.getFormat(), isSRGB);
+	images.clear();
 
-		if (texformat == PIXELFORMAT_UNKNOWN)
-			throw love::Exception("Could not parse compressed data: Unsupported format.");
+	// Attempt to parse the dds file.
+	dds::Parser parser(filedata->getData(), filedata->getSize());
 
-		if (parser.getMipmapCount() == 0)
-			throw love::Exception("Could not parse compressed data: No readable texture data.");
+	texformat = convertFormat(parser.getFormat(), isSRGB);
 
-		// Calculate the size of the block of memory we're returning.
-		for (size_t i = 0; i < parser.getMipmapCount(); i++)
-		{
-			const dds::Image *img = parser.getImageData(i);
-			dataSize += img->dataSize;
-		}
+	if (texformat == PIXELFORMAT_UNKNOWN)
+		throw love::Exception("Could not parse compressed data: Unsupported format.");
 
-		data = new uint8[dataSize];
+	if (parser.getMipmapCount() == 0)
+		throw love::Exception("Could not parse compressed data: No readable texture data.");
 
-		size_t dataOffset = 0;
+	// Calculate the size of the block of memory we're returning.
+	for (size_t i = 0; i < parser.getMipmapCount(); i++)
+	{
+		const dds::Image *img = parser.getImageData(i);
+		dataSize += img->dataSize;
+	}
 
-		// Copy the parsed mipmap levels from the FileData to our CompressedImageData.
-		for (size_t i = 0; i < parser.getMipmapCount(); i++)
-		{
-			// Fetch the data for this mipmap level.
-			const dds::Image *img = parser.getImageData(i);
+	memory.set(new CompressedImageData::Memory(dataSize), Acquire::NORETAIN);
 
-			CompressedImageData::SubImage mip;
+	size_t dataOffset = 0;
 
-			mip.width = img->width;
-			mip.height = img->height;
-			mip.size = img->dataSize;
+	// Copy the parsed mipmap levels from the FileData to our CompressedImageData.
+	for (size_t i = 0; i < parser.getMipmapCount(); i++)
+	{
+		// Fetch the data for this mipmap level.
+		const dds::Image *img = parser.getImageData(i);
 
-			// Copy the mipmap image from the FileData to our block of memory.
-			memcpy(data + dataOffset, img->data, mip.size);
-			mip.data = data + dataOffset;
+		// Copy the mipmap image from the FileData to our block of memory.
+		memcpy(memory->data + dataOffset, img->data, img->dataSize);
 
-			dataOffset += mip.size;
+		auto slice = new CompressedImageData::Slice(texformat, img->width, img->height, memory, dataOffset, img->dataSize);
+		images.emplace_back(slice, Acquire::NORETAIN);
 
-			images.push_back(mip);
-		}
-	}
-	catch (std::exception &e)
-	{
-		delete[] data;
-		images.clear();
-		throw love::Exception("%s", e.what());
+		dataOffset += img->dataSize;
 	}
 
 	format = texformat;
 	sRGB = isSRGB;
-	return data;
+	return memory;
 }
 
 PixelFormat DDSHandler::convertFormat(dds::Format ddsformat, bool &sRGB)

+ 5 - 2
src/modules/image/magpie/ddsHandler.h

@@ -47,8 +47,11 @@ public:
 	virtual ~DDSHandler() {}
 
 	// Implements CompressedFormatHandler.
-	virtual bool canParse(const filesystem::FileData *data);
-	virtual uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedImageData::SubImage> &images, size_t &dataSize, PixelFormat &format, bool &sRGB);
+	bool canParse(const filesystem::FileData *data) override;
+
+	StrongRef<CompressedImageData::Memory> parse(filesystem::FileData *filedata,
+	        std::vector<StrongRef<CompressedImageData::Slice>> &images,
+	        PixelFormat &format, bool &sRGB) override;
 
 private:
 

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

@@ -122,12 +122,23 @@ int w_isCompressed(lua_State *L)
 	return 1;
 }
 
+int w_newCubeFaces(lua_State *L)
+{
+	ImageData *id = luax_checkimagedata(L, 1);
+	std::vector<StrongRef<ImageData>> faces;
+	luax_catchexcept(L, [&](){ faces = instance()->newCubeFaces(id); });
+	for (auto face : faces)
+		luax_pushtype(L, face);
+	return (int) faces.size();
+}
+
 // List of functions to wrap.
 static const luaL_Reg functions[] =
 {
 	{ "newImageData",  w_newImageData },
 	{ "newCompressedData", w_newCompressedData },
 	{ "isCompressed", w_isCompressed },
+	{ "newCubeFaces", w_newCubeFaces },
 	{ 0, 0 }
 };