Browse Source

Merge branch '12.0-development' into metal

Alex Szpakowski 5 years ago
parent
commit
7a50f32585
57 changed files with 4468 additions and 2497 deletions
  1. 21 1
      .github/workflows/main.yml
  2. 2 0
      CMakeLists.txt
  3. 15 3
      license.txt
  4. 10 2
      platform/xcode/liblove.xcodeproj/project.pbxproj
  5. 1 1
      readme.md
  6. 15 0
      src/common/StringMap.h
  7. 495 301
      src/libraries/lodepng/lodepng.cpp
  8. 321 138
      src/libraries/lodepng/lodepng.h
  9. 197 88
      src/libraries/stb/stb_image.h
  10. 192 6
      src/modules/graphics/Buffer.cpp
  11. 92 38
      src/modules/graphics/Buffer.h
  12. 2 2
      src/modules/graphics/Font.cpp
  13. 2 2
      src/modules/graphics/Font.h
  14. 102 64
      src/modules/graphics/Graphics.cpp
  15. 26 35
      src/modules/graphics/Graphics.h
  16. 184 188
      src/modules/graphics/Mesh.cpp
  17. 30 30
      src/modules/graphics/Mesh.h
  18. 7 4
      src/modules/graphics/ParticleSystem.cpp
  19. 1 1
      src/modules/graphics/ParticleSystem.h
  20. 21 11
      src/modules/graphics/Polyline.cpp
  21. 3 3
      src/modules/graphics/Polyline.h
  22. 511 22
      src/modules/graphics/Shader.cpp
  23. 23 17
      src/modules/graphics/Shader.h
  24. 7 0
      src/modules/graphics/ShaderStage.cpp
  25. 2 0
      src/modules/graphics/ShaderStage.h
  26. 30 30
      src/modules/graphics/SpriteBatch.cpp
  27. 4 4
      src/modules/graphics/SpriteBatch.h
  28. 6 3
      src/modules/graphics/Text.cpp
  29. 2 2
      src/modules/graphics/Text.h
  30. 8 10
      src/modules/graphics/Texture.cpp
  31. 4 4
      src/modules/graphics/Video.cpp
  32. 142 90
      src/modules/graphics/opengl/Buffer.cpp
  33. 22 13
      src/modules/graphics/opengl/Buffer.h
  34. 49 36
      src/modules/graphics/opengl/Graphics.cpp
  35. 6 5
      src/modules/graphics/opengl/Graphics.h
  36. 188 93
      src/modules/graphics/opengl/OpenGL.cpp
  37. 25 8
      src/modules/graphics/opengl/OpenGL.h
  38. 207 18
      src/modules/graphics/opengl/Shader.cpp
  39. 6 0
      src/modules/graphics/opengl/Shader.h
  40. 10 0
      src/modules/graphics/opengl/Texture.cpp
  41. 189 216
      src/modules/graphics/vertex.cpp
  42. 118 81
      src/modules/graphics/vertex.h
  43. 474 0
      src/modules/graphics/wrap_Buffer.cpp
  44. 39 0
      src/modules/graphics/wrap_Buffer.h
  45. 377 148
      src/modules/graphics/wrap_Graphics.cpp
  46. 1 0
      src/modules/graphics/wrap_Graphics.h
  47. 0 520
      src/modules/graphics/wrap_GraphicsShader.lua
  48. 133 184
      src/modules/graphics/wrap_Mesh.cpp
  49. 0 3
      src/modules/graphics/wrap_Mesh.h
  50. 52 17
      src/modules/graphics/wrap_Shader.cpp
  51. 15 2
      src/modules/graphics/wrap_SpriteBatch.cpp
  52. 2 1
      src/modules/image/magpie/STBHandler.cpp
  53. 0 5
      src/modules/love/love.cpp
  54. 68 37
      src/modules/timer/Timer.cpp
  55. 5 6
      src/modules/timer/Timer.h
  56. 1 1
      src/scripts/boot.lua
  57. 3 3
      src/scripts/boot.lua.h

+ 21 - 1
.github/workflows/main.yml

@@ -3,7 +3,7 @@ on: [push, pull_request]
 
 jobs:
   linux-os:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-16.04
     steps:
     - name: Update APT
       run: sudo apt-get update
@@ -20,6 +20,26 @@ jobs:
       run: mkdir build && cd build && ../configure
     - name: Build
       run: cd build && make -j2
+    - name: Prepare appimagetool
+      run: |
+        cd build &&
+        wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O appimagetool &&
+        chmod +x appimagetool &&
+        sudo apt install -y appstream
+    - name: Clone love-appimages
+      uses: actions/checkout@v2
+      with:
+        path: build/love-appimages
+        repository: pfirsich/love-appimages
+    - name: Build AppImage
+      run: |
+        cd build &&
+        python3 love-appimages/build.py .. AppDir --builddir build --appimage love.AppImage
+    - name: Artifact
+      uses: actions/upload-artifact@v2-preview
+      with:
+        name: love.AppImage
+        path: build/love.AppImage
   windows-os:
     runs-on: windows-latest
     strategy:

+ 2 - 0
CMakeLists.txt

@@ -527,6 +527,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT
 	src/modules/graphics/Video.h
 	src/modules/graphics/Volatile.cpp
 	src/modules/graphics/Volatile.h
+	src/modules/graphics/wrap_Buffer.cpp
+	src/modules/graphics/wrap_Buffer.h
 	src/modules/graphics/wrap_Font.cpp
 	src/modules/graphics/wrap_Font.h
 	src/modules/graphics/wrap_Graphics.cpp

+ 15 - 3
license.txt

@@ -57,6 +57,12 @@ This distribution contains code from the following projects (full license text b
 		- LZ4 source repository : https://github.com/Cyan4973/lz4
 		- LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c
 
+ - LodePNG
+	Website: https://lodev.org/lodepng/
+	Source download: https://github.com/lvandeve/lodepng
+	License: zlib
+	Copyright (c) 2005-2020 Lode Vandevenne
+
  - TinyEXR
 	Website: https://github.com/syoyo/tinyexr
 	License: 3-Clause BSD
@@ -86,10 +92,16 @@ This distribution contains code from the following projects (full license text b
 	License: MIT/Expat
 	Copyright 2018 David Reid
 
+ - stb_image
+	Website: https://github.com/nothings/stb
+	Source download: https://github.com/nothings/stb/blob/f54acd4e13430c5122cab4ca657705c84aa61b08/stb_image.h
+	License: MIT/Expat
+	Copyright (c) 2017 Sean Barrett
+
  - OpenAL Soft
-     Website: http://kcat.strangesoft.net/openal.html
-     Source download: http://kcat.strangesoft.net/openal.html#download
-     License: Mixed, licensing information obtained from the debian project
+	Website: http://kcat.strangesoft.net/openal.html
+	Source download: http://kcat.strangesoft.net/openal.html#download
+	License: Mixed, licensing information obtained from the debian project
 		- Alc/backends/opensl.c
 			License: Apache 2.0
 			Copyright 2011 The Android Open Source Project

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

@@ -840,6 +840,9 @@
 		FA18CF4523DD1A8100263725 /* ShaderStage.h in Headers */ = {isa = PBXBuildFile; fileRef = FA18CF4323DD1A8000263725 /* ShaderStage.h */; };
 		FA18CF4623DD1A8100263725 /* ShaderStage.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA18CF4423DD1A8000263725 /* ShaderStage.mm */; };
 		FA18CF4723DD1A8100263725 /* ShaderStage.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA18CF4423DD1A8000263725 /* ShaderStage.mm */; };
+		FA18CEC523D3AE6700263725 /* wrap_Buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA18CEC323D3AE6700263725 /* wrap_Buffer.cpp */; };
+		FA18CEC623D3AE6800263725 /* wrap_Buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA18CEC323D3AE6700263725 /* wrap_Buffer.cpp */; };
+		FA18CEC723D3AE6800263725 /* wrap_Buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = FA18CEC423D3AE6700263725 /* wrap_Buffer.h */; };
 		FA1BA09D1E16CFCE00AA2803 /* Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA09B1E16CFCE00AA2803 /* Font.cpp */; };
 		FA1BA09E1E16CFCE00AA2803 /* Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA09B1E16CFCE00AA2803 /* Font.cpp */; };
 		FA1BA09F1E16CFCE00AA2803 /* Font.h in Headers */ = {isa = PBXBuildFile; fileRef = FA1BA09C1E16CFCE00AA2803 /* Font.h */; };
@@ -1896,6 +1899,8 @@
 		FA18CF1523DCF67800263725 /* spirv_glsl.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = spirv_glsl.hpp; sourceTree = "<group>"; };
 		FA18CF4323DD1A8000263725 /* ShaderStage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShaderStage.h; sourceTree = "<group>"; };
 		FA18CF4423DD1A8000263725 /* ShaderStage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ShaderStage.mm; sourceTree = "<group>"; };
+		FA18CEC323D3AE6700263725 /* wrap_Buffer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Buffer.cpp; sourceTree = "<group>"; };
+		FA18CEC423D3AE6700263725 /* wrap_Buffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = wrap_Buffer.h; sourceTree = "<group>"; };
 		FA1BA09B1E16CFCE00AA2803 /* Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Font.cpp; sourceTree = "<group>"; };
 		FA1BA09C1E16CFCE00AA2803 /* Font.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Font.h; sourceTree = "<group>"; };
 		FA1BA0A01E16D97500AA2803 /* wrap_Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Font.cpp; sourceTree = "<group>"; };
@@ -1982,7 +1987,6 @@
 		FA620A301AA2F8DB005DB4C2 /* wrap_Texture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Texture.cpp; sourceTree = "<group>"; };
 		FA620A311AA2F8DB005DB4C2 /* wrap_Texture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Texture.h; sourceTree = "<group>"; };
 		FA620A391AA305F6005DB4C2 /* types.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = types.cpp; sourceTree = "<group>"; };
-		FA665DC321C34C900074BBD6 /* wrap_GraphicsShader.lua */ = {isa = PBXFileReference; lastKnownFileType = text; path = wrap_GraphicsShader.lua; sourceTree = "<group>"; };
 		FA6A2B641F5F7B6B0074C308 /* wrap_Data.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Data.h; sourceTree = "<group>"; };
 		FA6A2B651F5F7B6B0074C308 /* wrap_Data.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Data.cpp; sourceTree = "<group>"; };
 		FA6A2B681F5F7F560074C308 /* DataView.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DataView.cpp; sourceTree = "<group>"; };
@@ -2967,12 +2971,13 @@
 				FADF54061E3D78F700012CC0 /* Video.h */,
 				FA0B7BC01A95902C000E1D17 /* Volatile.cpp */,
 				FA0B7BC11A95902C000E1D17 /* Volatile.h */,
+				FA18CEC323D3AE6700263725 /* wrap_Buffer.cpp */,
+				FA18CEC423D3AE6700263725 /* wrap_Buffer.h */,
 				FA1BA0A01E16D97500AA2803 /* wrap_Font.cpp */,
 				FA1BA0A11E16D97500AA2803 /* wrap_Font.h */,
 				FADF54391E3DAFF700012CC0 /* wrap_Graphics.cpp */,
 				FADF543A1E3DAFF700012CC0 /* wrap_Graphics.h */,
 				FADF54371E3DAFBA00012CC0 /* wrap_Graphics.lua */,
-				FA665DC321C34C900074BBD6 /* wrap_GraphicsShader.lua */,
 				FADF54281E3DAADA00012CC0 /* wrap_Mesh.cpp */,
 				FADF54291E3DAADA00012CC0 /* wrap_Mesh.h */,
 				FADF541E1E3DA52C00012CC0 /* wrap_ParticleSystem.cpp */,
@@ -4137,6 +4142,7 @@
 				FA0B7A631A958EA3000E1D17 /* b2ContactManager.h in Headers */,
 				FA0B7E771A95902C000E1D17 /* wrap_WeldJoint.h in Headers */,
 				FA0B7A911A958EA3000E1D17 /* b2FrictionJoint.h in Headers */,
+				FA18CEC723D3AE6800263725 /* wrap_Buffer.h in Headers */,
 				FA0B7E291A95902C000E1D17 /* PulleyJoint.h in Headers */,
 				FA6BDE5C1F31725300786805 /* Color.h in Headers */,
 				FA0B7E231A95902C000E1D17 /* PolygonShape.h in Headers */,
@@ -4731,6 +4737,7 @@
 				FAF6C9E023C2DE2900D7B5BC /* SpvTools.cpp in Sources */,
 				FAE64A8F2071364200BC7981 /* physfs_platform_unix.c in Sources */,
 				FA0B7A681A958EA3000E1D17 /* b2Island.cpp in Sources */,
+				FA18CEC623D3AE6800263725 /* wrap_Buffer.cpp in Sources */,
 				FA0B7E2B1A95902C000E1D17 /* RevoluteJoint.cpp in Sources */,
 				FA0B7B291A958EA3000E1D17 /* simplexnoise1234.cpp in Sources */,
 				FA0B7D261A95902C000E1D17 /* wrap_Font.cpp in Sources */,
@@ -4997,6 +5004,7 @@
 				FAF140A91E20934C00F898D2 /* SymbolTable.cpp in Sources */,
 				FA0B7E181A95902C000E1D17 /* MotorJoint.cpp in Sources */,
 				FA0B7E4E1A95902C000E1D17 /* wrap_Fixture.cpp in Sources */,
+				FA18CEC523D3AE6700263725 /* wrap_Buffer.cpp in Sources */,
 				FAF6C9F423C2DE2900D7B5BC /* Logger.cpp in Sources */,
 				FA0B7EBE1A95902C000E1D17 /* Thread.cpp in Sources */,
 				FAC7CD8F1FE35E95006A60C7 /* physfs_platform_posix.c in Sources */,

+ 1 - 1
readme.md

@@ -59,7 +59,7 @@ For code contributions, pull requests and patches are welcome. Be sure to read t
 Builds
 ------
 
-Releases are found in the [releases][releases] section on github, and are linked on [the site][site],
+Releases are found in the [releases][releases] section on GitHub, and are linked on [the site][site],
 and there's a ppa for ubuntu, [ppa:bartbes/love-stable][stableppa].
 
 There are also unstable/nightly builds:

+ 15 - 0
src/common/StringMap.h

@@ -182,6 +182,21 @@ private:
 
 }; // StringMap
 
+#define STRINGMAP_DECLARE(type) \
+bool getConstant(const char *in, type &out); \
+bool getConstant(type in, const char *&out); \
+std::vector<std::string> getConstants(type); \
+
+#define STRINGMAP_BEGIN(type, count, name) \
+static StringMap<type, count>::Entry name##Entries[] =
+
+#define STRINGMAP_END(type, count, name) \
+; \
+static StringMap<type, count> name##s(name##Entries, sizeof(name##Entries)); \
+bool getConstant(const char *in, type &out) { return name##s.find(in, out); } \
+bool getConstant(type in, const char *&out) { return name##s.find(in, out); } \
+std::vector<std::string> getConstants(type) { return name##s.getNames(); }
+
 } // love
 
 #endif // LOVE_STRING_MAP_H

File diff suppressed because it is too large
+ 495 - 301
src/libraries/lodepng/lodepng.cpp


+ 321 - 138
src/libraries/lodepng/lodepng.h

@@ -1,7 +1,7 @@
 /*
-LodePNG version 20170917
+LodePNG version 20200306
 
-Copyright (c) 2005-2017 Lode Vandevenne
+Copyright (c) 2005-2020 Lode Vandevenne
 
 This software is provided 'as-is', without any express or implied
 warranty. In no event will the authors be held liable for any damages
@@ -44,36 +44,44 @@ the custom_zlib field of the compress and decompress settings*/
 #ifndef LODEPNG_NO_COMPILE_ZLIB
 #define LODEPNG_COMPILE_ZLIB
 #endif
+
 /*png encoder and png decoder*/
 #ifndef LODEPNG_NO_COMPILE_PNG
 #define LODEPNG_COMPILE_PNG
 #endif
+
 /*deflate&zlib decoder and png decoder*/
 #ifndef LODEPNG_NO_COMPILE_DECODER
 #define LODEPNG_COMPILE_DECODER
 #endif
+
 /*deflate&zlib encoder and png encoder*/
 #ifndef LODEPNG_NO_COMPILE_ENCODER
 #define LODEPNG_COMPILE_ENCODER
 #endif
+
 /*the optional built in harddisk file loading and saving functions*/
 #ifndef LODEPNG_NO_COMPILE_DISK
 #define LODEPNG_COMPILE_DISK
 #endif
+
 /*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/
 #ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS
 #define LODEPNG_COMPILE_ANCILLARY_CHUNKS
 #endif
+
 /*ability to convert error numerical codes to English text string*/
 #ifndef LODEPNG_NO_COMPILE_ERROR_TEXT
 #define LODEPNG_COMPILE_ERROR_TEXT
 #endif
+
 /*Compile the default allocators (C's free, malloc and realloc). If you disable this,
 you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your
 source files with custom allocators.*/
 #ifndef LODEPNG_NO_COMPILE_ALLOCATORS
 #define LODEPNG_COMPILE_ALLOCATORS
 #endif
+
 /*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/
 #ifdef __cplusplus
 #ifndef LODEPNG_NO_COMPILE_CPP
@@ -87,14 +95,19 @@ source files with custom allocators.*/
 #endif /*LODEPNG_COMPILE_CPP*/
 
 #ifdef LODEPNG_COMPILE_PNG
-/*The PNG color types (also used for raw).*/
-typedef enum LodePNGColorType
-{
-  LCT_GREY = 0, /*greyscale: 1,2,4,8,16 bit*/
+/*The PNG color types (also used for raw image).*/
+typedef enum LodePNGColorType {
+  LCT_GREY = 0, /*grayscale: 1,2,4,8,16 bit*/
   LCT_RGB = 2, /*RGB: 8,16 bit*/
   LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/
-  LCT_GREY_ALPHA = 4, /*greyscale with alpha: 8,16 bit*/
-  LCT_RGBA = 6 /*RGB with alpha: 8,16 bit*/
+  LCT_GREY_ALPHA = 4, /*grayscale with alpha: 8,16 bit*/
+  LCT_RGBA = 6, /*RGB with alpha: 8,16 bit*/
+  /*LCT_MAX_OCTET_VALUE lets the compiler allow this enum to represent any invalid
+  byte value from 0 to 255 that could be present in an invalid PNG file header. Do
+  not use, compare with or set the name LCT_MAX_OCTET_VALUE, instead either use
+  the valid color type names above, or numeric values like 1 or 7 when checking for
+  particular disallowed color type byte values, or cast to integer to print it.*/
+  LCT_MAX_OCTET_VALUE = 255
 } LodePNGColorType;
 
 #ifdef LODEPNG_COMPILE_DECODER
@@ -196,8 +209,7 @@ unsigned lodepng_encode24_file(const char* filename,
 
 
 #ifdef LODEPNG_COMPILE_CPP
-namespace lodepng
-{
+namespace lodepng {
 #ifdef LODEPNG_COMPILE_DECODER
 /*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype
 is the format to output the pixels to. Default is RGBA 8-bit per channel.*/
@@ -253,17 +265,17 @@ const char* lodepng_error_text(unsigned code);
 #ifdef LODEPNG_COMPILE_DECODER
 /*Settings for zlib decompression*/
 typedef struct LodePNGDecompressSettings LodePNGDecompressSettings;
-struct LodePNGDecompressSettings
-{
+struct LodePNGDecompressSettings {
+  /* Check LodePNGDecoderSettings for more ignorable errors such as ignore_crc */
   unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/
+  unsigned ignore_nlen; /*ignore complement of len checksum in uncompressed blocks*/
 
   /*use custom zlib decoder instead of built in one (default: null)*/
   unsigned (*custom_zlib)(unsigned char**, size_t*,
                           const unsigned char*, size_t,
                           const LodePNGDecompressSettings*);
   /*use custom deflate decoder instead of built in one (default: null)
-  if custom_zlib is used, custom_deflate is ignored since only the built in
-  zlib function will call custom_deflate*/
+  if custom_zlib is not null, custom_inflate is ignored (the zlib format uses deflate)*/
   unsigned (*custom_inflate)(unsigned char**, size_t*,
                              const unsigned char*, size_t,
                              const LodePNGDecompressSettings*);
@@ -281,13 +293,12 @@ Settings for zlib compression. Tweaking these settings tweaks the balance
 between speed and compression ratio.
 */
 typedef struct LodePNGCompressSettings LodePNGCompressSettings;
-struct LodePNGCompressSettings /*deflate = compress*/
-{
+struct LodePNGCompressSettings /*deflate = compress*/ {
   /*LZ77 related settings*/
   unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/
   unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/
   unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/
-  unsigned minmatch; /*mininum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/
+  unsigned minmatch; /*minimum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/
   unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/
   unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/
 
@@ -315,8 +326,7 @@ Color mode of an image. Contains all information required to decode the pixel
 bits to RGBA colors. This information is the same as used in the PNG file
 format, and is used both for PNG and raw image data in LodePNG.
 */
-typedef struct LodePNGColorMode
-{
+typedef struct LodePNGColorMode {
   /*header (IHDR)*/
   LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/
   unsigned bitdepth;  /*bits per sample, see PNG standard or documentation further in this header file*/
@@ -325,23 +335,25 @@ typedef struct LodePNGColorMode
   palette (PLTE and tRNS)
 
   Dynamically allocated with the colors of the palette, including alpha.
-  When encoding a PNG, to store your colors in the palette of the LodePNGColorMode, first use
-  lodepng_palette_clear, then for each color use lodepng_palette_add.
-  If you encode an image without alpha with palette, don't forget to put value 255 in each A byte of the palette.
+  This field may not be allocated directly, use lodepng_color_mode_init first,
+  then lodepng_palette_add per color to correctly initialize it (to ensure size
+  of exactly 1024 bytes).
+
+  The alpha channels must be set as well, set them to 255 for opaque images.
 
   When decoding, by default you can ignore this palette, since LodePNG already
   fills the palette colors in the pixels of the raw RGBA output.
 
   The palette is only supported for color type 3.
   */
-  unsigned char* palette; /*palette in RGBARGBA... order. When allocated, must be either 0, or have size 1024*/
-  size_t palettesize; /*palette size in number of colors (amount of bytes is 4 * palettesize)*/
+  unsigned char* palette; /*palette in RGBARGBA... order. Must be either 0, or when allocated must have 1024 bytes*/
+  size_t palettesize; /*palette size in number of colors (amount of used bytes is 4 * palettesize)*/
 
   /*
   transparent color key (tRNS)
 
   This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit.
-  For greyscale PNGs, r, g and b will all 3 be set to the same.
+  For grayscale PNGs, r, g and b will all 3 be set to the same.
 
   When decoding, by default you can ignore this information, since LodePNG sets
   pixels with this key to transparent already in the raw RGBA output.
@@ -349,7 +361,7 @@ typedef struct LodePNGColorMode
   The color key is only supported for color types 0 and 2.
   */
   unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/
-  unsigned key_r;       /*red/greyscale component of color key*/
+  unsigned key_r;       /*red/grayscale component of color key*/
   unsigned key_g;       /*green component of color key*/
   unsigned key_b;       /*blue component of color key*/
 } LodePNGColorMode;
@@ -359,6 +371,8 @@ void lodepng_color_mode_init(LodePNGColorMode* info);
 void lodepng_color_mode_cleanup(LodePNGColorMode* info);
 /*return value is error code (0 means no error)*/
 unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source);
+/* Makes a temporary LodePNGColorMode that does not need cleanup (no palette) */
+LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth);
 
 void lodepng_palette_clear(LodePNGColorMode* info);
 /*add 1 color to the palette*/
@@ -370,7 +384,7 @@ unsigned lodepng_get_bpp(const LodePNGColorMode* info);
 /*get the amount of color channels used, based on colortype in the struct.
 If a palette is used, it counts as 1 channel.*/
 unsigned lodepng_get_channels(const LodePNGColorMode* info);
-/*is it a greyscale type? (only colortype 0 or 4)*/
+/*is it a grayscale type? (only colortype 0 or 4)*/
 unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info);
 /*has it got an alpha channel? (only colortype 2 or 6)*/
 unsigned lodepng_is_alpha_type(const LodePNGColorMode* info);
@@ -392,8 +406,7 @@ size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* colo
 
 #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
 /*The information of a Time chunk in PNG.*/
-typedef struct LodePNGTime
-{
+typedef struct LodePNGTime {
   unsigned year;    /*2 bytes used (0-65535)*/
   unsigned month;   /*1-12*/
   unsigned day;     /*1-31*/
@@ -404,28 +417,39 @@ typedef struct LodePNGTime
 #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
 
 /*Information about the PNG image, except pixels, width and height.*/
-typedef struct LodePNGInfo
-{
+typedef struct LodePNGInfo {
   /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/
   unsigned compression_method;/*compression method of the original file. Always 0.*/
   unsigned filter_method;     /*filter method of the original file*/
-  unsigned interlace_method;  /*interlace method of the original file*/
+  unsigned interlace_method;  /*interlace method of the original file: 0=none, 1=Adam7*/
   LodePNGColorMode color;     /*color type and bits, palette and transparency of the PNG file*/
 
 #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
   /*
-  suggested background color chunk (bKGD)
-  This color uses the same color mode as the PNG (except alpha channel), which can be 1-bit to 16-bit.
+  Suggested background color chunk (bKGD)
+
+  This uses the same color mode and bit depth as the PNG (except no alpha channel),
+  with values truncated to the bit depth in the unsigned integer.
+
+  For grayscale and palette PNGs, the value is stored in background_r. The values
+  in background_g and background_b are then unused.
 
-  For greyscale PNGs, r, g and b will all 3 be set to the same. When encoding
-  the encoder writes the red one. For palette PNGs: When decoding, the RGB value
-  will be stored, not a palette index. But when encoding, specify the index of
-  the palette in background_r, the other two are then ignored.
+  So when decoding, you may get these in a different color mode than the one you requested
+  for the raw pixels.
 
-  The decoder does not use this background color to edit the color of pixels.
+  When encoding with auto_convert, you must use the color model defined in info_png.color for
+  these values. The encoder normally ignores info_png.color when auto_convert is on, but will
+  use it to interpret these values (and convert copies of them to its chosen color model).
+
+  When encoding, avoid setting this to an expensive color, such as a non-gray value
+  when the image is gray, or the compression will be worse since it will be forced to
+  write the PNG with a more expensive color mode (when auto_convert is on).
+
+  The decoder does not use this background color to edit the color of pixels. This is a
+  completely optional metadata feature.
   */
   unsigned background_defined; /*is a suggested background color given?*/
-  unsigned background_r;       /*red component of suggested background color*/
+  unsigned background_r;       /*red/gray/palette component of suggested background color*/
   unsigned background_g;       /*green component of suggested background color*/
   unsigned background_b;       /*blue component of suggested background color*/
 
@@ -436,6 +460,10 @@ typedef struct LodePNGInfo
   text_strings, while text_keys are keywords that give a short description what
   the actual text represents, e.g. Title, Author, Description, or anything else.
 
+  All the string fields below including keys, names and language tags are null terminated.
+  The PNG specification uses null characters for the keys, names and tags, and forbids null
+  characters to appear in the main text which is why we can use null termination everywhere here.
+
   A keyword is minimum 1 character and maximum 79 characters long. It's
   discouraged to use a single line length longer than 79 characters for texts.
 
@@ -468,11 +496,86 @@ typedef struct LodePNGInfo
   unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/
 
   /*
-  unknown chunks
-  There are 3 buffers, one for each position in the PNG where unknown chunks can appear
-  each buffer contains all unknown chunks for that position consecutively
-  The 3 buffers are the unknown chunks between certain critical chunks:
-  0: IHDR-PLTE, 1: PLTE-IDAT, 2: IDAT-IEND
+  Color profile related chunks: gAMA, cHRM, sRGB, iCPP
+
+  LodePNG does not apply any color conversions on pixels in the encoder or decoder and does not interpret these color
+  profile values. It merely passes on the information. If you wish to use color profiles and convert colors, please
+  use these values with a color management library.
+
+  See the PNG, ICC and sRGB specifications for more information about the meaning of these values.
+  */
+
+  /* gAMA chunk: optional, overridden by sRGB or iCCP if those are present. */
+  unsigned gama_defined; /* Whether a gAMA chunk is present (0 = not present, 1 = present). */
+  unsigned gama_gamma;   /* Gamma exponent times 100000 */
+
+  /* cHRM chunk: optional, overridden by sRGB or iCCP if those are present. */
+  unsigned chrm_defined; /* Whether a cHRM chunk is present (0 = not present, 1 = present). */
+  unsigned chrm_white_x; /* White Point x times 100000 */
+  unsigned chrm_white_y; /* White Point y times 100000 */
+  unsigned chrm_red_x;   /* Red x times 100000 */
+  unsigned chrm_red_y;   /* Red y times 100000 */
+  unsigned chrm_green_x; /* Green x times 100000 */
+  unsigned chrm_green_y; /* Green y times 100000 */
+  unsigned chrm_blue_x;  /* Blue x times 100000 */
+  unsigned chrm_blue_y;  /* Blue y times 100000 */
+
+  /*
+  sRGB chunk: optional. May not appear at the same time as iCCP.
+  If gAMA is also present gAMA must contain value 45455.
+  If cHRM is also present cHRM must contain respectively 31270,32900,64000,33000,30000,60000,15000,6000.
+  */
+  unsigned srgb_defined; /* Whether an sRGB chunk is present (0 = not present, 1 = present). */
+  unsigned srgb_intent;  /* Rendering intent: 0=perceptual, 1=rel. colorimetric, 2=saturation, 3=abs. colorimetric */
+
+  /*
+  iCCP chunk: optional. May not appear at the same time as sRGB.
+
+  LodePNG does not parse or use the ICC profile (except its color space header field for an edge case), a
+  separate library to handle the ICC data (not included in LodePNG) format is needed to use it for color
+  management and conversions.
+
+  For encoding, if iCCP is present, gAMA and cHRM are recommended to be added as well with values that match the ICC
+  profile as closely as possible, if you wish to do this you should provide the correct values for gAMA and cHRM and
+  enable their '_defined' flags since LodePNG will not automatically compute them from the ICC profile.
+
+  For encoding, the ICC profile is required by the PNG specification to be an "RGB" profile for non-gray
+  PNG color types and a "GRAY" profile for gray PNG color types. If you disable auto_convert, you must ensure
+  the ICC profile type matches your requested color type, else the encoder gives an error. If auto_convert is
+  enabled (the default), and the ICC profile is not a good match for the pixel data, this will result in an encoder
+  error if the pixel data has non-gray pixels for a GRAY profile, or a silent less-optimal compression of the pixel
+  data if the pixels could be encoded as grayscale but the ICC profile is RGB.
+
+  To avoid this do not set an ICC profile in the image unless there is a good reason for it, and when doing so
+  make sure you compute it carefully to avoid the above problems.
+  */
+  unsigned iccp_defined;      /* Whether an iCCP chunk is present (0 = not present, 1 = present). */
+  char* iccp_name;            /* Null terminated string with profile name, 1-79 bytes */
+  /*
+  The ICC profile in iccp_profile_size bytes.
+  Don't allocate this buffer yourself. Use the init/cleanup functions
+  correctly and use lodepng_set_icc and lodepng_clear_icc.
+  */
+  unsigned char* iccp_profile;
+  unsigned iccp_profile_size; /* The size of iccp_profile in bytes */
+
+  /* End of color profile related chunks */
+
+
+  /*
+  unknown chunks: chunks not known by LodePNG, passed on byte for byte.
+
+  There are 3 buffers, one for each position in the PNG where unknown chunks can appear.
+  Each buffer contains all unknown chunks for that position consecutively.
+  The 3 positions are:
+  0: between IHDR and PLTE, 1: between PLTE and IDAT, 2: between IDAT and IEND.
+
+  For encoding, do not store critical chunks or known chunks that are enabled with a "_defined" flag
+  above in here, since the encoder will blindly follow this and could then encode an invalid PNG file
+  (such as one with two IHDR chunks or the disallowed combination of sRGB with iCCP). But do use
+  this if you wish to store an ancillary chunk that is not supported by LodePNG (such as sPLT or hIST),
+  or any non-standard PNG chunk.
+
   Do not allocate or traverse this data yourself. Use the chunk traversing functions declared
   later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct.
   */
@@ -488,12 +591,16 @@ void lodepng_info_cleanup(LodePNGInfo* info);
 unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source);
 
 #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
-void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/
 unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/
+void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/
 
-void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/
 unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag,
                            const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/
+void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/
+
+/*replaces if exists*/
+unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size);
+void lodepng_clear_icc(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/
 #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
 
 /*
@@ -516,11 +623,17 @@ unsigned lodepng_convert(unsigned char* out, const unsigned char* in,
 Settings for the decoder. This contains settings for the PNG and the Zlib
 decoder, but not the Info settings from the Info structs.
 */
-typedef struct LodePNGDecoderSettings
-{
+typedef struct LodePNGDecoderSettings {
   LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/
 
+  /* Check LodePNGDecompressSettings for more ignorable errors such as ignore_adler32 */
   unsigned ignore_crc; /*ignore CRC checksums*/
+  unsigned ignore_critical; /*ignore unknown critical chunks*/
+  unsigned ignore_end; /*ignore issues at end of file if possible (missing IEND chunk, too large chunk, ...)*/
+  /* TODO: make a system involving warnings with levels and a strict mode instead. Other potentially recoverable
+     errors: srgb rendering intent value, size of content of ancillary chunks, more than 79 characters for some
+     strings, placement/combination rules for ancillary chunks, crc of unknown chunks, allowed characters
+     in string keys, etc... */
 
   unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/
 
@@ -536,10 +649,14 @@ void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings);
 
 #ifdef LODEPNG_COMPILE_ENCODER
 /*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/
-typedef enum LodePNGFilterStrategy
-{
+typedef enum LodePNGFilterStrategy {
   /*every filter at zero*/
-  LFS_ZERO,
+  LFS_ZERO = 0,
+  /*every filter at 1, 2, 3 or 4 (paeth), unlike LFS_ZERO not a good choice, but for testing*/
+  LFS_ONE = 1,
+  LFS_TWO = 2,
+  LFS_THREE = 3,
+  LFS_FOUR = 4,
   /*Use filter that gives minimum sum, as described in the official PNG filter heuristic.*/
   LFS_MINSUM,
   /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending
@@ -554,36 +671,36 @@ typedef enum LodePNGFilterStrategy
   LFS_PREDEFINED
 } LodePNGFilterStrategy;
 
-/*Gives characteristics about the colors of the image, which helps decide which color model to use for encoding.
+/*Gives characteristics about the integer RGBA colors of the image (count, alpha channel usage, bit depth, ...),
+which helps decide which color model to use for encoding.
 Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/
-typedef struct LodePNGColorProfile
-{
-  unsigned colored; /*not greyscale*/
+typedef struct LodePNGColorStats {
+  unsigned colored; /*not grayscale*/
   unsigned key; /*image is not opaque and color key is possible instead of full alpha*/
   unsigned short key_r; /*key values, always as 16-bit, in 8-bit case the byte is duplicated, e.g. 65535 means 255*/
   unsigned short key_g;
   unsigned short key_b;
   unsigned alpha; /*image is not opaque and alpha channel or alpha palette required*/
-  unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16.*/
-  unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order*/
-  unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for greyscale only. 16 if 16-bit per channel required.*/
-} LodePNGColorProfile;
-
-void lodepng_color_profile_init(LodePNGColorProfile* profile);
-
-/*Get a LodePNGColorProfile of the image.*/
-unsigned lodepng_get_color_profile(LodePNGColorProfile* profile,
-                                   const unsigned char* image, unsigned w, unsigned h,
-                                   const LodePNGColorMode* mode_in);
-/*The function LodePNG uses internally to decide the PNG color with auto_convert.
-Chooses an optimal color model, e.g. grey if only grey pixels, palette if < 256 colors, ...*/
-unsigned lodepng_auto_choose_color(LodePNGColorMode* mode_out,
-                                   const unsigned char* image, unsigned w, unsigned h,
-                                   const LodePNGColorMode* mode_in);
+  unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16 or allow_palette is disabled.*/
+  unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order, only valid when numcolors is valid*/
+  unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for grayscale only. 16 if 16-bit per channel required.*/
+  size_t numpixels;
+
+  /*user settings for computing/using the stats*/
+  unsigned allow_palette; /*default 1. if 0, disallow choosing palette colortype in auto_choose_color, and don't count numcolors*/
+  unsigned allow_greyscale; /*default 1. if 0, choose RGB or RGBA even if the image only has gray colors*/
+} LodePNGColorStats;
+
+void lodepng_color_stats_init(LodePNGColorStats* stats);
+
+/*Get a LodePNGColorStats of the image. The stats must already have been inited.
+Returns error code (e.g. alloc fail) or 0 if ok.*/
+unsigned lodepng_compute_color_stats(LodePNGColorStats* stats,
+                                     const unsigned char* image, unsigned w, unsigned h,
+                                     const LodePNGColorMode* mode_in);
 
 /*Settings for the encoder.*/
-typedef struct LodePNGEncoderSettings
-{
+typedef struct LodePNGEncoderSettings {
   LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/
 
   unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/
@@ -619,8 +736,7 @@ void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings);
 
 #if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER)
 /*The settings, state and information for extended encoding and decoding.*/
-typedef struct LodePNGState
-{
+typedef struct LodePNGState {
 #ifdef LODEPNG_COMPILE_DECODER
   LodePNGDecoderSettings decoder; /*the decoding settings*/
 #endif /*LODEPNG_COMPILE_DECODER*/
@@ -630,10 +746,6 @@ typedef struct LodePNGState
   LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/
   LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/
   unsigned error;
-#ifdef LODEPNG_COMPILE_CPP
-  /* For the lodepng::State subclass. */
-  virtual ~LodePNGState(){}
-#endif
 } LodePNGState;
 
 /*init, cleanup and copy functions to use with this struct*/
@@ -653,7 +765,7 @@ unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h,
 
 /*
 Read the PNG header, but not the actual data. This returns only the information
-that is in the header chunk of the PNG, such as width, height and color type. The
+that is in the IHDR chunk of the PNG, such as width, height and color type. The
 information is placed in the info_png field of the LodePNGState.
 */
 unsigned lodepng_inspect(unsigned* w, unsigned* h,
@@ -661,6 +773,20 @@ unsigned lodepng_inspect(unsigned* w, unsigned* h,
                          const unsigned char* in, size_t insize);
 #endif /*LODEPNG_COMPILE_DECODER*/
 
+/*
+Reads one metadata chunk (other than IHDR) of the PNG file and outputs what it
+read in the state. Returns error code on failure.
+Use lodepng_inspect first with a new state, then e.g. lodepng_chunk_find_const
+to find the desired chunk type, and if non null use lodepng_inspect_chunk (with
+chunk_pointer - start_of_file as pos).
+Supports most metadata chunks from the PNG standard (gAMA, bKGD, tEXt, ...).
+Ignores unsupported, unknown, non-metadata or IHDR chunks (without error).
+Requirements: &in[pos] must point to start of a chunk, must use regular
+lodepng_inspect first since format of most other chunks depends on IHDR, and if
+there is a PLTE chunk, that one must be inspected before tRNS or bKGD.
+*/
+unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos,
+                               const unsigned char* in, size_t insize);
 
 #ifdef LODEPNG_COMPILE_ENCODER
 /*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/
@@ -674,11 +800,23 @@ The lodepng_chunk functions are normally not needed, except to traverse the
 unknown chunks stored in the LodePNGInfo struct, or add new ones to it.
 It also allows traversing the chunks of an encoded PNG file yourself.
 
-PNG standard chunk naming conventions:
-First byte: uppercase = critical, lowercase = ancillary
-Second byte: uppercase = public, lowercase = private
-Third byte: must be uppercase
-Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy
+The chunk pointer always points to the beginning of the chunk itself, that is
+the first byte of the 4 length bytes.
+
+In the PNG file format, chunks have the following format:
+-4 bytes length: length of the data of the chunk in bytes (chunk itself is 12 bytes longer)
+-4 bytes chunk type (ASCII a-z,A-Z only, see below)
+-length bytes of data (may be 0 bytes if length was 0)
+-4 bytes of CRC, computed on chunk name + data
+
+The first chunk starts at the 8th byte of the PNG file, the entire rest of the file
+exists out of concatenated chunks with the above format.
+
+PNG standard chunk ASCII naming conventions:
+-First byte: uppercase = critical, lowercase = ancillary
+-Second byte: uppercase = public, lowercase = private
+-Third byte: must be uppercase
+-Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy
 */
 
 /*
@@ -713,24 +851,38 @@ unsigned lodepng_chunk_check_crc(const unsigned char* chunk);
 /*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/
 void lodepng_chunk_generate_crc(unsigned char* chunk);
 
-/*iterate to next chunks. don't use on IEND chunk, as there is no next chunk then*/
-unsigned char* lodepng_chunk_next(unsigned char* chunk);
-const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk);
+/*
+Iterate to next chunks, allows iterating through all chunks of the PNG file.
+Input must be at the beginning of a chunk (result of a previous lodepng_chunk_next call,
+or the 8th byte of a PNG file which always has the first chunk), or alternatively may
+point to the first byte of the PNG file (which is not a chunk but the magic header, the
+function will then skip over it and return the first real chunk).
+Will output pointer to the start of the next chunk, or at or beyond end of the file if there
+is no more chunk after this or possibly if the chunk is corrupt.
+Start this process at the 8th byte of the PNG file.
+In a non-corrupt PNG file, the last chunk should have name "IEND".
+*/
+unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end);
+const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end);
+
+/*Finds the first chunk with the given type in the range [chunk, end), or returns NULL if not found.*/
+unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]);
+const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]);
 
 /*
 Appends chunk to the data in out. The given chunk should already have its chunk header.
-The out variable and outlength are updated to reflect the new reallocated buffer.
+The out variable and outsize are updated to reflect the new reallocated buffer.
 Returns error code (0 if it went ok)
 */
-unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk);
+unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk);
 
 /*
 Appends new chunk to out. The chunk to append is given by giving its length, type
 and data separately. The type is a 4-letter string.
-The out variable and outlength are updated to reflect the new reallocated buffer.
+The out variable and outsize are updated to reflect the new reallocated buffer.
 Returne error code (0 if it went ok)
 */
-unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length,
+unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length,
                               const char* type, const unsigned char* data);
 
 
@@ -814,15 +966,13 @@ unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const
 
 #ifdef LODEPNG_COMPILE_CPP
 /* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */
-namespace lodepng
-{
+namespace lodepng {
 #ifdef LODEPNG_COMPILE_PNG
-class State : public LodePNGState
-{
+class State : public LodePNGState {
   public:
     State();
     State(const State& other);
-    virtual ~State();
+    ~State();
     State& operator=(const State& other);
 };
 
@@ -890,16 +1040,21 @@ TODO:
 [.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often
 [.] check compatibility with various compilers  - done but needs to be redone for every newer version
 [X] converting color to 16-bit per channel types
-[ ] read all public PNG chunk types (but never let the color profile and gamma ones touch RGB values)
+[X] support color profile chunk types (but never let them touch RGB values by default)
+[ ] support all public PNG chunk types (almost done except sBIT, sPLT and hIST)
 [ ] make sure encoder generates no chunks with size > (2^31)-1
 [ ] partial decoding (stream processing)
 [X] let the "isFullyOpaque" function check color keys and transparent palettes too
 [X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl"
-[ ] don't stop decoding on errors like 69, 57, 58 (make warnings)
+[ ] allow treating some errors like warnings, when image is recoverable (e.g. 69, 57, 58)
 [ ] make warnings like: oob palette, checksum fail, data after iend, wrong/unknown crit chunk, no null terminator in text, ...
+[ ] error messages with line numbers (and version)
+[ ] errors in state instead of as return code?
+[ ] new errors/warnings like suspiciously big decompressed ztxt or iccp chunk
 [ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes
 [ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ...
 [ ] allow user to give data (void*) to custom allocator
+[X] provide alternatives for C library functions not present on some platforms (memcpy, ...)
 */
 
 #endif /*LODEPNG_H inclusion guard*/
@@ -994,8 +1149,10 @@ The following features are supported by the decoder:
 *) zlib decompression (inflate)
 *) zlib compression (deflate)
 *) CRC32 and ADLER32 checksums
+*) colorimetric color profile conversions: currently experimentally available in lodepng_util.cpp only,
+   plus alternatively ability to pass on chroma/gamma/ICC profile information to other color management system.
 *) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks.
-*) the following chunks are supported (generated/interpreted) by both encoder and decoder:
+*) the following chunks are supported by both encoder and decoder:
     IHDR: header information
     PLTE: color palette
     IDAT: pixel data
@@ -1007,6 +1164,10 @@ The following features are supported by the decoder:
     bKGD: suggested background color
     pHYs: physical dimensions
     tIME: modification time
+    cHRM: RGB chromaticities
+    gAMA: RGB gamma correction
+    iCCP: ICC color profile
+    sRGB: rendering intent
 
 1.2. features not supported
 ---------------------------
@@ -1015,10 +1176,10 @@ The following features are _not_ supported:
 
 *) some features needed to make a conformant PNG-Editor might be still missing.
 *) partial loading/stream processing. All data must be available and is processed in one call.
-*) The following public chunks are not supported but treated as unknown chunks by LodePNG
-    cHRM, gAMA, iCCP, sRGB, sBIT, hIST, sPLT
-   Some of these are not supported on purpose: LodePNG wants to provide the RGB values
-   stored in the pixels, not values modified by system dependent gamma or color models.
+*) The following public chunks are not (yet) supported but treated as unknown chunks by LodePNG:
+    sBIT
+    hIST
+    sPLT
 
 
 2. C and C++ version
@@ -1092,7 +1253,7 @@ LodePNGColorMode info_raw
 When decoding, here you can specify which color type you want
 the resulting raw image to be. If this is different from the colortype of the
 PNG, then the decoder will automatically convert the result. This conversion
-always works, except if you want it to convert a color PNG to greyscale or to
+always works, except if you want it to convert a color PNG to grayscale or to
 a palette with missing colors.
 
 By default, 32-bit color is used for the result.
@@ -1188,7 +1349,7 @@ can encode the colors of all pixels without information loss.
 An important thing to note about LodePNG, is that the color type of the PNG, and
 the color type of the raw image, are completely independent. By default, when
 you decode a PNG, you get the result as a raw image in the color type you want,
-no matter whether the PNG was encoded with a palette, greyscale or RGBA color.
+no matter whether the PNG was encoded with a palette, grayscale or RGBA color.
 And if you encode an image, by default LodePNG will automatically choose the PNG
 color type that gives good compression based on the values of colors and amount
 of colors in the image. It can be configured to let you control it instead as
@@ -1196,10 +1357,10 @@ well, though.
 
 To be able to do this, LodePNG does conversions from one color mode to another.
 It can convert from almost any color type to any other color type, except the
-following conversions: RGB to greyscale is not supported, and converting to a
+following conversions: RGB to grayscale is not supported, and converting to a
 palette when the palette doesn't have a required color is not supported. This is
 not supported on purpose: this is information loss which requires a color
-reduction algorithm that is beyong the scope of a PNG encoder (yes, RGB to grey
+reduction algorithm that is beyond the scope of a PNG encoder (yes, RGB to gray
 is easy, but there are multiple ways if you want to give some channels more
 weight).
 
@@ -1220,10 +1381,10 @@ decoding to have another color type, a conversion is done by LodePNG.
 
 The PNG specification gives the following color types:
 
-0: greyscale, bit depths 1, 2, 4, 8, 16
+0: grayscale, bit depths 1, 2, 4, 8, 16
 2: RGB, bit depths 8 and 16
 3: palette, bit depths 1, 2, 4 and 8
-4: greyscale with alpha, bit depths 8 and 16
+4: grayscale with alpha, bit depths 8 and 16
 6: RGBA, bit depths 8 and 16
 
 Bit depth is the amount of bits per pixel per color channel. So the total amount
@@ -1272,15 +1433,22 @@ To avoid some confusion:
  the raw image correctly before encoding.
 -both encoder and decoder use the same color converter.
 
+The function lodepng_convert does the color conversion. It is available in the
+interface but normally isn't needed since the encoder and decoder already call
+it.
+
 Non supported color conversions:
--color to greyscale: no error is thrown, but the result will look ugly because
-only the red channel is taken
--anything to palette when that palette does not have that color in it: in this
-case an error is thrown
+-color to grayscale when non-gray pixels are present: no error is thrown, but
+the result will look ugly because only the red channel is taken (it assumes all
+three channels are the same in this case so ignores green and blue). The reason
+no error is given is to allow converting from three-channel grayscale images to
+one-channel even if there are numerical imprecisions.
+-anything to palette when the palette does not have an exact match for a from-color
+in it: in this case an error is thrown
 
 Supported color conversions:
 -anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA
--any grey or grey+alpha, to grey or grey+alpha
+-any gray or gray+alpha, to gray or gray+alpha
 -anything to a palette, as long as the palette has the requested colors in it
 -removing alpha channel
 -higher to smaller bitdepth, and vice versa
@@ -1293,10 +1461,6 @@ false.
 as the PNG has, by setting the color_convert setting to false. Settings in
 info_raw are then ignored.
 
-The function lodepng_convert does the color conversion. It is available in the
-interface but normally isn't needed since the encoder and decoder already call
-it.
-
 6.3. padding bits
 -----------------
 
@@ -1305,7 +1469,7 @@ have a bit amount that isn't a multiple of 8, then padding bits are used so that
 scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output.
 The raw input image you give to the encoder, and the raw output image you get from the decoder
 will NOT have these padding bits, e.g. in the case of a 1-bit image with a width
-of 7 pixels, the first pixel of the second scanline will the the 8th bit of the first byte,
+of 7 pixels, the first pixel of the second scanline will the 8th bit of the first byte,
 not the first bit of a new byte.
 
 6.4. A note about 16-bits per channel and endianness
@@ -1406,12 +1570,12 @@ Iterate to the next chunk. This works if you have a buffer with consecutive chun
 functions do no boundary checking of the allocated data whatsoever, so make sure there is enough
 data available in the buffer to be able to go to the next chunk.
 
-unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk):
-unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length,
+unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk):
+unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length,
                               const char* type, const unsigned char* data):
 
 These functions are used to create new chunks that are appended to the data in *out that has
-length *outlength. The append function appends an existing chunk to the new data. The create
+length *outsize. The append function appends an existing chunk to the new data. The create
 function creates a new chunk with the given parameters and appends it. Type is the 4-letter
 name of the chunk.
 
@@ -1422,7 +1586,7 @@ The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3
 buffers (each with size) to contain 3 types of unknown chunks:
 the ones that come before the PLTE chunk, the ones that come between the PLTE
 and the IDAT chunks, and the ones that come after the IDAT chunks.
-It's necessary to make the distionction between these 3 cases because the PNG
+It's necessary to make the distinction between these 3 cases because the PNG
 standard forces to keep the ordering of unknown chunks compared to the critical
 chunks, but does not force any other ordering rules.
 
@@ -1505,7 +1669,7 @@ C and C++.
 *) Other Compilers
 
 If you encounter problems on any compilers, feel free to let me know and I may
-try to fix it if the compiler is modern and standards complient.
+try to fix it if the compiler is modern and standards compliant.
 
 
 10. examples
@@ -1520,8 +1684,7 @@ examples can be found on the LodePNG website.
 #include "lodepng.h"
 #include <iostream>
 
-int main(int argc, char *argv[])
-{
+int main(int argc, char *argv[]) {
   const char* filename = argc > 1 ? argv[1] : "test.png";
 
   //load and decode
@@ -1540,8 +1703,7 @@ int main(int argc, char *argv[])
 
 #include "lodepng.h"
 
-int main(int argc, char *argv[])
-{
+int main(int argc, char *argv[]) {
   unsigned error;
   unsigned char* image;
   size_t width, height;
@@ -1567,6 +1729,8 @@ For decoding:
 state.decoder.zlibsettings.ignore_adler32: ignore ADLER32 checksums
 state.decoder.zlibsettings.custom_...: use custom inflate function
 state.decoder.ignore_crc: ignore CRC checksums
+state.decoder.ignore_critical: ignore unknown critical chunks
+state.decoder.ignore_end: ignore missing IEND chunk. May fail if this corruption causes other errors
 state.decoder.color_convert: convert internal PNG color to chosen one
 state.decoder.read_text_chunks: whether to read in text metadata chunks
 state.decoder.remember_unknown_chunks: whether to read in unknown chunks
@@ -1608,6 +1772,24 @@ yyyymmdd.
 Some changes aren't backwards compatible. Those are indicated with a (!)
 symbol.
 
+Not all changes are listed here, the commit history in github lists more:
+https://github.com/lvandeve/lodepng
+
+*) 06 mar 2020: simplified some of the dynamic memory allocations.
+*) 12 jan 2020: (!) added 'end' argument to lodepng_chunk_next to allow correct
+   overflow checks.
+*) 14 aug 2019: around 25% faster decoding thanks to huffman lookup tables.
+*) 15 jun 2019: (!) auto_choose_color API changed (for bugfix: don't use palette
+   if gray ICC profile) and non-ICC LodePNGColorProfile renamed to
+   LodePNGColorStats.
+*) 30 dec 2018: code style changes only: removed newlines before opening braces.
+*) 10 sep 2018: added way to inspect metadata chunks without full decoding.
+*) 19 aug 2018: (!) fixed color mode bKGD is encoded with and made it use
+   palette index in case of palette.
+*) 10 aug 2018: (!) added support for gAMA, cHRM, sRGB and iCCP chunks. This
+   change is backwards compatible unless you relied on unknown_chunks for those.
+*) 11 jun 2018: less restrictive check for pixel size integer overflow
+*) 14 jan 2018: allow optionally ignoring a few more recoverable errors
 *) 17 sep 2017: fix memory leak for some encoder input error cases
 *) 27 nov 2016: grey+alpha auto color model detection bugfix
 *) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort).
@@ -1616,6 +1798,7 @@ symbol.
 *) 08 dec 2015: Made load_file function return error if file can't be opened.
 *) 24 okt 2015: Bugfix with decoding to palette output.
 *) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding.
+*) 24 aug 2014: Moved to github
 *) 23 aug 2014: Reduced needless memory usage of decoder.
 *) 28 jun 2014: Removed fix_png setting, always support palette OOB for
     simplicity. Made ColorProfile public.
@@ -1623,25 +1806,25 @@ symbol.
 *) 22 dec 2013: Power of two windowsize required for optimization.
 *) 15 apr 2013: Fixed bug with LAC_ALPHA and color key.
 *) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png).
-*) 11 mar 2013 (!): Bugfix with custom free. Changed from "my" to "lodepng_"
+*) 11 mar 2013: (!) Bugfix with custom free. Changed from "my" to "lodepng_"
     prefix for the custom allocators and made it possible with a new #define to
     use custom ones in your project without needing to change lodepng's code.
 *) 28 jan 2013: Bugfix with color key.
 *) 27 okt 2012: Tweaks in text chunk keyword length error handling.
-*) 8 okt 2012 (!): Added new filter strategy (entropy) and new auto color mode.
+*) 8 okt 2012: (!) Added new filter strategy (entropy) and new auto color mode.
     (no palette). Better deflate tree encoding. New compression tweak settings.
     Faster color conversions while decoding. Some internal cleanups.
 *) 23 sep 2012: Reduced warnings in Visual Studio a little bit.
-*) 1 sep 2012 (!): Removed #define's for giving custom (de)compression functions
+*) 1 sep 2012: (!) Removed #define's for giving custom (de)compression functions
     and made it work with function pointers instead.
 *) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc
     and free functions and toggle #defines from compiler flags. Small fixes.
-*) 6 may 2012 (!): Made plugging in custom zlib/deflate functions more flexible.
-*) 22 apr 2012 (!): Made interface more consistent, renaming a lot. Removed
+*) 6 may 2012: (!) Made plugging in custom zlib/deflate functions more flexible.
+*) 22 apr 2012: (!) Made interface more consistent, renaming a lot. Removed
     redundant C++ codec classes. Reduced amount of structs. Everything changed,
     but it is cleaner now imho and functionality remains the same. Also fixed
     several bugs and shrunk the implementation code. Made new samples.
-*) 6 nov 2011 (!): By default, the encoder now automatically chooses the best
+*) 6 nov 2011: (!) By default, the encoder now automatically chooses the best
     PNG color model and bit depth, based on the amount and type of colors of the
     raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color.
 *) 9 okt 2011: simpler hash chain implementation for the encoder.
@@ -1650,7 +1833,7 @@ symbol.
     A bug with the PNG filtertype heuristic was fixed, so that it chooses much
     better ones (it's quite significant). A setting to do an experimental, slow,
     brute force search for PNG filter types is added.
-*) 17 aug 2011 (!): changed some C zlib related function names.
+*) 17 aug 2011: (!) changed some C zlib related function names.
 *) 16 aug 2011: made the code less wide (max 120 characters per line).
 *) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors.
 *) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled.
@@ -1758,5 +1941,5 @@ Domain: gmail dot com.
 Account: lode dot vandevenne.
 
 
-Copyright (c) 2005-2017 Lode Vandevenne
+Copyright (c) 2005-2020 Lode Vandevenne
 */

+ 197 - 88
src/libraries/stb/stb_image.h

@@ -1,4 +1,4 @@
-/* stb_image - v2.22 - public domain image loader - http://nothings.org/stb
+/* stb_image - v2.25 - public domain image loader - http://nothings.org/stb
                                   no warranty implied; use at your own risk
 
    Do this:
@@ -48,9 +48,12 @@ LICENSE
 
 RECENT REVISION HISTORY:
 
+      2.25  (2020-02-02) fix warnings
+      2.24  (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically
+      2.23  (2019-08-11) fix clang static analysis warning
       2.22  (2019-03-04) gif fixes, fix warnings
       2.21  (2019-02-25) fix typo in comment
-      2.20  (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs 
+      2.20  (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs
       2.19  (2018-02-11) fix warning
       2.18  (2018-01-30) fix warnings
       2.17  (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings
@@ -104,7 +107,8 @@ RECENT REVISION HISTORY:
     Oriol Ferrer Mesia      Josh Tobin         Matthew Gregan     github:phprus
     Julian Raschke          Gregory Mullen     Baldur Karlsson    github:poppolopoppo
     Christian Floisand      Kevin Schmidt      JR Smith           github:darealshinji
-    Blazej Dariusz Roszkowski                                     github:Michaelangel007
+    Brad Weinberger         Matvey Cherevko                       github:Michaelangel007
+    Blazej Dariusz Roszkowski                  Alexander Veselov
 */
 
 #ifndef STBI_INCLUDE_STB_IMAGE_H
@@ -433,7 +437,7 @@ STBIDEF int      stbi_is_hdr_from_file(FILE *f);
 
 
 // get a VERY brief reason for failure
-// NOT THREADSAFE
+// on most compilers (and ALL modern mainstream compilers) this is threadsafe
 STBIDEF const char *stbi_failure_reason  (void);
 
 // free the loaded image -- this is just free()
@@ -466,6 +470,11 @@ STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert);
 // flip the image vertically, so the first pixel in the output array is the bottom left
 STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip);
 
+// as above, but only applies to images loaded on the thread that calls the function
+// this function is only available if your compiler supports thread-local variables;
+// calling it will fail to link if your compiler doesn't
+STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip);
+
 // ZLIB client - used by PNG, available for other purposes
 
 STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen);
@@ -562,6 +571,17 @@ STBIDEF int   stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch
    #define stbi_inline __forceinline
 #endif
 
+#ifndef STBI_NO_THREAD_LOCALS
+   #if defined(__cplusplus) &&  __cplusplus >= 201103L
+      #define STBI_THREAD_LOCAL       thread_local
+   #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+      #define STBI_THREAD_LOCAL       _Thread_local
+   #elif defined(__GNUC__)
+      #define STBI_THREAD_LOCAL       __thread
+   #elif defined(_MSC_VER)
+      #define STBI_THREAD_LOCAL       __declspec(thread)
+#endif
+#endif
 
 #ifdef _MSC_VER
 typedef unsigned short stbi__uint16;
@@ -872,19 +892,24 @@ static void    *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int
 static int      stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp);
 #endif
 
-// this is not threadsafe
-static const char *stbi__g_failure_reason;
+static
+#ifdef STBI_THREAD_LOCAL
+STBI_THREAD_LOCAL
+#endif
+const char *stbi__g_failure_reason;
 
 STBIDEF const char *stbi_failure_reason(void)
 {
    return stbi__g_failure_reason;
 }
 
+#ifndef STBI_NO_FAILURE_STRINGS
 static int stbi__err(const char *str)
 {
    stbi__g_failure_reason = str;
    return 0;
 }
+#endif
 
 static void *stbi__malloc(size_t size)
 {
@@ -923,11 +948,13 @@ static int stbi__mul2sizes_valid(int a, int b)
    return a <= INT_MAX/b;
 }
 
+#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR)
 // returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow
 static int stbi__mad2sizes_valid(int a, int b, int add)
 {
    return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add);
 }
+#endif
 
 // returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow
 static int stbi__mad3sizes_valid(int a, int b, int c, int add)
@@ -945,12 +972,14 @@ static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add)
 }
 #endif
 
+#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR)
 // mallocs with size overflow checking
 static void *stbi__malloc_mad2(int a, int b, int add)
 {
    if (!stbi__mad2sizes_valid(a, b, add)) return NULL;
    return stbi__malloc(a*b + add);
 }
+#endif
 
 static void *stbi__malloc_mad3(int a, int b, int c, int add)
 {
@@ -994,13 +1023,29 @@ static float   *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp);
 static stbi_uc *stbi__hdr_to_ldr(float   *data, int x, int y, int comp);
 #endif
 
-static int stbi__vertically_flip_on_load = 0;
+static int stbi__vertically_flip_on_load_global = 0;
 
 STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip)
 {
-    stbi__vertically_flip_on_load = flag_true_if_should_flip;
+   stbi__vertically_flip_on_load_global = flag_true_if_should_flip;
 }
 
+#ifndef STBI_THREAD_LOCAL
+#define stbi__vertically_flip_on_load  stbi__vertically_flip_on_load_global
+#else
+static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set;
+
+STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip)
+{
+   stbi__vertically_flip_on_load_local = flag_true_if_should_flip;
+   stbi__vertically_flip_on_load_set = 1;
+}
+
+#define stbi__vertically_flip_on_load  (stbi__vertically_flip_on_load_set       \
+                                         ? stbi__vertically_flip_on_load_local  \
+                                         : stbi__vertically_flip_on_load_global)
+#endif // STBI_THREAD_LOCAL
+
 static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)
 {
    memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields
@@ -1022,6 +1067,8 @@ static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int re
    #endif
    #ifndef STBI_NO_PSD
    if (stbi__psd_test(s))  return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc);
+   #else
+   STBI_NOTUSED(bpc);
    #endif
    #ifndef STBI_NO_PIC
    if (stbi__pic_test(s))  return stbi__pic_load(s,x,y,comp,req_comp, ri);
@@ -1110,8 +1157,8 @@ static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int byt
 
    stbi_uc *bytes = (stbi_uc *)image;
    for (slice = 0; slice < z; ++slice) {
-      stbi__vertical_flip(bytes, w, h, bytes_per_pixel); 
-      bytes += slice_size; 
+      stbi__vertical_flip(bytes, w, h, bytes_per_pixel);
+      bytes += slice_size;
    }
 }
 #endif
@@ -1197,7 +1244,7 @@ static FILE *stbi__fopen(char const *filename, char const *mode)
    wchar_t wFilename[1024];
 	if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)))
       return 0;
-	
+
 	if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)))
       return 0;
 
@@ -1299,15 +1346,15 @@ STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *u
 STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp)
 {
    unsigned char *result;
-   stbi__context s; 
-   stbi__start_mem(&s,buffer,len); 
-   
+   stbi__context s;
+   stbi__start_mem(&s,buffer,len);
+
    result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp);
    if (stbi__vertically_flip_on_load) {
-      stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); 
+      stbi__vertical_flip_slices( result, *x, *y, *z, *comp );
    }
 
-   return result; 
+   return result;
 }
 #endif
 
@@ -1476,6 +1523,9 @@ stbi_inline static stbi_uc stbi__get8(stbi__context *s)
    return 0;
 }
 
+#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)
+// nothing
+#else
 stbi_inline static int stbi__at_eof(stbi__context *s)
 {
    if (s->io.read) {
@@ -1487,7 +1537,11 @@ stbi_inline static int stbi__at_eof(stbi__context *s)
 
    return s->img_buffer >= s->img_buffer_end;
 }
+#endif
 
+#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC)
+// nothing
+#else
 static void stbi__skip(stbi__context *s, int n)
 {
    if (n < 0) {
@@ -1504,7 +1558,11 @@ static void stbi__skip(stbi__context *s, int n)
    }
    s->img_buffer += n;
 }
+#endif
 
+#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM)
+// nothing
+#else
 static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n)
 {
    if (s->io.read) {
@@ -1528,18 +1586,27 @@ static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n)
    } else
       return 0;
 }
+#endif
 
+#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC)
+// nothing
+#else
 static int stbi__get16be(stbi__context *s)
 {
    int z = stbi__get8(s);
    return (z << 8) + stbi__get8(s);
 }
+#endif
 
+#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC)
+// nothing
+#else
 static stbi__uint32 stbi__get32be(stbi__context *s)
 {
    stbi__uint32 z = stbi__get16be(s);
    return (z << 16) + stbi__get16be(s);
 }
+#endif
 
 #if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF)
 // nothing
@@ -1561,7 +1628,9 @@ static stbi__uint32 stbi__get32le(stbi__context *s)
 
 #define STBI__BYTECAST(x)  ((stbi_uc) ((x) & 255))  // truncate int to byte without warnings
 
-
+#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)
+// nothing
+#else
 //////////////////////////////////////////////////////////////////////////////
 //
 //  generic converter from built-in img_n to req_comp
@@ -1577,7 +1646,11 @@ static stbi_uc stbi__compute_y(int r, int g, int b)
 {
    return (stbi_uc) (((r*77) + (g*150) +  (29*b)) >> 8);
 }
+#endif
 
+#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)
+// nothing
+#else
 static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y)
 {
    int i,j;
@@ -1621,12 +1694,20 @@ static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int r
    STBI_FREE(data);
    return good;
 }
+#endif
 
+#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)
+// nothing
+#else
 static stbi__uint16 stbi__compute_y_16(int r, int g, int b)
 {
    return (stbi__uint16) (((r*77) + (g*150) +  (29*b)) >> 8);
 }
+#endif
 
+#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)
+// nothing
+#else
 static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y)
 {
    int i,j;
@@ -1670,6 +1751,7 @@ static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int r
    STBI_FREE(data);
    return good;
 }
+#endif
 
 #ifndef STBI_NO_LINEAR
 static float   *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp)
@@ -4941,6 +5023,8 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
                ++s->img_n;
             }
             STBI_FREE(z->expanded); z->expanded = NULL;
+            // end of PNG chunk, read and skip CRC
+            stbi__get32be(s);
             return 1;
          }
 
@@ -5079,7 +5163,7 @@ static int stbi__high_bit(unsigned int z)
    if (z >= 0x00100) { n +=  8; z >>=  8; }
    if (z >= 0x00010) { n +=  4; z >>=  4; }
    if (z >= 0x00004) { n +=  2; z >>=  2; }
-   if (z >= 0x00002) { n +=  1; z >>=  1; }
+   if (z >= 0x00002) { n +=  1;/* >>=  1;*/ }
    return n;
 }
 
@@ -5110,7 +5194,7 @@ static int stbi__shiftsigned(unsigned int v, int shift, int bits)
       v <<= -shift;
    else
       v >>= shift;
-   STBI_ASSERT(v >= 0 && v < 256);
+   STBI_ASSERT(v < 256);
    v >>= (8-bits);
    STBI_ASSERT(bits >= 0 && bits <= 8);
    return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits];
@@ -5120,6 +5204,7 @@ typedef struct
 {
    int bpp, offset, hsz;
    unsigned int mr,mg,mb,ma, all_a;
+   int extra_read;
 } stbi__bmp_data;
 
 static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
@@ -5132,6 +5217,7 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
    info->offset = stbi__get32le(s);
    info->hsz = hsz = stbi__get32le(s);
    info->mr = info->mg = info->mb = info->ma = 0;
+   info->extra_read = 14;
 
    if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown");
    if (hsz == 12) {
@@ -5175,6 +5261,7 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
                info->mr = stbi__get32le(s);
                info->mg = stbi__get32le(s);
                info->mb = stbi__get32le(s);
+               info->extra_read += 12;
                // not documented, but generated by photoshop and handled by mspaint
                if (info->mr == info->mg && info->mg == info->mb) {
                   // ?!?!?
@@ -5231,13 +5318,19 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req
 
    if (info.hsz == 12) {
       if (info.bpp < 24)
-         psize = (info.offset - 14 - 24) / 3;
+         psize = (info.offset - info.extra_read - 24) / 3;
    } else {
       if (info.bpp < 16)
-         psize = (info.offset - 14 - info.hsz) >> 2;
+         psize = (info.offset - info.extra_read - info.hsz) >> 2;
+   }
+   if (psize == 0) {
+      STBI_ASSERT(info.offset == (s->img_buffer - s->buffer_start));
    }
 
-   s->img_n = ma ? 4 : 3;
+   if (info.bpp == 24 && ma == 0xff000000)
+      s->img_n = 3;
+   else
+      s->img_n = ma ? 4 : 3;
    if (req_comp && req_comp >= 3) // we can directly decode 3 or 4
       target = req_comp;
    else
@@ -5259,7 +5352,7 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req
          if (info.hsz != 12) stbi__get8(s);
          pal[i][3] = 255;
       }
-      stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4));
+      stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4));
       if (info.bpp == 1) width = (s->img_x + 7) >> 3;
       else if (info.bpp == 4) width = (s->img_x + 1) >> 1;
       else if (info.bpp == 8) width = s->img_x;
@@ -5308,7 +5401,7 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req
       int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0;
       int z = 0;
       int easy=0;
-      stbi__skip(s, info.offset - 14 - info.hsz);
+      stbi__skip(s, info.offset - info.extra_read - info.hsz);
       if (info.bpp == 24) width = 3 * s->img_x;
       else if (info.bpp == 16) width = 2*s->img_x;
       else /* bpp = 32 and pad = 0 */ width=0;
@@ -5547,6 +5640,8 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req
    int RLE_repeating = 0;
    int read_next_pixel = 1;
    STBI_NOTUSED(ri);
+   STBI_NOTUSED(tga_x_origin); // @TODO
+   STBI_NOTUSED(tga_y_origin); // @TODO
 
    //   do a tiny bit of precessing
    if ( tga_image_type >= 8 )
@@ -5710,6 +5805,7 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req
    //   Microsoft's C compilers happy... [8^(
    tga_palette_start = tga_palette_len = tga_palette_bits =
          tga_x_origin = tga_y_origin = 0;
+   STBI_NOTUSED(tga_palette_start);
    //   OK, done
    return tga_data;
 }
@@ -6195,7 +6291,7 @@ typedef struct
    int w,h;
    stbi_uc *out;                 // output buffer (always 4 components)
    stbi_uc *background;          // The current "background" as far as a gif is concerned
-   stbi_uc *history; 
+   stbi_uc *history;
    int flags, bgindex, ratio, transparent, eflags;
    stbi_uc  pal[256][4];
    stbi_uc lpal[256][4];
@@ -6283,7 +6379,7 @@ static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp)
 static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code)
 {
    stbi_uc *p, *c;
-   int idx; 
+   int idx;
 
    // recurse to decode the prefixes, since the linked-list is backwards,
    // and working backwards through an interleaved image would be nasty
@@ -6292,12 +6388,12 @@ static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code)
 
    if (g->cur_y >= g->max_y) return;
 
-   idx = g->cur_x + g->cur_y; 
+   idx = g->cur_x + g->cur_y;
    p = &g->out[idx];
-   g->history[idx / 4] = 1;  
+   g->history[idx / 4] = 1;
 
    c = &g->color_table[g->codes[code].suffix * 4];
-   if (c[3] > 128) { // don't render transparent pixels; 
+   if (c[3] > 128) { // don't render transparent pixels;
       p[0] = c[2];
       p[1] = c[1];
       p[2] = c[0];
@@ -6406,14 +6502,14 @@ static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g)
 // two back is the image from two frames ago, used for a very specific disposal format
 static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back)
 {
-   int dispose; 
-   int first_frame; 
-   int pi; 
-   int pcount; 
+   int dispose;
+   int first_frame;
+   int pi;
+   int pcount;
    STBI_NOTUSED(req_comp);
 
    // on first frame, any non-written pixels get the background colour (non-transparent)
-   first_frame = 0; 
+   first_frame = 0;
    if (g->out == 0) {
       if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header
       if (!stbi__mad3sizes_valid(4, g->w, g->h, 0))
@@ -6425,17 +6521,17 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i
       if (!g->out || !g->background || !g->history)
          return stbi__errpuc("outofmem", "Out of memory");
 
-      // image is treated as "transparent" at the start - ie, nothing overwrites the current background; 
+      // image is treated as "transparent" at the start - ie, nothing overwrites the current background;
       // background colour is only used for pixels that are not rendered first frame, after that "background"
-      // color refers to the color that was there the previous frame. 
+      // color refers to the color that was there the previous frame.
       memset(g->out, 0x00, 4 * pcount);
       memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent)
       memset(g->history, 0x00, pcount);        // pixels that were affected previous frame
-      first_frame = 1; 
+      first_frame = 1;
    } else {
       // second frame - how do we dispoase of the previous one?
-      dispose = (g->eflags & 0x1C) >> 2; 
-      pcount = g->w * g->h; 
+      dispose = (g->eflags & 0x1C) >> 2;
+      pcount = g->w * g->h;
 
       if ((dispose == 3) && (two_back == 0)) {
          dispose = 2; // if I don't have an image to revert back to, default to the old background
@@ -6444,32 +6540,32 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i
       if (dispose == 3) { // use previous graphic
          for (pi = 0; pi < pcount; ++pi) {
             if (g->history[pi]) {
-               memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); 
+               memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 );
             }
          }
-      } else if (dispose == 2) { 
-         // restore what was changed last frame to background before that frame; 
+      } else if (dispose == 2) {
+         // restore what was changed last frame to background before that frame;
          for (pi = 0; pi < pcount; ++pi) {
             if (g->history[pi]) {
-               memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); 
+               memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 );
             }
          }
       } else {
-         // This is a non-disposal case eithe way, so just 
+         // This is a non-disposal case eithe way, so just
          // leave the pixels as is, and they will become the new background
          // 1: do not dispose
          // 0:  not specified.
       }
 
-      // background is what out is after the undoing of the previou frame; 
-      memcpy( g->background, g->out, 4 * g->w * g->h ); 
+      // background is what out is after the undoing of the previou frame;
+      memcpy( g->background, g->out, 4 * g->w * g->h );
    }
 
-   // clear my history; 
+   // clear my history;
    memset( g->history, 0x00, g->w * g->h );        // pixels that were affected previous frame
 
    for (;;) {
-      int tag = stbi__get8(s); 
+      int tag = stbi__get8(s);
       switch (tag) {
          case 0x2C: /* Image Descriptor */
          {
@@ -6514,19 +6610,19 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i
             } else if (g->flags & 0x80) {
                g->color_table = (stbi_uc *) g->pal;
             } else
-               return stbi__errpuc("missing color table", "Corrupt GIF");            
-            
+               return stbi__errpuc("missing color table", "Corrupt GIF");
+
             o = stbi__process_gif_raster(s, g);
             if (!o) return NULL;
 
-            // if this was the first frame, 
-            pcount = g->w * g->h; 
+            // if this was the first frame,
+            pcount = g->w * g->h;
             if (first_frame && (g->bgindex > 0)) {
                // if first frame, any pixel not drawn to gets the background color
                for (pi = 0; pi < pcount; ++pi) {
                   if (g->history[pi] == 0) {
-                     g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; 
-                     memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); 
+                     g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be;
+                     memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 );
                   }
                }
             }
@@ -6537,7 +6633,7 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i
          case 0x21: // Comment Extension.
          {
             int len;
-            int ext = stbi__get8(s); 
+            int ext = stbi__get8(s);
             if (ext == 0xF9) { // Graphic Control Extension.
                len = stbi__get8(s);
                if (len == 4) {
@@ -6546,23 +6642,23 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i
 
                   // unset old transparent
                   if (g->transparent >= 0) {
-                     g->pal[g->transparent][3] = 255; 
-                  } 
+                     g->pal[g->transparent][3] = 255;
+                  }
                   if (g->eflags & 0x01) {
                      g->transparent = stbi__get8(s);
                      if (g->transparent >= 0) {
-                        g->pal[g->transparent][3] = 0; 
+                        g->pal[g->transparent][3] = 0;
                      }
                   } else {
                      // don't need transparent
-                     stbi__skip(s, 1); 
-                     g->transparent = -1; 
+                     stbi__skip(s, 1);
+                     g->transparent = -1;
                   }
                } else {
                   stbi__skip(s, len);
                   break;
                }
-            } 
+            }
             while ((len = stbi__get8(s)) != 0) {
                stbi__skip(s, len);
             }
@@ -6581,15 +6677,15 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i
 static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp)
 {
    if (stbi__gif_test(s)) {
-      int layers = 0; 
+      int layers = 0;
       stbi_uc *u = 0;
       stbi_uc *out = 0;
-      stbi_uc *two_back = 0; 
+      stbi_uc *two_back = 0;
       stbi__gif g;
-      int stride; 
+      int stride;
       memset(&g, 0, sizeof(g));
       if (delays) {
-         *delays = 0; 
+         *delays = 0;
       }
 
       do {
@@ -6599,44 +6695,52 @@ static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y,
          if (u) {
             *x = g.w;
             *y = g.h;
-            ++layers; 
-            stride = g.w * g.h * 4; 
-         
+            ++layers;
+            stride = g.w * g.h * 4;
+
             if (out) {
-               out = (stbi_uc*) STBI_REALLOC( out, layers * stride ); 
+               void *tmp = (stbi_uc*) STBI_REALLOC( out, layers * stride );
+               if (NULL == tmp) {
+                  STBI_FREE(g.out);
+                  STBI_FREE(g.history);
+                  STBI_FREE(g.background);
+                  return stbi__errpuc("outofmem", "Out of memory");
+               }
+               else
+                  out = (stbi_uc*) tmp;
                if (delays) {
-                  *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers ); 
+                  *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers );
                }
             } else {
-               out = (stbi_uc*)stbi__malloc( layers * stride ); 
+               out = (stbi_uc*)stbi__malloc( layers * stride );
                if (delays) {
-                  *delays = (int*) stbi__malloc( layers * sizeof(int) ); 
+                  *delays = (int*) stbi__malloc( layers * sizeof(int) );
                }
             }
-            memcpy( out + ((layers - 1) * stride), u, stride ); 
+            memcpy( out + ((layers - 1) * stride), u, stride );
             if (layers >= 2) {
-               two_back = out - 2 * stride; 
+               two_back = out - 2 * stride;
             }
 
             if (delays) {
-               (*delays)[layers - 1U] = g.delay; 
+               (*delays)[layers - 1U] = g.delay;
             }
          }
-      } while (u != 0); 
+      } while (u != 0);
 
-      // free temp buffer; 
-      STBI_FREE(g.out); 
-      STBI_FREE(g.history); 
-      STBI_FREE(g.background); 
+      // free temp buffer;
+      STBI_FREE(g.out);
+      STBI_FREE(g.history);
+      STBI_FREE(g.background);
 
-      // do the final conversion after loading everything; 
+      // do the final conversion after loading everything;
       if (req_comp && req_comp != 4)
          out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h);
 
-      *z = layers; 
+      *z = layers;
       return out;
    } else {
-      return stbi__errpuc("not GIF", "Image was not as a gif type."); 
+      return stbi__errpuc("not GIF", "Image was not as a gif type.");
    }
 }
 
@@ -6654,7 +6758,7 @@ static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req
       *y = g.h;
 
       // moved conversion to after successful load so that the same
-      // can be done for multiple frames. 
+      // can be done for multiple frames.
       if (req_comp && req_comp != 4)
          u = stbi__convert_format(u, 4, req_comp, g.w, g.h);
    } else if (g.out) {
@@ -6662,9 +6766,9 @@ static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req
       STBI_FREE(g.out);
    }
 
-   // free buffers needed for multiple frame loading; 
+   // free buffers needed for multiple frame loading;
    STBI_FREE(g.history);
-   STBI_FREE(g.background); 
+   STBI_FREE(g.background);
 
    return u;
 }
@@ -6936,7 +7040,12 @@ static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp)
       return 0;
    if (x) *x = s->img_x;
    if (y) *y = s->img_y;
-   if (comp) *comp = info.ma ? 4 : 3;
+   if (comp) {
+      if (info.bpp == 24 && info.ma == 0xff000000)
+         *comp = 3;
+      else
+         *comp = info.ma ? 4 : 3;
+   }
    return 1;
 }
 #endif
@@ -7322,7 +7431,7 @@ STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user
 
 /*
    revision history:
-      2.20  (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs 
+      2.20  (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs
       2.19  (2018-02-11) fix warning
       2.18  (2018-01-30) fix warnings
       2.17  (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug

+ 192 - 6
src/modules/graphics/Buffer.cpp

@@ -19,24 +19,210 @@
  **/
 
 #include "Buffer.h"
+#include "Graphics.h"
 
 namespace love
 {
 namespace graphics
 {
 
-Buffer::Buffer(size_t size, BufferType type, vertex::Usage usage, uint32 mapflags)
-	: size(size)
-	, type(type)
-	, usage(usage)
-	, map_flags(mapflags)
-	, is_mapped(false)
+love::Type Buffer::type("GraphicsBuffer", &Object::type);
+
+Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDeclaration> &bufferformat, size_t size, size_t arraylength)
+	: arrayLength(0)
+	, arrayStride(0)
+	, size(size)
+	, typeFlags(settings.typeFlags)
+	, usage(settings.usage)
+	, mapFlags(settings.mapFlags)
+	, mapped(false)
 {
+	if (size == 0 && arraylength == 0)
+		throw love::Exception("Size or array length must be specified.");
+
+	if (bufferformat.size() == 0)
+		throw love::Exception("Data format must contain values.");
+
+	const auto &caps = gfx->getCapabilities();
+	bool supportsGLSL3 = caps.features[Graphics::FEATURE_GLSL3];
+
+	bool indexbuffer = settings.typeFlags & TYPEFLAG_INDEX;
+	bool vertexbuffer = settings.typeFlags & TYPEFLAG_VERTEX;
+	bool texelbuffer = settings.typeFlags & TYPEFLAG_TEXEL;
+
+	if (!indexbuffer && !vertexbuffer && !texelbuffer)
+		throw love::Exception("Buffer must be created with at least one buffer type (index, vertex, or texel).");
+
+	if (texelbuffer && !caps.features[Graphics::FEATURE_TEXEL_BUFFER])
+		throw love::Exception("Texel buffers are not supported on this system.");
+
+	size_t offset = 0;
+	size_t stride = 0;
+
+	for (const DataDeclaration &decl : bufferformat)
+	{
+		DataMember member(decl);
+
+		DataFormat format = member.decl.format;
+		const DataFormatInfo &info = member.info;
+
+		if (indexbuffer)
+		{
+			if (format != DATAFORMAT_UINT16 && format != DATAFORMAT_UINT32)
+				throw love::Exception("Index buffers only support uint16 and uint32 data types.");
+
+			if (bufferformat.size() > 1)
+				throw love::Exception("Index buffers only support a single value per element.");
+
+			if (decl.arrayLength > 0)
+				throw love::Exception("Arrays are not supported in index buffers.");
+		}
+
+		if (vertexbuffer)
+		{
+			if (decl.arrayLength > 0)
+				throw love::Exception("Arrays are not supported in vertex buffers.");
+
+			if (info.isMatrix)
+				throw love::Exception("Matrix types are not supported in vertex buffers.");
+
+			if (info.baseType == DATA_BASETYPE_BOOL)
+				throw love::Exception("Bool types are not supported in vertex buffers.");
+
+			if ((info.baseType == DATA_BASETYPE_INT || info.baseType == DATA_BASETYPE_UINT) && !supportsGLSL3)
+				throw love::Exception("Integer vertex attribute data types require GLSL 3 support.");
+
+			if (decl.name.empty())
+				throw love::Exception("Vertex buffer attributes must have a name.");
+		}
+
+		if (texelbuffer)
+		{
+			if (format != bufferformat[0].format)
+				throw love::Exception("All values in a texel buffer must have the same format.");
+
+			if (decl.arrayLength > 0)
+				throw love::Exception("Arrays are not supported in texel buffers.");
+
+			if (info.isMatrix)
+				throw love::Exception("Matrix types are not supported in texel buffers.");
+
+			if (info.baseType == DATA_BASETYPE_BOOL)
+				throw love::Exception("Bool types are not supported in texel buffers.");
+
+			if (info.components == 3)
+				throw love::Exception("3-component formats are not supported in texel buffers.");
+
+			if (info.baseType == DATA_BASETYPE_SNORM)
+				throw love::Exception("Signed normalized formats are not supported in texel buffers.");
+		}
+
+		// TODO: alignment
+		member.offset = offset;
+		member.size = member.info.size;
+
+		offset += member.size;
+
+		dataMembers.push_back(member);
+	}
+
+	stride = offset;
+
+	if (size != 0)
+	{
+		size_t remainder = size % stride;
+		if (remainder > 0)
+			size += stride - remainder;
+		arraylength = size / stride;
+	}
+	else
+	{
+		size = arraylength * stride;
+	}
+
+	this->arrayStride = stride;
+	this->arrayLength = arraylength;
+	this->size = size;
+
+	if (texelbuffer && arraylength * dataMembers.size() > caps.limits[Graphics::LIMIT_TEXEL_BUFFER_SIZE])
+		throw love::Exception("Cannot create texel buffer: total number of values in the buffer (%d * %d) is too large for this system (maximum %d).", (int) dataMembers.size(), (int) arraylength, caps.limits[Graphics::LIMIT_TEXEL_BUFFER_SIZE]);
 }
 
 Buffer::~Buffer()
 {
 }
 
+int Buffer::getDataMemberIndex(const std::string &name) const
+{
+	for (size_t i = 0; i < dataMembers.size(); i++)
+	{
+		if (dataMembers[i].decl.name == name)
+			return (int) i;
+	}
+
+	return -1;
+}
+
+std::vector<Buffer::DataDeclaration> Buffer::getCommonFormatDeclaration(CommonFormat format)
+{
+	switch (format)
+	{
+	case CommonFormat::NONE:
+		return {};
+	case CommonFormat::XYf:
+		return {
+			{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 }
+		};
+	case CommonFormat::XYZf:
+		return {
+			{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC3 }
+		};
+	case CommonFormat::RGBAub:
+		return {
+			{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 }
+		};
+	case CommonFormat::STf_RGBAub:
+		return {
+			{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
+		};
+	case CommonFormat::STPf_RGBAub:
+		return {
+			{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC3 },
+			{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
+		};
+	case CommonFormat::XYf_STf:
+		return {
+			{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 },
+		};
+	case CommonFormat::XYf_STPf:
+		return {
+			{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC3 },
+		};
+	case CommonFormat::XYf_STf_RGBAub:
+		return {
+			{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
+		};
+	case CommonFormat::XYf_STus_RGBAub:
+		return {
+			{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_UNORM16_VEC2 },
+			{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
+		};
+	case CommonFormat::XYf_STPf_RGBAub:
+		return {
+			{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
+		};
+	}
+
+	return {};
+}
+
 } // graphics
 } // love

+ 92 - 38
src/modules/graphics/Buffer.h

@@ -23,40 +23,102 @@
 // LOVE
 #include "common/config.h"
 #include "common/int.h"
+#include "common/Object.h"
 #include "vertex.h"
 #include "Resource.h"
 
 // C
 #include <stddef.h>
+#include <string>
+#include <vector>
 
 namespace love
 {
 namespace graphics
 {
 
+class Graphics;
+
 /**
- * A block of GPU-owned memory. Currently meant for internal use.
+ * A block of GPU-owned memory.
  **/
-class Buffer : public Resource
+class Buffer : public love::Object, public Resource
 {
 public:
 
+	static love::Type type;
+
 	enum MapFlags
 	{
+		MAP_NONE = 0,
 		MAP_EXPLICIT_RANGE_MODIFY = (1 << 0), // see setMappedRangeModified.
 		MAP_READ = (1 << 1),
 	};
 
-	Buffer(size_t size, BufferType type, vertex::Usage usage, uint32 mapflags);
-	virtual ~Buffer();
+	enum TypeFlags
+	{
+		TYPEFLAG_NONE = 0,
+		TYPEFLAG_VERTEX = 1 << BUFFERTYPE_VERTEX,
+		TYPEFLAG_INDEX = 1 << BUFFERTYPE_INDEX,
+		TYPEFLAG_TEXEL = 1 << BUFFERTYPE_TEXEL,
+	};
 
-	size_t getSize() const { return size; }
+	struct DataDeclaration
+	{
+		std::string name;
+		DataFormat format;
+		int arrayLength;
+
+		DataDeclaration(const std::string &name, DataFormat format, int arrayLength = 0)
+			: name(name)
+			, format(format)
+			, arrayLength(arrayLength)
+		{}
+	};
+
+	struct DataMember
+	{
+		DataDeclaration decl;
+		DataFormatInfo info;
+		size_t offset;
+		size_t size;
+
+		DataMember(const DataDeclaration &decl)
+			: decl(decl)
+			, info(getDataFormatInfo(decl.format))
+			, offset(0)
+			, size(0)
+		{}
+	};
 
-	BufferType getType() const { return type; }
+	struct Settings
+	{
+		TypeFlags typeFlags;
+		MapFlags mapFlags;
+		BufferUsage usage;
+
+		Settings(uint32 typeflags, uint32 mapflags, BufferUsage usage)
+			: typeFlags((TypeFlags)typeflags)
+			, mapFlags((MapFlags)mapflags)
+			, usage(usage)
+		{}
+	};
 
-	vertex::Usage getUsage() const { return usage; }
+	Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDeclaration> &format, size_t size, size_t arraylength);
+	virtual ~Buffer();
 
-	bool isMapped() const { return is_mapped; }
+	size_t getSize() const { return size; }
+	TypeFlags getTypeFlags() const { return typeFlags; }
+	BufferUsage getUsage() const { return usage; }
+	bool isMapped() const { return mapped; }
+	uint32 getMapFlags() const { return mapFlags; }
+
+	size_t getArrayLength() const { return arrayLength; }
+	size_t getArrayStride() const { return arrayStride; }
+	const std::vector<DataMember> &getDataMembers() const { return dataMembers; }
+	const DataMember &getDataMember(int index) const { return dataMembers[index]; }
+	size_t getMemberOffset(int index) const { return dataMembers[index].offset; }
+	int getDataMemberIndex(const std::string &name) const;
 
 	/**
 	 * Map the Buffer to client memory.
@@ -80,10 +142,6 @@ public:
 
 	/**
 	 * Fill a portion of the buffer with data and marks the range as modified.
-	 *
-	 * @param offset The offset in the GLBuffer to store the data.
-	 * @param size The size of the incoming data.
-	 * @param data Pointer to memory to copy data from.
 	 */
 	virtual void fill(size_t offset, size_t size, const void *data) = 0;
 
@@ -92,58 +150,54 @@ public:
 	 **/
 	virtual void copyTo(size_t offset, size_t size, Buffer *other, size_t otheroffset) = 0;
 
-	uint32 getMapFlags() const { return map_flags; }
+	/**
+	 * Texel buffers may use an additional texture handle as well as a buffer
+	 * handle.
+	 **/
+	virtual ptrdiff_t getTexelBufferHandle() const = 0;
+
+	static std::vector<DataDeclaration> getCommonFormatDeclaration(CommonFormat format);
 
 	class Mapper
 	{
 	public:
 
-		/**
-		 * Memory-maps a Buffer.
-		 */
 		Mapper(Buffer &buffer)
-			: buf(buffer)
+			: buffer(buffer)
 		{
-			elems = buf.map();
+			data = buffer.map();
 		}
 
-		/**
-		 * unmaps the buffer
-		 */
 		~Mapper()
 		{
-			buf.unmap();
-		}
-
-		/**
-		 * Get pointer to memory mapped region
-		 */
-		void *get()
-		{
-			return elems;
+			if (buffer.getMapFlags() & MAP_EXPLICIT_RANGE_MODIFY)
+				buffer.setMappedRangeModified(0, buffer.getSize());
+			buffer.unmap();
 		}
 
-	private:
-
-		Buffer &buf;
-		void *elems;
+		Buffer &buffer;
+		void *data;
 
 	}; // Mapper
 
 protected:
 
+	std::vector<DataMember> dataMembers;
+	size_t arrayLength;
+	size_t arrayStride;
+
 	// The size of the buffer, in bytes.
 	size_t size;
 
 	// The type of the buffer object.
-	BufferType type;
+	TypeFlags typeFlags;
 
 	// Usage hint. GL_[DYNAMIC, STATIC, STREAM]_DRAW.
-	vertex::Usage usage;
+	BufferUsage usage;
 	
-	uint32 map_flags;
+	uint32 mapFlags;
 
-	bool is_mapped;
+	bool mapped;
 	
 }; // Buffer
 

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

@@ -45,7 +45,7 @@ static inline uint16 normToUint16(double n)
 love::Type Font::type("Font", &Object::type);
 int Font::fontCount = 0;
 
-const vertex::CommonFormat Font::vertexFormat = vertex::CommonFormat::XYf_STus_RGBAub;
+const CommonFormat Font::vertexFormat = CommonFormat::XYf_STus_RGBAub;
 
 Font::Font(love::font::Rasterizer *r, const SamplerState &s)
 	: rasterizers({r})
@@ -647,7 +647,7 @@ void Font::printv(graphics::Graphics *gfx, const Matrix4 &t, const std::vector<D
 	{
 		Graphics::BatchedDrawCommand streamcmd;
 		streamcmd.formats[0] = vertexFormat;
-		streamcmd.indexMode = vertex::TriangleIndexMode::QUADS;
+		streamcmd.indexMode = TRIANGLEINDEX_QUADS;
 		streamcmd.vertexCount = cmd.vertexcount;
 		streamcmd.texture = cmd.texture;
 

+ 2 - 2
src/modules/graphics/Font.h

@@ -51,9 +51,9 @@ public:
 	static love::Type type;
 
 	typedef std::vector<uint32> Codepoints;
-	typedef vertex::XYf_STus_RGBAub GlyphVertex;
+	typedef XYf_STus_RGBAub GlyphVertex;
 
-	static const vertex::CommonFormat vertexFormat;
+	static const CommonFormat vertexFormat;
 
 	enum AlignMode
 	{

+ 102 - 64
src/modules/graphics/Graphics.cpp

@@ -105,8 +105,6 @@ bool isDebugEnabled()
 
 love::Type Graphics::type("graphics", &Module::type);
 
-Graphics::DefaultShaderCode Graphics::defaultShaderCode[Shader::STANDARD_MAX_ENUM][Shader::LANGUAGE_MAX_ENUM][2];
-
 namespace opengl { extern love::graphics::Graphics *createInstance(); }
 #if defined(LOVE_MACOS) || defined(LOVE_IOS)
 namespace metal { extern love::graphics::Graphics *createInstance(); }
@@ -203,10 +201,12 @@ void Graphics::createQuadIndexBuffer()
 		return;
 
 	size_t size = sizeof(uint16) * (LOVE_UINT16_MAX / 4) * 6;
-	quadIndexBuffer = newBuffer(size, nullptr, BUFFER_INDEX, vertex::USAGE_STATIC, 0);
+
+	Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, 0, BUFFERUSAGE_STATIC);
+	quadIndexBuffer = newBuffer(settings, DATAFORMAT_UINT16, nullptr, size, 0);
 
 	Buffer::Mapper map(*quadIndexBuffer);
-	vertex::fillIndices(vertex::TriangleIndexMode::QUADS, 0, LOVE_UINT16_MAX, (uint16 *) map.get());
+	fillIndices(TRIANGLEINDEX_QUADS, 0, LOVE_UINT16_MAX, (uint16 *) map.data);
 }
 
 Quad *Graphics::newQuad(Quad::Viewport v, double sw, double sh)
@@ -234,7 +234,7 @@ Video *Graphics::newVideo(love::video::VideoStream *stream, float dpiscale)
 	return new Video(this, stream, dpiscale);
 }
 
-love::graphics::SpriteBatch *Graphics::newSpriteBatch(Texture *texture, int size, vertex::Usage usage)
+love::graphics::SpriteBatch *Graphics::newSpriteBatch(Texture *texture, int size, BufferUsage usage)
 {
 	return new SpriteBatch(this, texture, size, usage);
 }
@@ -244,13 +244,8 @@ love::graphics::ParticleSystem *Graphics::newParticleSystem(Texture *texture, in
 	return new ParticleSystem(texture, size);
 }
 
-ShaderStage *Graphics::newShaderStage(ShaderStage::StageType stage, const std::string &optsource)
+ShaderStage *Graphics::newShaderStage(ShaderStage::StageType stage, const std::string &source, const Shader::SourceInfo &info)
 {
-	if (stage == ShaderStage::STAGE_MAX_ENUM)
-		throw love::Exception("Invalid shader stage.");
-
-	const std::string &source = optsource.empty() ? getCurrentDefaultShaderCode().source[stage] : optsource;
-
 	ShaderStage *s = nullptr;
 	std::string cachekey;
 
@@ -271,7 +266,8 @@ ShaderStage *Graphics::newShaderStage(ShaderStage::StageType stage, const std::s
 
 	if (s == nullptr)
 	{
-		s = newShaderStageInternal(stage, cachekey, source, usesGLSLES());
+		std::string glsl = Shader::createShaderStageCode(this, stage, source, info);
+		s = newShaderStageInternal(stage, cachekey, glsl, usesGLSLES());
 		if (!cachekey.empty())
 			cachedShaderStages[stage][cachekey] = s;
 	}
@@ -279,37 +275,71 @@ ShaderStage *Graphics::newShaderStage(ShaderStage::StageType stage, const std::s
 	return s;
 }
 
-Shader *Graphics::newShader(const std::string &vertex, const std::string &pixel)
+Shader *Graphics::newShader(const std::vector<std::string> &stagessource)
 {
-	if (vertex.empty() && pixel.empty())
-		throw love::Exception("Error creating shader: no source code!");
+	StrongRef<ShaderStage> stages[ShaderStage::STAGE_MAX_ENUM] = {};
 
-	StrongRef<ShaderStage> vertexstage(newShaderStage(ShaderStage::STAGE_VERTEX, vertex), Acquire::NORETAIN);
-	StrongRef<ShaderStage> pixelstage(newShaderStage(ShaderStage::STAGE_PIXEL, pixel), Acquire::NORETAIN);
+	bool validstages[ShaderStage::STAGE_MAX_ENUM] = {};
+	validstages[ShaderStage::STAGE_VERTEX] = true;
+	validstages[ShaderStage::STAGE_PIXEL] = true;
 
-	return newShaderInternal(vertexstage.get(), pixelstage.get());
-}
+	for (const std::string &source : stagessource)
+	{
+		Shader::SourceInfo info = Shader::getSourceInfo(source);
+		bool isanystage = false;
 
-Mesh *Graphics::newMesh(const std::vector<Vertex> &vertices, PrimitiveType drawmode, vertex::Usage usage)
-{
-	return newMesh(Mesh::getDefaultVertexFormat(), &vertices[0], vertices.size() * sizeof(Vertex), drawmode, usage);
+		for (int i = 0; i < ShaderStage::STAGE_MAX_ENUM; i++)
+		{
+			if (!validstages[i])
+				continue;
+
+			if (info.isStage[i])
+			{
+				isanystage = true;
+				stages[i].set(newShaderStage((ShaderStage::StageType) i, source, info), Acquire::NORETAIN);
+			}
+		}
+
+		if (!isanystage)
+			throw love::Exception("Could not parse shader code (missing 'position' or 'effect' function?)");
+	}
+
+	for (int i = 0; i < ShaderStage::STAGE_MAX_ENUM; i++)
+	{
+		auto stype = (ShaderStage::StageType) i;
+		if (validstages[i] && stages[i].get() == nullptr)
+		{
+			const std::string &source = Shader::getDefaultCode(Shader::STANDARD_DEFAULT, stype);
+			Shader::SourceInfo info = Shader::getSourceInfo(source);
+			stages[i].set(newShaderStage(stype, source, info), Acquire::NORETAIN);
+		}
+
+	}
+
+	return newShaderInternal(stages[ShaderStage::STAGE_VERTEX], stages[ShaderStage::STAGE_PIXEL]);
 }
 
-Mesh *Graphics::newMesh(int vertexcount, PrimitiveType drawmode, vertex::Usage usage)
+Buffer *Graphics::newBuffer(const Buffer::Settings &settings, DataFormat format, const void *data, size_t size, size_t arraylength)
 {
-	return newMesh(Mesh::getDefaultVertexFormat(), vertexcount, drawmode, usage);
+	std::vector<Buffer::DataDeclaration> dataformat = {{"", format, 0}};
+	return newBuffer(settings, dataformat, data, size, arraylength);
 }
 
-love::graphics::Mesh *Graphics::newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, int vertexcount, PrimitiveType drawmode, vertex::Usage usage)
+Mesh *Graphics::newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage)
 {
 	return new Mesh(this, vertexformat, vertexcount, drawmode, usage);
 }
 
-love::graphics::Mesh *Graphics::newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, vertex::Usage usage)
+Mesh *Graphics::newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage)
 {
 	return new Mesh(this, vertexformat, data, datasize, drawmode, usage);
 }
 
+Mesh *Graphics::newMesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType drawmode)
+{
+	return new Mesh(attributes, drawmode);
+}
+
 love::graphics::Text *Graphics::newText(graphics::Font *font, const std::vector<Font::ColoredString> &text)
 {
 	return new Text(font, text);
@@ -320,26 +350,44 @@ void Graphics::cleanupCachedShaderStage(ShaderStage::StageType type, const std::
 	cachedShaderStages[type].erase(hashkey);
 }
 
-bool Graphics::validateShader(bool gles, const std::string &vertex, const std::string &pixel, std::string &err)
+bool Graphics::validateShader(bool gles, const std::vector<std::string> &stagessource, std::string &err)
 {
-	if (vertex.empty() && pixel.empty())
-	{
-		err = "Error validating shader: no source code!";
-		return false;
-	}
+	StrongRef<ShaderStage> stages[ShaderStage::STAGE_MAX_ENUM] = {};
 
-	StrongRef<ShaderStage> vertexstage;
-	StrongRef<ShaderStage> pixelstage;
+	bool validstages[ShaderStage::STAGE_MAX_ENUM] = {};
+	validstages[ShaderStage::STAGE_VERTEX] = true;
+	validstages[ShaderStage::STAGE_PIXEL] = true;
 
 	// Don't use cached shader stages, since the gles flag may not match the
 	// current renderer.
-	if (!vertex.empty())
-		vertexstage.set(new ShaderStageForValidation(this, ShaderStage::STAGE_VERTEX, vertex, gles), Acquire::NORETAIN);
+	for (const std::string &source : stagessource)
+	{
+		Shader::SourceInfo info = Shader::getSourceInfo(source);
+		bool isanystage = false;
 
-	if (!pixel.empty())
-		pixelstage.set(new ShaderStageForValidation(this, ShaderStage::STAGE_PIXEL, pixel, gles), Acquire::NORETAIN);
+		for (int i = 0; i < ShaderStage::STAGE_MAX_ENUM; i++)
+		{
+			auto stype = (ShaderStage::StageType) i;
+
+			if (!validstages[i])
+				continue;
+
+			if (info.isStage[i])
+			{
+				isanystage = true;
+				std::string glsl = Shader::createShaderStageCode(this, stype, source, info);
+				stages[i].set(new ShaderStageForValidation(this, stype, glsl, gles), Acquire::NORETAIN);
+			}
+		}
 
-	return Shader::validate(vertexstage.get(), pixelstage.get(), err);
+		if (!isanystage)
+		{
+			err = "Could not parse shader code (missing 'position' or 'effect' function?)";
+			return false;
+		}
+	}
+
+	return Shader::validate(stages[ShaderStage::STAGE_VERTEX], stages[ShaderStage::STAGE_PIXEL], err);
 }
 
 int Graphics::getWidth() const
@@ -942,7 +990,7 @@ CullMode Graphics::getMeshCullMode() const
 	return states.back().meshCullMode;
 }
 
-vertex::Winding Graphics::getFrontFaceWinding() const
+Winding Graphics::getFrontFaceWinding() const
 {
 	return states.back().winding;
 }
@@ -1031,8 +1079,6 @@ void Graphics::captureScreenshot(const ScreenshotInfo &info)
 
 Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawCommand &cmd)
 {
-	using namespace vertex;
-
 	BatchedDrawState &state = batchedDrawState;
 
 	bool shouldflush = false;
@@ -1040,7 +1086,7 @@ Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawComman
 
 	if (cmd.primitiveMode != state.primitiveMode
 		|| cmd.formats[0] != state.formats[0] || cmd.formats[1] != state.formats[1]
-		|| ((cmd.indexMode != TriangleIndexMode::NONE) != (state.indexCount > 0))
+		|| ((cmd.indexMode != TRIANGLEINDEX_NONE) != (state.indexCount > 0))
 		|| cmd.texture != state.texture
 		|| cmd.standardShaderType != state.standardShaderType)
 	{
@@ -1050,7 +1096,7 @@ Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawComman
 	int totalvertices = state.vertexCount + cmd.vertexCount;
 
 	// We only support uint16 index buffers for now.
-	if (totalvertices > LOVE_UINT16_MAX && cmd.indexMode != TriangleIndexMode::NONE)
+	if (totalvertices > LOVE_UINT16_MAX && cmd.indexMode != TRIANGLEINDEX_NONE)
 		shouldflush = true;
 
 	int reqIndexCount = getIndexCount(cmd.indexMode, cmd.vertexCount);
@@ -1079,7 +1125,7 @@ Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawComman
 		newdatasizes[i] = stride * cmd.vertexCount;
 	}
 
-	if (cmd.indexMode != TriangleIndexMode::NONE)
+	if (cmd.indexMode != TRIANGLEINDEX_NONE)
 	{
 		size_t datasize = (state.indexCount + reqIndexCount) * sizeof(uint16);
 
@@ -1117,18 +1163,18 @@ Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawComman
 			if (state.vb[i]->getSize() < buffersizes[i])
 			{
 				delete state.vb[i];
-				state.vb[i] = newStreamBuffer(BUFFER_VERTEX, buffersizes[i]);
+				state.vb[i] = newStreamBuffer(BUFFERTYPE_VERTEX, buffersizes[i]);
 			}
 		}
 
 		if (state.indexBuffer->getSize() < buffersizes[2])
 		{
 			delete state.indexBuffer;
-			state.indexBuffer = newStreamBuffer(BUFFER_INDEX, buffersizes[2]);
+			state.indexBuffer = newStreamBuffer(BUFFERTYPE_INDEX, buffersizes[2]);
 		}
 	}
 
-	if (cmd.indexMode != TriangleIndexMode::NONE)
+	if (cmd.indexMode != TRIANGLEINDEX_NONE)
 	{
 		if (state.indexBufferMap.data == nullptr)
 			state.indexBufferMap = state.indexBuffer->map(reqIndexSize);
@@ -1165,14 +1211,12 @@ Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawComman
 
 void Graphics::flushBatchedDraws()
 {
-	using namespace vertex;
-
 	auto &sbstate = batchedDrawState;
 
 	if (sbstate.vertexCount == 0 && sbstate.indexCount == 0)
 		return;
 
-	Attributes attributes;
+	VertexAttributes attributes;
 	BufferBindings buffers;
 
 	size_t usedsizes[3] = {0, 0, 0};
@@ -1315,8 +1359,8 @@ void Graphics::points(const Vector2 *positions, const Colorf *colors, size_t num
 
 	BatchedDrawCommand cmd;
 	cmd.primitiveMode = PRIMITIVE_POINTS;
-	cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
-	cmd.formats[1] = vertex::CommonFormat::RGBAub;
+	cmd.formats[0] = getSinglePositionFormat(is2D);
+	cmd.formats[1] = CommonFormat::RGBAub;
 	cmd.vertexCount = (int) numpoints;
 
 	BatchedVertexData data = requestBatchedDraw(cmd);
@@ -1610,9 +1654,9 @@ void Graphics::polygon(DrawMode mode, const Vector2 *coords, size_t count, bool
 		bool is2D = t.isAffine2DTransform();
 
 		BatchedDrawCommand cmd;
-		cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
-		cmd.formats[1] = vertex::CommonFormat::RGBAub;
-		cmd.indexMode = vertex::TriangleIndexMode::FAN;
+		cmd.formats[0] = getSinglePositionFormat(is2D);
+		cmd.formats[1] = CommonFormat::RGBAub;
+		cmd.indexMode = TRIANGLEINDEX_FAN;
 		cmd.vertexCount = (int)count - (skipLastFilledVertex ? 1 : 0);
 
 		BatchedVertexData data = requestBatchedDraw(cmd);
@@ -1786,14 +1830,6 @@ Vector2 Graphics::inverseTransformPoint(Vector2 point)
 	return p;
 }
 
-const Graphics::DefaultShaderCode &Graphics::getCurrentDefaultShaderCode() const
-{
-	int languageindex = (int) getShaderLanguageTarget();
-	int gammaindex = isGammaCorrect() ? 1 : 0;
-
-	return defaultShaderCode[Shader::STANDARD_DEFAULT][languageindex][gammaindex];
-}
-
 /**
  * Constants.
  **/
@@ -1939,6 +1975,7 @@ StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM>::Entry Graphics::featur
 	{ "glsl3",                    FEATURE_GLSL3                },
 	{ "glsl4",                    FEATURE_GLSL4                },
 	{ "instancing",               FEATURE_INSTANCING           },
+	{ "texelbuffer",              FEATURE_TEXEL_BUFFER         },
 };
 
 StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM> Graphics::features(Graphics::featureEntries, sizeof(Graphics::featureEntries));
@@ -1950,6 +1987,7 @@ StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM>::Entry Graphics::syst
 	{ "texturelayers",     LIMIT_TEXTURE_LAYERS      },
 	{ "volumetexturesize", LIMIT_VOLUME_TEXTURE_SIZE },
 	{ "cubetexturesize",   LIMIT_CUBE_TEXTURE_SIZE   },
+	{ "texelbuffersize",   LIMIT_TEXEL_BUFFER_SIZE   },
 	{ "rendertargets",     LIMIT_RENDER_TARGETS      },
 	{ "texturemsaa",       LIMIT_TEXTURE_MSAA        },
 	{ "anisotropy",        LIMIT_ANISOTROPY          },

+ 26 - 35
src/modules/graphics/Graphics.h

@@ -143,6 +143,7 @@ public:
 		FEATURE_GLSL3,
 		FEATURE_GLSL4,
 		FEATURE_INSTANCING,
+		FEATURE_TEXEL_BUFFER,
 		FEATURE_MAX_ENUM
 	};
 
@@ -161,6 +162,7 @@ public:
 		LIMIT_VOLUME_TEXTURE_SIZE,
 		LIMIT_CUBE_TEXTURE_SIZE,
 		LIMIT_TEXTURE_LAYERS,
+		LIMIT_TEXEL_BUFFER_SIZE,
 		LIMIT_RENDER_TARGETS,
 		LIMIT_TEXTURE_MSAA,
 		LIMIT_ANISOTROPY,
@@ -210,8 +212,8 @@ public:
 	{
 		PrimitiveType primitiveType = PRIMITIVE_TRIANGLES;
 
-		const vertex::Attributes *attributes;
-		const vertex::BufferBindings *buffers;
+		const VertexAttributes *attributes;
+		const BufferBindings *buffers;
 
 		int vertexStart = 0;
 		int vertexCount = 0;
@@ -222,7 +224,7 @@ public:
 		// TODO: This should be moved out to a state transition API?
 		CullMode cullMode = CULL_NONE;
 
-		DrawCommand(const vertex::Attributes *attribs, const vertex::BufferBindings *buffers)
+		DrawCommand(const VertexAttributes *attribs, const BufferBindings *buffers)
 			: attributes(attribs)
 			, buffers(buffers)
 		{}
@@ -232,8 +234,8 @@ public:
 	{
 		PrimitiveType primitiveType = PRIMITIVE_TRIANGLES;
 
-		const vertex::Attributes *attributes;
-		const vertex::BufferBindings *buffers;
+		const VertexAttributes *attributes;
+		const BufferBindings *buffers;
 
 		int indexCount = 0;
 		int instanceCount = 1;
@@ -247,7 +249,7 @@ public:
 		// TODO: This should be moved out to a state transition API?
 		CullMode cullMode = CULL_NONE;
 
-		DrawIndexedCommand(const vertex::Attributes *attribs, const vertex::BufferBindings *buffers, Resource *indexbuffer)
+		DrawIndexedCommand(const VertexAttributes *attribs, const BufferBindings *buffers, Resource *indexbuffer)
 			: attributes(attribs)
 			, buffers(buffers)
 			, indexBuffer(indexbuffer)
@@ -257,8 +259,8 @@ public:
 	struct BatchedDrawCommand
 	{
 		PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES;
-		vertex::CommonFormat formats[2];
-		vertex::TriangleIndexMode indexMode = vertex::TriangleIndexMode::NONE;
+		CommonFormat formats[2];
+		TriangleIndexMode indexMode = TRIANGLEINDEX_NONE;
 		int vertexCount = 0;
 		Texture *texture = nullptr;
 		Shader::StandardShader standardShaderType = Shader::STANDARD_DEFAULT;
@@ -266,7 +268,7 @@ public:
 		BatchedDrawCommand()
 		{
 			// VS2013 can't initialize arrays in the above manner...
-			formats[1] = formats[0] = vertex::CommonFormat::NONE;
+			formats[1] = formats[0] = CommonFormat::NONE;
 		}
 	};
 
@@ -416,11 +418,6 @@ public:
 		}
 	};
 
-	struct DefaultShaderCode
-	{
-		std::string source[ShaderStage::STAGE_MAX_ENUM];
-	};
-
 	Graphics();
 	virtual ~Graphics();
 
@@ -434,22 +431,21 @@ public:
 	Font *newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting);
 	Video *newVideo(love::video::VideoStream *stream, float dpiscale);
 
-	SpriteBatch *newSpriteBatch(Texture *texture, int size, vertex::Usage usage);
+	SpriteBatch *newSpriteBatch(Texture *texture, int size, BufferUsage usage);
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 
-	ShaderStage *newShaderStage(ShaderStage::StageType stage, const std::string &source);
-	Shader *newShader(const std::string &vertex, const std::string &pixel);
+	Shader *newShader(const std::vector<std::string> &stagessource);
 
-	virtual Buffer *newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags) = 0;
+	virtual Buffer *newBuffer(const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &format, const void *data, size_t size, size_t arraylength) = 0;
+	virtual Buffer *newBuffer(const Buffer::Settings &settings, DataFormat format, const void *data, size_t size, size_t arraylength);
 
-	Mesh *newMesh(const std::vector<Vertex> &vertices, PrimitiveType drawmode, vertex::Usage usage);
-	Mesh *newMesh(int vertexcount, PrimitiveType drawmode, vertex::Usage usage);
-	Mesh *newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, int vertexcount, PrimitiveType drawmode, vertex::Usage usage);
-	Mesh *newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, vertex::Usage usage);
+	Mesh *newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage);
+	Mesh *newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage);
+	Mesh *newMesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType drawmode);
 
 	Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
 
-	bool validateShader(bool gles, const std::string &vertex, const std::string &pixel, std::string &err);
+	bool validateShader(bool gles, const std::vector<std::string> &stages, std::string &err);
 
 	/**
 	 * Resets the current color, background color, line style, and so forth.
@@ -589,8 +585,8 @@ public:
 	void setMeshCullMode(CullMode cull);
 	CullMode getMeshCullMode() const;
 
-	virtual void setFrontFaceWinding(vertex::Winding winding) = 0;
-	vertex::Winding getFrontFaceWinding() const;
+	virtual void setFrontFaceWinding(Winding winding) = 0;
+	Winding getFrontFaceWinding() const;
 
 	/**
 	 * Sets the enabled color components when rendering.
@@ -826,16 +822,13 @@ public:
 
 	virtual void draw(const DrawCommand &cmd) = 0;
 	virtual void draw(const DrawIndexedCommand &cmd) = 0;
-	virtual void drawQuads(int start, int count, const vertex::Attributes &attributes, const vertex::BufferBindings &buffers, Texture *texture) = 0;
+	virtual void drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, Texture *texture) = 0;
 
 	void flushBatchedDraws();
 	BatchedVertexData requestBatchedDraw(const BatchedDrawCommand &command);
 
 	static void flushBatchedDrawsGlobal();
 
-	virtual Shader::Language getShaderLanguageTarget() const = 0;
-	const DefaultShaderCode &getCurrentDefaultShaderCode() const;
-
 	void cleanupCachedShaderStage(ShaderStage::StageType type, const std::string &cachekey);
 
 	template <typename T>
@@ -877,9 +870,6 @@ public:
 	static bool getConstant(StackType in, const char *&out);
 	static std::vector<std::string> getConstants(StackType);
 
-	// Default shader code (a shader is always required internally.)
-	static DefaultShaderCode defaultShaderCode[Shader::STANDARD_MAX_ENUM][Shader::LANGUAGE_MAX_ENUM][2];
-
 protected:
 
 	struct DisplayState
@@ -904,7 +894,7 @@ protected:
 		bool depthWrite = false;
 
 		CullMode meshCullMode = CULL_NONE;
-		vertex::Winding winding = vertex::WINDING_CCW;
+		Winding winding = WINDING_CCW;
 
 		StrongRef<Font> font;
 		StrongRef<Shader> shader;
@@ -924,7 +914,7 @@ protected:
 		StreamBuffer *indexBuffer = nullptr;
 
 		PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES;
-		vertex::CommonFormat formats[2];
+		CommonFormat formats[2];
 		StrongRef<Texture> texture;
 		Shader::StandardShader standardShaderType = Shader::STANDARD_DEFAULT;
 		int vertexCount = 0;
@@ -936,7 +926,7 @@ protected:
 		BatchedDrawState()
 		{
 			vb[0] = vb[1] = nullptr;
-			formats[0] = formats[1] = vertex::CommonFormat::NONE;
+			formats[0] = formats[1] = CommonFormat::NONE;
 			vbMap[0] = vbMap[1] = StreamBuffer::MapInfo();
 		}
 	};
@@ -952,6 +942,7 @@ protected:
 		{}
 	};
 
+	ShaderStage *newShaderStage(ShaderStage::StageType stage, const std::string &source, const Shader::SourceInfo &info);
 	virtual ShaderStage *newShaderStageInternal(ShaderStage::StageType stage, const std::string &cachekey, const std::string &source, bool gles) = 0;
 	virtual Shader *newShaderInternal(ShaderStage *vertex, ShaderStage *pixel) = 0;
 	virtual StreamBuffer *newStreamBuffer(BufferType type, size_t size) = 0;

+ 184 - 188
src/modules/graphics/Mesh.cpp

@@ -34,36 +34,22 @@ namespace love
 namespace graphics
 {
 
-static const char *getBuiltinAttribName(BuiltinVertexAttribute attribid)
-{
-	const char *name = "";
-	vertex::getConstant(attribid, name);
-	return name;
-}
-
 static_assert(offsetof(Vertex, x) == sizeof(float) * 0, "Incorrect position offset in Vertex struct");
 static_assert(offsetof(Vertex, s) == sizeof(float) * 2, "Incorrect texture coordinate offset in Vertex struct");
 static_assert(offsetof(Vertex, color.r) == sizeof(float) * 4, "Incorrect color offset in Vertex struct");
 
-std::vector<Mesh::AttribFormat> Mesh::getDefaultVertexFormat()
+std::vector<Buffer::DataDeclaration> Mesh::getDefaultVertexFormat()
 {
-	// Corresponds to the love::Vertex struct.
-	std::vector<Mesh::AttribFormat> vertexformat = {
-		{ getBuiltinAttribName(ATTRIB_POS),      vertex::DATA_FLOAT,  2 },
-		{ getBuiltinAttribName(ATTRIB_TEXCOORD), vertex::DATA_FLOAT,  2 },
-		{ getBuiltinAttribName(ATTRIB_COLOR),    vertex::DATA_UNORM8, 4 },
-	};
-
-	return vertexformat;
+	return Buffer::getCommonFormatDeclaration(CommonFormat::XYf_STf_RGBAub);
 }
 
 love::Type Mesh::type("Mesh", &Drawable::type);
 
-Mesh::Mesh(graphics::Graphics *gfx, const std::vector<AttribFormat> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, vertex::Usage usage)
-	: vertexFormat(vertexformat)
-	, vertexBuffer(nullptr)
+Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage)
+	: vertexBuffer(nullptr)
 	, vertexCount(0)
 	, vertexStride(0)
+	, vertexScratchBuffer(nullptr)
 	, indexBuffer(nullptr)
 	, useIndexBuffer(false)
 	, indexCount(0)
@@ -72,29 +58,29 @@ Mesh::Mesh(graphics::Graphics *gfx, const std::vector<AttribFormat> &vertexforma
 	, rangeStart(-1)
 	, rangeCount(-1)
 {
-	setupAttachedAttributes();
-	calculateAttributeSizes(gfx);
+	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY | Buffer::MAP_READ, usage);
+	vertexBuffer.set(gfx->newBuffer(settings, vertexformat, data, datasize, 0), Acquire::NORETAIN);
 
-	vertexCount = datasize / vertexStride;
-	indexDataType = vertex::getIndexDataTypeFromMax(vertexCount);
+	vertexCount = vertexBuffer->getArrayLength();
+	vertexStride = vertexBuffer->getArrayStride();
+	vertexFormat = vertexBuffer->getDataMembers();
 
-	if (vertexCount == 0)
-		throw love::Exception("Data size is too small for specified vertex attribute formats.");
+	setupAttachedAttributes();
 
-	vertexBuffer = gfx->newBuffer(datasize, data, BUFFER_VERTEX, usage, Buffer::MAP_EXPLICIT_RANGE_MODIFY | Buffer::MAP_READ);
+	indexDataType = getIndexDataTypeFromMax(vertexCount);
 
 	vertexScratchBuffer = new char[vertexStride];
 }
 
-Mesh::Mesh(graphics::Graphics *gfx, const std::vector<AttribFormat> &vertexformat, int vertexcount, PrimitiveType drawmode, vertex::Usage usage)
-	: vertexFormat(vertexformat)
-	, vertexBuffer(nullptr)
+Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage)
+	: vertexBuffer(nullptr)
 	, vertexCount((size_t) vertexcount)
 	, vertexStride(0)
+	, vertexScratchBuffer(nullptr)
 	, indexBuffer(nullptr)
 	, useIndexBuffer(false)
 	, indexCount(0)
-	, indexDataType(vertex::getIndexDataTypeFromMax(vertexcount))
+	, indexDataType(getIndexDataTypeFromMax(vertexcount))
 	, primitiveType(drawmode)
 	, rangeStart(-1)
 	, rangeCount(-1)
@@ -102,83 +88,82 @@ Mesh::Mesh(graphics::Graphics *gfx, const std::vector<AttribFormat> &vertexforma
 	if (vertexcount <= 0)
 		throw love::Exception("Invalid number of vertices (%d).", vertexcount);
 
-	setupAttachedAttributes();
-	calculateAttributeSizes(gfx);
+	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY | Buffer::MAP_READ, usage);
+	vertexBuffer.set(gfx->newBuffer(settings, vertexformat, nullptr, 0, vertexcount), Acquire::NORETAIN);
 
-	size_t buffersize = vertexCount * vertexStride;
+	vertexStride = vertexBuffer->getArrayStride();
+	vertexFormat = vertexBuffer->getDataMembers();
 
-	vertexBuffer = gfx->newBuffer(buffersize, nullptr, BUFFER_VERTEX, usage, Buffer::MAP_EXPLICIT_RANGE_MODIFY | Buffer::MAP_READ);
+	setupAttachedAttributes();
 
-	// Initialize the buffer's contents to 0.
-	memset(vertexBuffer->map(), 0, buffersize);
+	memset(vertexBuffer->map(), 0, vertexBuffer->getSize());
 	vertexBuffer->setMappedRangeModified(0, vertexBuffer->getSize());
 	vertexBuffer->unmap();
 
 	vertexScratchBuffer = new char[vertexStride];
 }
 
-Mesh::~Mesh()
+Mesh::Mesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType drawmode)
+	: vertexBuffer(nullptr)
+	, vertexCount(0)
+	, vertexStride(0)
+	, vertexScratchBuffer(nullptr)
+	, indexBuffer(nullptr)
+	, useIndexBuffer(false)
+	, indexCount(0)
+	, indexDataType(INDEX_UINT16)
+	, primitiveType(drawmode)
+	, rangeStart(-1)
+	, rangeCount(-1)
 {
-	delete vertexBuffer;
-	delete indexBuffer;
-	delete vertexScratchBuffer;
+	if (attributes.size() == 0)
+		throw love::Exception("At least one buffer attribute must be specified in this constructor.");
+
+	attachedAttributes = attributes;
+
+	vertexCount = attachedAttributes.size() > 0 ? LOVE_UINT32_MAX : 0;
 
 	for (const auto &attrib : attachedAttributes)
 	{
-		if (attrib.second.mesh != this)
-			attrib.second.mesh->release();
+		if ((attrib.buffer->getTypeFlags() & Buffer::TYPEFLAG_VERTEX) == 0)
+			throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");
+
+		if (getAttachedAttributeIndex(attrib.name) != -1)
+			throw love::Exception("Duplicate vertex attribute name: %s", attrib.name.c_str());
+
+		vertexCount = std::min(vertexCount, attrib.buffer->getArrayLength());
 	}
+
+	indexDataType = getIndexDataTypeFromMax(vertexCount);
+}
+
+Mesh::~Mesh()
+{
+	delete vertexScratchBuffer;
 }
 
 void Mesh::setupAttachedAttributes()
 {
 	for (size_t i = 0; i < vertexFormat.size(); i++)
 	{
-		const std::string &name = vertexFormat[i].name;
+		const std::string &name = vertexFormat[i].decl.name;
 
-		if (attachedAttributes.find(name) != attachedAttributes.end())
+		if (getAttachedAttributeIndex(name) != -1)
 			throw love::Exception("Duplicate vertex attribute name: %s", name.c_str());
 
-		attachedAttributes[name] = {this, (int) i, STEP_PER_VERTEX, true};
+		attachedAttributes.push_back({name, vertexBuffer, (int) i, STEP_PER_VERTEX, true});
 	}
 }
 
-void Mesh::calculateAttributeSizes(Graphics *gfx)
+int Mesh::getAttachedAttributeIndex(const std::string &name) const
 {
-	bool supportsGLSL3 = gfx->getCapabilities().features[Graphics::FEATURE_GLSL3];
-
-	size_t stride = 0;
-
-	for (const AttribFormat &format : vertexFormat)
+	for (int i = 0; i < (int) attachedAttributes.size(); i++)
 	{
-		size_t size = vertex::getDataTypeSize(format.type) * format.components;
-
-		if (format.components <= 0 || format.components > 4)
-			throw love::Exception("Vertex attributes must have between 1 and 4 components.");
-
-		// Hardware really doesn't like attributes that aren't 32 bit-aligned.
-		if (size % 4 != 0)
-			throw love::Exception("Vertex attributes must have enough components to be a multiple of 32 bits.");
-
-		if (vertex::isDataTypeInteger(format.type) && !supportsGLSL3)
-			throw love::Exception("Integer vertex attribute data types require GLSL 3 support.");
-
-		// Total size in bytes of each attribute in a single vertex.
-		attributeSizes.push_back(size);
-		stride += size;
+		if (attachedAttributes[i].name == name)
+			return i;
 	}
 
-	vertexStride = stride;
-}
-
-size_t Mesh::getAttributeOffset(size_t attribindex) const
-{
-	size_t offset = 0;
-
-	for (size_t i = 0; i < attribindex; i++)
-		offset += attributeSizes[i];
-
-	return offset;
+	return -1;
 }
 
 void Mesh::setVertex(size_t vertindex, const void *data, size_t datasize)
@@ -186,6 +171,9 @@ void Mesh::setVertex(size_t vertindex, const void *data, size_t datasize)
 	if (vertindex >= vertexCount)
 		throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
 
+	if (vertexBuffer.get() == nullptr)
+		throw love::Exception("setVertex can only be called on a Mesh which owns its own vertex buffer.");
+
 	size_t offset = vertindex * vertexStride;
 	size_t size = std::min(datasize, vertexStride);
 
@@ -200,6 +188,9 @@ size_t Mesh::getVertex(size_t vertindex, void *data, size_t datasize)
 	if (vertindex >= vertexCount)
 		throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
 
+	if (vertexBuffer.get() == nullptr)
+		throw love::Exception("getVertex can only be called on a Mesh which owns its own vertex buffer.");
+
 	size_t offset = vertindex * vertexStride;
 	size_t size = std::min(datasize, vertexStride);
 
@@ -223,8 +214,13 @@ void Mesh::setVertexAttribute(size_t vertindex, int attribindex, const void *dat
 	if (attribindex >= (int) vertexFormat.size())
 		throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
 
-	size_t offset = vertindex * vertexStride + getAttributeOffset(attribindex);
-	size_t size = std::min(datasize, attributeSizes[attribindex]);
+	if (vertexBuffer.get() == nullptr)
+		throw love::Exception("setVertexAttribute can only be called on a Mesh which owns its own vertex buffer.");
+
+	const auto &member = vertexFormat[attribindex];
+
+	size_t offset = vertindex * vertexStride + member.offset;
+	size_t size = std::min(datasize, member.info.size);
 
 	uint8 *bufferdata = (uint8 *) vertexBuffer->map();
 	memcpy(bufferdata + offset, data, size);
@@ -240,8 +236,13 @@ size_t Mesh::getVertexAttribute(size_t vertindex, int attribindex, void *data, s
 	if (attribindex >= (int) vertexFormat.size())
 		throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
 
-	size_t offset = vertindex * vertexStride + getAttributeOffset(attribindex);
-	size_t size = std::min(datasize, attributeSizes[attribindex]);
+	if (vertexBuffer.get() == nullptr)
+		throw love::Exception("getVertexAttribute can only be called on a Mesh which owns its own vertex buffer.");
+
+	const auto &member = vertexFormat[attribindex];
+
+	size_t offset = vertindex * vertexStride + member.offset;
+	size_t size = std::min(datasize, member.info.size);
 
 	// We're relying on map() returning read/write data... ew.
 	const uint8 *bufferdata = (const uint8 *) vertexBuffer->map();
@@ -260,128 +261,106 @@ size_t Mesh::getVertexStride() const
 	return vertexStride;
 }
 
-const std::vector<Mesh::AttribFormat> &Mesh::getVertexFormat() const
+Buffer *Mesh::getVertexBuffer() const
 {
-	return vertexFormat;
-}
-
-vertex::DataType Mesh::getAttributeInfo(int attribindex, int &components) const
-{
-	if (attribindex < 0 || attribindex >= (int) vertexFormat.size())
-		throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
-
-	components = vertexFormat[attribindex].components;
-	return vertexFormat[attribindex].type;
+	return vertexBuffer;
 }
 
-int Mesh::getAttributeIndex(const std::string &name) const
+const std::vector<Buffer::DataMember> &Mesh::getVertexFormat() const
 {
-	for (int i = 0; i < (int) vertexFormat.size(); i++)
-	{
-		if (vertexFormat[i].name == name)
-			return i;
-	}
-
-	return -1;
+	return vertexFormat;
 }
 
 void Mesh::setAttributeEnabled(const std::string &name, bool enable)
 {
-	auto it = attachedAttributes.find(name);
-
-	if (it == attachedAttributes.end())
+	int index = getAttachedAttributeIndex(name);
+	if (index == -1)
 		throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str());
 
-	it->second.enabled = enable;
+	attachedAttributes[index].enabled = enable;
 }
 
 bool Mesh::isAttributeEnabled(const std::string &name) const
 {
-	const auto it = attachedAttributes.find(name);
-
-	if (it == attachedAttributes.end())
+	int index = getAttachedAttributeIndex(name);
+	if (index == -1)
 		throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str());
 
-	return it->second.enabled;
+	return attachedAttributes[index].enabled;
 }
 
-void Mesh::attachAttribute(const std::string &name, Mesh *mesh, const std::string &attachname, AttributeStep step)
+void Mesh::attachAttribute(const std::string &name, Buffer *buffer, const std::string &attachname, AttributeStep step)
 {
+	if ((buffer->getTypeFlags() & Buffer::TYPEFLAG_VERTEX) == 0)
+		throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");
+
 	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 	if (step == STEP_PER_INSTANCE && !gfx->getCapabilities().features[Graphics::FEATURE_INSTANCING])
 		throw love::Exception("Vertex attribute instancing is not supported on this system.");
 
-	if (mesh != this)
-	{
-		for (const auto &it : mesh->attachedAttributes)
-		{
-			// If the supplied Mesh has attached attributes of its own, then we
-			// prevent it from being attached to avoid reference cycles.
-			if (it.second.mesh != mesh)
-				throw love::Exception("Cannot attach a Mesh which has attached Meshes of its own.");
-		}
-	}
-
-	AttachedAttribute oldattrib = {};
-	AttachedAttribute newattrib = {};
+	BufferAttribute oldattrib = {};
+	BufferAttribute newattrib = {};
 
-	auto it = attachedAttributes.find(name);
-	if (it != attachedAttributes.end())
-		oldattrib = it->second;
-	else if (attachedAttributes.size() + 1 > vertex::Attributes::MAX)
-		throw love::Exception("A maximum of %d attributes can be attached at once.", vertex::Attributes::MAX);
+	int oldindex = getAttachedAttributeIndex(name);
+	if (oldindex != -1)
+		oldattrib = attachedAttributes[oldindex];
+	else if (attachedAttributes.size() + 1 > VertexAttributes::MAX)
+		throw love::Exception("A maximum of %d attributes can be attached at once.", VertexAttributes::MAX);
 
-	newattrib.mesh = mesh;
-	newattrib.enabled = oldattrib.mesh ? oldattrib.enabled : true;
-	newattrib.index = mesh->getAttributeIndex(attachname);
+	newattrib.name = name;
+	newattrib.buffer = buffer;
+	newattrib.enabled = oldattrib.buffer.get() ? oldattrib.enabled : true;
+	newattrib.indexInBuffer = buffer->getDataMemberIndex(attachname);
 	newattrib.step = step;
 
-	if (newattrib.index < 0)
-		throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", attachname.c_str());
-
-	if (newattrib.mesh != this)
-		newattrib.mesh->retain();
+	if (newattrib.indexInBuffer < 0)
+		throw love::Exception("The specified vertex buffer does not have a vertex attribute named '%s'", attachname.c_str());
 
-	attachedAttributes[name] = newattrib;
-
-	if (oldattrib.mesh && oldattrib.mesh != this)
-		oldattrib.mesh->release();
+	if (oldindex != -1)
+		attachedAttributes[oldindex] = newattrib;
+	else
+		attachedAttributes.push_back(newattrib);
 }
 
 bool Mesh::detachAttribute(const std::string &name)
 {
-	auto it = attachedAttributes.find(name);
+	int index = getAttachedAttributeIndex(name);
+	if (index == -1)
+		return false;
 
-	if (it != attachedAttributes.end() && it->second.mesh != this)
-	{
-		it->second.mesh->release();
-		attachedAttributes.erase(it);
+	attachedAttributes.erase(attachedAttributes.begin() + index);
 
-		if (getAttributeIndex(name) != -1)
-			attachAttribute(name, this, name);
+	if (vertexBuffer.get() && vertexBuffer->getDataMemberIndex(name) != -1)
+		attachAttribute(name, vertexBuffer, name);
 
-		return true;
-	}
+	return true;
+}
 
-	return false;
+const std::vector<Mesh::BufferAttribute> &Mesh::getAttachedAttributes() const
+{
+	return attachedAttributes;
 }
 
 void *Mesh::mapVertexData()
 {
-	return vertexBuffer->map();
+	return vertexBuffer.get() != nullptr ? vertexBuffer->map() : nullptr;
 }
 
 void Mesh::unmapVertexData(size_t modifiedoffset, size_t modifiedsize)
 {
+	if (!vertexBuffer.get())
+		return;
+
 	vertexBuffer->setMappedRangeModified(modifiedoffset, modifiedsize);
 	vertexBuffer->unmap();
 }
 
 void Mesh::flush()
 {
-	vertexBuffer->unmap();
+	if (vertexBuffer.get())
+		vertexBuffer->unmap();
 
-	if (indexBuffer != nullptr)
+	if (indexBuffer.get())
 		indexBuffer->unmap();
 }
 
@@ -391,7 +370,7 @@ void Mesh::flush()
 template <typename T>
 static void copyToIndexBuffer(const std::vector<uint32> &indices, Buffer::Mapper &buffermap, size_t maxval)
 {
-	T *elems = (T *) buffermap.get();
+	T *elems = (T *) buffermap.data;
 
 	for (size_t i = 0; i < indices.size(); i++)
 	{
@@ -406,21 +385,18 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
 {
 	size_t maxval = getVertexCount();
 
-	IndexDataType datatype = vertex::getIndexDataTypeFromMax(maxval);
+	IndexDataType datatype = getIndexDataTypeFromMax(maxval);
+	DataFormat dataformat = getIndexDataFormat(datatype);
 
 	// Calculate the size in bytes of the index buffer data.
-	size_t size = map.size() * vertex::getIndexDataSize(datatype);
-
-	if (indexBuffer && size > indexBuffer->getSize())
-	{
-		delete indexBuffer;
-		indexBuffer = nullptr;
-	}
+	size_t size = map.size() * getIndexDataSize(datatype);
 
-	if (!indexBuffer && size > 0)
+	if (indexBuffer.get() == nullptr || size > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat)
 	{
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-		indexBuffer = gfx->newBuffer(size, nullptr, BUFFER_INDEX, vertexBuffer->getUsage(), Buffer::MAP_READ);
+		auto usage = vertexBuffer.get() ? vertexBuffer->getUsage() : BUFFERUSAGE_DYNAMIC;
+		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_READ, usage);
+		indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, size, 0), Acquire::NORETAIN);
 	}
 
 	useIndexBuffer = true;
@@ -448,25 +424,23 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
 
 void Mesh::setVertexMap(IndexDataType datatype, const void *data, size_t datasize)
 {
-	if (indexBuffer && datasize > indexBuffer->getSize())
-	{
-		delete indexBuffer;
-		indexBuffer = nullptr;
-	}
+	DataFormat dataformat = getIndexDataFormat(datatype);
 
-	if (!indexBuffer && datasize > 0)
+	if (indexBuffer.get() == nullptr || datasize > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat)
 	{
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-		indexBuffer = gfx->newBuffer(datasize, nullptr, BUFFER_INDEX, vertexBuffer->getUsage(), Buffer::MAP_READ);
+		auto usage = vertexBuffer.get() ? vertexBuffer->getUsage() : BUFFERUSAGE_DYNAMIC;
+		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_READ, usage);
+		indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, datasize, 0), Acquire::NORETAIN);
 	}
 
-	indexCount = datasize / vertex::getIndexDataSize(datatype);
+	indexCount = datasize / getIndexDataSize(datatype);
 
 	if (!indexBuffer || indexCount == 0)
 		return;
 
 	Buffer::Mapper ibomap(*indexBuffer);
-	memcpy(ibomap.get(), data, datasize);
+	memcpy(ibomap.data, data, datasize);
 
 	useIndexBuffer = true;
 	indexDataType = datatype;
@@ -499,6 +473,9 @@ bool Mesh::getVertexMap(std::vector<uint32> &map) const
 	if (!indexBuffer || indexCount == 0)
 		return true;
 
+	if ((indexBuffer->getMapFlags() & Buffer::MAP_READ) == 0)
+		return false;
+
 	// We unmap the buffer in Mesh::draw, Mesh::setVertexMap, and Mesh::flush.
 	void *buffer = indexBuffer->map();
 
@@ -517,7 +494,27 @@ bool Mesh::getVertexMap(std::vector<uint32> &map) const
 	return true;
 }
 
-size_t Mesh::getVertexMapCount() const
+void Mesh::setIndexBuffer(Buffer *buffer)
+{
+	// Buffer constructor does the rest of the validation for index buffers
+	// (data member formats, etc.)
+	if (buffer != nullptr && (buffer->getTypeFlags() & Buffer::TYPEFLAG_INDEX) == 0)
+		throw love::Exception("setIndexBuffer requires a Buffer created as an index buffer.");
+
+	indexBuffer.set(buffer);
+	useIndexBuffer = buffer != nullptr;
+	indexCount = buffer != nullptr ? buffer->getArrayLength() : 0;
+
+	if (buffer != nullptr)
+		indexDataType = getIndexDataType(buffer->getDataMember(0).decl.format);
+}
+
+Buffer *Mesh::getIndexBuffer() const
+{
+	return indexBuffer;
+}
+
+size_t Mesh::getIndexCount() const
 {
 	return indexCount;
 }
@@ -592,43 +589,42 @@ void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
 	if (Shader::current && texture.get())
 		Shader::current->checkMainTexture(texture);
 
-	vertex::Attributes attributes;
-	vertex::BufferBindings buffers;
+	VertexAttributes attributes;
+	BufferBindings buffers;
 
 	int activebuffers = 0;
 
 	for (const auto &attrib : attachedAttributes)
 	{
-		if (!attrib.second.enabled)
+		if (!attrib.enabled)
 			continue;
 
-		Mesh *mesh = attrib.second.mesh;
+		Buffer *buffer = attrib.buffer.get();
 		int attributeindex = -1;
 
 		// If the attribute is one of the LOVE-defined ones, use the constant
 		// attribute index for it, otherwise query the index from the shader.
 		BuiltinVertexAttribute builtinattrib;
-		if (vertex::getConstant(attrib.first.c_str(), builtinattrib))
+		if (getConstant(attrib.name.c_str(), builtinattrib))
 			attributeindex = (int) builtinattrib;
 		else if (Shader::current)
-			attributeindex = Shader::current->getVertexAttributeIndex(attrib.first);
+			attributeindex = Shader::current->getVertexAttributeIndex(attrib.name);
 
 		if (attributeindex >= 0)
 		{
 			// Make sure the buffer isn't mapped (sends data to GPU if needed.)
-			mesh->vertexBuffer->unmap();
+			buffer->unmap();
 
-			const auto &formats = mesh->getVertexFormat();
-			const auto &format = formats[attrib.second.index];
+			const auto &member = buffer->getDataMember(attrib.indexInBuffer);
 
-			uint16 offset = (uint16) mesh->getAttributeOffset(attrib.second.index);
-			uint16 stride = (uint16) mesh->getVertexStride();
+			uint16 offset = (uint16) member.offset;
+			uint16 stride = (uint16) buffer->getArrayStride();
 
-			attributes.set(attributeindex, format.type, (uint8) format.components, offset, activebuffers);
-			attributes.setBufferLayout(activebuffers, stride, attrib.second.step);
+			attributes.set(attributeindex, member.decl.format, offset, activebuffers);
+			attributes.setBufferLayout(activebuffers, stride, attrib.step);
 
 			// TODO: Ideally we want to reuse buffers with the same stride+step.
-			buffers.set(activebuffers, mesh->vertexBuffer, 0);
+			buffers.set(activebuffers, buffer, 0);
 			activebuffers++;
 		}
 	}
@@ -653,7 +649,7 @@ void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
 		cmd.cullMode = gfx->getMeshCullMode();
 
 		int start = std::min(std::max(0, rangeStart), (int) indexCount - 1);
-		cmd.indexBufferOffset = start * vertex::getIndexDataSize(indexDataType);
+		cmd.indexBufferOffset = start * getIndexDataSize(indexDataType);
 
 		cmd.indexCount = (int) indexCount;
 		if (rangeCount > 0)

+ 30 - 30
src/modules/graphics/Mesh.h

@@ -50,17 +50,20 @@ class Mesh : public Drawable
 {
 public:
 
-	static love::Type type;
-
-	struct AttribFormat
+	struct BufferAttribute
 	{
 		std::string name;
-		vertex::DataType type;
-		int components; // max 4
+		StrongRef<Buffer> buffer;
+		int indexInBuffer;
+		AttributeStep step;
+		bool enabled;
 	};
 
-	Mesh(Graphics *gfx, const std::vector<AttribFormat> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, vertex::Usage usage);
-	Mesh(Graphics *gfx, const std::vector<AttribFormat> &vertexformat, int vertexcount, PrimitiveType drawmode, vertex::Usage usage);
+	static love::Type type;
+
+	Mesh(Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage);
+	Mesh(Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage);
+	Mesh(const std::vector<BufferAttribute> &attributes, PrimitiveType drawmode);
 
 	virtual ~Mesh();
 
@@ -92,12 +95,15 @@ public:
 	 **/
 	size_t getVertexStride() const;
 
+	/**
+	 * Gets the Buffer that holds the Mesh's vertices.
+	 **/
+	Buffer *getVertexBuffer() const;
+
 	/**
 	 * Gets the format of each vertex attribute stored in the Mesh.
 	 **/
-	const std::vector<AttribFormat> &getVertexFormat() const;
-	vertex::DataType getAttributeInfo(int attribindex, int &components) const;
-	int getAttributeIndex(const std::string &name) const;
+	const std::vector<Buffer::DataMember> &getVertexFormat() const;
 
 	/**
 	 * Sets whether a specific vertex attribute is used when drawing the Mesh.
@@ -106,11 +112,12 @@ public:
 	bool isAttributeEnabled(const std::string &name) const;
 
 	/**
-	 * Attaches a vertex attribute from another Mesh to this one. The attribute
-	 * will be used when drawing this Mesh.
+	 * Attaches a vertex attribute from another vertex buffer to this Mesh. The
+	 * attribute will be used when drawing this Mesh.
 	 **/
-	void attachAttribute(const std::string &name, Mesh *mesh, const std::string &attachname, AttributeStep step = STEP_PER_VERTEX);
+	void attachAttribute(const std::string &name, Buffer *buffer, const std::string &attachname, AttributeStep step = STEP_PER_VERTEX);
 	bool detachAttribute(const std::string &name);
+	const std::vector<BufferAttribute> &getAttachedAttributes() const;
 
 	void *mapVertexData();
 	void unmapVertexData(size_t modifiedoffset = 0, size_t modifiedsize = -1);
@@ -136,10 +143,13 @@ public:
 	 **/
 	bool getVertexMap(std::vector<uint32> &map) const;
 
+	void setIndexBuffer(Buffer *buffer);
+	Buffer *getIndexBuffer() const;
+
 	/**
 	 * Gets the total number of elements in the vertex map array.
 	 **/
-	size_t getVertexMapCount() const;
+	size_t getIndexCount() const;
 
 	/**
 	 * Sets the texture used when drawing the Mesh.
@@ -172,31 +182,21 @@ public:
 
 	void drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount);
 
-	static std::vector<AttribFormat> getDefaultVertexFormat();
+	static std::vector<Buffer::DataDeclaration> getDefaultVertexFormat();
 
 private:
 
 	friend class SpriteBatch;
 
-	struct AttachedAttribute
-	{
-		Mesh *mesh;
-		int index;
-		AttributeStep step;
-		bool enabled;
-	};
-
 	void setupAttachedAttributes();
-	void calculateAttributeSizes(Graphics *gfx);
-	size_t getAttributeOffset(size_t attribindex) const;
+	int getAttachedAttributeIndex(const std::string &name) const;
 
-	std::vector<AttribFormat> vertexFormat;
-	std::vector<size_t> attributeSizes;
+	std::vector<Buffer::DataMember> vertexFormat;
 
-	std::unordered_map<std::string, AttachedAttribute> attachedAttributes;
+	std::vector<BufferAttribute> attachedAttributes;
 
 	// Vertex buffer, for the vertex data.
-	Buffer *vertexBuffer;
+	StrongRef<Buffer> vertexBuffer;
 	size_t vertexCount;
 	size_t vertexStride;
 
@@ -205,7 +205,7 @@ private:
 	char *vertexScratchBuffer;
 
 	// Index buffer, for the vertex map.
-	Buffer *indexBuffer;
+	StrongRef<Buffer> indexBuffer;
 	bool useIndexBuffer;
 	size_t indexCount;
 	IndexDataType indexDataType;

+ 7 - 4
src/modules/graphics/ParticleSystem.cpp

@@ -93,7 +93,7 @@ ParticleSystem::ParticleSystem(Texture *texture, uint32 size)
 	, offset(float(texture->getWidth())*0.5f, float(texture->getHeight())*0.5f)
 	, defaultOffset(true)
 	, relativeRotation(false)
-	, vertexAttributes(vertex::CommonFormat::XYf_STf_RGBAub, 0)
+	, vertexAttributes(CommonFormat::XYf_STf_RGBAub, 0)
 	, buffer(nullptr)
 {
 	if (size == 0 || size > MAX_PARTICLES)
@@ -191,7 +191,9 @@ void ParticleSystem::createBuffers(size_t size)
 		auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 
 		size_t bytes = sizeof(Vertex) * size * 4;
-		buffer = gfx->newBuffer(bytes, nullptr, BUFFER_VERTEX, vertex::USAGE_STREAM, 0);
+		Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, 0, BUFFERUSAGE_STREAM);
+		auto decl = Buffer::getCommonFormatDeclaration(CommonFormat::XYf_STf_RGBAub);
+		buffer = gfx->newBuffer(settings, decl, nullptr, bytes, 0);
 	}
 	catch (std::bad_alloc &)
 	{
@@ -203,7 +205,8 @@ void ParticleSystem::createBuffers(size_t size)
 void ParticleSystem::deleteBuffers()
 {
 	delete[] pMem;
-	delete buffer;
+	if (buffer)
+		buffer->release();
 
 	pMem = nullptr;
 	buffer = nullptr;
@@ -1080,7 +1083,7 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
 
 	Graphics::TempTransform transform(gfx, m);
 
-	vertex::BufferBindings vertexbuffers;
+	BufferBindings vertexbuffers;
 	vertexbuffers.set(0, buffer, 0);
 
 	gfx->drawQuads(0, pCount, vertexAttributes, vertexbuffers, texture);

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

@@ -673,7 +673,7 @@ private:
 
 	bool relativeRotation;
 
-	const vertex::Attributes vertexAttributes;
+	const VertexAttributes vertexAttributes;
 	Buffer *buffer;
 
 	static StringMap<AreaSpreadDistribution, DISTRIBUTION_MAX_ENUM>::Entry distributionsEntries[];

+ 21 - 11
src/modules/graphics/Polyline.cpp

@@ -82,7 +82,7 @@ void Polyline::render(const Vector2 *coords, size_t count, size_t size_hint, flo
 		// extra degenerate triangle in between the core line and the overdraw
 		// line in order to break up the strip into two. This will let us draw
 		// everything in one draw call.
-		if (triangle_mode == vertex::TriangleIndexMode::STRIP)
+		if (triangle_mode == TRIANGLEINDEX_STRIP)
 			extra_vertices = 2;
 	}
 
@@ -383,7 +383,7 @@ void Polyline::draw(love::graphics::Graphics *gfx)
 	int maxvertices = LOVE_UINT16_MAX - 3;
 
 	int advance = maxvertices;
-	if (triangle_mode == vertex::TriangleIndexMode::STRIP)
+	if (triangle_mode == TRIANGLEINDEX_STRIP)
 		advance -= 2;
 
 	for (int vertex_start = 0; vertex_start < total_vertex_count; vertex_start += advance)
@@ -391,8 +391,8 @@ void Polyline::draw(love::graphics::Graphics *gfx)
 		const Vector2 *verts = vertices + vertex_start;
 
 		Graphics::BatchedDrawCommand cmd;
-		cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
-		cmd.formats[1] = vertex::CommonFormat::RGBAub;
+		cmd.formats[0] = getSinglePositionFormat(is2D);
+		cmd.formats[1] = CommonFormat::RGBAub;
 		cmd.indexMode = triangle_mode;
 		cmd.vertexCount = std::min(maxvertices, total_vertex_count - vertex_start);
 
@@ -405,18 +405,28 @@ void Polyline::draw(love::graphics::Graphics *gfx)
 
 		Color32 *colordata = (Color32 *) data.stream[1];
 
+		int draw_rough_count = std::min(cmd.vertexCount, (int) vertex_count - vertex_start);
+
 		// Constant vertex color up to the overdraw vertices.
-		for (int i = 0; i < std::min(cmd.vertexCount, (int) vertex_count - vertex_start); i++)
+		for (int i = 0; i < draw_rough_count; i++)
 			colordata[i] = curcolor;
 
-		int colorcount = 0;
 		if (overdraw)
-			colorcount = std::min(cmd.vertexCount, overdraw_count - (vertex_start - overdraw_start));
-
-		if (colorcount > 0)
 		{
-			Color32 *colors = colordata + std::max(0, (overdraw_start - vertex_start));
-			fill_color_array(curcolor, colors, colorcount);
+			int draw_remaining_count = cmd.vertexCount - draw_rough_count;
+
+			int draw_overdraw_begin = overdraw_start - vertex_start;
+			int draw_overdraw_end = draw_overdraw_begin + overdraw_count;
+
+			draw_overdraw_begin = std::max(0, draw_overdraw_begin);
+
+			int draw_overdraw_count = std::min(draw_remaining_count, draw_overdraw_end - draw_overdraw_begin);
+
+			if (draw_overdraw_count > 0)
+			{
+				Color32 *colors = colordata + draw_overdraw_begin;
+				fill_color_array(curcolor, colors, draw_overdraw_count);
+			}
 		}
 	}
 }

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

@@ -44,7 +44,7 @@ class Polyline
 {
 public:
 
-	Polyline(vertex::TriangleIndexMode mode = vertex::TriangleIndexMode::STRIP)
+	Polyline(TriangleIndexMode mode = TRIANGLEINDEX_STRIP)
 		: vertices(nullptr)
 		, overdraw(nullptr)
 		, vertex_count(0)
@@ -94,7 +94,7 @@ protected:
 	Vector2 *overdraw;
 	size_t vertex_count;
 	size_t overdraw_vertex_count;
-	vertex::TriangleIndexMode triangle_mode;
+	TriangleIndexMode triangle_mode;
 	size_t overdraw_vertex_start;
 
 }; // Polyline
@@ -109,7 +109,7 @@ class NoneJoinPolyline : public Polyline
 public:
 
 	NoneJoinPolyline()
-		: Polyline(vertex::TriangleIndexMode::QUADS)
+		: Polyline(TRIANGLEINDEX_QUADS)
 	{}
 
 	void render(const Vector2 *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)

+ 511 - 22
src/modules/graphics/Shader.cpp

@@ -28,12 +28,402 @@
 
 // C++
 #include <string>
+#include <regex>
+#include <sstream>
 
 namespace love
 {
 namespace graphics
 {
 
+namespace glsl
+{
+static const char global_syntax[] = R"(
+#if !defined(GL_ES) && __VERSION__ < 140
+	#define lowp
+	#define mediump
+	#define highp
+#endif
+#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
+	#define LOVE_HIGHP_OR_MEDIUMP highp
+#else
+	#define LOVE_HIGHP_OR_MEDIUMP mediump
+#endif
+#define number float
+#define Image sampler2D
+#define ArrayImage sampler2DArray
+#define CubeImage samplerCube
+#define VolumeImage sampler3D
+#if __VERSION__ >= 300 && !defined(LOVE_GLSL1_ON_GLSL3)
+	#define DepthImage sampler2DShadow
+	#define DepthArrayImage sampler2DArrayShadow
+	#define DepthCubeImage samplerCubeShadow
+#endif
+#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
+#ifdef GL_OES_standard_derivatives
+#extension GL_OES_standard_derivatives : enable
+#endif
+)";
+
+static const char global_uniforms[] = R"(
+// According to the GLSL ES 1.0 spec, uniform precision must match between stages,
+// but we can't guarantee that highp is always supported in fragment shaders...
+// We *really* don't want to use mediump for these in vertex shaders though.
+uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_UniformsPerDraw[13];
+
+// These are initialized in love_initializeBuiltinUniforms below. GLSL ES can't
+// do it as an initializer.
+LOVE_HIGHP_OR_MEDIUMP mat4 TransformMatrix;
+LOVE_HIGHP_OR_MEDIUMP mat4 ProjectionMatrix;
+LOVE_HIGHP_OR_MEDIUMP mat3 NormalMatrix;
+
+LOVE_HIGHP_OR_MEDIUMP vec4 love_ScreenSize;
+LOVE_HIGHP_OR_MEDIUMP vec4 ConstantColor;
+
+#define TransformProjectionMatrix (ProjectionMatrix * TransformMatrix)
+
+// Alternate names
+#define ViewSpaceFromLocal TransformMatrix
+#define ClipSpaceFromView ProjectionMatrix
+#define ClipSpaceFromLocal TransformProjectionMatrix
+#define ViewNormalFromLocal NormalMatrix
+
+void love_initializeBuiltinUniforms() {
+	TransformMatrix = mat4(
+	   love_UniformsPerDraw[0],
+	   love_UniformsPerDraw[1],
+	   love_UniformsPerDraw[2],
+	   love_UniformsPerDraw[3]
+	);
+
+	ProjectionMatrix = mat4(
+	   love_UniformsPerDraw[4],
+	   love_UniformsPerDraw[5],
+	   love_UniformsPerDraw[6],
+	   love_UniformsPerDraw[7]
+	);
+
+	NormalMatrix = mat3(
+	   love_UniformsPerDraw[8].xyz,
+	   love_UniformsPerDraw[9].xyz,
+	   love_UniformsPerDraw[10].xyz
+	);
+
+	love_ScreenSize = love_UniformsPerDraw[11];
+	ConstantColor = love_UniformsPerDraw[12];
+}
+)";
+
+static const char global_functions[] = R"(
+#ifdef GL_ES
+	#if __VERSION__ >= 300 || defined(GL_EXT_texture_array)
+		precision lowp sampler2DArray;
+	#endif
+	#if __VERSION__ >= 300 || defined(GL_OES_texture_3D)
+		precision lowp sampler3D;
+	#endif
+	#if __VERSION__ >= 300
+		precision lowp sampler2DShadow;
+		precision lowp samplerCubeShadow;
+		precision lowp sampler2DArrayShadow;
+	#endif
+#endif
+
+#if __VERSION__ >= 130 && !defined(LOVE_GLSL1_ON_GLSL3)
+	#define Texel texture
+#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
+
+float gammaToLinearPrecise(float c) {
+	return c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4);
+}
+vec3 gammaToLinearPrecise(vec3 c) {
+	bvec3 leq = lessThanEqual(c, vec3(0.04045));
+	c.r = leq.r ? c.r / 12.92 : pow((c.r + 0.055) / 1.055, 2.4);
+	c.g = leq.g ? c.g / 12.92 : pow((c.g + 0.055) / 1.055, 2.4);
+	c.b = leq.b ? c.b / 12.92 : pow((c.b + 0.055) / 1.055, 2.4);
+	return c;
+}
+vec4 gammaToLinearPrecise(vec4 c) { return vec4(gammaToLinearPrecise(c.rgb), c.a); }
+float linearToGammaPrecise(float c) {
+	return c < 0.0031308 ? c * 12.92 : 1.055 * pow(c, 1.0 / 2.4) - 0.055;
+}
+vec3 linearToGammaPrecise(vec3 c) {
+	bvec3 lt = lessThanEqual(c, vec3(0.0031308));
+	c.r = lt.r ? c.r * 12.92 : 1.055 * pow(c.r, 1.0 / 2.4) - 0.055;
+	c.g = lt.g ? c.g * 12.92 : 1.055 * pow(c.g, 1.0 / 2.4) - 0.055;
+	c.b = lt.b ? c.b * 12.92 : 1.055 * pow(c.b, 1.0 / 2.4) - 0.055;
+	return c;
+}
+vec4 linearToGammaPrecise(vec4 c) { return vec4(linearToGammaPrecise(c.rgb), c.a); }
+
+// http://chilliant.blogspot.com.au/2012/08/srgb-approximations-for-hlsl.html?m=1
+
+mediump float gammaToLinearFast(mediump float c) { return c * (c * (c * 0.305306011 + 0.682171111) + 0.012522878); }
+mediump vec3 gammaToLinearFast(mediump vec3 c) { return c * (c * (c * 0.305306011 + 0.682171111) + 0.012522878); }
+mediump vec4 gammaToLinearFast(mediump vec4 c) { return vec4(gammaToLinearFast(c.rgb), c.a); }
+
+mediump float linearToGammaFast(mediump float c) { return max(1.055 * pow(max(c, 0.0), 0.41666666) - 0.055, 0.0); }
+mediump vec3 linearToGammaFast(mediump vec3 c) { return max(1.055 * pow(max(c, vec3(0.0)), vec3(0.41666666)) - 0.055, vec3(0.0)); }
+mediump vec4 linearToGammaFast(mediump vec4 c) { return vec4(linearToGammaFast(c.rgb), c.a); }
+
+#define gammaToLinear gammaToLinearFast
+#define linearToGamma linearToGammaFast
+
+#ifdef LOVE_GAMMA_CORRECT
+	#define gammaCorrectColor gammaToLinear
+	#define unGammaCorrectColor linearToGamma
+	#define gammaCorrectColorPrecise gammaToLinearPrecise
+	#define unGammaCorrectColorPrecise linearToGammaPrecise
+	#define gammaCorrectColorFast gammaToLinearFast
+	#define unGammaCorrectColorFast linearToGammaFast
+#else
+	#define gammaCorrectColor
+	#define unGammaCorrectColor
+	#define gammaCorrectColorPrecise
+	#define unGammaCorrectColorPrecise
+	#define gammaCorrectColorFast
+	#define unGammaCorrectColorFast
+#endif
+)";
+
+static const char vertex_header[] = R"(
+#define love_Position gl_Position
+
+#if __VERSION__ >= 130
+	#define attribute in
+	#define varying out
+	#ifndef LOVE_GLSL1_ON_GLSL3
+		#define love_VertexID gl_VertexID
+		#define love_InstanceID gl_InstanceID
+	#endif
+#endif
+
+#ifdef GL_ES
+	uniform mediump float love_PointSize;
+#endif
+)";
+
+static const char vertex_functions[] = R"(
+void setPointSize() {
+#ifdef GL_ES
+	gl_PointSize = love_PointSize;
+#endif
+}
+)";
+
+static const char vertex_main[] = R"(
+attribute vec4 VertexPosition;
+attribute vec4 VertexTexCoord;
+attribute vec4 VertexColor;
+
+varying vec4 VaryingTexCoord;
+varying vec4 VaryingColor;
+
+vec4 position(mat4 clipSpaceFromLocal, vec4 localPosition);
+
+void main() {
+	love_initializeBuiltinUniforms();
+	VaryingTexCoord = VertexTexCoord;
+	VaryingColor = gammaCorrectColor(VertexColor) * ConstantColor;
+	setPointSize();
+	love_Position = position(ClipSpaceFromLocal, VertexPosition);
+}
+)";
+
+static const char pixel_header[] = R"(
+#ifdef GL_ES
+	precision mediump float;
+#endif
+
+#define love_MaxRenderTargets gl_MaxDrawBuffers
+
+#if __VERSION__ >= 130
+	#define varying in
+	// Some drivers seem to make the pixel shader do more work when multiple
+	// pixel shader outputs are defined, even when only one is actually used.
+	// TODO: We should use reflection or something instead of this, to determine
+	// how many outputs are actually used in the shader code.
+	#ifdef LOVE_MULTI_RENDER_TARGETS
+		layout(location = 0) out vec4 love_RenderTargets[love_MaxRenderTargets];
+		#define love_PixelColor love_RenderTargets[0]
+	#else
+		layout(location = 0) out vec4 love_PixelColor;
+	#endif
+#else
+	#ifdef LOVE_MULTI_RENDER_TARGETS
+		#define love_RenderTargets gl_FragData
+	#endif
+	#define love_PixelColor gl_FragColor
+#endif
+
+// Legacy
+#define love_MaxCanvases love_MaxRenderTargets
+#define love_Canvases love_RenderTargets
+#ifdef LOVE_MULTI_RENDER_TARGETS
+#define LOVE_MULTI_CANVASES 1
+#endif
+
+// See Shader::updateScreenParams in Shader.cpp.
+#define love_PixelCoord (vec2(gl_FragCoord.x, (gl_FragCoord.y * love_ScreenSize.z) + love_ScreenSize.w))
+)";
+
+static const char pixel_functions[] = R"(
+uniform sampler2D love_VideoYChannel;
+uniform sampler2D love_VideoCbChannel;
+uniform sampler2D love_VideoCrChannel;
+
+vec4 VideoTexel(vec2 texcoords) {
+	vec3 yuv;
+	yuv[0] = Texel(love_VideoYChannel, texcoords).r;
+	yuv[1] = Texel(love_VideoCbChannel, texcoords).r;
+	yuv[2] = Texel(love_VideoCrChannel, texcoords).r;
+	yuv += vec3(-0.0627451017, -0.501960814, -0.501960814);
+
+	vec4 color;
+	color.r = dot(yuv, vec3(1.164,  0.000,  1.596));
+	color.g = dot(yuv, vec3(1.164, -0.391, -0.813));
+	color.b = dot(yuv, vec3(1.164,  2.018,  0.000));
+	color.a = 1.0;
+
+	return gammaCorrectColor(color);
+}
+)";
+
+static const char pixel_main[] = R"(
+uniform sampler2D MainTex;
+varying LOVE_HIGHP_OR_MEDIUMP vec4 VaryingTexCoord;
+varying mediump vec4 VaryingColor;
+
+vec4 effect(vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord);
+
+void main() {
+	love_initializeBuiltinUniforms();
+	love_PixelColor = effect(VaryingColor, MainTex, VaryingTexCoord.st, love_PixelCoord);
+}
+)";
+
+static const char pixel_main_custom[] = R"(
+varying LOVE_HIGHP_OR_MEDIUMP vec4 VaryingTexCoord;
+varying mediump vec4 VaryingColor;
+
+void effect();
+
+void main() {
+	love_initializeBuiltinUniforms();
+	effect();
+}
+)";
+
+struct StageInfo
+{
+	const char *name;
+	const char *header;
+	const char *functions;
+	const char *main;
+	const char *main_custom;
+};
+
+static const StageInfo stageInfo[] =
+{
+	{ "VERTEX", vertex_header, vertex_functions, vertex_main, vertex_main },
+	{ "PIXEL", pixel_header, pixel_functions, pixel_main, pixel_main_custom },
+};
+
+static_assert((sizeof(stageInfo) / sizeof(StageInfo)) == ShaderStage::STAGE_MAX_ENUM, "Stages array size must match ShaderStage enum.");
+
+struct Version
+{
+	std::string glsl;
+	std::string glsles;
+};
+
+// Indexed by Shader::Version
+static const Version versions[] =
+{
+	{ "#version 120", "#version 100" },
+	{ "#version 330 core", "#version 300 es" },
+	{ "#version 430 core", "#version 310 es" },
+};
+
+static Shader::Language getTargetLanguage(const std::string &src)
+{
+	std::regex r("^\\s*#pragma language (\\w+)");
+	std::smatch m;
+	std::string langstr = std::regex_search(src, m, r) && m.size() > 1 ? m[1] : std::string("glsl1");
+	Shader::Language lang = Shader::LANGUAGE_MAX_ENUM;
+	Shader::getConstant(langstr.c_str(), lang);
+	return lang;
+}
+
+static bool isVertexCode(const std::string &src)
+{
+	std::regex r("vec4\\s+position\\s*\\(");
+	std::smatch m;
+	return std::regex_search(src, m, r);
+}
+
+static bool isPixelCode(const std::string &src, bool &custompixel, bool &mrt)
+{
+	custompixel = false;
+	mrt = false;
+	std::smatch m;
+	if (std::regex_search(src, m, std::regex("vec4\\s+effect\\s*\\(")))
+		return true;
+
+	if (std::regex_search(src, m, std::regex("void\\s+effect\\s*\\(")))
+	{
+		custompixel = true;
+		if (src.find("love_RenderTargets") != std::string::npos || src.find("love_Canvases") != std::string::npos)
+			mrt = true;
+		return true;
+	}
+
+	return false;
+}
+
+} // glsl
+
 static_assert(sizeof(Shader::BuiltinUniformData) == sizeof(float) * 4 * 13, "Update the array in wrap_GraphicsShader.lua if this changes.");
 
 love::Type Shader::type("Shader", &Object::type);
@@ -41,6 +431,59 @@ love::Type Shader::type("Shader", &Object::type);
 Shader *Shader::current = nullptr;
 Shader *Shader::standardShaders[Shader::STANDARD_MAX_ENUM] = {nullptr};
 
+Shader::SourceInfo Shader::getSourceInfo(const std::string &src)
+{
+	SourceInfo info = {};
+	info.language = glsl::getTargetLanguage(src);
+	info.isStage[ShaderStage::STAGE_VERTEX] = glsl::isVertexCode(src);
+	info.isStage[ShaderStage::STAGE_PIXEL] = glsl::isPixelCode(src, info.customPixelFunction, info.usesMRT);
+	return info;
+}
+
+std::string Shader::createShaderStageCode(Graphics *gfx, ShaderStage::StageType stage, const std::string &code, const Shader::SourceInfo &info)
+{
+	if (info.language == Shader::LANGUAGE_MAX_ENUM)
+		throw love::Exception("Invalid shader language");
+
+	const auto &features = gfx->getCapabilities().features;
+
+	if (info.language == LANGUAGE_GLSL3 && !features[Graphics::FEATURE_GLSL3])
+		throw love::Exception("GLSL 3 shaders are not supported on this system.");
+
+	if (info.language == LANGUAGE_GLSL4 && !features[Graphics::FEATURE_GLSL4])
+		throw love::Exception("GLSL 4 shaders are not supported on this system.");
+
+	bool gles = gfx->getRenderer() == Graphics::RENDERER_OPENGLES;
+	bool glsl1on3 = info.language == LANGUAGE_GLSL1 && features[Graphics::FEATURE_GLSL3];
+
+	Language lang = info.language;
+	if (glsl1on3)
+		lang = LANGUAGE_GLSL3;
+
+	glsl::StageInfo stageinfo = glsl::stageInfo[stage];
+
+	std::stringstream ss;
+
+	ss << (gles ? glsl::versions[lang].glsles : glsl::versions[lang].glsl) << "\n";
+	ss << "#define " << stageinfo.name << " " << stageinfo.name << "\n";
+	if (glsl1on3)
+		ss << "#define LOVE_GLSL1_ON_GLSL3 1\n";
+	if (isGammaCorrect())
+		ss << "#define LOVE_GAMMA_CORRECT 1\n";
+	if (info.usesMRT)
+		ss << "#define LOVE_MULTI_RENDER_TARGETS 1";
+	ss << glsl::global_syntax;
+	ss << stageinfo.header;
+	ss << glsl::global_uniforms;
+	ss << glsl::global_functions;
+	ss << stageinfo.functions;
+	ss << (info.customPixelFunction ? stageinfo.main_custom : stageinfo.main);
+	ss << ((!gles && (lang == Shader::LANGUAGE_GLSL1 || glsl1on3)) ? "#line 0\n" : "#line 1\n");
+	ss << code;
+
+	return ss.str();
+}
+
 Shader::Shader(ShaderStage *vertex, ShaderStage *pixel)
 	: stages()
 {
@@ -157,47 +600,93 @@ void Shader::deinitialize()
 	glslang::FinalizeProcess();
 }
 
-bool Shader::getConstant(const char *in, Language &out)
+static const std::string defaultVertex = R"(
+vec4 position(mat4 clipSpaceFromLocal, vec4 localPosition)
 {
-	return languages.find(in, out);
+	return clipSpaceFromLocal * localPosition;
 }
+)";
 
-bool Shader::getConstant(Language in, const char *&out)
+static const std::string defaultStandardPixel = R"(
+vec4 effect(vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord)
 {
-	return languages.find(in, out);
+	return Texel(tex, texcoord) * vcolor;
 }
+)";
 
-bool Shader::getConstant(const char *in, BuiltinUniform &out)
+static const std::string defaultVideoPixel = R"(
+void effect()
 {
-	return builtinNames.find(in, out);
+	love_PixelColor = VideoTexel(VaryingTexCoord.xy) * VaryingColor;
 }
+)";
 
-bool Shader::getConstant(BuiltinUniform in, const char *&out)
+static const std::string defaultArrayPixel = R"(
+uniform ArrayImage MainTex;
+void effect()
 {
-	return builtinNames.find(in, out);
+	love_PixelColor = Texel(MainTex, VaryingTexCoord.xyz) * VaryingColor;
+}
+)";
+
+const std::string &Shader::getDefaultCode(StandardShader shader, ShaderStage::StageType stage)
+{
+	if (stage == ShaderStage::STAGE_VERTEX)
+		return defaultVertex;
+
+	static std::string nocode = "";
+
+	switch (shader)
+	{
+		case STANDARD_DEFAULT: return defaultStandardPixel;
+		case STANDARD_VIDEO: return defaultVideoPixel;
+		case STANDARD_ARRAY: return defaultArrayPixel;
+		case STANDARD_MAX_ENUM: return nocode;
+	}
+
+	return nocode;
 }
 
-StringMap<Shader::Language, Shader::LANGUAGE_MAX_ENUM>::Entry Shader::languageEntries[] =
+static StringMap<Shader::Language, Shader::LANGUAGE_MAX_ENUM>::Entry languageEntries[] =
 {
-	{ "glsl1", LANGUAGE_GLSL1 },
-	{ "essl1", LANGUAGE_ESSL1 },
-	{ "glsl3", LANGUAGE_GLSL3 },
-	{ "essl3", LANGUAGE_ESSL3 },
+	{ "glsl1", Shader::LANGUAGE_GLSL1 },
+	{ "glsl3", Shader::LANGUAGE_GLSL3 },
+	{ "glsl4", Shader::LANGUAGE_GLSL4 },
 };
 
-StringMap<Shader::Language, Shader::LANGUAGE_MAX_ENUM> Shader::languages(Shader::languageEntries, sizeof(Shader::languageEntries));
+static StringMap<Shader::Language, Shader::LANGUAGE_MAX_ENUM> languages(languageEntries, sizeof(languageEntries));
 
-StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM>::Entry Shader::builtinNameEntries[] =
+static StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM>::Entry builtinNameEntries[] =
 {
-	{ "MainTex",              BUILTIN_TEXTURE_MAIN      },
-	{ "love_VideoYChannel",   BUILTIN_TEXTURE_VIDEO_Y   },
-	{ "love_VideoCbChannel",  BUILTIN_TEXTURE_VIDEO_CB  },
-	{ "love_VideoCrChannel",  BUILTIN_TEXTURE_VIDEO_CR  },
-	{ "love_UniformsPerDraw", BUILTIN_UNIFORMS_PER_DRAW },
-	{ "love_PointSize",       BUILTIN_POINT_SIZE        },
+	{ "MainTex",              Shader::BUILTIN_TEXTURE_MAIN      },
+	{ "love_VideoYChannel",   Shader::BUILTIN_TEXTURE_VIDEO_Y   },
+	{ "love_VideoCbChannel",  Shader::BUILTIN_TEXTURE_VIDEO_CB  },
+	{ "love_VideoCrChannel",  Shader::BUILTIN_TEXTURE_VIDEO_CR  },
+	{ "love_UniformsPerDraw", Shader::BUILTIN_UNIFORMS_PER_DRAW },
+	{ "love_PointSize",       Shader::BUILTIN_POINT_SIZE        },
 };
 
-StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM> Shader::builtinNames(Shader::builtinNameEntries, sizeof(Shader::builtinNameEntries));
+static StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM> builtinNames(builtinNameEntries, sizeof(builtinNameEntries));
+
+bool Shader::getConstant(const char *in, Language &out)
+{
+	return languages.find(in, out);
+}
+
+bool Shader::getConstant(Language in, const char *&out)
+{
+	return languages.find(in, out);
+}
+
+bool Shader::getConstant(const char *in, BuiltinUniform &out)
+{
+	return builtinNames.find(in, out);
+}
+
+bool Shader::getConstant(BuiltinUniform in, const char *&out)
+{
+	return builtinNames.find(in, out);
+}
 
 } // graphics
 } // love

+ 23 - 17
src/modules/graphics/Shader.h

@@ -33,17 +33,13 @@
 #include <vector>
 #include <stddef.h>
 
-namespace glslang
-{
-class TShader;
-}
-
 namespace love
 {
 namespace graphics
 {
 
 class Graphics;
+class Buffer;
 
 // A GLSL shader
 class Shader : public Object, public Resource
@@ -55,9 +51,8 @@ public:
 	enum Language
 	{
 		LANGUAGE_GLSL1,
-		LANGUAGE_ESSL1,
 		LANGUAGE_GLSL3,
-		LANGUAGE_ESSL3,
+		LANGUAGE_GLSL4,
 		LANGUAGE_MAX_ENUM
 	};
 
@@ -82,6 +77,7 @@ public:
 		UNIFORM_UINT,
 		UNIFORM_BOOL,
 		UNIFORM_SAMPLER,
+		UNIFORM_TEXELBUFFER,
 		UNIFORM_UNKNOWN,
 		UNIFORM_MAX_ENUM
 	};
@@ -94,6 +90,14 @@ public:
 		STANDARD_MAX_ENUM
 	};
 
+	struct SourceInfo
+	{
+		Language language;
+		bool isStage[ShaderStage::STAGE_MAX_ENUM];
+		bool customPixelFunction;
+		bool usesMRT;
+	};
+
 	struct MatrixSize
 	{
 		short columns;
@@ -113,6 +117,7 @@ public:
 
 		UniformType baseType;
 		TextureType textureType;
+		DataBaseType texelBufferType;
 		bool isDepthSampler;
 		std::string name;
 
@@ -126,7 +131,11 @@ public:
 
 		size_t dataSize;
 
-		Texture **textures;
+		union
+		{
+			Texture **textures;
+			Buffer **buffers;
+		};
 	};
 
 	// The members in here must respect uniform buffer alignment/padding rules.
@@ -176,6 +185,7 @@ public:
 	virtual void updateUniform(const UniformInfo *info, int count) = 0;
 
 	virtual void sendTextures(const UniformInfo *info, Texture **textures, int count) = 0;
+	virtual void sendBuffers(const UniformInfo *info, Buffer **buffers, int count) = 0;
 
 	/**
 	 * Gets whether a uniform with the specified name exists and is actively
@@ -192,11 +202,16 @@ public:
 	void checkMainTextureType(TextureType textype, bool isDepthSampler) const;
 	void checkMainTexture(Texture *texture) const;
 
+	static SourceInfo getSourceInfo(const std::string &src);
+	static std::string createShaderStageCode(Graphics *gfx, ShaderStage::StageType stage, const std::string &code, const SourceInfo &info);
+
 	static bool validate(ShaderStage *vertex, ShaderStage *pixel, std::string &err);
 
 	static bool initialize();
 	static void deinitialize();
 
+	static const std::string &getDefaultCode(StandardShader shader, ShaderStage::StageType stage);
+
 	static bool getConstant(const char *in, Language &out);
 	static bool getConstant(Language in, const char *&out);
 
@@ -207,15 +222,6 @@ protected:
 
 	StrongRef<ShaderStage> stages[ShaderStage::STAGE_MAX_ENUM];
 
-private:
-
-	static StringMap<Language, LANGUAGE_MAX_ENUM>::Entry languageEntries[];
-	static StringMap<Language, LANGUAGE_MAX_ENUM> languages;
-	
-	// Names for the built-in uniform variables.
-	static StringMap<BuiltinUniform, BUILTIN_MAX_ENUM>::Entry builtinNameEntries[];
-	static StringMap<BuiltinUniform, BUILTIN_MAX_ENUM> builtinNames;
-
 }; // Shader
 
 } // graphics

+ 7 - 0
src/modules/graphics/ShaderStage.cpp

@@ -205,6 +205,13 @@ bool ShaderStage::getConstant(StageType in, const char *&out)
 	return stageNames.find(in, out);
 }
 
+const char *ShaderStage::getConstant(StageType in)
+{
+	const char *name = nullptr;
+	getConstant(in, name);
+	return name;
+}
+
 StringMap<ShaderStage::StageType, ShaderStage::STAGE_MAX_ENUM>::Entry ShaderStage::stageNameEntries[] =
 {
 	{ "vertex", STAGE_VERTEX },

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

@@ -43,6 +43,7 @@ class ShaderStage : public love::Object
 {
 public:
 
+	// Order is used for stages array in ShaderStage.cpp
 	enum StageType
 	{
 		STAGE_VERTEX,
@@ -62,6 +63,7 @@ public:
 
 	static bool getConstant(const char *in, StageType &out);
 	static bool getConstant(StageType in, const char *&out);
+	static const char *getConstant(StageType in);
 
 protected:
 

+ 30 - 30
src/modules/graphics/SpriteBatch.cpp

@@ -40,7 +40,7 @@ namespace graphics
 
 love::Type SpriteBatch::type("SpriteBatch", &Drawable::type);
 
-SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usage usage)
+SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferUsage usage)
 	: texture(texture)
 	, size(size)
 	, next(0)
@@ -57,14 +57,16 @@ SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usag
 		throw love::Exception("A texture must be used when creating a SpriteBatch.");
 
 	if (texture->getTextureType() == TEXTURE_2D_ARRAY)
-		vertex_format = vertex::CommonFormat::XYf_STPf_RGBAub;
+		vertex_format = CommonFormat::XYf_STPf_RGBAub;
 	else
-		vertex_format = vertex::CommonFormat::XYf_STf_RGBAub;
+		vertex_format = CommonFormat::XYf_STf_RGBAub;
 
-	vertex_stride = vertex::getFormatStride(vertex_format);
+	vertex_stride = getFormatStride(vertex_format);
 
 	size_t vertex_size = vertex_stride * 4 * size;
-	array_buf = gfx->newBuffer(vertex_size, nullptr, BUFFER_VERTEX, usage, Buffer::MAP_EXPLICIT_RANGE_MODIFY);
+	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY, usage);
+	auto decl = Buffer::getCommonFormatDeclaration(vertex_format);
+	array_buf = gfx->newBuffer(settings, decl, nullptr, vertex_size, 0);
 }
 
 SpriteBatch::~SpriteBatch()
@@ -79,8 +81,6 @@ int SpriteBatch::add(const Matrix4 &m, int index /*= -1*/)
 
 int SpriteBatch::add(Quad *quad, const Matrix4 &m, int index /*= -1*/)
 {
-	using namespace vertex;
-
 	if (vertex_format == CommonFormat::XYf_STPf_RGBAub)
 		return addLayer(quad->getLayer(), quad, m, index);
 
@@ -122,8 +122,6 @@ int SpriteBatch::addLayer(int layer, const Matrix4 &m, int index)
 
 int SpriteBatch::addLayer(int layer, Quad *quad, const Matrix4 &m, int index)
 {
-	using namespace vertex;
-
 	if (vertex_format != CommonFormat::XYf_STPf_RGBAub)
 		throw love::Exception("addLayer can only be called on a SpriteBatch that uses an Array Texture!");
 
@@ -222,7 +220,9 @@ void SpriteBatch::setBufferSize(int newsize)
 	try
 	{
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-		new_array_buf = gfx->newBuffer(vertex_size, nullptr, array_buf->getType(), array_buf->getUsage(), array_buf->getMapFlags());
+		Buffer::Settings settings(array_buf->getTypeFlags(), array_buf->getMapFlags(), array_buf->getUsage());
+		auto decl = Buffer::getCommonFormatDeclaration(vertex_format);
+		new_array_buf = gfx->newBuffer(settings, decl, nullptr, vertex_size, 0);
 
 		// Copy as much of the old data into the new GLBuffer as can fit.
 		size_t copy_size = vertex_stride * 4 * new_next;
@@ -248,24 +248,27 @@ int SpriteBatch::getBufferSize() const
 	return size;
 }
 
-void SpriteBatch::attachAttribute(const std::string &name, Mesh *mesh)
+void SpriteBatch::attachAttribute(const std::string &name, Buffer *buffer)
 {
+	if ((buffer->getTypeFlags() & Buffer::TYPEFLAG_VERTEX) == 0)
+		throw love::Exception("GraphicsBuffer must be created with vertex buffer support to be used as a SpriteBatch vertex attribute.");
+
 	AttachedAttribute oldattrib = {};
 	AttachedAttribute newattrib = {};
 
-	if (mesh->getVertexCount() < (size_t) next * 4)
-		throw love::Exception("Mesh has too few vertices to be attached to this SpriteBatch (at least %d vertices are required)", next*4);
+	if (buffer->getArrayLength() < (size_t) next * 4)
+		throw love::Exception("Buffer has too few vertices to be attached to this SpriteBatch (at least %d vertices are required)", next*4);
 
 	auto it = attached_attributes.find(name);
 	if (it != attached_attributes.end())
 		oldattrib = it->second;
 
-	newattrib.index = mesh->getAttributeIndex(name);
+	newattrib.index = buffer->getDataMemberIndex(name);
 
 	if (newattrib.index < 0)
-		throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", name.c_str());
+		throw love::Exception("The specified Buffer does not have a vertex attribute named '%s'", name.c_str());
 
-	newattrib.mesh = mesh;
+	newattrib.buffer = buffer;
 
 	attached_attributes[name] = newattrib;
 }
@@ -296,8 +299,6 @@ bool SpriteBatch::getDrawRange(int &start, int &count) const
 
 void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 {
-	using namespace vertex;
-
 	if (next == 0)
 		return;
 
@@ -321,7 +322,7 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 	// Make sure the buffer isn't mapped when we draw (sends data to GPU if needed.)
 	array_buf->unmap();
 
-	Attributes attributes;
+	VertexAttributes attributes;
 	BufferBindings buffers;
 
 	{
@@ -333,19 +334,19 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 
 	for (const auto &it : attached_attributes)
 	{
-		Mesh *mesh = it.second.mesh.get();
+		Buffer *buffer = it.second.buffer.get();
 
 		// We have to do this check here as wll because setBufferSize can be
 		// called after attachAttribute.
-		if (mesh->getVertexCount() < (size_t) next * 4)
-			throw love::Exception("Mesh with attribute '%s' attached to this SpriteBatch has too few vertices", it.first.c_str());
+		if (buffer->getArrayLength() < (size_t) next * 4)
+			throw love::Exception("Buffer with attribute '%s' attached to this SpriteBatch has too few vertices", it.first.c_str());
 
 		int attributeindex = -1;
 
 		// If the attribute is one of the LOVE-defined ones, use the constant
 		// attribute index for it, otherwise query the index from the shader.
 		BuiltinVertexAttribute builtinattrib;
-		if (vertex::getConstant(it.first.c_str(), builtinattrib))
+		if (getConstant(it.first.c_str(), builtinattrib))
 			attributeindex = (int) builtinattrib;
 		else if (Shader::current)
 			attributeindex = Shader::current->getVertexAttributeIndex(it.first);
@@ -353,19 +354,18 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 		if (attributeindex >= 0)
 		{
 			// Make sure the buffer isn't mapped (sends data to GPU if needed.)
-			mesh->vertexBuffer->unmap();
+			buffer->unmap();
 
-			const auto &formats = mesh->getVertexFormat();
-			const auto &format = formats[it.second.index];
+			const auto &member = buffer->getDataMember(it.second.index);
 
-			uint16 offset = (uint16) mesh->getAttributeOffset(it.second.index);
-			uint16 stride = (uint16) mesh->getVertexStride();
+			uint16 offset = (uint16) buffer->getMemberOffset(it.second.index);
+			uint16 stride = (uint16) buffer->getArrayStride();
 
-			attributes.set(attributeindex, format.type, (uint8) format.components, offset, activebuffers);
+			attributes.set(attributeindex, member.decl.format, offset, activebuffers);
 			attributes.setBufferLayout(activebuffers, stride);
 
 			// TODO: We should reuse buffer bindings with the same buffer+stride+step.
-			buffers.set(activebuffers, mesh->vertexBuffer, 0);
+			buffers.set(activebuffers, buffer, 0);
 			activebuffers++;
 		}
 	}

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

@@ -51,7 +51,7 @@ public:
 
 	static love::Type type;
 
-	SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usage usage);
+	SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferUsage usage);
 	virtual ~SpriteBatch();
 
 	int add(const Matrix4 &m, int index = -1);
@@ -93,7 +93,7 @@ public:
 	 * Attaches a specific vertex attribute from a Mesh to this SpriteBatch.
 	 * The vertex attribute will be used when drawing the SpriteBatch.
 	 **/
-	void attachAttribute(const std::string &name, Mesh *mesh);
+	void attachAttribute(const std::string &name, Buffer *buffer);
 
 	void setDrawRange(int start, int count);
 	void setDrawRange();
@@ -106,7 +106,7 @@ private:
 
 	struct AttachedAttribute
 	{
-		StrongRef<Mesh> mesh;
+		StrongRef<Buffer> buffer;
 		int index;
 	};
 
@@ -128,7 +128,7 @@ private:
 	Color32 color;
 	Colorf colorf;
 
-	vertex::CommonFormat vertex_format;
+	CommonFormat vertex_format;
 	size_t vertex_stride;
 	
 	love::graphics::Buffer *array_buf;

+ 6 - 3
src/modules/graphics/Text.cpp

@@ -42,7 +42,8 @@ Text::Text(Font *font, const std::vector<Font::ColoredString> &text)
 
 Text::~Text()
 {
-	delete vertex_buffer;
+	if (vertex_buffer)
+		vertex_buffer->release();
 }
 
 void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t vertoffset)
@@ -60,12 +61,14 @@ void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t
 			newsize = std::max(size_t(vertex_buffer->getSize() * 1.5), newsize);
 
 		auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
-		Buffer *new_buffer = gfx->newBuffer(newsize, nullptr, BUFFER_VERTEX, vertex::USAGE_DYNAMIC, 0);
+		Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, 0, BUFFERUSAGE_DYNAMIC);
+		auto decl = Buffer::getCommonFormatDeclaration(Font::vertexFormat);
+		Buffer *new_buffer = gfx->newBuffer(settings, decl, nullptr, newsize, 0);
 
 		if (vertex_buffer != nullptr)
 			vertex_buffer->copyTo(0, vertex_buffer->getSize(), new_buffer, 0);
 
-		delete vertex_buffer;
+		vertex_buffer->release();
 		vertex_buffer = new_buffer;
 
 		vertexBuffers.set(0, vertex_buffer, 0);

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

@@ -85,8 +85,8 @@ private:
 
 	StrongRef<Font> font;
 
-	vertex::Attributes vertexAttributes;
-	vertex::BufferBindings vertexBuffers;
+	VertexAttributes vertexAttributes;
+	BufferBindings vertexBuffers;
 
 	Buffer *vertex_buffer;
 

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

@@ -192,6 +192,8 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices)
 		love::image::ImageDataBase *slice = slices->get(0, 0);
 
 		format = slice->getFormat();
+		if (sRGB)
+			format = getSRGBPixelFormat(format);
 
 		pixelWidth = slice->getWidth();
 		pixelHeight = slice->getHeight();
@@ -305,8 +307,6 @@ void Texture::draw(Graphics *gfx, const Matrix4 &m)
 
 void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform)
 {
-	using namespace vertex;
-
 	if (!readable)
 		throw love::Exception("Textures with non-readable formats cannot be drawn.");
 
@@ -323,9 +323,9 @@ void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform)
 	bool is2D = tm.isAffine2DTransform();
 
 	Graphics::BatchedDrawCommand cmd;
-	cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
+	cmd.formats[0] = getSinglePositionFormat(is2D);
 	cmd.formats[1] = CommonFormat::STf_RGBAub;
-	cmd.indexMode = TriangleIndexMode::QUADS;
+	cmd.indexMode = TRIANGLEINDEX_QUADS;
 	cmd.vertexCount = 4;
 	cmd.texture = this;
 
@@ -339,7 +339,7 @@ void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform)
 		t.transformXY0((Vector3 *) data.stream[0], q->getVertexPositions(), 4);
 
 	const Vector2 *texcoords = q->getVertexTexCoords();
-	vertex::STf_RGBAub *vertexdata = (vertex::STf_RGBAub *) data.stream[1];
+	STf_RGBAub *vertexdata = (STf_RGBAub *) data.stream[1];
 
 	Color32 c = toColor32(gfx->getColor());
 
@@ -358,8 +358,6 @@ void Texture::drawLayer(Graphics *gfx, int layer, const Matrix4 &m)
 
 void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m)
 {
-	using namespace vertex;
-
 	if (!readable)
 		throw love::Exception("Textures with non-readable formats cannot be drawn.");
 
@@ -380,9 +378,9 @@ void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m)
 	Matrix4 t(tm, m);
 
 	Graphics::BatchedDrawCommand cmd;
-	cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
+	cmd.formats[0] = getSinglePositionFormat(is2D);
 	cmd.formats[1] = CommonFormat::STPf_RGBAub;
-	cmd.indexMode = TriangleIndexMode::QUADS;
+	cmd.indexMode = TRIANGLEINDEX_QUADS;
 	cmd.vertexCount = 4;
 	cmd.texture = this;
 	cmd.standardShaderType = Shader::STANDARD_ARRAY;
@@ -395,7 +393,7 @@ void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m)
 		t.transformXY0((Vector3 *) data.stream[0], q->getVertexPositions(), 4);
 
 	const Vector2 *texcoords = q->getVertexTexCoords();
-	vertex::STPf_RGBAub *vertexdata = (vertex::STPf_RGBAub *) data.stream[1];
+	STPf_RGBAub *vertexdata = (STPf_RGBAub *) data.stream[1];
 
 	for (int i = 0; i < 4; i++)
 	{

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

@@ -121,9 +121,9 @@ void Video::draw(Graphics *gfx, const Matrix4 &m)
 	Matrix4 t(tm, m);
 
 	Graphics::BatchedDrawCommand cmd;
-	cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
-	cmd.formats[1] = vertex::CommonFormat::STf_RGBAub;
-	cmd.indexMode = vertex::TriangleIndexMode::QUADS;
+	cmd.formats[0] = getSinglePositionFormat(is2D);
+	cmd.formats[1] = CommonFormat::STf_RGBAub;
+	cmd.indexMode = TRIANGLEINDEX_QUADS;
 	cmd.vertexCount = 4;
 	cmd.standardShaderType = Shader::STANDARD_VIDEO;
 
@@ -134,7 +134,7 @@ void Video::draw(Graphics *gfx, const Matrix4 &m)
 	else
 		t.transformXY0((Vector3 *) data.stream[0], vertices, 4);
 
-	vertex::STf_RGBAub *verts = (vertex::STf_RGBAub *) data.stream[1];
+	STf_RGBAub *verts = (STf_RGBAub *) data.stream[1];
 
 	Color32 c = toColor32(gfx->getColor());
 

+ 142 - 90
src/modules/graphics/opengl/Buffer.cpp

@@ -35,18 +35,54 @@ namespace graphics
 namespace opengl
 {
 
-Buffer::Buffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags)
-	: love::graphics::Buffer(size, type, usage, mapflags)
-	, vbo(0)
-	, memory_map(nullptr)
-	, modified_offset(0)
-	, modified_size(0)
+static GLenum getGLFormat(DataFormat format)
 {
-	target = OpenGL::getGLBufferType(type);
+	switch (format)
+	{
+		case DATAFORMAT_FLOAT: return GL_R32F;
+		case DATAFORMAT_FLOAT_VEC2: return GL_RG32F;
+		case DATAFORMAT_FLOAT_VEC3: return GL_RGB32F;
+		case DATAFORMAT_FLOAT_VEC4: return GL_RGBA32F;
+		case DATAFORMAT_INT32: return GL_R32I;
+		case DATAFORMAT_INT32_VEC2: return GL_RG32I;
+		case DATAFORMAT_INT32_VEC3: return GL_RGB32I;
+		case DATAFORMAT_INT32_VEC4: return GL_RGBA32I;
+		case DATAFORMAT_UINT32: return GL_R32UI;
+		case DATAFORMAT_UINT32_VEC2: return GL_RG32UI;
+		case DATAFORMAT_UINT32_VEC3: return GL_RGB32UI;
+		case DATAFORMAT_UINT32_VEC4: return GL_RGBA32UI;
+		case DATAFORMAT_UNORM8_VEC4: return GL_RGBA8;
+		case DATAFORMAT_INT8_VEC4: return GL_RGBA8I;
+		case DATAFORMAT_UINT8_VEC4: return GL_RGBA8UI;
+		case DATAFORMAT_UNORM16_VEC2: return GL_RG16;
+		case DATAFORMAT_UNORM16_VEC4: return GL_RGBA16;
+		case DATAFORMAT_INT16_VEC2: return GL_RG16I;
+		case DATAFORMAT_INT16_VEC4: return GL_RGBA16I;
+		case DATAFORMAT_UINT16: return GL_R16UI;
+		case DATAFORMAT_UINT16_VEC2: return GL_RG16UI;
+		case DATAFORMAT_UINT16_VEC4: return GL_RGBA16UI;
+		default: return GL_ZERO;
+	}
+}
+
+Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const std::vector<DataDeclaration> &format, const void *data, size_t size, size_t arraylength)
+	: love::graphics::Buffer(gfx, settings, format, size, arraylength)
+{
+	size = getSize();
+	arraylength = getArrayLength();
+
+	if (typeFlags & TYPEFLAG_TEXEL)
+		mapType = BUFFERTYPE_TEXEL;
+	else if (typeFlags & TYPEFLAG_VERTEX)
+		mapType = BUFFERTYPE_VERTEX;
+	else if (typeFlags & TYPEFLAG_INDEX)
+		mapType = BUFFERTYPE_INDEX;
+
+	target = OpenGL::getGLBufferType(mapType);
 
 	try
 	{
-		memory_map = new char[size];
+		memoryMap = new char[size];
 	}
 	catch (std::bad_alloc &)
 	{
@@ -54,34 +90,78 @@ Buffer::Buffer(size_t size, const void *data, BufferType type, vertex::Usage usa
 	}
 
 	if (data != nullptr)
-		memcpy(memory_map, data, size);
+		memcpy(memoryMap, data, size);
 
 	if (!load(data != nullptr))
 	{
-		delete[] memory_map;
-		throw love::Exception("Could not load vertex buffer (out of VRAM?)");
+		unloadVolatile();
+		delete[] memoryMap;
+		throw love::Exception("Could not create buffer (out of VRAM?)");
 	}
 }
 
 Buffer::~Buffer()
 {
-	if (vbo != 0)
-		unload();
+	unloadVolatile();
+	delete[] memoryMap;
+}
+
+bool Buffer::loadVolatile()
+{
+	if (buffer != 0)
+		return true;
 
-	delete[] memory_map;
+	return load(true);
+}
+
+void Buffer::unloadVolatile()
+{
+	mapped = false;
+	if (buffer != 0)
+		gl.deleteBuffer(buffer);
+	buffer = 0;
+	if (texture != 0)
+		gl.deleteTexture(texture);
+	texture = 0;
+}
+
+bool Buffer::load(bool restore)
+{
+	while (glGetError() != GL_NO_ERROR)
+		/* Clear the error buffer. */;
+
+	glGenBuffers(1, &buffer);
+	gl.bindBuffer(mapType, buffer);
+
+	// Copy the old buffer only if 'restore' was requested.
+	const GLvoid *src = restore ? memoryMap : nullptr;
+
+	// Note that if 'src' is '0', no data will be copied.
+	glBufferData(target, (GLsizeiptr) getSize(), src, OpenGL::getGLBufferUsage(getUsage()));
+
+	if (getTypeFlags() & TYPEFLAG_TEXEL)
+	{
+		glGenTextures(1, &texture);
+		gl.bindBufferTextureToUnit(texture, 0, false, true);
+
+		glTexBuffer(target, getGLFormat(getDataMember(0).decl.format), buffer);
+	}
+
+	return (glGetError() == GL_NO_ERROR);
 }
 
 void *Buffer::map()
 {
-	if (is_mapped)
-		return memory_map;
+	if (mapped)
+		return memoryMap;
 
-	is_mapped = true;
+	mapped = true;
 
-	modified_offset = 0;
-	modified_size = 0;
+	modifiedOffset = 0;
+	modifiedSize = 0;
+	isMappedDataModified = false;
 
-	return memory_map;
+	return memoryMap;
 }
 
 void Buffer::unmapStatic(size_t offset, size_t size)
@@ -90,8 +170,8 @@ void Buffer::unmapStatic(size_t offset, size_t size)
 		return;
 
 	// Upload the mapped data to the buffer.
-	gl.bindBuffer(type, vbo);
-	glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, memory_map + offset);
+	gl.bindBuffer(mapType, buffer);
+	glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, memoryMap + offset);
 }
 
 void Buffer::unmapStream()
@@ -100,133 +180,105 @@ void Buffer::unmapStream()
 
 	// "orphan" current buffer to avoid implicit synchronisation on the GPU:
 	// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
-	gl.bindBuffer(type, vbo);
+	gl.bindBuffer(mapType, buffer);
 	glBufferData(target, (GLsizeiptr) getSize(), nullptr, glusage);
 
 #if LOVE_WINDOWS
 	// TODO: Verify that this codepath is a useful optimization.
 	if (gl.getVendor() == OpenGL::VENDOR_INTEL)
-		glBufferData(target, (GLsizeiptr) getSize(), memory_map, glusage);
+		glBufferData(target, (GLsizeiptr) getSize(), memoryMap, glusage);
 	else
 #endif
-		glBufferSubData(target, 0, (GLsizeiptr) getSize(), memory_map);
+		glBufferSubData(target, 0, (GLsizeiptr) getSize(), memoryMap);
 }
 
 void Buffer::unmap()
 {
-	if (!is_mapped)
+	if (!mapped)
 		return;
 
-	if ((map_flags & MAP_EXPLICIT_RANGE_MODIFY) != 0)
+	mapped = false;
+
+	if ((mapFlags & MAP_EXPLICIT_RANGE_MODIFY) != 0)
 	{
-		modified_offset = std::min(modified_offset, getSize() - 1);
-		modified_size = std::min(modified_size, getSize() - modified_offset);
+		if (!isMappedDataModified)
+			return;
+
+		modifiedOffset = std::min(modifiedOffset, getSize() - 1);
+		modifiedSize = std::min(modifiedSize, getSize() - modifiedOffset);
 	}
 	else
 	{
-		modified_offset = 0;
-		modified_size = getSize();
+		modifiedOffset = 0;
+		modifiedSize = getSize();
 	}
 
-	if (modified_size > 0)
+	if (modifiedSize > 0)
 	{
 		switch (getUsage())
 		{
-		case vertex::USAGE_STATIC:
-			unmapStatic(modified_offset, modified_size);
+		case BUFFERUSAGE_STATIC:
+			unmapStatic(modifiedOffset, modifiedSize);
 			break;
-		case vertex::USAGE_STREAM:
+		case BUFFERUSAGE_STREAM:
 			unmapStream();
 			break;
-		case vertex::USAGE_DYNAMIC:
+		case BUFFERUSAGE_DYNAMIC:
 		default:
 			// It's probably more efficient to treat it like a streaming buffer if
 			// at least a third of its contents have been modified during the map().
-			if (modified_size >= getSize() / 3)
+			if (modifiedSize >= getSize() / 3)
 				unmapStream();
 			else
-				unmapStatic(modified_offset, modified_size);
+				unmapStatic(modifiedOffset, modifiedSize);
 			break;
 		}
 	}
 
-	modified_offset = 0;
-	modified_size = 0;
-
-	is_mapped = false;
+	modifiedOffset = 0;
+	modifiedSize = 0;
 }
 
 void Buffer::setMappedRangeModified(size_t offset, size_t modifiedsize)
 {
-	if (!is_mapped || !(map_flags & MAP_EXPLICIT_RANGE_MODIFY))
+	if (!mapped || !(mapFlags & MAP_EXPLICIT_RANGE_MODIFY))
 		return;
 
+	if (!isMappedDataModified)
+	{
+		modifiedOffset = offset;
+		modifiedSize = modifiedsize;
+		isMappedDataModified = true;
+		return;
+	}
+
 	// We're being conservative right now by internally marking the whole range
 	// from the start of section a to the end of section b as modified if both
 	// a and b are marked as modified.
 
-	size_t old_range_end = modified_offset + modified_size;
-	modified_offset = std::min(modified_offset, offset);
+	size_t oldrangeend = modifiedOffset + modifiedSize;
+	modifiedOffset = std::min(modifiedOffset, offset);
 
-	size_t new_range_end = std::max(offset + modifiedsize, old_range_end);
-	modified_size = new_range_end - modified_offset;
+	size_t newrangeend = std::max(offset + modifiedsize, oldrangeend);
+	modifiedSize = newrangeend - modifiedOffset;
 }
 
 void Buffer::fill(size_t offset, size_t size, const void *data)
 {
-	memcpy(memory_map + offset, data, size);
+	memcpy(memoryMap + offset, data, size);
 
-	if (is_mapped)
+	if (mapped)
 		setMappedRangeModified(offset, size);
 	else
 	{
-		gl.bindBuffer(type, vbo);
+		gl.bindBuffer(mapType, buffer);
 		glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, data);
 	}
 }
 
-ptrdiff_t Buffer::getHandle() const
-{
-	return vbo;
-}
-
 void Buffer::copyTo(size_t offset, size_t size, love::graphics::Buffer *other, size_t otheroffset)
 {
-	other->fill(otheroffset, size, memory_map + offset);
-}
-
-bool Buffer::loadVolatile()
-{
-	return load(true);
-}
-
-void Buffer::unloadVolatile()
-{
-	unload();
-}
-
-bool Buffer::load(bool restore)
-{
-	glGenBuffers(1, &vbo);
-	gl.bindBuffer(type, vbo);
-
-	while (glGetError() != GL_NO_ERROR)
-		/* Clear the error buffer. */;
-
-	// Copy the old buffer only if 'restore' was requested.
-	const GLvoid *src = restore ? memory_map : nullptr;
-
-	// Note that if 'src' is '0', no data will be copied.
-	glBufferData(target, (GLsizeiptr) getSize(), src, OpenGL::getGLBufferUsage(getUsage()));
-
-	return (glGetError() == GL_NO_ERROR);
-}
-
-void Buffer::unload()
-{
-	is_mapped = false;
-	gl.deleteBuffer(vbo);
-	vbo = 0;
+	other->fill(otheroffset, size, memoryMap + offset);
 }
 
 } // opengl

+ 22 - 13
src/modules/graphics/opengl/Buffer.h

@@ -32,6 +32,9 @@ namespace love
 {
 namespace graphics
 {
+
+class Graphics;
+
 namespace opengl
 {
 
@@ -39,39 +42,45 @@ class Buffer final : public love::graphics::Buffer, public Volatile
 {
 public:
 
-	Buffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags);
+	Buffer(love::graphics::Graphics *gfx, const Settings &settings, const std::vector<DataDeclaration> &format, const void *data, size_t size, size_t arraylength);
 	virtual ~Buffer();
 
+	// Implements Volatile.
+	bool loadVolatile() override;
+	void unloadVolatile() override;
+
 	void *map() override;
 	void unmap() override;
 	void setMappedRangeModified(size_t offset, size_t size) override;
 	void fill(size_t offset, size_t size, const void *data) override;
-	ptrdiff_t getHandle() const override;
 
-	void copyTo(size_t offset, size_t size, love::graphics::Buffer *other, size_t otheroffset) override;
+	ptrdiff_t getHandle() const override { return buffer; };
+	ptrdiff_t getTexelBufferHandle() const override { return texture; };
 
-	// Implements Volatile.
-	bool loadVolatile() override;
-	void unloadVolatile() override;
+	void copyTo(size_t offset, size_t size, love::graphics::Buffer *other, size_t otheroffset) override;
 
 private:
 
 	bool load(bool restore);
-	void unload();
 
 	void unmapStatic(size_t offset, size_t size);
 	void unmapStream();
 
-	GLenum target;
+	BufferType mapType = BUFFERTYPE_VERTEX;
+	GLenum target = 0;
+
+	// The buffer object identifier. Assigned by OpenGL.
+	GLuint buffer = 0;
 
-	// The VBO identifier. Assigned by OpenGL.
-	GLuint vbo;
+	// Used for Texel Buffer types.
+	GLuint texture = 0;
 
 	// A pointer to mapped memory.
-	char *memory_map;
+	char *memoryMap = nullptr;
 
-	size_t modified_offset;
-	size_t modified_size;
+	size_t modifiedOffset = 0;
+	size_t modifiedSize = 0;
+	bool isMappedDataModified = false;
 
 }; // Buffer
 

+ 49 - 36
src/modules/graphics/opengl/Graphics.cpp

@@ -107,6 +107,7 @@ love::graphics::Graphics *createInstance()
 Graphics::Graphics()
 	: windowHasStencil(false)
 	, mainVAO(0)
+	, defaultBuffers()
 	, supportedFormats()
 {
 	gl = OpenGL();
@@ -162,9 +163,9 @@ love::graphics::Shader *Graphics::newShaderInternal(love::graphics::ShaderStage
 	return new Shader(vertex, pixel);
 }
 
-love::graphics::Buffer *Graphics::newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags)
+love::graphics::Buffer *Graphics::newBuffer(const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &format, const void *data, size_t size, size_t arraylength)
 {
-	return new Buffer(size, data, type, usage, mapflags);
+	return new Buffer(this, settings, format, data, size, arraylength);
 }
 
 void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelheight)
@@ -255,11 +256,32 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
 	{
 		// Initial sizes that should be good enough for most cases. It will
 		// resize to fit if needed, later.
-		batchedDrawState.vb[0] = CreateStreamBuffer(BUFFER_VERTEX, 1024 * 1024 * 1);
-		batchedDrawState.vb[1] = CreateStreamBuffer(BUFFER_VERTEX, 256  * 1024 * 1);
-		batchedDrawState.indexBuffer = CreateStreamBuffer(BUFFER_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
+		batchedDrawState.vb[0] = CreateStreamBuffer(BUFFERTYPE_VERTEX, 1024 * 1024 * 1);
+		batchedDrawState.vb[1] = CreateStreamBuffer(BUFFERTYPE_VERTEX, 256  * 1024 * 1);
+		batchedDrawState.indexBuffer = CreateStreamBuffer(BUFFERTYPE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
 	}
 
+	if (capabilities.features[FEATURE_TEXEL_BUFFER] && defaultBuffers[BUFFERTYPE_TEXEL].get() == nullptr)
+	{
+		Buffer::Settings settings(Buffer::TYPEFLAG_TEXEL, 0, BUFFERUSAGE_STATIC);
+		std::vector<Buffer::DataDeclaration> format = {{"", DATAFORMAT_FLOAT_VEC4, 0}};
+
+		const float texel[] = {0.0f, 0.0f, 0.0f, 1.0f};
+
+		love::graphics::Buffer *buffer = newBuffer(settings, format, texel, sizeof(texel), 1);
+		defaultBuffers[BUFFERTYPE_TEXEL].set(buffer, Acquire::NORETAIN);
+	}
+
+	// Load default resources before other Volatile.
+	for (int i = 0; i < BUFFERTYPE_MAX_ENUM; i++)
+	{
+		if (defaultBuffers[i].get())
+			((Buffer *) defaultBuffers[i].get())->loadVolatile();
+	}
+
+	if (defaultBuffers[BUFFERTYPE_TEXEL].get())
+		gl.setDefaultTexelBuffer((GLuint) defaultBuffers[BUFFERTYPE_TEXEL]->getTexelBufferHandle());
+
 	// Reload all volatile objects.
 	if (!Volatile::loadAll())
 		::printf("Could not reload all volatile objects.\n");
@@ -269,12 +291,11 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
 	// Restore the graphics state.
 	restoreState(states.back());
 
-	int gammacorrect = isGammaCorrect() ? 1 : 0;
-	Shader::Language target = getShaderLanguageTarget();
-
 	// We always need a default shader.
 	for (int i = 0; i < Shader::STANDARD_MAX_ENUM; i++)
 	{
+		auto stype = (Shader::StandardShader) i;
+
 		if (i == Shader::STANDARD_ARRAY && !capabilities.textureTypes[TEXTURE_2D_ARRAY])
 			continue;
 
@@ -284,8 +305,10 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
 		{
 			if (!Shader::standardShaders[i])
 			{
-				const auto &code = defaultShaderCode[i][target][gammacorrect];
-				Shader::standardShaders[i] = love::graphics::Graphics::newShader(code.source[ShaderStage::STAGE_VERTEX], code.source[ShaderStage::STAGE_PIXEL]);
+				std::vector<std::string> stages;
+				stages.push_back(Shader::getDefaultCode(stype, ShaderStage::STAGE_VERTEX));
+				stages.push_back(Shader::getDefaultCode(stype, ShaderStage::STAGE_PIXEL));
+				Shader::standardShaders[i] = newShader(stages);
 			}
 		}
 		catch (love::Exception &)
@@ -376,7 +399,7 @@ void Graphics::draw(const DrawIndexedCommand &cmd)
 	GLenum glprimitivetype = OpenGL::getGLPrimitiveType(cmd.primitiveType);
 	GLenum gldatatype = OpenGL::getGLIndexDataType(cmd.indexType);
 
-	gl.bindBuffer(BUFFER_INDEX, cmd.indexBuffer->getHandle());
+	gl.bindBuffer(BUFFERTYPE_INDEX, cmd.indexBuffer->getHandle());
 
 	if (cmd.instanceCount > 1)
 		glDrawElementsInstanced(glprimitivetype, cmd.indexCount, gldatatype, gloffset, cmd.instanceCount);
@@ -386,13 +409,13 @@ void Graphics::draw(const DrawIndexedCommand &cmd)
 	++drawCalls;
 }
 
-static inline void advanceVertexOffsets(const vertex::Attributes &attributes, vertex::BufferBindings &buffers, int vertexcount)
+static inline void advanceVertexOffsets(const VertexAttributes &attributes, BufferBindings &buffers, int vertexcount)
 {
 	// TODO: Figure out a better way to avoid touching the same buffer multiple
 	// times, if multiple attributes share the buffer.
 	uint32 touchedbuffers = 0;
 
-	for (unsigned int i = 0; i < vertex::Attributes::MAX; i++)
+	for (unsigned int i = 0; i < VertexAttributes::MAX; i++)
 	{
 		if (!attributes.isEnabled(i))
 			continue;
@@ -409,7 +432,7 @@ static inline void advanceVertexOffsets(const vertex::Attributes &attributes, ve
 	}
 }
 
-void Graphics::drawQuads(int start, int count, const vertex::Attributes &attributes, const vertex::BufferBindings &buffers, love::graphics::Texture *texture)
+void Graphics::drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, love::graphics::Texture *texture)
 {
 	const int MAX_VERTICES_PER_DRAW = LOVE_UINT16_MAX;
 	const int MAX_QUADS_PER_DRAW    = MAX_VERTICES_PER_DRAW / 4;
@@ -418,7 +441,7 @@ void Graphics::drawQuads(int start, int count, const vertex::Attributes &attribu
 	gl.bindTextureToUnit(texture, 0, false);
 	gl.setCullMode(CULL_NONE);
 
-	gl.bindBuffer(BUFFER_INDEX, quadIndexBuffer->getHandle());
+	gl.bindBuffer(BUFFERTYPE_INDEX, quadIndexBuffer->getHandle());
 
 	if (gl.isBaseVertexSupported())
 	{
@@ -438,7 +461,7 @@ void Graphics::drawQuads(int start, int count, const vertex::Attributes &attribu
 	}
 	else
 	{
-		vertex::BufferBindings bufferscopy = buffers;
+		BufferBindings bufferscopy = buffers;
 		if (start > 0)
 			advanceVertexOffsets(attributes, bufferscopy, start * 4);
 
@@ -525,7 +548,7 @@ void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int w, int h,
 	endPass();
 
 	bool iswindow = rts.getFirstTarget().texture == nullptr;
-	vertex::Winding vertexwinding = state.winding;
+	Winding vertexwinding = state.winding;
 
 	if (iswindow)
 	{
@@ -543,10 +566,10 @@ void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int w, int h,
 
 		// Flip front face winding when rendering to a texture, since our
 		// projection matrix is flipped.
-		vertexwinding = vertexwinding == vertex::WINDING_CW ? vertex::WINDING_CCW : vertex::WINDING_CW;
+		vertexwinding = vertexwinding == WINDING_CW ? WINDING_CCW : WINDING_CW;
 	}
 
-	glFrontFace(vertexwinding == vertex::WINDING_CW ? GL_CW : GL_CCW);
+	glFrontFace(vertexwinding == WINDING_CW ? GL_CW : GL_CCW);
 
 	gl.setViewport({0, 0, pixelw, pixelh});
 
@@ -1246,7 +1269,7 @@ void Graphics::setDepthMode(CompareMode compare, bool write)
 	}
 }
 
-void Graphics::setFrontFaceWinding(vertex::Winding winding)
+void Graphics::setFrontFaceWinding(Winding winding)
 {
 	DisplayState &state = states.back();
 
@@ -1256,9 +1279,9 @@ void Graphics::setFrontFaceWinding(vertex::Winding winding)
 	state.winding = winding;
 
 	if (isRenderTargetActive())
-		winding = winding == vertex::WINDING_CW ? vertex::WINDING_CCW : vertex::WINDING_CW;
+		winding = winding == WINDING_CW ? WINDING_CCW : WINDING_CW;
 
-	glFrontFace(winding == vertex::WINDING_CW ? GL_CW : GL_CCW);
+	glFrontFace(winding == WINDING_CW ? GL_CW : GL_CCW);
 }
 
 void Graphics::setColor(Colorf c)
@@ -1388,17 +1411,19 @@ void Graphics::initCapabilities()
 	capabilities.features[FEATURE_GLSL3] = GLAD_ES_VERSION_3_0 || gl.isCoreProfile();
 	capabilities.features[FEATURE_GLSL4] = GLAD_ES_VERSION_3_1 || (gl.isCoreProfile() && GLAD_VERSION_4_3);
 	capabilities.features[FEATURE_INSTANCING] = gl.isInstancingSupported();
-	static_assert(FEATURE_MAX_ENUM == 10, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
+	capabilities.features[FEATURE_TEXEL_BUFFER] = gl.areTexelBuffersSupported();
+	static_assert(FEATURE_MAX_ENUM == 11, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
 
 	capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize();
 	capabilities.limits[LIMIT_TEXTURE_SIZE] = gl.getMax2DTextureSize();
 	capabilities.limits[LIMIT_TEXTURE_LAYERS] = gl.getMaxTextureLayers();
 	capabilities.limits[LIMIT_VOLUME_TEXTURE_SIZE] = gl.getMax3DTextureSize();
 	capabilities.limits[LIMIT_CUBE_TEXTURE_SIZE] = gl.getMaxCubeTextureSize();
+	capabilities.limits[LIMIT_TEXEL_BUFFER_SIZE] = gl.getMaxTexelBufferSize();
 	capabilities.limits[LIMIT_RENDER_TARGETS] = gl.getMaxRenderTargets();
 	capabilities.limits[LIMIT_TEXTURE_MSAA] = gl.getMaxSamples();
 	capabilities.limits[LIMIT_ANISOTROPY] = gl.getMaxAnisotropy();
-	static_assert(LIMIT_MAX_ENUM == 8, "Graphics::initCapabilities must be updated when adding a new system limit!");
+	static_assert(LIMIT_MAX_ENUM == 9, "Graphics::initCapabilities must be updated when adding a new system limit!");
 
 	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
 		capabilities.textureTypes[i] = gl.isTextureTypeSupported((TextureType) i);
@@ -1524,18 +1549,6 @@ bool Graphics::isPixelFormatSupported(PixelFormat format, bool rendertarget, boo
 	return supported.value;
 }
 
-Shader::Language Graphics::getShaderLanguageTarget() const
-{
-	if (gl.isCoreProfile())
-		return Shader::LANGUAGE_GLSL3;
-	else if (GLAD_ES_VERSION_3_0)
-		return Shader::LANGUAGE_ESSL3;
-	else if (GLAD_ES_VERSION_2_0)
-		return Shader::LANGUAGE_ESSL1;
-	else
-		return Shader::LANGUAGE_GLSL1;
-}
-
 } // opengl
 } // graphics
 } // love

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

@@ -60,7 +60,7 @@ public:
 	const char *getName() const override;
 
 	love::graphics::Texture *newTexture(const Texture::Settings &settings, const Texture::Slices *data = nullptr) override;
-	love::graphics::Buffer *newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags) override;
+	love::graphics::Buffer *newBuffer(const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &format, const void *data, size_t size, size_t arraylength) override;
 
 	void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override;
 	bool setMode(void *context, int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil) override;
@@ -70,7 +70,7 @@ public:
 
 	void draw(const DrawCommand &cmd) override;
 	void draw(const DrawIndexedCommand &cmd) override;
-	void drawQuads(int start, int count, const vertex::Attributes &attributes, const vertex::BufferBindings &buffers, love::graphics::Texture *texture) override;
+	void drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, love::graphics::Texture *texture) override;
 
 	void clear(OptionalColorf color, OptionalInt stencil, OptionalDouble depth) override;
 	void clear(const std::vector<OptionalColorf> &colors, OptionalInt stencil, OptionalDouble depth) override;
@@ -91,7 +91,7 @@ public:
 
 	void setDepthMode(CompareMode compare, bool write) override;
 
-	void setFrontFaceWinding(vertex::Winding winding) override;
+	void setFrontFaceWinding(Winding winding) override;
 
 	void setColorMask(ColorChannelMask mask) override;
 
@@ -107,8 +107,6 @@ public:
 	bool usesGLSLES() const override;
 	RendererInfo getRendererInfo() const override;
 
-	Shader::Language getShaderLanguageTarget() const override;
-
 	// Internal use.
 	void cleanupRenderTexture(love::graphics::Texture *texture);
 
@@ -150,6 +148,9 @@ private:
 	bool windowHasStencil;
 	GLuint mainVAO;
 
+	// Only needed for buffer types that can be bound to shaders.
+	StrongRef<love::graphics::Buffer> defaultBuffers[BUFFERTYPE_MAX_ENUM];
+
 	// [rendertarget][readable][srgb]
 	OptionalBool supportedFormats[PIXELFORMAT_MAX_ENUM][2][2][2];
 

+ 188 - 93
src/modules/graphics/opengl/OpenGL.cpp

@@ -155,11 +155,11 @@ bool OpenGL::initContext()
 #ifdef LOVE_WINDOWS
 	if (getVendor() == VENDOR_AMD)
 	{
-		// Radeon HD drivers switched from "ATI Radeon" to "AMD Radeon" around
+		// Radeon drivers switched from "ATI Radeon" to "AMD Radeon" around
 		// the 7000 series. We'll assume this bug doesn't affect those newer
 		// GPUs / drivers.
 		const char *device = (const char *) glGetString(GL_RENDERER);
-		if (strstr(device, "ATI Radeon HD ") || strstr(device, "ATI Mobility Radeon HD"))
+		if (strstr(device, "ATI Radeon") || strstr(device, "ATI Mobility Radeon"))
 			bugs.texStorageBreaksSubImage = true;
 	}
 #endif
@@ -185,7 +185,7 @@ void OpenGL::setupContext()
 	state.enabledAttribArrays = (uint32) ((1ull << uint32(maxvertexattribs)) - 1);
 	state.instancedAttribArrays = 0;
 
-	setVertexAttributes(vertex::Attributes(), vertex::BufferBindings());
+	setVertexAttributes(VertexAttributes(), BufferBindings());
 
 	// Get the current viewport.
 	glGetIntegerv(GL_VIEWPORT, (GLint *) &state.viewport.x);
@@ -222,14 +222,14 @@ void OpenGL::setupContext()
 	glGetIntegerv(GL_CULL_FACE_MODE, &faceCull);
 	state.faceCullMode = faceCull;
 
-	for (int i = 0; i < (int) BUFFER_MAX_ENUM; i++)
+	for (int i = 0; i < (int) BUFFERTYPE_MAX_ENUM; i++)
 	{
 		state.boundBuffers[i] = 0;
 		glBindBuffer(getGLBufferType((BufferType) i), 0);
 	}
 
 	// Initialize multiple texture unit support for shaders.
-	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
+	for (int i = 0; i < TEXTURE_MAX_ENUM + 1; i++)
 	{
 		state.boundTextures[i].clear();
 		state.boundTextures[i].resize(maxTextureUnits, 0);
@@ -280,6 +280,10 @@ void OpenGL::deInitContext()
 		}
 	}
 
+	if (state.defaultTexelBuffer != 0)
+		gl.deleteTexture(state.defaultTexelBuffer);
+	state.defaultTexelBuffer = 0;
+
 	contextInitialized = false;
 }
 
@@ -459,6 +463,11 @@ void OpenGL::initMaxValues()
 	else
 		maxTextureArrayLayers = 0;
 
+	if (areTexelBuffersSupported())
+		glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTexelBufferSize);
+	else
+		maxTexelBufferSize = 0;
+
 	int maxattachments = 1;
 	int maxdrawbuffers = 1;
 
@@ -562,16 +571,11 @@ GLenum OpenGL::getGLPrimitiveType(PrimitiveType type)
 {
 	switch (type)
 	{
-	case PRIMITIVE_TRIANGLES:
-		return GL_TRIANGLES;
-	case PRIMITIVE_TRIANGLE_STRIP:
-		return GL_TRIANGLE_STRIP;
-	case PRIMITIVE_TRIANGLE_FAN:
-		return GL_TRIANGLE_FAN;
-	case PRIMITIVE_POINTS:
-		return GL_POINTS;
-	case PRIMITIVE_MAX_ENUM:
-		return GL_ZERO;
+		case PRIMITIVE_TRIANGLES: return GL_TRIANGLES;
+		case PRIMITIVE_TRIANGLE_STRIP: return GL_TRIANGLE_STRIP;
+		case PRIMITIVE_TRIANGLE_FAN: return GL_TRIANGLE_FAN;
+		case PRIMITIVE_POINTS: return GL_POINTS;
+		case PRIMITIVE_MAX_ENUM: return GL_ZERO;
 	}
 
 	return GL_ZERO;
@@ -581,12 +585,10 @@ GLenum OpenGL::getGLBufferType(BufferType type)
 {
 	switch (type)
 	{
-	case BUFFER_VERTEX:
-		return GL_ARRAY_BUFFER;
-	case BUFFER_INDEX:
-		return GL_ELEMENT_ARRAY_BUFFER;
-	case BUFFER_MAX_ENUM:
-		return GL_ZERO;
+		case BUFFERTYPE_VERTEX: return GL_ARRAY_BUFFER;
+		case BUFFERTYPE_INDEX: return GL_ELEMENT_ARRAY_BUFFER;
+		case BUFFERTYPE_TEXEL: return GL_TEXTURE_BUFFER;
+		case BUFFERTYPE_MAX_ENUM: return GL_ZERO;
 	}
 
 	return GL_ZERO;
@@ -596,16 +598,11 @@ 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;
+		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_TEXTURE_BUFFER; // Hack
 	}
 
 	return GL_ZERO;
@@ -615,74 +612,159 @@ GLenum OpenGL::getGLIndexDataType(IndexDataType type)
 {
 	switch (type)
 	{
-	case INDEX_UINT16:
-		return GL_UNSIGNED_SHORT;
-	case INDEX_UINT32:
-		return GL_UNSIGNED_INT;
-	default:
-		return GL_ZERO;
+		case INDEX_UINT16: return GL_UNSIGNED_SHORT;
+		case INDEX_UINT32: return GL_UNSIGNED_INT;
+		default: return GL_ZERO;
 	}
 }
 
-GLenum OpenGL::getGLVertexDataType(vertex::DataType type, GLboolean &normalized, bool &intformat)
+GLenum OpenGL::getGLVertexDataType(DataFormat format, int &components, GLboolean &normalized, bool &intformat)
 {
 	normalized = GL_FALSE;
 	intformat = false;
+	components = 1;
 
-	switch (type)
+	switch (format)
 	{
-	case vertex::DATA_SNORM8:
+	case DATAFORMAT_FLOAT:
+		components = 1;
+		return GL_FLOAT;
+	case DATAFORMAT_FLOAT_VEC2:
+		components = 2;
+		return GL_FLOAT;
+	case DATAFORMAT_FLOAT_VEC3:
+		components = 3;
+		return GL_FLOAT;
+	case DATAFORMAT_FLOAT_VEC4:
+		components = 4;
+		return GL_FLOAT;
+
+	case DATAFORMAT_FLOAT_MAT2X2:
+	case DATAFORMAT_FLOAT_MAT2X3:
+	case DATAFORMAT_FLOAT_MAT2X4:
+	case DATAFORMAT_FLOAT_MAT3X2:
+	case DATAFORMAT_FLOAT_MAT3X3:
+	case DATAFORMAT_FLOAT_MAT3X4:
+	case DATAFORMAT_FLOAT_MAT4X2:
+	case DATAFORMAT_FLOAT_MAT4X3:
+	case DATAFORMAT_FLOAT_MAT4X4:
+		return GL_ZERO;
+
+	case DATAFORMAT_INT32:
+		components = 1;
+		intformat = true;
+		return GL_INT;
+	case DATAFORMAT_INT32_VEC2:
+		components = 2;
+		intformat = true;
+		return GL_INT;
+	case DATAFORMAT_INT32_VEC3:
+		components = 3;
+		intformat = true;
+		return GL_INT;
+	case DATAFORMAT_INT32_VEC4:
+		components = 4;
+		intformat = true;
+		return GL_INT;
+
+	case DATAFORMAT_UINT32:
+		components = 1;
+		intformat = true;
+		return GL_UNSIGNED_INT;
+	case DATAFORMAT_UINT32_VEC2:
+		components = 2;
+		intformat = true;
+		return GL_UNSIGNED_INT;
+	case DATAFORMAT_UINT32_VEC3:
+		components = 3;
+		intformat = true;
+		return GL_UNSIGNED_INT;
+	case DATAFORMAT_UINT32_VEC4:
+		components = 4;
+		intformat = true;
+		return GL_UNSIGNED_INT;
+
+	case DATAFORMAT_SNORM8_VEC4:
+		components = 4;
 		normalized = GL_TRUE;
 		return GL_BYTE;
-	case vertex::DATA_UNORM8:
+
+	case DATAFORMAT_UNORM8_VEC4:
+		components = 4;
 		normalized = GL_TRUE;
 		return GL_UNSIGNED_BYTE;
-	case vertex::DATA_INT8:
+
+	case DATAFORMAT_INT8_VEC4:
+		components = 4;
 		intformat = true;
 		return GL_BYTE;
-	case vertex::DATA_UINT8:
+
+	case DATAFORMAT_UINT8_VEC4:
+		components = 4;
 		intformat = true;
 		return GL_UNSIGNED_BYTE;
-	case vertex::DATA_SNORM16:
+
+	case DATAFORMAT_SNORM16_VEC2:
+		components = 2;
 		normalized = GL_TRUE;
-		return GL_SHORT;
-	case vertex::DATA_UNORM16:
+		return GL_BYTE;
+	case DATAFORMAT_SNORM16_VEC4:
+		components = 4;
+		normalized = GL_TRUE;
+		return GL_BYTE;
+
+	case DATAFORMAT_UNORM16_VEC2:
+		components = 2;
 		normalized = GL_TRUE;
 		return GL_UNSIGNED_SHORT;
-	case vertex::DATA_INT16:
+	case DATAFORMAT_UNORM16_VEC4:
+		components = 4;
+		normalized = GL_TRUE;
+		return GL_UNSIGNED_SHORT;
+
+	case DATAFORMAT_INT16_VEC2:
+		components = 2;
+		intformat = true;
+		return GL_SHORT;
+	case DATAFORMAT_INT16_VEC4:
+		components = 4;
 		intformat = true;
 		return GL_SHORT;
-	case vertex::DATA_UINT16:
+
+	case DATAFORMAT_UINT16:
+		components = 1;
 		intformat = true;
 		return GL_UNSIGNED_SHORT;
-	case vertex::DATA_INT32:
+	case DATAFORMAT_UINT16_VEC2:
+		components = 2;
 		intformat = true;
-		return GL_INT;
-	case vertex::DATA_UINT32:
+		return GL_UNSIGNED_SHORT;
+	case DATAFORMAT_UINT16_VEC4:
+		components = 4;
 		intformat = true;
-		return GL_UNSIGNED_INT;
-	case vertex::DATA_FLOAT:
-		normalized = GL_FALSE;
-		return GL_FLOAT;
-	case vertex::DATA_MAX_ENUM:
+		return GL_UNSIGNED_SHORT;
+
+	case DATAFORMAT_BOOL:
+	case DATAFORMAT_BOOL_VEC2:
+	case DATAFORMAT_BOOL_VEC3:
+	case DATAFORMAT_BOOL_VEC4:
+		return GL_ZERO;
+
+	case DATAFORMAT_MAX_ENUM:
 		return GL_ZERO;
 	}
 
 	return GL_ZERO;
 }
 
-GLenum OpenGL::getGLBufferUsage(vertex::Usage usage)
+GLenum OpenGL::getGLBufferUsage(BufferUsage usage)
 {
 	switch (usage)
 	{
-	case vertex::USAGE_STREAM:
-		return GL_STREAM_DRAW;
-	case vertex::USAGE_DYNAMIC:
-		return GL_DYNAMIC_DRAW;
-	case vertex::USAGE_STATIC:
-		return GL_STATIC_DRAW;
-	default:
-		return 0;
+		case BUFFERUSAGE_STREAM: return GL_STREAM_DRAW;
+		case BUFFERUSAGE_DYNAMIC: return GL_DYNAMIC_DRAW;
+		case BUFFERUSAGE_STATIC: return GL_STATIC_DRAW;
+		default: return 0;
 	}
 }
 
@@ -699,14 +781,14 @@ void OpenGL::deleteBuffer(GLuint buffer)
 {
 	glDeleteBuffers(1, &buffer);
 
-	for (int i = 0; i < (int) BUFFER_MAX_ENUM; i++)
+	for (int i = 0; i < (int) BUFFERTYPE_MAX_ENUM; i++)
 	{
 		if (state.boundBuffers[i] == buffer)
 			state.boundBuffers[i] = 0;
 	}
 }
 
-void OpenGL::setVertexAttributes(const vertex::Attributes &attributes, const vertex::BufferBindings &buffers)
+void OpenGL::setVertexAttributes(const VertexAttributes &attributes, const BufferBindings &buffers)
 {
 	uint32 enablediff = attributes.enableBits ^ state.enabledAttribArrays;
 	uint32 instanceattribbits = 0;
@@ -739,18 +821,19 @@ void OpenGL::setVertexAttributes(const vertex::Attributes &attributes, const ver
 			if ((state.instancedAttribArrays & bit) ^ divisorbit)
 				glVertexAttribDivisor(i, divisor);
 
+			int components = 0;
 			GLboolean normalized = GL_FALSE;
 			bool intformat = false;
-			GLenum gltype = getGLVertexDataType(attrib.type, normalized, intformat);
+			GLenum gltype = getGLVertexDataType(attrib.format, components, normalized, intformat);
 
 			const void *offsetpointer = reinterpret_cast<void*>(bufferinfo.offset + attrib.offsetFromVertex);
 
-			bindBuffer(BUFFER_VERTEX, (GLuint) bufferinfo.buffer->getHandle());
+			bindBuffer(BUFFERTYPE_VERTEX, (GLuint) bufferinfo.buffer->getHandle());
 
 			if (intformat)
-				glVertexAttribIPointer(i, attrib.components, gltype, layout.stride, offsetpointer);
+				glVertexAttribIPointer(i, components, gltype, layout.stride, offsetpointer);
 			else
-				glVertexAttribPointer(i, attrib.components, gltype, normalized, layout.stride, offsetpointer);
+				glVertexAttribPointer(i, components, gltype, normalized, layout.stride, offsetpointer);
 		}
 
 		i++;
@@ -988,7 +1071,7 @@ void OpenGL::setTextureUnit(int textureunit)
 	state.curTextureUnit = textureunit;
 }
 
-void OpenGL::bindTextureToUnit(TextureType target, GLuint texture, int textureunit, bool restoreprev)
+void OpenGL::bindTextureToUnit(TextureType target, GLuint texture, int textureunit, bool restoreprev, bool bindforedit)
 {
 	if (texture != state.boundTextures[target][textureunit])
 	{
@@ -1004,9 +1087,19 @@ void OpenGL::bindTextureToUnit(TextureType target, GLuint texture, int textureun
 		else
 			state.curTextureUnit = textureunit;
 	}
+	else if (bindforedit && !restoreprev && textureunit != state.curTextureUnit)
+	{
+		glActiveTexture(GL_TEXTURE0 + textureunit);
+		state.curTextureUnit = textureunit;
+	}
+}
+
+void OpenGL::bindBufferTextureToUnit(GLuint texture, int textureunit, bool restoreprev, bool bindforedit)
+{
+	bindTextureToUnit(TEXTURE_MAX_ENUM, texture, textureunit, restoreprev, bindforedit);
 }
 
-void OpenGL::bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev)
+void OpenGL::bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev, bool bindforedit)
 {
 	TextureType textype = TEXTURE_2D;
 	GLuint handle = 0;
@@ -1028,14 +1121,14 @@ void OpenGL::bindTextureToUnit(Texture *texture, int textureunit, bool restorepr
 		handle = getDefaultTexture(textype);
 	}
 
-	bindTextureToUnit(textype, handle, textureunit, restoreprev);
+	bindTextureToUnit(textype, handle, textureunit, restoreprev, bindforedit);
 }
 
 void OpenGL::deleteTexture(GLuint texture)
 {
 	// glDeleteTextures binds texture 0 to all texture units the deleted texture
 	// was bound to before deletion.
-	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
+	for (int i = 0; i < TEXTURE_MAX_ENUM + 1; i++)
 	{
 		for (GLuint &texid : state.boundTextures[i])
 		{
@@ -1068,24 +1161,15 @@ GLint OpenGL::getGLCompareMode(CompareMode mode)
 {
 	switch (mode)
 	{
-	case COMPARE_LESS:
-		return GL_LESS;
-	case COMPARE_LEQUAL:
-		return GL_LEQUAL;
-	case COMPARE_EQUAL:
-		return GL_EQUAL;
-	case COMPARE_GEQUAL:
-		return GL_GEQUAL;
-	case COMPARE_GREATER:
-		return GL_GREATER;
-	case COMPARE_NOTEQUAL:
-		return GL_NOTEQUAL;
-	case COMPARE_ALWAYS:
-		return GL_ALWAYS;
-	case COMPARE_NEVER:
-		return GL_NEVER;
-	default:
-		return GL_NEVER;
+		case COMPARE_LESS: return GL_LESS;
+		case COMPARE_LEQUAL: return GL_LEQUAL;
+		case COMPARE_EQUAL: return GL_EQUAL;
+		case COMPARE_GEQUAL: return GL_GEQUAL;
+		case COMPARE_GREATER: return GL_GREATER;
+		case COMPARE_NOTEQUAL: return GL_NOTEQUAL;
+		case COMPARE_ALWAYS: return GL_ALWAYS;
+		case COMPARE_NEVER: return GL_NEVER;
+		default: return GL_NEVER;
 	}
 }
 
@@ -1333,6 +1417,12 @@ bool OpenGL::isMultiFormatMRTSupported() const
 	return getMaxRenderTargets() > 1 && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object);
 }
 
+bool OpenGL::areTexelBuffersSupported() const
+{
+	// Not supported in ES until 3.2, which we don't support shaders for...
+	return GLAD_VERSION_3_1;
+}
+
 int OpenGL::getMax2DTextureSize() const
 {
 	return std::max(max2DTextureSize, 1);
@@ -1353,6 +1443,11 @@ int OpenGL::getMaxTextureLayers() const
 	return std::max(maxTextureArrayLayers, 1);
 }
 
+int OpenGL::getMaxTexelBufferSize() const
+{
+	return maxTexelBufferSize;
+}
+
 int OpenGL::getMaxRenderTargets() const
 {
 	return std::min(maxRenderTargets, MAX_COLOR_RENDER_TARGETS);

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

@@ -166,7 +166,7 @@ public:
 		 * initial full-size one (determined after some investigation with an
 		 * affected user on Discord.)
 		 * https://bitbucket.org/rude/love/issues/1436/bug-with-lovegraphicsprint-on-older-ati
-		 *
+		 * https://github.com/love2d/love/issues/1563
 		 **/
 		bool texStorageBreaksSubImage;
 
@@ -238,7 +238,7 @@ public:
 	/**
 	 * Set all vertex attribute state.
 	 **/
-	void setVertexAttributes(const vertex::Attributes &attributes, const vertex::BufferBindings &buffers);
+	void setVertexAttributes(const VertexAttributes &attributes, const BufferBindings &buffers);
 
 	/**
 	 * Wrapper for glCullFace which eliminates redundant state setting.
@@ -306,6 +306,12 @@ public:
 	 **/
 	GLuint getDefaultTexture(TextureType type) const;
 
+	/**
+	 * Gets the texture ID for love's default texel buffer.
+	 **/
+	GLuint getDefaultTexelBuffer() const { return state.defaultTexelBuffer; }
+	void setDefaultTexelBuffer(GLuint tex) { state.defaultTexelBuffer = tex; }
+
 	/**
 	 * Helper for setting the active texture unit.
 	 *
@@ -318,9 +324,12 @@ public:
 	 *
 	 * @param textureunit Index in the range of [0, maxtextureunits-1]
 	 * @param restoreprev Restore previously bound texture unit when done.
+	 * @param bindforedit If false, the active texture unit may be left alone.
 	 **/
-	void bindTextureToUnit(TextureType target, GLuint texture, int textureunit, bool restoreprev);
-	void bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev);
+	void bindTextureToUnit(TextureType target, GLuint texture, int textureunit, bool restoreprev, bool bindforedit = true);
+	void bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev, bool bindforedit = true);
+
+	void bindBufferTextureToUnit(GLuint texture, int textureunit, bool restoreprev, bool bindforedit);
 
 	/**
 	 * Helper for deleting an OpenGL texture.
@@ -348,6 +357,7 @@ public:
 	bool isSamplerLODBiasSupported() const;
 	bool isBaseVertexSupported() const;
 	bool isMultiFormatMRTSupported() const;
+	bool areTexelBuffersSupported() const;
 
 	/**
 	 * Returns the maximum supported width or height of a texture.
@@ -357,6 +367,11 @@ public:
 	int getMaxCubeTextureSize() const;
 	int getMaxTextureLayers() const;
 
+	/**
+	 * Returns the maximum number of values in a texel buffer.
+	 **/
+	int getMaxTexelBufferSize() const;
+
 	/**
 	 * Returns the maximum supported number of simultaneous render targets.
 	 **/
@@ -398,8 +413,8 @@ public:
 	static GLenum getGLPrimitiveType(PrimitiveType type);
 	static GLenum getGLBufferType(BufferType type);
 	static GLenum getGLIndexDataType(IndexDataType type);
-	static GLenum getGLVertexDataType(vertex::DataType type, GLboolean &normalized, bool &intformat);
-	static GLenum getGLBufferUsage(vertex::Usage usage);
+	static GLenum getGLVertexDataType(DataFormat format, int &components, GLboolean &normalized, bool &intformat);
+	static GLenum getGLBufferUsage(BufferUsage usage);
 	static GLenum getGLTextureType(TextureType type);
 	static GLint getGLWrapMode(SamplerState::WrapMode wmode);
 	static GLint getGLCompareMode(CompareMode mode);
@@ -435,6 +450,7 @@ private:
 	int max3DTextureSize;
 	int maxCubeTextureSize;
 	int maxTextureArrayLayers;
+	int maxTexelBufferSize;
 	int maxRenderTargets;
 	int maxSamples;
 	int maxTextureUnits;
@@ -447,10 +463,10 @@ private:
 	// Tracked OpenGL state.
 	struct
 	{
-		GLuint boundBuffers[BUFFER_MAX_ENUM];
+		GLuint boundBuffers[BUFFERTYPE_MAX_ENUM];
 
 		// Texture unit state (currently bound texture for each texture unit.)
-		std::vector<GLuint> boundTextures[TEXTURE_MAX_ENUM];
+		std::vector<GLuint> boundTextures[TEXTURE_MAX_ENUM + 1];
 
 		bool enableState[ENABLE_MAX_ENUM];
 
@@ -471,6 +487,7 @@ private:
 		GLuint boundFramebuffers[2];
 
 		GLuint defaultTexture[TEXTURE_MAX_ENUM];
+		GLuint defaultTexelBuffer;
 
 	} state;
 

+ 207 - 18
src/modules/graphics/opengl/Shader.cpp

@@ -24,6 +24,7 @@
 #include "Shader.h"
 #include "ShaderStage.h"
 #include "Graphics.h"
+#include "graphics/vertex.h"
 
 // C++
 #include <algorithm>
@@ -68,6 +69,16 @@ Shader::~Shader()
 
 			delete[] p.second.textures;
 		}
+		else if (p.second.baseType == UNIFORM_TEXELBUFFER)
+		{
+			for (int i = 0; i < p.second.count; i++)
+			{
+				if (p.second.buffers[i] != nullptr)
+					p.second.buffers[i]->release();
+			}
+
+			delete[] p.second.buffers;
+		}
 	}
 }
 
@@ -106,6 +117,7 @@ void Shader::mapActiveUniforms()
 		u.location = glGetUniformLocation(program, u.name.c_str());
 		u.baseType = getUniformBaseType(gltype);
 		u.textureType = getUniformTextureType(gltype);
+		u.texelBufferType = getUniformTexelBufferType(gltype);
 		u.isDepthSampler = isDepthTextureType(gltype);
 
 		if (u.baseType == UNIFORM_MATRIX)
@@ -129,12 +141,22 @@ void Shader::mapActiveUniforms()
 		if (u.location == -1)
 			continue;
 
-		if (u.baseType == UNIFORM_SAMPLER && builtin != BUILTIN_TEXTURE_MAIN)
+		if ((u.baseType == UNIFORM_SAMPLER && builtin != BUILTIN_TEXTURE_MAIN) || u.baseType == UNIFORM_TEXELBUFFER)
 		{
 			TextureUnit unit;
 			unit.type = u.textureType;
 			unit.active = true;
-			unit.texture = gl.getDefaultTexture(u.textureType);
+
+			if (u.baseType == UNIFORM_TEXELBUFFER)
+			{
+				unit.isTexelBuffer = true;
+				unit.texture = gl.getDefaultTexelBuffer();
+			}
+			else
+			{
+				unit.isTexelBuffer = false;
+				unit.texture = gl.getDefaultTexture(u.textureType);
+			}
 
 			for (int i = 0; i < u.count; i++)
 				textureUnits.push_back(unit);
@@ -164,6 +186,7 @@ void Shader::mapActiveUniforms()
 			case UNIFORM_INT:
 			case UNIFORM_BOOL:
 			case UNIFORM_SAMPLER:
+			case UNIFORM_TEXELBUFFER:
 				u.dataSize = sizeof(int) * u.components * u.count;
 				u.data = malloc(u.dataSize);
 				break;
@@ -183,7 +206,7 @@ void Shader::mapActiveUniforms()
 			{
 				memset(u.data, 0, u.dataSize);
 
-				if (u.baseType == UNIFORM_SAMPLER)
+				if (u.baseType == UNIFORM_SAMPLER || u.baseType == UNIFORM_TEXELBUFFER)
 				{
 					int startunit = (int) textureUnits.size() - u.count;
 
@@ -195,8 +218,16 @@ void Shader::mapActiveUniforms()
 
 					glUniform1iv(u.location, u.count, u.ints);
 
-					u.textures = new love::graphics::Texture*[u.count];
-					memset(u.textures, 0, sizeof(Texture *) * u.count);
+					if (u.baseType == UNIFORM_TEXELBUFFER)
+					{
+						u.buffers = new love::graphics::Buffer*[u.count];
+						memset(u.buffers, 0, sizeof(Buffer *) * u.count);
+					}
+					else
+					{
+						u.textures = new love::graphics::Texture*[u.count];
+						memset(u.textures, 0, sizeof(Texture *) * u.count);
+					}
 				}
 			}
 
@@ -257,7 +288,6 @@ void Shader::mapActiveUniforms()
 			{
 				if (u.textures[i] == nullptr)
 					continue;
-
 				Volatile *v = dynamic_cast<Volatile *>(u.textures[i]);
 				if (v != nullptr)
 					v->loadVolatile();
@@ -265,6 +295,19 @@ void Shader::mapActiveUniforms()
 
 			sendTextures(&u, u.textures, u.count, true);
 		}
+		else if (u.baseType == UNIFORM_TEXELBUFFER)
+		{
+			for (int i = 0; i < u.count; i++)
+			{
+				if (u.buffers[i] == nullptr)
+					continue;
+				Volatile *v = dynamic_cast<Volatile *>(u.buffers[i]);
+				if (v != nullptr)
+					v->loadVolatile();
+			}
+
+			sendBuffers(&u, u.buffers, u.count, true);
+		}
 	}
 
 	// Make sure uniforms that existed before but don't exist anymore are
@@ -275,16 +318,26 @@ void Shader::mapActiveUniforms()
 		{
 			free(p.second.data);
 
-			if (p.second.baseType != UNIFORM_SAMPLER)
-				continue;
-
-			for (int i = 0; i < p.second.count; i++)
+			if (p.second.baseType == UNIFORM_SAMPLER)
 			{
-				if (p.second.textures[i] != nullptr)
-					p.second.textures[i]->release();
+				for (int i = 0; i < p.second.count; i++)
+				{
+					if (p.second.textures[i] != nullptr)
+						p.second.textures[i]->release();
+				}
+
+				delete[] p.second.textures;
 			}
+			else if (p.second.baseType == UNIFORM_TEXELBUFFER)
+			{
+				for (int i = 0; i < p.second.count; i++)
+				{
+					if (p.second.buffers[i] != nullptr)
+						p.second.buffers[i]->release();
+				}
 
-			delete[] p.second.textures;
+				delete[] p.second.buffers;
+			}
 		}
 	}
 
@@ -322,7 +375,7 @@ bool Shader::loadVolatile()
 	for (int i = 0; i < int(ATTRIB_MAX_ENUM); i++)
 	{
 		const char *name = nullptr;
-		if (vertex::getConstant((BuiltinVertexAttribute) i, name))
+		if (graphics::getConstant((BuiltinVertexAttribute) i, name))
 			glBindAttribLocation(program, i, (const GLchar *) name);
 	}
 
@@ -430,7 +483,12 @@ void Shader::attach()
 		{
 			const TextureUnit &unit = textureUnits[i];
 			if (unit.active)
-				gl.bindTextureToUnit(unit.type, unit.texture, i, false);
+			{
+				if (unit.isTexelBuffer)
+					gl.bindBufferTextureToUnit(unit.texture, i, false, false);
+				else
+					gl.bindTextureToUnit(unit.type, unit.texture, i, false, false);
+			}
 		}
 
 		// send any pending uniforms to the shader program.
@@ -493,7 +551,7 @@ void Shader::updateUniform(const UniformInfo *info, int count, bool internalupda
 			break;
 		}
 	}
-	else if (type == UNIFORM_INT || type == UNIFORM_BOOL || type == UNIFORM_SAMPLER)
+	else if (type == UNIFORM_INT || type == UNIFORM_BOOL || type == UNIFORM_SAMPLER || type == UNIFORM_TEXELBUFFER)
 	{
 		switch (info->components)
 		{
@@ -627,7 +685,110 @@ void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **tex
 		int texunit = info->ints[i];
 
 		if (shaderactive)
-			gl.bindTextureToUnit(info->textureType, gltex, texunit, false);
+			gl.bindTextureToUnit(info->textureType, gltex, texunit, false, false);
+
+		// Store texture id so it can be re-bound to the texture unit later.
+		textureUnits[texunit].texture = gltex;
+	}
+}
+
+void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count)
+{
+	Shader::sendBuffers(info, buffers, count, false);
+}
+
+static bool isTexelBufferTypeCompatible(DataBaseType a, DataBaseType b)
+{
+	if (a == DATA_BASETYPE_FLOAT || a == DATA_BASETYPE_UNORM || a == DATA_BASETYPE_SNORM)
+		return b == DATA_BASETYPE_FLOAT || b == DATA_BASETYPE_UNORM || b == DATA_BASETYPE_SNORM;
+
+	if (a == DATA_BASETYPE_INT && b == DATA_BASETYPE_INT)
+		return true;
+
+	if (a == DATA_BASETYPE_UINT && b == DATA_BASETYPE_UINT)
+		return true;
+
+	return false;
+}
+
+void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count, bool internalUpdate)
+{
+	if (info->baseType != UNIFORM_TEXELBUFFER)
+		return;
+
+	uint32 requiredtypeflags = Buffer::TYPEFLAG_TEXEL;
+
+	bool shaderactive = current == this;
+
+	if (!internalUpdate && shaderactive)
+		flushBatchedDraws();
+
+	count = std::min(count, info->count);
+
+	// Bind the textures to the texture units.
+	for (int i = 0; i < count; i++)
+	{
+		love::graphics::Buffer *buffer = buffers[i];
+
+		if (buffer != nullptr)
+		{
+			if ((buffer->getTypeFlags() & requiredtypeflags) == 0)
+			{
+				if (internalUpdate)
+					continue;
+				else
+					throw love::Exception("Shader uniform '%s' is a texel buffer, but the given Buffer was not created with texel buffer capabilities.", info->name.c_str());
+			}
+
+			DataBaseType basetype = buffer->getDataMember(0).info.baseType;
+			if (!isTexelBufferTypeCompatible(basetype, info->texelBufferType))
+			{
+				if (internalUpdate)
+					continue;
+				else
+					throw love::Exception("Texel buffer's data format base type must match the variable declared in the shader.");
+			}
+
+			buffer->retain();
+		}
+
+		bool addbuffertoarray = true;
+
+		if (info->buffers[i] != nullptr)
+		{
+			Buffer *oldbuffer = info->buffers[i];
+			auto it = std::find(buffersToUnmap.begin(), buffersToUnmap.end(), oldbuffer);
+			if (it != buffersToUnmap.end())
+			{
+				addbuffertoarray = false;
+				if (buffer != nullptr)
+					*it = buffer;
+				else
+				{
+					auto last = buffersToUnmap.end() - 1;
+					*it = *last;
+					buffersToUnmap.erase(last);
+				}
+			}
+
+			oldbuffer->release();
+		}
+
+		if (addbuffertoarray && buffer != nullptr)
+			buffersToUnmap.push_back(buffer);
+
+		info->buffers[i] = buffer;
+
+		GLuint gltex = 0;
+		if (buffers[i] != nullptr)
+			gltex = (GLuint) buffer->getTexelBufferHandle();
+		else
+			gltex = gl.getDefaultTexelBuffer();
+
+		int texunit = info->ints[i];
+
+		if (shaderactive)
+			gl.bindBufferTextureToUnit(gltex, texunit, false, false);
 
 		// Store texture id so it can be re-bound to the texture unit later.
 		textureUnits[texunit].texture = gltex;
@@ -746,11 +907,20 @@ void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW,
 	GLint location = builtinUniforms[BUILTIN_UNIFORMS_PER_DRAW];
 	if (location >= 0)
 		glUniform4fv(location, 13, (const GLfloat *) &data);
+
+	// TODO: Find a better place to put this.
+	// Buffers used in this shader can be mapped by external code without
+	// unmapping. We need to make sure the data on the GPU is up to date,
+	// otherwise the shader can read from old data.
+	for (Buffer *buffer : buffersToUnmap)
+		buffer->unmap();
 }
 
 int Shader::getUniformTypeComponents(GLenum type) const
 {
-	if (getUniformBaseType(type) == UNIFORM_SAMPLER)
+	UniformType basetype = getUniformBaseType(type);
+
+	if (basetype == UNIFORM_SAMPLER || basetype == UNIFORM_TEXELBUFFER)
 		return 1;
 
 	switch (type)
@@ -879,6 +1049,10 @@ Shader::UniformType Shader::getUniformBaseType(GLenum type) const
 	case GL_SAMPLER_CUBE_MAP_ARRAY:
 	case GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW:
 		return UNIFORM_SAMPLER;
+	case GL_SAMPLER_BUFFER:
+	case GL_INT_SAMPLER_BUFFER:
+	case GL_UNSIGNED_INT_SAMPLER_BUFFER:
+		return UNIFORM_TEXELBUFFER;
 	default:
 		return UNIFORM_UNKNOWN;
 	}
@@ -922,6 +1096,21 @@ TextureType Shader::getUniformTextureType(GLenum type) const
 	}
 }
 
+DataBaseType Shader::getUniformTexelBufferType(GLenum type) const
+{
+	switch (type)
+	{
+	case GL_SAMPLER_BUFFER:
+		return DATA_BASETYPE_FLOAT;
+	case GL_INT_SAMPLER_BUFFER:
+		return DATA_BASETYPE_INT;
+	case GL_UNSIGNED_INT_SAMPLER_BUFFER:
+		return DATA_BASETYPE_UINT;
+	default:
+		return DATA_BASETYPE_MAX_ENUM;
+	}
+}
+
 bool Shader::isDepthTextureType(GLenum type) const
 {
 	switch (type)

+ 6 - 0
src/modules/graphics/opengl/Shader.h

@@ -62,6 +62,7 @@ public:
 	const UniformInfo *getUniformInfo(BuiltinUniform builtin) const override;
 	void updateUniform(const UniformInfo *info, int count) override;
 	void sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count) override;
+	void sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count) override;
 	bool hasUniform(const std::string &name) const override;
 	ptrdiff_t getHandle() const override;
 	void setVideoTextures(love::graphics::Texture *ytexture, love::graphics::Texture *cbtexture, love::graphics::Texture *crtexture) override;
@@ -75,6 +76,7 @@ private:
 	{
 		GLuint texture = 0;
 		TextureType type = TEXTURE_2D;
+		bool isTexelBuffer = false;
 		bool active = false;
 	};
 
@@ -83,11 +85,13 @@ private:
 
 	void updateUniform(const UniformInfo *info, int count, bool internalupdate);
 	void sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count, bool internalupdate);
+	void sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count, bool internalupdate);
 
 	int getUniformTypeComponents(GLenum type) const;
 	MatrixSize getMatrixSize(GLenum type) const;
 	UniformType getUniformBaseType(GLenum type) const;
 	TextureType getUniformTextureType(GLenum type) const;
+	DataBaseType getUniformTexelBufferType(GLenum type) const;
 	bool isDepthTextureType(GLenum type) const;
 
 	void flushBatchedDraws() const;
@@ -112,6 +116,8 @@ private:
 
 	std::vector<std::pair<const UniformInfo *, int>> pendingUniformUpdates;
 
+	std::vector<Buffer *> buffersToUnmap;
+
 	float lastPointSize;
 
 }; // Shader

+ 10 - 0
src/modules/graphics/opengl/Texture.cpp

@@ -42,6 +42,11 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo
 	glGenFramebuffers(1, &framebuffer);
 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, framebuffer);
 
+	// Intel driver bug: https://github.com/love2d/love/issues/1592
+	bool current_srgb = gl.isStateEnabled(OpenGL::ENABLE_FRAMEBUFFER_SRGB);
+	if (current_srgb && isPixelFormatDepthStencil(format))
+		gl.setEnableState(OpenGL::ENABLE_FRAMEBUFFER_SRGB, false);
+
 	if (texture != 0)
 	{
 		if (isPixelFormatDepthStencil(format) && (GLAD_ES_VERSION_3_0 || !GLAD_ES_VERSION_2_0))
@@ -103,6 +108,11 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo
 	GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
 
 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
+
+	// Restore sRGB state if we turned it off above.
+	if (current_srgb && isPixelFormatDepthStencil(format))
+		gl.setEnableState(OpenGL::ENABLE_FRAMEBUFFER_SRGB, current_srgb);
+
 	return status;
 }
 

+ 189 - 216
src/modules/graphics/vertex.cpp

@@ -25,8 +25,6 @@ namespace love
 {
 namespace graphics
 {
-namespace vertex
-{
 
 static_assert(sizeof(Color32) == 4, "sizeof(Color32) incorrect!");
 static_assert(sizeof(STf_RGBAub) == sizeof(float)*2 + sizeof(Color32), "sizeof(STf_RGBAub) incorrect!");
@@ -103,6 +101,68 @@ int getFormatPositionComponents(CommonFormat format)
 	return 0;
 }
 
+// Order here relies on order of DataFormat enum.
+static const DataFormatInfo dataFormatInfo[]
+{
+	// baseType, isMatrix, components, rows, columns, componentSize, size
+	{ DATA_BASETYPE_FLOAT, false, 1, 0, 0, 4, 4  }, // DATAFORMAT_FLOAT
+	{ DATA_BASETYPE_FLOAT, false, 2, 0, 0, 4, 8  }, // DATAFORMAT_FLOAT_VEC2
+	{ DATA_BASETYPE_FLOAT, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_FLOAT_VEC3
+	{ DATA_BASETYPE_FLOAT, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_FLOAT_VEC4
+
+	{ DATA_BASETYPE_FLOAT, true, 0, 2, 2, 4, 16 }, // DATAFORMAT_FLOAT_MAT2X2
+	{ DATA_BASETYPE_FLOAT, true, 0, 2, 3, 4, 24 }, // DATAFORMAT_FLOAT_MAT2X3
+	{ DATA_BASETYPE_FLOAT, true, 0, 2, 4, 4, 32 }, // DATAFORMAT_FLOAT_MAT2X4
+
+	{ DATA_BASETYPE_FLOAT, true, 0, 3, 2, 4, 24 }, // DATAFORMAT_FLOAT_MAT3X2
+	{ DATA_BASETYPE_FLOAT, true, 0, 3, 3, 4, 36 }, // DATAFORMAT_FLOAT_MAT3X3
+	{ DATA_BASETYPE_FLOAT, true, 0, 3, 4, 4, 48 }, // DATAFORMAT_FLOAT_MAT3X4
+
+	{ DATA_BASETYPE_FLOAT, true, 0, 4, 2, 4, 32 }, // DATAFORMAT_FLOAT_MAT4X2
+	{ DATA_BASETYPE_FLOAT, true, 0, 4, 3, 4, 48 }, // DATAFORMAT_FLOAT_MAT4X3
+	{ DATA_BASETYPE_FLOAT, true, 0, 4, 4, 4, 64 }, // DATAFORMAT_FLOAT_MAT4X4
+
+	{ DATA_BASETYPE_INT, false, 1, 0, 0, 4, 4  }, // DATAFORMAT_INT32
+	{ DATA_BASETYPE_INT, false, 2, 0, 0, 4, 8  }, // DATAFORMAT_INT32_VEC2
+	{ DATA_BASETYPE_INT, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_INT32_VEC3
+	{ DATA_BASETYPE_INT, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_INT32_VEC4
+
+	{ DATA_BASETYPE_UINT, false, 1, 0, 0, 4, 4  }, // DATAFORMAT_UINT32
+	{ DATA_BASETYPE_UINT, false, 2, 0, 0, 4, 8  }, // DATAFORMAT_UINT32_VEC2
+	{ DATA_BASETYPE_UINT, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_UINT32_VEC3
+	{ DATA_BASETYPE_UINT, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_UINT32_VEC4
+
+	{ DATA_BASETYPE_SNORM, false, 4, 0, 0, 1, 4 }, // DATAFORMAT_SNORM8_VEC4
+	{ DATA_BASETYPE_UNORM, false, 4, 0, 0, 1, 4 }, // DATAFORMAT_UNORM8_VEC4
+	{ DATA_BASETYPE_INT,   false, 4, 0, 0, 1, 4 }, // DATAFORMAT_INT8_VEC4
+	{ DATA_BASETYPE_UINT,  false, 4, 0, 0, 1, 4 }, // DATAFORMAT_UINT8_VEC4
+
+	{ DATA_BASETYPE_SNORM, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_SNORM16_VEC2
+	{ DATA_BASETYPE_SNORM, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_SNORM16_VEC4
+
+	{ DATA_BASETYPE_UNORM, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_UNORM16_VEC2
+	{ DATA_BASETYPE_UNORM, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_UNORM16_VEC4
+
+	{ DATA_BASETYPE_INT, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_INT16_VEC2
+	{ DATA_BASETYPE_INT, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_INT16_VEC4
+
+	{ DATA_BASETYPE_UINT, false, 1, 0, 0, 2, 2 }, // DATAFORMAT_UINT16
+	{ DATA_BASETYPE_UINT, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_UINT16_VEC2
+	{ DATA_BASETYPE_UINT, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_UINT16_VEC4
+
+	{ DATA_BASETYPE_BOOL, false, 1, 0, 0, 4, 4  }, // DATAFORMAT_BOOL
+	{ DATA_BASETYPE_BOOL, false, 2, 0, 0, 4, 8  }, // DATAFORMAT_BOOL_VEC2
+	{ DATA_BASETYPE_BOOL, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_BOOL_VEC3
+	{ DATA_BASETYPE_BOOL, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_BOOL_VEC4
+};
+
+static_assert((sizeof(dataFormatInfo) / sizeof(DataFormatInfo)) == DATAFORMAT_MAX_ENUM, "dataFormatInfo array size must match number of DataFormat enum values.");
+
+const DataFormatInfo &getDataFormatInfo(DataFormat format)
+{
+	return dataFormatInfo[format];
+}
+
 size_t getIndexDataSize(IndexDataType type)
 {
 	switch (type)
@@ -113,63 +173,36 @@ size_t getIndexDataSize(IndexDataType type)
 	}
 }
 
-size_t getDataTypeSize(DataType datatype)
+IndexDataType getIndexDataTypeFromMax(size_t maxvalue)
 {
-	switch (datatype)
-	{
-	case DATA_SNORM8:
-	case DATA_UNORM8:
-	case DATA_INT8:
-	case DATA_UINT8:
-		return sizeof(uint8);
-	case DATA_SNORM16:
-	case DATA_UNORM16:
-	case DATA_INT16:
-	case DATA_UINT16:
-		return sizeof(uint16);
-	case DATA_INT32:
-	case DATA_UINT32:
-		return sizeof(uint32);
-	case DATA_FLOAT:
-		return sizeof(float);
-	case DATA_MAX_ENUM:
-		return 0;
-	}
-	return 0;
+	return maxvalue > LOVE_UINT16_MAX ? INDEX_UINT32 : INDEX_UINT16;
 }
 
-bool isDataTypeInteger(DataType datatype)
+DataFormat getIndexDataFormat(IndexDataType type)
 {
-	switch (datatype)
-	{
-	case DATA_INT8:
-	case DATA_UINT8:
-	case DATA_INT16:
-	case DATA_UINT16:
-	case DATA_INT32:
-	case DATA_UINT32:
-		return true;
-	default:
-		return false;
-	}
+	return type == INDEX_UINT32 ? DATAFORMAT_UINT32 : DATAFORMAT_UINT16;
 }
 
-IndexDataType getIndexDataTypeFromMax(size_t maxvalue)
+IndexDataType getIndexDataType(DataFormat format)
 {
-	IndexDataType types[] = {INDEX_UINT16, INDEX_UINT32};
-	return types[maxvalue > LOVE_UINT16_MAX ? 1 : 0];
+	switch (format)
+	{
+		case DATAFORMAT_UINT16: return INDEX_UINT16;
+		case DATAFORMAT_UINT32: return INDEX_UINT32;
+		default: return INDEX_MAX_ENUM;
+	}
 }
 
 int getIndexCount(TriangleIndexMode mode, int vertexCount)
 {
 	switch (mode)
 	{
-	case TriangleIndexMode::NONE:
+	case TRIANGLEINDEX_NONE:
 		return 0;
-	case TriangleIndexMode::STRIP:
-	case TriangleIndexMode::FAN:
+	case TRIANGLEINDEX_STRIP:
+	case TRIANGLEINDEX_FAN:
 		return 3 * (vertexCount - 2);
-	case TriangleIndexMode::QUADS:
+	case TRIANGLEINDEX_QUADS:
 		return vertexCount * 6 / 4;
 	}
 	return 0;
@@ -180,9 +213,9 @@ static void fillIndicesT(TriangleIndexMode mode, T vertexStart, T vertexCount, T
 {
 	switch (mode)
 	{
-	case TriangleIndexMode::NONE:
+	case TRIANGLEINDEX_NONE:
 		break;
-	case TriangleIndexMode::STRIP:
+	case TRIANGLEINDEX_STRIP:
 		{
 			int i = 0;
 			for (T index = 0; index < vertexCount - 2; index++)
@@ -193,7 +226,7 @@ static void fillIndicesT(TriangleIndexMode mode, T vertexStart, T vertexCount, T
 			}
 		}
 		break;
-	case TriangleIndexMode::FAN:
+	case TRIANGLEINDEX_FAN:
 		{
 			int i = 0;
 			for (T index = 2; index < vertexCount; index++)
@@ -204,7 +237,7 @@ static void fillIndicesT(TriangleIndexMode mode, T vertexStart, T vertexCount, T
 			}
 		}
 		break;
-	case TriangleIndexMode::QUADS:
+	case TRIANGLEINDEX_QUADS:
 		{
 			// 0---2
 			// | / |
@@ -238,7 +271,7 @@ void fillIndices(TriangleIndexMode mode, uint32 vertexStart, uint32 vertexCount,
 	fillIndicesT(mode, vertexStart, vertexCount, indices);
 }
 
-void Attributes::setCommonFormat(CommonFormat format, uint8 bufferindex)
+void VertexAttributes::setCommonFormat(CommonFormat format, uint8 bufferindex)
 {
 	setBufferLayout(bufferindex, (uint16) getFormatStride(format));
 
@@ -247,241 +280,181 @@ void Attributes::setCommonFormat(CommonFormat format, uint8 bufferindex)
 	case CommonFormat::NONE:
 		break;
 	case CommonFormat::XYf:
-		set(ATTRIB_POS, DATA_FLOAT, 2, 0, bufferindex);
+		set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex);
 		break;
 	case CommonFormat::XYZf:
-		set(ATTRIB_POS, DATA_FLOAT, 3, 0, bufferindex);
+		set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC3, 0, bufferindex);
 		break;
 	case CommonFormat::RGBAub:
-		set(ATTRIB_COLOR, DATA_UNORM8, 4, 0, bufferindex);
+		set(ATTRIB_COLOR, DATAFORMAT_UNORM8_VEC4, 0, bufferindex);
 		break;
 	case CommonFormat::STf_RGBAub:
-		set(ATTRIB_TEXCOORD, DATA_FLOAT, 2, 0, bufferindex);
-		set(ATTRIB_COLOR, DATA_UNORM8, 4, uint16(sizeof(float) * 2), bufferindex);
+		set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC2, 0, bufferindex);
+		set(ATTRIB_COLOR, DATAFORMAT_UNORM8_VEC4, uint16(sizeof(float) * 2), bufferindex);
 		break;
 	case CommonFormat::STPf_RGBAub:
-		set(ATTRIB_TEXCOORD, DATA_FLOAT, 3, 0, bufferindex);
-		set(ATTRIB_COLOR, DATA_UNORM8, 4, uint16(sizeof(float) * 3), bufferindex);
+		set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC3, 0, bufferindex);
+		set(ATTRIB_COLOR, DATAFORMAT_UNORM8_VEC4, uint16(sizeof(float) * 3), bufferindex);
 		break;
 	case CommonFormat::XYf_STf:
-		set(ATTRIB_POS, DATA_FLOAT, 2, 0, bufferindex);
-		set(ATTRIB_TEXCOORD, DATA_FLOAT, 2, uint16(sizeof(float) * 2), bufferindex);
+		set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex);
+		set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC2, uint16(sizeof(float) * 2), bufferindex);
 		break;
 	case CommonFormat::XYf_STPf:
-		set(ATTRIB_POS, DATA_FLOAT, 2, 0, bufferindex);
-		set(ATTRIB_TEXCOORD, DATA_FLOAT, 3, uint16(sizeof(float) * 2), bufferindex);
+		set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex);
+		set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC3, uint16(sizeof(float) * 2), bufferindex);
 		break;
 	case CommonFormat::XYf_STf_RGBAub:
-		set(ATTRIB_POS, DATA_FLOAT, 2, 0, bufferindex);
-		set(ATTRIB_TEXCOORD, DATA_FLOAT, 2, uint16(sizeof(float) * 2), bufferindex);
-		set(ATTRIB_COLOR, DATA_UNORM8, 4, uint16(sizeof(float) * 4), bufferindex);
+		set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex);
+		set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC2, uint16(sizeof(float) * 2), bufferindex);
+		set(ATTRIB_COLOR, DATAFORMAT_UNORM8_VEC4, uint16(sizeof(float) * 4), bufferindex);
 		break;
 	case CommonFormat::XYf_STus_RGBAub:
-		set(ATTRIB_POS, DATA_FLOAT, 2, 0, bufferindex);
-		set(ATTRIB_TEXCOORD, DATA_UNORM16, 2, uint16(sizeof(float) * 2), bufferindex);
-		set(ATTRIB_COLOR, DATA_UNORM8, 4, uint16(sizeof(float) * 2 + sizeof(uint16) * 2), bufferindex);
+		set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex);
+		set(ATTRIB_TEXCOORD, DATAFORMAT_UNORM16_VEC2, uint16(sizeof(float) * 2), bufferindex);
+		set(ATTRIB_COLOR, DATAFORMAT_UNORM8_VEC4, uint16(sizeof(float) * 2 + sizeof(uint16) * 2), bufferindex);
 		break;
 	case CommonFormat::XYf_STPf_RGBAub:
-		set(ATTRIB_POS, DATA_FLOAT, 2, 0, bufferindex);
-		set(ATTRIB_TEXCOORD, DATA_FLOAT, 3, uint16(sizeof(float) * 2), bufferindex);
-		set(ATTRIB_COLOR, DATA_UNORM8, 4, uint16(sizeof(float) * 5), bufferindex);
+		set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex);
+		set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC3, uint16(sizeof(float) * 2), bufferindex);
+		set(ATTRIB_COLOR, DATAFORMAT_UNORM8_VEC4, uint16(sizeof(float) * 5), bufferindex);
 		break;
 	}
 }
 
-static StringMap<BuiltinVertexAttribute, ATTRIB_MAX_ENUM>::Entry attribNameEntries[] =
+STRINGMAP_BEGIN(BuiltinVertexAttribute, ATTRIB_MAX_ENUM, attribName)
 {
 	{ "VertexPosition", ATTRIB_POS           },
 	{ "VertexTexCoord", ATTRIB_TEXCOORD      },
 	{ "VertexColor",    ATTRIB_COLOR         },
-};
-
-static StringMap<BuiltinVertexAttribute, ATTRIB_MAX_ENUM> attribNames(attribNameEntries, sizeof(attribNameEntries));
-
-static StringMap<IndexDataType, INDEX_MAX_ENUM>::Entry indexTypeEntries[] =
-{
-	{ "uint16", INDEX_UINT16 },
-	{ "uint32", INDEX_UINT32 },
-};
-
-static StringMap<IndexDataType, INDEX_MAX_ENUM> indexTypes(indexTypeEntries, sizeof(indexTypeEntries));
-
-static StringMap<Usage, USAGE_MAX_ENUM>::Entry usageEntries[] =
-{
-	{ "stream",  USAGE_STREAM  },
-	{ "dynamic", USAGE_DYNAMIC },
-	{ "static",  USAGE_STATIC  },
-};
-
-static StringMap<Usage, USAGE_MAX_ENUM> usages(usageEntries, sizeof(usageEntries));
-
-static StringMap<PrimitiveType, PRIMITIVE_MAX_ENUM>::Entry primitiveTypeEntries[] =
-{
-	{ "fan",       PRIMITIVE_TRIANGLE_FAN   },
-	{ "strip",     PRIMITIVE_TRIANGLE_STRIP },
-	{ "triangles", PRIMITIVE_TRIANGLES      },
-	{ "points",    PRIMITIVE_POINTS         },
-};
-
-static StringMap<PrimitiveType, PRIMITIVE_MAX_ENUM> primitiveTypes(primitiveTypeEntries, sizeof(primitiveTypeEntries));
-
-static StringMap<AttributeStep, STEP_MAX_ENUM>::Entry attributeStepEntries[] =
-{
-	{ "pervertex",   STEP_PER_VERTEX   },
-	{ "perinstance", STEP_PER_INSTANCE },
-};
-
-static StringMap<AttributeStep, STEP_MAX_ENUM> attributeSteps(attributeStepEntries, sizeof(attributeStepEntries));
-
-static StringMap<DataType, DATA_MAX_ENUM>::Entry dataTypeEntries[] =
-{
-	{ "snorm8",  DATA_SNORM8  },
-	{ "unorm8",  DATA_UNORM8  },
-	{ "int8",    DATA_INT8    },
-	{ "uint8",   DATA_UINT8   },
-	{ "snorm16", DATA_SNORM16 },
-	{ "unorm16", DATA_UNORM16 },
-	{ "int16",   DATA_INT16   },
-	{ "uint16",  DATA_UINT16  },
-	{ "int32",   DATA_INT32   },
-	{ "uint32",  DATA_UINT32  },
-	{ "float",   DATA_FLOAT   },
-};
-
-static StringMap<DataType, DATA_MAX_ENUM> dataTypes(dataTypeEntries, sizeof(dataTypeEntries));
-
-static StringMap<CullMode, CULL_MAX_ENUM>::Entry cullModeEntries[] =
-{
-	{ "none",  CULL_NONE  },
-	{ "back",  CULL_BACK  },
-	{ "front", CULL_FRONT },
-};
-
-static StringMap<CullMode, CULL_MAX_ENUM> cullModes(cullModeEntries, sizeof(cullModeEntries));
-
-static StringMap<Winding, WINDING_MAX_ENUM>::Entry windingEntries[] =
-{
-	{ "cw",  WINDING_CW  },
-	{ "ccw", WINDING_CCW },
-};
-
-static StringMap<Winding, WINDING_MAX_ENUM> windings(windingEntries, sizeof(windingEntries));
-
-bool getConstant(const char *in, BuiltinVertexAttribute &out)
-{
-	return attribNames.find(in, out);
-}
-
-bool getConstant(BuiltinVertexAttribute in, const char *&out)
-{
-	return attribNames.find(in, out);
 }
+STRINGMAP_END(BuiltinVertexAttribute, ATTRIB_MAX_ENUM, attribName)
 
-bool getConstant(const char *in, IndexDataType &out)
+const char *getConstant(BuiltinVertexAttribute attrib)
 {
-	return indexTypes.find(in, out);
+	const char *name = nullptr;
+	getConstant(attrib, name);
+	return name;
 }
 
-bool getConstant(IndexDataType in, const char *&out)
+STRINGMAP_BEGIN(BufferType, BUFFERTYPE_MAX_ENUM, bufferTypeName)
 {
-	return indexTypes.find(in, out);
+	{ "vertex", BUFFERTYPE_VERTEX },
+	{ "index",  BUFFERTYPE_INDEX  },
+	{ "texel",  BUFFERTYPE_TEXEL  },
 }
+STRINGMAP_END(BufferType, BUFFERTYPE_MAX_ENUM, bufferTypeName)
 
-std::vector<std::string> getConstants(IndexDataType)
+STRINGMAP_BEGIN(IndexDataType, INDEX_MAX_ENUM, indexType)
 {
-	return indexTypes.getNames();
+	{ "uint16", INDEX_UINT16 },
+	{ "uint32", INDEX_UINT32 },
 }
+STRINGMAP_END(IndexDataType, INDEX_MAX_ENUM, indexType)
 
-bool getConstant(const char *in, Usage &out)
+STRINGMAP_BEGIN(BufferUsage, BUFFERUSAGE_MAX_ENUM, bufferUsage)
 {
-	return usages.find(in, out);
+	{ "stream",  BUFFERUSAGE_STREAM  },
+	{ "dynamic", BUFFERUSAGE_DYNAMIC },
+	{ "static",  BUFFERUSAGE_STATIC  },
 }
+STRINGMAP_END(BufferUsage, BUFFERUSAGE_MAX_ENUM, bufferUsage)
 
-bool getConstant(Usage in, const char *&out)
+STRINGMAP_BEGIN(PrimitiveType, PRIMITIVE_MAX_ENUM, primitiveType)
 {
-	return usages.find(in, out);
+	{ "fan",       PRIMITIVE_TRIANGLE_FAN   },
+	{ "strip",     PRIMITIVE_TRIANGLE_STRIP },
+	{ "triangles", PRIMITIVE_TRIANGLES      },
+	{ "points",    PRIMITIVE_POINTS         },
 }
+STRINGMAP_END(PrimitiveType, PRIMITIVE_MAX_ENUM, primitiveType)
 
-std::vector<std::string> getConstants(Usage)
+STRINGMAP_BEGIN(AttributeStep, STEP_MAX_ENUM, attributeStep)
 {
-	return usages.getNames();
+	{ "pervertex",   STEP_PER_VERTEX   },
+	{ "perinstance", STEP_PER_INSTANCE },
 }
+STRINGMAP_END(AttributeStep, STEP_MAX_ENUM, attributeStep)
 
-bool getConstant(const char *in, PrimitiveType &out)
+STRINGMAP_BEGIN(DataFormat, DATAFORMAT_MAX_ENUM, dataFormat)
 {
-	return primitiveTypes.find(in, out);
-}
+	{ "float",     DATAFORMAT_FLOAT      },
+	{ "floatvec2", DATAFORMAT_FLOAT_VEC2 },
+	{ "floatvec3", DATAFORMAT_FLOAT_VEC3 },
+	{ "floatvec4", DATAFORMAT_FLOAT_VEC4 },
 
-bool getConstant(PrimitiveType in, const char *&out)
-{
-	return primitiveTypes.find(in, out);
-}
+	{ "floatmat2x2", DATAFORMAT_FLOAT_MAT2X2 },
+	{ "floatmat2x3", DATAFORMAT_FLOAT_MAT2X3 },
+	{ "floatmat2x4", DATAFORMAT_FLOAT_MAT2X4 },
 
-std::vector<std::string> getConstants(PrimitiveType)
-{
-	return primitiveTypes.getNames();
-}
+	{ "floatmat3x2", DATAFORMAT_FLOAT_MAT3X2 },
+	{ "floatmat3x3", DATAFORMAT_FLOAT_MAT3X3 },
+	{ "floatmat3x4", DATAFORMAT_FLOAT_MAT3X4 },
 
-bool getConstant(const char *in, AttributeStep &out)
-{
-	return attributeSteps.find(in, out);
-}
+	{ "floatmat4x2", DATAFORMAT_FLOAT_MAT4X2 },
+	{ "floatmat4x3", DATAFORMAT_FLOAT_MAT4X3 },
+	{ "floatmat4x4", DATAFORMAT_FLOAT_MAT4X4 },
 
-bool getConstant(AttributeStep in, const char *&out)
-{
-	return attributeSteps.find(in, out);
-}
+	{ "int32",     DATAFORMAT_INT32      },
+	{ "int32vec2", DATAFORMAT_INT32_VEC2 },
+	{ "int32vec3", DATAFORMAT_INT32_VEC3 },
+	{ "int32vec4", DATAFORMAT_INT32_VEC4 },
 
-std::vector<std::string> getConstants(AttributeStep)
-{
-	return attributeSteps.getNames();
-}
+	{ "uint32",     DATAFORMAT_UINT32      },
+	{ "uint32vec2", DATAFORMAT_UINT32_VEC2 },
+	{ "uint32vec3", DATAFORMAT_UINT32_VEC3 },
+	{ "uint32vec4", DATAFORMAT_UINT32_VEC4 },
 
-bool getConstant(const char *in, DataType &out)
-{
-	return dataTypes.find(in, out);
-}
+	{ "snorm8vec4", DATAFORMAT_SNORM8_VEC4 },
+	{ "unorm8vec4", DATAFORMAT_UNORM8_VEC4 },
+	{ "int8vec4",   DATAFORMAT_INT8_VEC4   },
+	{ "uint8vec4",  DATAFORMAT_UINT8_VEC4  },
 
-bool getConstant(DataType in, const char *&out)
-{
-	return dataTypes.find(in, out);
-}
+	{ "snorm16vec2", DATAFORMAT_SNORM16_VEC2 },
+	{ "snorm16vec4", DATAFORMAT_SNORM16_VEC4 },
 
-std::vector<std::string> getConstants(DataType)
-{
-	return dataTypes.getNames();
-}
+	{ "unorm16vec2", DATAFORMAT_UNORM16_VEC2 },
+	{ "unorm16vec4", DATAFORMAT_UNORM16_VEC4 },
 
-bool getConstant(const char *in, CullMode &out)
-{
-	return cullModes.find(in, out);
-}
+	{ "int16vec2", DATAFORMAT_INT16_VEC2 },
+	{ "int16vec4", DATAFORMAT_INT16_VEC4 },
 
-bool getConstant(CullMode in, const char *&out)
-{
-	return cullModes.find(in, out);
-}
+	{ "uint16",     DATAFORMAT_UINT16      },
+	{ "uint16vec2", DATAFORMAT_UINT16_VEC2 },
+	{ "uint16vec4", DATAFORMAT_UINT16_VEC4 },
 
-std::vector<std::string> getConstants(CullMode)
-{
-	return cullModes.getNames();
+	{ "bool",     DATAFORMAT_BOOL      },
+	{ "boolvec2", DATAFORMAT_BOOL_VEC2 },
+	{ "boolvec3", DATAFORMAT_BOOL_VEC3 },
+	{ "boolvec4", DATAFORMAT_BOOL_VEC4 },
 }
+STRINGMAP_END(DataFormat, DATAFORMAT_MAX_ENUM, dataFormat)
 
-bool getConstant(const char *in, Winding &out)
+STRINGMAP_BEGIN(DataBaseType, DATA_BASETYPE_MAX_ENUM, dataBaseType)
 {
-	return windings.find(in, out);
+	{ "float", DATA_BASETYPE_FLOAT },
+	{ "int",   DATA_BASETYPE_INT   },
+	{ "uint",  DATA_BASETYPE_UINT  },
+	{ "snorm", DATA_BASETYPE_SNORM },
+	{ "unorm", DATA_BASETYPE_UNORM },
+	{ "bool",  DATA_BASETYPE_BOOL  },
 }
+STRINGMAP_END(DataBaseType, DATA_BASETYPE_MAX_ENUM, dataBaseType)
 
-bool getConstant(Winding in, const char *&out)
+STRINGMAP_BEGIN(CullMode, CULL_MAX_ENUM, cullMode)
 {
-	return windings.find(in, out);
+	{ "none",  CULL_NONE  },
+	{ "back",  CULL_BACK  },
+	{ "front", CULL_FRONT },
 }
+STRINGMAP_END(CullMode, CULL_MAX_ENUM, cullMode)
 
-std::vector<std::string> getConstants(Winding)
+STRINGMAP_BEGIN(Winding, WINDING_MAX_ENUM, winding)
 {
-	return windings.getNames();
+	{ "cw",  WINDING_CW  },
+	{ "ccw", WINDING_CCW },
 }
+STRINGMAP_END(Winding, WINDING_MAX_ENUM, winding)
 
-} // vertex
 } // graphics
 } // love

+ 118 - 81
src/modules/graphics/vertex.h

@@ -23,6 +23,7 @@
 // LOVE
 #include "common/int.h"
 #include "common/Color.h"
+#include "common/StringMap.h"
 
 // C
 #include <stddef.h>
@@ -46,7 +47,7 @@ enum BuiltinVertexAttribute
 	ATTRIB_MAX_ENUM
 };
 
-enum BuiltinVertexAttributeFlag
+enum BuiltinVertexAttributeFlags
 {
 	ATTRIBFLAG_POS = 1 << ATTRIB_POS,
 	ATTRIBFLAG_TEXCOORD = 1 << ATTRIB_TEXCOORD,
@@ -55,10 +56,10 @@ enum BuiltinVertexAttributeFlag
 
 enum BufferType
 {
-	BUFFER_VERTEX = 0,
-	BUFFER_INDEX,
-	BUFFER_UNIFORM,
-	BUFFER_MAX_ENUM
+	BUFFERTYPE_VERTEX = 0,
+	BUFFERTYPE_INDEX,
+	BUFFERTYPE_TEXEL,
+	BUFFERTYPE_MAX_ENUM
 };
 
 enum IndexDataType
@@ -93,36 +94,81 @@ enum CullMode
 	CULL_MAX_ENUM
 };
 
-namespace vertex
+// The expected usage pattern of buffer data.
+enum BufferUsage
 {
-
-// The expected usage pattern of vertex data.
-enum Usage
-{
-	USAGE_STREAM,
-	USAGE_DYNAMIC,
-	USAGE_STATIC,
-	USAGE_MAX_ENUM
+	BUFFERUSAGE_STREAM,
+	BUFFERUSAGE_DYNAMIC,
+	BUFFERUSAGE_STATIC,
+	BUFFERUSAGE_MAX_ENUM
 };
 
-enum DataType
+// Value types used when interfacing with the GPU (vertex and shader data).
+// The order of this enum affects the dataFormatInfo array.
+enum DataFormat
 {
-	DATA_SNORM8,
-	DATA_UNORM8,
-	DATA_INT8,
-	DATA_UINT8,
+	DATAFORMAT_FLOAT,
+	DATAFORMAT_FLOAT_VEC2,
+	DATAFORMAT_FLOAT_VEC3,
+	DATAFORMAT_FLOAT_VEC4,
+
+	DATAFORMAT_FLOAT_MAT2X2,
+	DATAFORMAT_FLOAT_MAT2X3,
+	DATAFORMAT_FLOAT_MAT2X4,
+
+	DATAFORMAT_FLOAT_MAT3X2,
+	DATAFORMAT_FLOAT_MAT3X3,
+	DATAFORMAT_FLOAT_MAT3X4,
+
+	DATAFORMAT_FLOAT_MAT4X2,
+	DATAFORMAT_FLOAT_MAT4X3,
+	DATAFORMAT_FLOAT_MAT4X4,
+
+	DATAFORMAT_INT32,
+	DATAFORMAT_INT32_VEC2,
+	DATAFORMAT_INT32_VEC3,
+	DATAFORMAT_INT32_VEC4,
+
+	DATAFORMAT_UINT32,
+	DATAFORMAT_UINT32_VEC2,
+	DATAFORMAT_UINT32_VEC3,
+	DATAFORMAT_UINT32_VEC4,
+
+	DATAFORMAT_SNORM8_VEC4,
+	DATAFORMAT_UNORM8_VEC4,
+	DATAFORMAT_INT8_VEC4,
+	DATAFORMAT_UINT8_VEC4,
 
-	DATA_SNORM16,
-	DATA_UNORM16,
-	DATA_INT16,
-	DATA_UINT16,
+	DATAFORMAT_SNORM16_VEC2,
+	DATAFORMAT_SNORM16_VEC4,
 
-	DATA_INT32,
-	DATA_UINT32,
+	DATAFORMAT_UNORM16_VEC2,
+	DATAFORMAT_UNORM16_VEC4,
 
-	DATA_FLOAT,
+	DATAFORMAT_INT16_VEC2,
+	DATAFORMAT_INT16_VEC4,
 
-	DATA_MAX_ENUM
+	DATAFORMAT_UINT16,
+	DATAFORMAT_UINT16_VEC2,
+	DATAFORMAT_UINT16_VEC4,
+
+	DATAFORMAT_BOOL,
+	DATAFORMAT_BOOL_VEC2,
+	DATAFORMAT_BOOL_VEC3,
+	DATAFORMAT_BOOL_VEC4,
+
+	DATAFORMAT_MAX_ENUM
+};
+
+enum DataBaseType
+{
+	DATA_BASETYPE_FLOAT,
+	DATA_BASETYPE_INT,
+	DATA_BASETYPE_UINT,
+	DATA_BASETYPE_SNORM,
+	DATA_BASETYPE_UNORM,
+	DATA_BASETYPE_BOOL,
+	DATA_BASETYPE_MAX_ENUM
 };
 
 enum Winding
@@ -132,12 +178,12 @@ enum Winding
 	WINDING_MAX_ENUM
 };
 
-enum class TriangleIndexMode
+enum TriangleIndexMode
 {
-	NONE,
-	STRIP,
-	FAN,
-	QUADS,
+	TRIANGLEINDEX_NONE,
+	TRIANGLEINDEX_STRIP,
+	TRIANGLEINDEX_FAN,
+	TRIANGLEINDEX_QUADS,
 };
 
 enum class CommonFormat
@@ -155,6 +201,17 @@ enum class CommonFormat
 	XYf_STPf_RGBAub,
 };
 
+struct DataFormatInfo
+{
+	DataBaseType baseType;
+	bool isMatrix;
+	int components;
+	int matrixRows;
+	int matrixColumns;
+	size_t componentSize;
+	size_t size;
+};
+
 struct STf_RGBAub
 {
 	float s, t;
@@ -186,6 +243,8 @@ struct XYf_STf_RGBAub
 	Color32 color;
 };
 
+typedef XYf_STf_RGBAub Vertex;
+
 struct XYf_STus_RGBAub
 {
 	float  x, y;
@@ -222,42 +281,41 @@ struct BufferBindings
 	void clear() { useBits = 0; }
 };
 
-struct AttributeInfo
+struct VertexAttributeInfo
 {
-	DataType type;
-	uint8 components;
 	uint8 bufferIndex;
+	DataFormat format : 8;
 	uint16 offsetFromVertex;
 };
 
-struct BufferLayout
+struct VertexBufferLayout
 {
+	// Attribute step rate is stored outside this struct as a bitmask.
 	uint16 stride;
 };
 
-struct Attributes
+struct VertexAttributes
 {
 	static const uint32 MAX = 32;
 
 	uint32 enableBits = 0; // indexed by attribute
 	uint32 instanceBits = 0; // indexed by buffer
 
-	AttributeInfo attribs[MAX];
-	BufferLayout bufferLayouts[BufferBindings::MAX];
+	VertexAttributeInfo attribs[MAX];
+	VertexBufferLayout bufferLayouts[BufferBindings::MAX];
 
-	Attributes() {}
-	Attributes(CommonFormat format, uint8 bufferindex)
+	VertexAttributes() {}
+	VertexAttributes(CommonFormat format, uint8 bufferindex)
 	{
 		setCommonFormat(format, bufferindex);
 	}
 
-	void set(uint32 index, DataType type, uint8 components, uint16 offsetfromvertex, uint8 bufferindex)
+	void set(uint32 index, DataFormat format, uint16 offsetfromvertex, uint8 bufferindex)
 	{
 		enableBits |= (1u << index);
 
 		attribs[index].bufferIndex = bufferindex;
-		attribs[index].type = type;
-		attribs[index].components = components;
+		attribs[index].format = format;
 		attribs[index].offsetFromVertex = offsetfromvertex;
 	}
 
@@ -307,51 +365,30 @@ inline CommonFormat getSinglePositionFormat(bool is2D)
 	return is2D ? CommonFormat::XYf : CommonFormat::XYZf;
 }
 
-size_t getIndexDataSize(IndexDataType type);
-size_t getDataTypeSize(DataType datatype);
-bool isDataTypeInteger(DataType datatype);
+const DataFormatInfo &getDataFormatInfo(DataFormat format);
 
+size_t getIndexDataSize(IndexDataType type);
 IndexDataType getIndexDataTypeFromMax(size_t maxvalue);
+DataFormat getIndexDataFormat(IndexDataType type);
+IndexDataType getIndexDataType(DataFormat format);
 
 int getIndexCount(TriangleIndexMode mode, int vertexCount);
 
 void fillIndices(TriangleIndexMode mode, uint16 vertexStart, uint16 vertexCount, uint16 *indices);
 void fillIndices(TriangleIndexMode mode, uint32 vertexStart, uint32 vertexCount, uint32 *indices);
 
-bool getConstant(const char *in, BuiltinVertexAttribute &out);
-bool getConstant(BuiltinVertexAttribute in, const char *&out);
-
-bool getConstant(const char *in, IndexDataType &out);
-bool getConstant(IndexDataType in, const char *&out);
-std::vector<std::string> getConstants(IndexDataType);
-
-bool getConstant(const char *in, Usage &out);
-bool getConstant(Usage in, const char *&out);
-std::vector<std::string> getConstants(Usage);
-
-bool getConstant(const char *in, PrimitiveType &out);
-bool getConstant(PrimitiveType in, const char *&out);
-std::vector<std::string> getConstants(PrimitiveType);
-
-bool getConstant(const char *in, AttributeStep &out);
-bool getConstant(AttributeStep in, const char *&out);
-std::vector<std::string> getConstants(AttributeStep);
-
-bool getConstant(const char *in, DataType &out);
-bool getConstant(DataType in, const char *&out);
-std::vector<std::string> getConstants(DataType);
-
-bool getConstant(const char *in, CullMode &out);
-bool getConstant(CullMode in, const char *&out);
-std::vector<std::string> getConstants(CullMode);
-
-bool getConstant(const char *in, Winding &out);
-bool getConstant(Winding in, const char *&out);
-std::vector<std::string> getConstants(Winding);
-
-} // vertex
-
-typedef vertex::XYf_STf_RGBAub Vertex;
+STRINGMAP_DECLARE(BuiltinVertexAttribute);
+STRINGMAP_DECLARE(BufferType);
+STRINGMAP_DECLARE(IndexDataType);
+STRINGMAP_DECLARE(BufferUsage);
+STRINGMAP_DECLARE(PrimitiveType);
+STRINGMAP_DECLARE(AttributeStep);
+STRINGMAP_DECLARE(DataFormat);
+STRINGMAP_DECLARE(DataBaseType);
+STRINGMAP_DECLARE(CullMode);
+STRINGMAP_DECLARE(Winding);
+
+const char *getConstant(BuiltinVertexAttribute attrib);
 
 } // graphics
 } // love

+ 474 - 0
src/modules/graphics/wrap_Buffer.cpp

@@ -0,0 +1,474 @@
+/**
+* Copyright (c) 2006-2020 LOVE Development Team
+*
+* This software is provided 'as-is', without any express or implied
+* warranty.  In no event will the authors be held liable for any damages
+* arising from the use of this software.
+*
+* Permission is granted to anyone to use this software for any purpose,
+* including commercial applications, and to alter it and redistribute it
+* freely, subject to the following restrictions:
+*
+* 1. The origin of this software must not be misrepresented; you must not
+*    claim that you wrote the original software. If you use this software
+*    in a product, an acknowledgment in the product documentation would be
+*    appreciated but is not required.
+* 2. Altered source versions must be plainly marked as such, and must not be
+*    misrepresented as being the original software.
+* 3. This notice may not be removed or altered from any source distribution.
+**/
+
+#include "wrap_Buffer.h"
+#include "Buffer.h"
+#include "common/Data.h"
+
+namespace love
+{
+namespace graphics
+{
+
+static const double defaultComponents[] = {0.0, 0.0, 0.0, 1.0};
+
+template <typename T>
+static inline size_t writeData(lua_State *L, int startidx, int components, char *data)
+{
+	auto componentdata = (T *) data;
+
+	for (int i = 0; i < components; i++)
+		componentdata[i] = (T) (luaL_optnumber(L, startidx + i, defaultComponents[i]));
+
+	return sizeof(T) * components;
+}
+
+template <typename T>
+static inline size_t writeSNormData(lua_State *L, int startidx, int components, char *data)
+{
+	auto componentdata = (T *) data;
+	const auto maxval = std::numeric_limits<T>::max();
+
+	for (int i = 0; i < components; i++)
+		componentdata[i] = (T) (luax_optnumberclamped(L, startidx + i, -1.0, 1.0, defaultComponents[i]) * maxval);
+
+	return sizeof(T) * components;
+}
+
+template <typename T>
+static inline size_t writeUNormData(lua_State *L, int startidx, int components, char *data)
+{
+	auto componentdata = (T *) data;
+	const auto maxval = std::numeric_limits<T>::max();
+
+	for (int i = 0; i < components; i++)
+		componentdata[i] = (T) (luax_optnumberclamped01(L, startidx + i, 1.0) * maxval);
+
+	return sizeof(T) * components;
+}
+
+void luax_writebufferdata(lua_State *L, int startidx, DataFormat format, char *data)
+{
+	switch (format)
+	{
+		case DATAFORMAT_FLOAT:      writeData<float>(L, startidx, 1, data); break;
+		case DATAFORMAT_FLOAT_VEC2: writeData<float>(L, startidx, 2, data); break;
+		case DATAFORMAT_FLOAT_VEC3: writeData<float>(L, startidx, 3, data); break;
+		case DATAFORMAT_FLOAT_VEC4: writeData<float>(L, startidx, 4, data); break;
+
+		case DATAFORMAT_INT32:      writeData<int32>(L, startidx, 1, data); break;
+		case DATAFORMAT_INT32_VEC2: writeData<int32>(L, startidx, 2, data); break;
+		case DATAFORMAT_INT32_VEC3: writeData<int32>(L, startidx, 3, data); break;
+		case DATAFORMAT_INT32_VEC4: writeData<int32>(L, startidx, 4, data); break;
+
+		case DATAFORMAT_UINT32:      writeData<uint32>(L, startidx, 1, data); break;
+		case DATAFORMAT_UINT32_VEC2: writeData<uint32>(L, startidx, 2, data); break;
+		case DATAFORMAT_UINT32_VEC3: writeData<uint32>(L, startidx, 3, data); break;
+		case DATAFORMAT_UINT32_VEC4: writeData<uint32>(L, startidx, 4, data); break;
+
+		case DATAFORMAT_SNORM8_VEC4: writeSNormData<int8>(L, startidx, 4, data); break;
+		case DATAFORMAT_UNORM8_VEC4: writeUNormData<uint8>(L, startidx, 4, data); break;
+		case DATAFORMAT_INT8_VEC4:   writeData<int8>(L, startidx, 4, data); break;
+		case DATAFORMAT_UINT8_VEC4:  writeData<uint8>(L, startidx, 4, data); break;
+
+		case DATAFORMAT_SNORM16_VEC2: writeSNormData<int16>(L, startidx, 2, data); break;
+		case DATAFORMAT_SNORM16_VEC4: writeSNormData<int16>(L, startidx, 4, data); break;
+
+		case DATAFORMAT_UNORM16_VEC2: writeUNormData<uint16>(L, startidx, 2, data); break;
+		case DATAFORMAT_UNORM16_VEC4: writeUNormData<uint16>(L, startidx, 4, data); break;
+
+		case DATAFORMAT_INT16_VEC2: writeData<int16>(L, startidx, 2, data); break;
+		case DATAFORMAT_INT16_VEC4: writeData<int16>(L, startidx, 4, data); break;
+
+		case DATAFORMAT_UINT16:      writeData<uint16>(L, startidx, 1, data); break;
+		case DATAFORMAT_UINT16_VEC2: writeData<uint16>(L, startidx, 2, data); break;
+		case DATAFORMAT_UINT16_VEC4: writeData<uint16>(L, startidx, 4, data); break;
+
+		default: break;
+	}
+}
+
+template <typename T>
+static inline size_t readData(lua_State *L, int components, const char *data)
+{
+	const auto componentdata = (const T *) data;
+
+	for (int i = 0; i < components; i++)
+		lua_pushnumber(L, (lua_Number) componentdata[i]);
+
+	return sizeof(T) * components;
+}
+
+template <typename T>
+static inline size_t readSNormData(lua_State *L, int components, const char *data)
+{
+	const auto componentdata = (const T *) data;
+	const auto maxval = std::numeric_limits<T>::max();
+
+	for (int i = 0; i < components; i++)
+		lua_pushnumber(L, std::max(-1.0, (lua_Number) componentdata[i] / (lua_Number)maxval));
+
+	return sizeof(T) * components;
+}
+
+template <typename T>
+static inline size_t readUNormData(lua_State *L, int components, const char *data)
+{
+	const auto componentdata = (const T *) data;
+	const auto maxval = std::numeric_limits<T>::max();
+
+	for (int i = 0; i < components; i++)
+		lua_pushnumber(L, (lua_Number) componentdata[i] / (lua_Number)maxval);
+
+	return sizeof(T) * components;
+}
+
+void luax_readbufferdata(lua_State *L, DataFormat format, const char *data)
+{
+	switch (format)
+	{
+		case DATAFORMAT_FLOAT:      readData<float>(L, 1, data); break;
+		case DATAFORMAT_FLOAT_VEC2: readData<float>(L, 2, data); break;
+		case DATAFORMAT_FLOAT_VEC3: readData<float>(L, 3, data); break;
+		case DATAFORMAT_FLOAT_VEC4: readData<float>(L, 4, data); break;
+
+		case DATAFORMAT_INT32:      readData<int32>(L, 1, data); break;
+		case DATAFORMAT_INT32_VEC2: readData<int32>(L, 2, data); break;
+		case DATAFORMAT_INT32_VEC3: readData<int32>(L, 3, data); break;
+		case DATAFORMAT_INT32_VEC4: readData<int32>(L, 4, data); break;
+
+		case DATAFORMAT_UINT32:      readData<uint32>(L, 1, data); break;
+		case DATAFORMAT_UINT32_VEC2: readData<uint32>(L, 2, data); break;
+		case DATAFORMAT_UINT32_VEC3: readData<uint32>(L, 3, data); break;
+		case DATAFORMAT_UINT32_VEC4: readData<uint32>(L, 4, data); break;
+
+		case DATAFORMAT_SNORM8_VEC4: readSNormData<int8>(L, 4, data); break;
+		case DATAFORMAT_UNORM8_VEC4: readUNormData<uint8>(L, 4, data); break;
+		case DATAFORMAT_INT8_VEC4:   readData<int8>(L, 4, data); break;
+		case DATAFORMAT_UINT8_VEC4:  readData<uint8>(L, 4, data); break;
+
+		case DATAFORMAT_SNORM16_VEC2: readSNormData<int16>(L, 2, data); break;
+		case DATAFORMAT_SNORM16_VEC4: readSNormData<int16>(L, 4, data); break;
+
+		case DATAFORMAT_UNORM16_VEC2: readUNormData<uint16>(L, 2, data); break;
+		case DATAFORMAT_UNORM16_VEC4: readUNormData<uint16>(L, 4, data); break;
+
+		case DATAFORMAT_INT16_VEC2: readData<int16>(L, 2, data); break;
+		case DATAFORMAT_INT16_VEC4: readData<int16>(L, 4, data); break;
+
+		case DATAFORMAT_UINT16:      readData<uint16>(L, 1, data); break;
+		case DATAFORMAT_UINT16_VEC2: readData<uint16>(L, 2, data); break;
+		case DATAFORMAT_UINT16_VEC4: readData<uint16>(L, 4, data); break;
+
+		default: break;
+	}
+}
+
+Buffer *luax_checkbuffer(lua_State *L, int idx)
+{
+	return luax_checktype<Buffer>(L, idx);
+}
+
+static int w_Buffer_flush(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	t->unmap();
+	return 0;
+}
+
+static int w_Buffer_setArrayData(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+
+	int startindex = (int) luaL_optnumber(L, 3, 1) - 1;
+
+	int count = -1;
+	if (!lua_isnoneornil(L, 4))
+	{
+		count = (int) luaL_checknumber(L, 4);
+		if (count <= 0)
+			return luaL_error(L, "Element count must be greater than 0.");
+	}
+
+	size_t stride = t->getArrayStride();
+	size_t offset = startindex * stride;
+	int arraylength = (int) t->getArrayLength();
+
+	if (startindex >= arraylength || startindex < 0)
+		return luaL_error(L, "Invalid vertex start index (must be between 1 and %d)", arraylength);
+
+	if (luax_istype(L, 2, Data::type))
+	{
+		Data *d = luax_checktype<Data>(L, 2);
+
+		count = count >= 0 ? count : (arraylength - startindex);
+		if (startindex + count > arraylength)
+			return luaL_error(L, "Too many array elements (expected at most %d, got %d)", arraylength - startindex, count);
+
+		size_t datasize = std::min(d->getSize(), count * stride);
+		char *bytedata = (char *) t->map() + offset;
+
+		memcpy(bytedata, d->getData(), datasize);
+
+		t->setMappedRangeModified(offset, datasize);
+		t->unmap();
+		return 0;
+	}
+
+	const std::vector<Buffer::DataMember> &members = t->getDataMembers();
+
+	int ncomponents = 0;
+	for (const Buffer::DataMember &member : members)
+		ncomponents += member.info.components;
+
+	luaL_checktype(L, 2, LUA_TTABLE);
+	int tablelen = (int) luax_objlen(L, 2);
+
+	lua_rawgeti(L, 2, 1);
+	bool tableoftables = lua_istable(L, -1);
+	lua_pop(L, 1);
+
+	if (!tableoftables)
+	{
+		if (tablelen % ncomponents != 0)
+			return luaL_error(L, "Array length in flat array variant of Buffer:setArrayData must be a multiple of the total number of components (%d)", ncomponents);
+		tablelen /= ncomponents;
+	}
+
+	count = count >= 0 ? std::min(count, tablelen) : tablelen;
+	if (startindex + count > arraylength)
+		return luaL_error(L, "Too many array elements (expected at most %d, got %d)", arraylength - startindex, count);
+
+	char *data = (char *) t->map() + offset;
+
+	if (tableoftables)
+	{
+		for (int i = 0; i < count; i++)
+		{
+			// get arraydata[index]
+			lua_rawgeti(L, 2, i + 1);
+			luaL_checktype(L, -1, LUA_TTABLE);
+
+			// get arraydata[index][j]
+			for (int j = 1; j <= ncomponents; j++)
+				lua_rawgeti(L, -j, j);
+
+			int idx = -ncomponents;
+
+			for (const Buffer::DataMember &member : members)
+			{
+				luax_writebufferdata(L, idx, member.decl.format, data + member.offset);
+				idx += member.info.components;
+			}
+
+			lua_pop(L, ncomponents + 1);
+			data += stride;
+		}
+	}
+	else // Flat array
+	{
+		for (int i = 0; i < count; i++)
+		{
+			// get arraydata[arrayindex * ncomponents + componentindex]
+			for (int componentindex = 1; componentindex <= ncomponents; componentindex++)
+				lua_rawgeti(L, 2, i * ncomponents + componentindex);
+
+			int idx = -ncomponents;
+
+			for (const Buffer::DataMember &member : members)
+			{
+				luax_writebufferdata(L, idx, member.decl.format, data + member.offset);
+				idx += member.info.components;
+			}
+
+			lua_pop(L, ncomponents);
+			data += stride;
+		}
+	}
+
+	t->setMappedRangeModified(offset, count * stride);
+	t->unmap();
+
+	return 0;
+}
+
+static int w_Buffer_setElement(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+
+	size_t index = (size_t) (luaL_checkinteger(L, 2) - 1);
+	if (index >= t->getArrayLength())
+		return luaL_error(L, "Invalid Buffer element index: %d", (int) index + 1);
+
+	size_t stride = t->getArrayStride();
+	size_t offset = index * stride;
+	char *data = (char *) t->map() + offset;
+	const auto &members = t->getDataMembers();
+
+	bool istable = lua_istable(L, 3);
+	int idx = istable ? 1 : 3;
+
+	if (istable)
+	{
+		for (const Buffer::DataMember &member : members)
+		{
+			int components = member.info.components;
+
+			for (int i = idx; i < idx + components; i++)
+				lua_rawgeti(L, 3, i);
+
+			luax_writebufferdata(L, -components, member.decl.format, data + member.offset);
+
+			idx += components;
+			lua_pop(L, components);
+		}
+	}
+	else
+	{
+		for (const Buffer::DataMember &member : members)
+		{
+			luax_writebufferdata(L, idx, member.decl.format, data + member.offset);
+			idx += member.info.components;
+		}
+	}
+
+	t->setMappedRangeModified(offset, stride);
+	return 0;
+}
+
+static int w_Buffer_getElement(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	if ((t->getMapFlags() & Buffer::MAP_READ) == 0)
+		return luaL_error(L, "Buffer:getElement requires the buffer to be created with the 'cpureadable' setting set to true.");
+
+	size_t index = (size_t) (luaL_checkinteger(L, 2) - 1);
+	if (index >= t->getArrayLength())
+		return luaL_error(L, "Invalid Buffer element index: %d", (int) index + 1);
+
+	size_t offset = index * t->getArrayStride();
+	const char *data = (const char *) t->map() + offset;
+	const auto &members = t->getDataMembers();
+
+	int n = 0;
+
+	for (const Buffer::DataMember &member : members)
+	{
+		luax_readbufferdata(L, member.decl.format, data + member.offset);
+		n += member.info.components;
+	}
+
+	return n;
+}
+
+static int w_Buffer_getElementCount(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	lua_pushinteger(L, t->getArrayLength());
+	return 1;
+}
+
+static int w_Buffer_getElementStride(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	lua_pushinteger(L, t->getArrayStride());
+	return 1;
+}
+
+static int w_Buffer_getSize(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	lua_pushinteger(L, t->getSize());
+	return 1;
+}
+
+static int w_Buffer_getFormat(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	const auto &members = t->getDataMembers();
+
+	lua_createtable(L, (int) members.size(), 0);
+
+	for (size_t i = 0; i < members.size(); i++)
+	{
+		const Buffer::DataMember &member = members[i];
+
+		lua_createtable(L, 0, 4);
+
+		lua_pushstring(L, member.decl.name.c_str());
+		lua_setfield(L, -2, "name");
+
+		const char *formatstr = "unknown";
+		getConstant(member.decl.format, formatstr);
+		lua_pushstring(L, formatstr);
+		lua_setfield(L, -2, "format");
+
+		lua_pushinteger(L, member.decl.arrayLength);
+		lua_setfield(L, -2, "arraylength");
+
+		lua_pushinteger(L, member.offset);
+		lua_setfield(L, -2, "offset");
+
+		lua_rawseti(L, -2, i + 1);
+	}
+
+	return 1;
+}
+
+static int w_Buffer_isBufferType(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	BufferType buffertype = BUFFERTYPE_MAX_ENUM;
+	const char *typestr = luaL_checkstring(L, 2);
+	if (!getConstant(typestr, buffertype))
+		return luax_enumerror(L, "buffer type", getConstants(buffertype), typestr);
+	luax_pushboolean(L, (t->getTypeFlags() & (1 << buffertype)) != 0);
+	return 1;
+}
+
+static int w_Buffer_isCPUReadable(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	luax_pushboolean(L, (t->getMapFlags() & Buffer::MAP_READ) != 0);
+	return 1;
+}
+
+static const luaL_Reg w_Buffer_functions[] =
+{
+	{ "flush", w_Buffer_flush },
+	{ "setArrayData", w_Buffer_setArrayData },
+	{ "setElement", w_Buffer_setElement },
+	{ "getElement", w_Buffer_getElement },
+	{ "getElementCount", w_Buffer_getElementCount },
+	{ "getElementStride", w_Buffer_getElementStride },
+	{ "getSize", w_Buffer_getSize },
+	{ "getFormat", w_Buffer_getFormat },
+	{ "isBufferType", w_Buffer_isBufferType },
+	{ "isCPUReadable", w_Buffer_isCPUReadable },
+	{ 0, 0 }
+};
+
+extern "C" int luaopen_graphicsbuffer(lua_State *L)
+{
+	return luax_register_type(L, &Buffer::type, w_Buffer_functions, nullptr);
+}
+
+} // graphics
+} // love

+ 39 - 0
src/modules/graphics/wrap_Buffer.h

@@ -0,0 +1,39 @@
+/**
+* Copyright (c) 2006-2020 LOVE Development Team
+*
+* This software is provided 'as-is', without any express or implied
+* warranty.  In no event will the authors be held liable for any damages
+* arising from the use of this software.
+*
+* Permission is granted to anyone to use this software for any purpose,
+* including commercial applications, and to alter it and redistribute it
+* freely, subject to the following restrictions:
+*
+* 1. The origin of this software must not be misrepresented; you must not
+*    claim that you wrote the original software. If you use this software
+*    in a product, an acknowledgment in the product documentation would be
+*    appreciated but is not required.
+* 2. Altered source versions must be plainly marked as such, and must not be
+*    misrepresented as being the original software.
+* 3. This notice may not be removed or altered from any source distribution.
+**/
+
+#pragma once
+
+// LOVE
+#include "common/runtime.h"
+#include "Buffer.h"
+
+namespace love
+{
+namespace graphics
+{
+
+void luax_writebufferdata(lua_State *L, int startidx, DataFormat format, char *data);
+void luax_readbufferdata(lua_State *L, DataFormat format, const char *data);
+
+Buffer *luax_checkbuffer(lua_State *L, int idx);
+extern "C" int luaopen_graphicsbuffer(lua_State *L);
+
+} // graphics
+} // love

+ 377 - 148
src/modules/graphics/wrap_Graphics.cpp

@@ -37,6 +37,7 @@
 #include <cassert>
 #include <cstring>
 #include <cstdlib>
+#include <sstream>
 
 #include <algorithm>
 
@@ -45,11 +46,6 @@ static const char graphics_lua[] =
 #include "wrap_Graphics.lua"
 ;
 
-// This is in a separate file because VS2013 has a 16KB limit for raw strings..
-static const char graphics_shader_lua[] =
-#include "wrap_GraphicsShader.lua"
-;
-
 namespace love
 {
 namespace graphics
@@ -748,15 +744,15 @@ static int w__pushNewTexture(lua_State *L, Texture::Slices *slices, const Textur
 static void luax_checktexturesettings(lua_State *L, int idx, bool opt, bool checkType, bool checkDimensions, OptionalBool forceRenderTarget, Texture::Settings &s, bool &setdpiscale)
 {
 	setdpiscale = false;
+	if (forceRenderTarget.hasValue)
+		s.renderTarget = forceRenderTarget.value;
 
 	if (opt && lua_isnoneornil(L, idx))
 		return;
 
 	luax_checktablefields<Texture::SettingType>(L, idx, "texture setting name", Texture::getConstant);
 
-	if (forceRenderTarget.hasValue)
-		s.renderTarget = forceRenderTarget.value;
-	else
+	if (!forceRenderTarget.hasValue)
 		s.renderTarget = luax_boolflag(L, idx, Texture::getConstant(Texture::SETTING_RENDER_TARGET), s.renderTarget);
 
 	lua_getfield(L, idx, Texture::getConstant(Texture::SETTING_FORMAT));
@@ -1199,25 +1195,25 @@ int w_newVolumeTexture(lua_State *L)
 
 int w_newImage(lua_State *L)
 {
-	luax_markdeprecated(L, "love.graphics.newImage", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.newTexture");
+	//luax_markdeprecated(L, "love.graphics.newImage", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.newTexture");
 	return w_newTexture(L);
 }
 
 int w_newCubeImage(lua_State *L)
 {
-	luax_markdeprecated(L, "love.graphics.newCubeImage", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.newCubeTexture");
+	//luax_markdeprecated(L, "love.graphics.newCubeImage", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.newCubeTexture");
 	return w_newCubeTexture(L);
 }
 
 int w_newArrayImage(lua_State *L)
 {
-	luax_markdeprecated(L, "love.graphics.newArrayImage", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.newArrayTexture");
+	//luax_markdeprecated(L, "love.graphics.newArrayImage", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.newArrayTexture");
 	return w_newArrayTexture(L);
 }
 
 int w_newVolumeImage(lua_State *L)
 {
-	luax_markdeprecated(L, "love.graphics.newVolumeImage", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.newVolumeTexture");
+	//luax_markdeprecated(L, "love.graphics.newVolumeImage", API_FUNCTION, DEPRECATED_RENAMED, "love.graphics.newVolumeTexture");
 	return w_newVolumeTexture(L);
 }
 
@@ -1329,12 +1325,12 @@ int w_newSpriteBatch(lua_State *L)
 
 	Texture *texture = luax_checktexture(L, 1);
 	int size = (int) luaL_optinteger(L, 2, 1000);
-	vertex::Usage usage = vertex::USAGE_DYNAMIC;
+	BufferUsage usage = BUFFERUSAGE_DYNAMIC;
 	if (lua_gettop(L) > 2)
 	{
 		const char *usagestr = luaL_checkstring(L, 3);
-		if (!vertex::getConstant(usagestr, usage))
-			return luax_enumerror(L, "usage hint", vertex::getConstants(usage), usagestr);
+		if (!getConstant(usagestr, usage))
+			return luax_enumerror(L, "usage hint", getConstants(usage), usagestr);
 	}
 
 	SpriteBatch *t = nullptr;
@@ -1366,7 +1362,7 @@ int w_newParticleSystem(lua_State *L)
 	return 1;
 }
 
-static int w_getShaderSource(lua_State *L, int startidx, bool gles, std::string &vertexsource, std::string &pixelsource)
+static int w_getShaderSource(lua_State *L, int startidx, std::vector<std::string> &stages)
 {
 	using namespace love::filesystem;
 
@@ -1426,61 +1422,23 @@ static int w_getShaderSource(lua_State *L, int startidx, bool gles, std::string
 	if (!(has_arg1 || has_arg2))
 		luaL_checkstring(L, startidx);
 
-	luax_getfunction(L, "graphics", "_shaderCodeToGLSL");
-
-	// push vertexcode and pixelcode strings to the top of the stack
-	lua_pushboolean(L, gles);
-
 	if (has_arg1)
-		lua_pushvalue(L, startidx + 0);
-	else
-		lua_pushnil(L);
-
+		stages.push_back(luax_checkstring(L, startidx + 0));
 	if (has_arg2)
-		lua_pushvalue(L, startidx + 1);
-	else
-		lua_pushnil(L);
-
-	// call effectCodeToGLSL, returned values will be at the top of the stack
-	if (lua_pcall(L, 3, 2, 0) != 0)
-		return luaL_error(L, "%s", lua_tostring(L, -1));
-
-	// vertex shader code
-	if (lua_isstring(L, -2))
-		vertexsource = luax_checkstring(L, -2);
-	else if (has_arg1 && has_arg2)
-		return luaL_error(L, "Could not parse vertex shader code (missing 'position' function?)");
-
-	// pixel shader code
-	if (lua_isstring(L, -1))
-		pixelsource = luax_checkstring(L, -1);
-	else if (has_arg1 && has_arg2)
-		return luaL_error(L, "Could not parse pixel shader code (missing 'effect' function?)");
-
-	if (vertexsource.empty() && pixelsource.empty())
-	{
-		// Original args had source code, but effectCodeToGLSL couldn't translate it
-		for (int i = startidx; i < startidx + 2; i++)
-		{
-			if (lua_isstring(L, i))
-				return luaL_argerror(L, i, "missing 'position' or 'effect' function?");
-		}
-	}
+		stages.push_back(luax_checkstring(L, startidx + 1));
 
 	return 0;
 }
 
 int w_newShader(lua_State *L)
 {
-	bool gles = instance()->usesGLSLES();
-
-	std::string vertexsource, pixelsource;
-	w_getShaderSource(L, 1, gles, vertexsource, pixelsource);
+	std::vector<std::string> stages;
+	w_getShaderSource(L, 1, stages);
 
 	bool should_error = false;
 	try
 	{
-		Shader *shader = instance()->newShader(vertexsource, pixelsource);
+		Shader *shader = instance()->newShader(stages);
 		luax_pushtype(L, shader);
 		shader->release();
 	}
@@ -1504,14 +1462,14 @@ int w_validateShader(lua_State *L)
 {
 	bool gles = luax_checkboolean(L, 1);
 
-	std::string vertexsource, pixelsource;
-	w_getShaderSource(L, 2, gles, vertexsource, pixelsource);
+	std::vector<std::string> stages;
+	w_getShaderSource(L, 2, stages);
 
 	bool success = true;
 	std::string err;
 	try
 	{
-		success = instance()->validateShader(gles, vertexsource, pixelsource, err);
+		success = instance()->validateShader(gles, stages, err);
 	}
 	catch (love::Exception &e)
 	{
@@ -1530,22 +1488,311 @@ int w_validateShader(lua_State *L)
 	return 1;
 }
 
-static vertex::Usage luax_optmeshusage(lua_State *L, int idx, vertex::Usage def)
+static BufferUsage luax_optbufferusage(lua_State *L, int idx, BufferUsage def)
 {
 	const char *usagestr = lua_isnoneornil(L, idx) ? nullptr : luaL_checkstring(L, idx);
 
-	if (usagestr && !vertex::getConstant(usagestr, def))
-		luax_enumerror(L, "usage hint", vertex::getConstants(def), usagestr);
+	if (usagestr && !getConstant(usagestr, def))
+		luax_enumerror(L, "usage hint", getConstants(def), usagestr);
 
 	return def;
 }
 
+static void luax_optbuffersettings(lua_State *L, int idx, Buffer::Settings &settings)
+{
+	if (lua_isnoneornil(L, idx))
+		return;
+
+	luaL_checktype(L, idx, LUA_TTABLE);
+
+	lua_getfield(L, idx, "usage");
+	settings.usage = luax_optbufferusage(L, -1, settings.usage);
+	lua_pop(L, 1);
+
+	if (luax_boolflag(L, idx, "cpureadable", settings.mapFlags & Buffer::MAP_READ))
+		settings.mapFlags = (Buffer::MapFlags)(settings.mapFlags | Buffer::MAP_READ);
+	else
+		settings.mapFlags = (Buffer::MapFlags)(settings.mapFlags & (~Buffer::MAP_READ));
+}
+
+static void luax_checkbufferformat(lua_State *L, int idx, std::vector<Buffer::DataDeclaration> &format)
+{
+	if (lua_type(L, idx) == LUA_TSTRING)
+	{
+		Buffer::DataDeclaration decl("", DATAFORMAT_MAX_ENUM);
+		const char *formatstr = luaL_checkstring(L, idx);
+		if (!getConstant(formatstr, decl.format))
+			luax_enumerror(L, "data format", getConstants(decl.format), formatstr);
+		format.push_back(decl);
+		return;
+	}
+
+	luaL_checktype(L, idx, LUA_TTABLE);
+	int tablelen = luax_objlen(L, idx);
+
+	for (int i = 1; i <= tablelen; i++)
+	{
+		lua_rawgeti(L, idx, i);
+		luaL_checktype(L, -1, LUA_TTABLE);
+
+		Buffer::DataDeclaration decl("", DATAFORMAT_MAX_ENUM);
+
+		lua_getfield(L, -1, "name");
+		if (!lua_isnoneornil(L, -1))
+			decl.name = luax_checkstring(L, -1);
+		lua_pop(L, 1);
+
+		lua_getfield(L, -1, "format");
+		if (lua_type(L, -1) != LUA_TSTRING)
+		{
+			std::ostringstream ss;
+			ss << "'format' field expected in array element #";
+			ss << i;
+			ss << " of format table";
+			std::string str = ss.str();
+			luaL_argerror(L, idx, str.c_str());
+		}
+		const char *formatstr = luaL_checkstring(L, -1);
+		if (!getConstant(formatstr, decl.format))
+			luax_enumerror(L, "data format", getConstants(decl.format), formatstr);
+		lua_pop(L, 1);
+
+		decl.arrayLength = luax_intflag(L, -1, "arraylength", 0);
+
+		format.push_back(decl);
+		lua_pop(L, 1);
+	}
+}
+
+static Buffer *luax_newbuffer(lua_State *L, int idx, const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &format)
+{
+	size_t arraylength = 0;
+	size_t bytesize = 0;
+	Data *data = nullptr;
+	const void *initialdata = nullptr;
+
+	int ncomponents = 0;
+	for (const Buffer::DataDeclaration &decl : format)
+		ncomponents += getDataFormatInfo(decl.format).components;
+
+	if (luax_istype(L, idx, Data::type))
+	{
+		data = luax_checktype<Data>(L, idx);
+		initialdata = data->getData();
+		bytesize = data->getSize();
+	}
+
+	bool tableoftables = false;
+
+	if (lua_istable(L, idx))
+	{
+		arraylength = luax_objlen(L, idx);
+
+		lua_rawgeti(L, idx, 1);
+		tableoftables = lua_istable(L, -1);
+		lua_pop(L, 1);
+
+		if (!tableoftables)
+		{
+			if (arraylength % ncomponents != 0)
+				luaL_error(L, "Array length in flat array variant of newBuffer must be a multiple of the total number of components (%d)", ncomponents);
+			arraylength /= ncomponents;
+		}
+	}
+	else if (data == nullptr)
+	{
+		lua_Integer len = luaL_checkinteger(L, idx);
+		if (len <= 0)
+			luaL_argerror(L, idx, "number of elements must be greater than 0");
+		arraylength = (size_t) len;
+	}
+
+	Buffer *b = nullptr;
+	luax_catchexcept(L, [&] { b = instance()->newBuffer(settings, format, initialdata, bytesize, arraylength); });
+
+	if (lua_istable(L, idx))
+	{
+		Buffer::Mapper mapper(*b);
+		char *data = (char *) mapper.data;
+		const auto &members = b->getDataMembers();
+		size_t stride = b->getArrayStride();
+
+		if (tableoftables)
+		{
+			for (size_t i = 0; i < arraylength; i++)
+			{
+				// get arraydata[index]
+				lua_rawgeti(L, 2, i + 1);
+				luaL_checktype(L, -1, LUA_TTABLE);
+
+				// get arraydata[index][j]
+				for (int j = 1; j <= ncomponents; j++)
+					lua_rawgeti(L, -j, j);
+
+				int idx = -ncomponents;
+
+				for (const Buffer::DataMember &member : members)
+				{
+					luax_writebufferdata(L, idx, member.decl.format, data + member.offset);
+					idx += member.info.components;
+				}
+
+				lua_pop(L, ncomponents + 1);
+				data += stride;
+			}
+		}
+		else // Flat array
+		{
+			for (size_t i = 0; i < arraylength; i++)
+			{
+				// get arraydata[arrayindex * ncomponents + componentindex]
+				for (int componentindex = 1; componentindex <= ncomponents; componentindex++)
+					lua_rawgeti(L, 2, i * ncomponents + componentindex);
+
+				int idx = -ncomponents;
+
+				for (const Buffer::DataMember &member : members)
+				{
+					luax_writebufferdata(L, idx, member.decl.format, data + member.offset);
+					idx += member.info.components;
+				}
+
+				lua_pop(L, ncomponents);
+				data += stride;
+			}
+		}
+	}
+
+	return b;
+}
+
+int w_newBuffer(lua_State *L)
+{
+	Buffer::Settings settings(0, Buffer::MAP_EXPLICIT_RANGE_MODIFY, BUFFERUSAGE_DYNAMIC);
+
+	luaL_checktype(L, 3, LUA_TTABLE);
+
+	for (int i = 0; i < BUFFERTYPE_MAX_ENUM; i++)
+	{
+		BufferType buffertype = (BufferType) i;
+		const char *tname = nullptr;
+		if (!getConstant(buffertype, tname))
+			continue;
+		if (luax_boolflag(L, 3, tname, false))
+			settings.typeFlags = (Buffer::TypeFlags)(settings.typeFlags | (1u << i));
+	}
+
+	luax_optbuffersettings(L, 3, settings);
+
+	std::vector<Buffer::DataDeclaration> format;
+	luax_checkbufferformat(L, 1, format);
+
+	Buffer *b = luax_newbuffer(L, 2, settings, format);
+
+	luax_pushtype(L, b);
+	b->release();
+	return 1;
+}
+
+int w_newVertexBuffer(lua_State *L)
+{
+	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY, BUFFERUSAGE_DYNAMIC);
+	luax_optbuffersettings(L, 3, settings);
+
+	std::vector<Buffer::DataDeclaration> format;
+	luax_checkbufferformat(L, 1, format);
+
+	Buffer *b = luax_newbuffer(L, 2, settings, format);
+
+	luax_pushtype(L, b);
+	b->release();
+	return 1;
+}
+
+int w_newIndexBuffer(lua_State *L)
+{
+	Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY, BUFFERUSAGE_DYNAMIC);
+	luax_optbuffersettings(L, 3, settings);
+
+	size_t arraylength = 0;
+	size_t bytesize = 0;
+	DataFormat format = DATAFORMAT_UINT16;
+	Data *data = nullptr;
+	const void *initialdata = nullptr;
+
+	if (luax_istype(L, 1, Data::type))
+	{
+		data = luax_checktype<Data>(L, 1);
+		initialdata = data->getData();
+		bytesize = data->getSize();
+	}
+
+	if (lua_istable(L, 1))
+	{
+		arraylength = luax_objlen(L, 1);
+
+		// Scan array for invalid types and the max value.
+		lua_Integer maxvalue = 0;
+		for (size_t i = 0; i < arraylength; i++)
+		{
+			lua_rawgeti(L, 1, i + 1);
+			lua_Integer v = luaL_checkinteger(L, -1);
+			lua_pop(L, 1);
+			if (v < 0)
+				return luaL_argerror(L, 1, "expected positive integer values in array");
+			else
+				maxvalue = std::max(maxvalue, v);
+		}
+
+		format = getIndexDataFormat(getIndexDataTypeFromMax(maxvalue));
+	}
+	else if (data == nullptr)
+	{
+		lua_Integer len = luaL_checkinteger(L, 1);
+		if (len <= 0)
+			return luaL_argerror(L, 1, "number of elements must be greater than 0");
+		arraylength = (size_t) len;
+	}
+
+	if (data != nullptr || !lua_isnoneornil(L, 2))
+	{
+		const char *formatstr = luaL_checkstring(L, 2);
+		if (!getConstant(formatstr, format))
+			return luax_enumerror(L, "index data format", getConstants(format), formatstr);
+	}
+
+	Buffer *b = nullptr;
+	luax_catchexcept(L, [&] { b = instance()->newBuffer(settings, format, initialdata, bytesize, arraylength); });
+
+	if (lua_istable(L, 1))
+	{
+		Buffer::Mapper mapper(*b);
+		uint16 *u16data = (uint16 *) mapper.data;
+		uint32 *u32data = (uint32 *) mapper.data;
+
+		for (size_t i = 0; i < arraylength; i++)
+		{
+			lua_rawgeti(L, 1, i + 1);
+			lua_Integer v = luaL_checkinteger(L, -1);
+			lua_pop(L, 1);
+			if (format == DATAFORMAT_UINT16)
+				u16data[i] = (uint16) v;
+			else
+				u32data[i] = (uint32) v;
+		}
+	}
+
+	luax_pushtype(L, b);
+	b->release();
+	return 1;
+}
+
 static PrimitiveType luax_optmeshdrawmode(lua_State *L, int idx, PrimitiveType def)
 {
 	const char *modestr = lua_isnoneornil(L, idx) ? nullptr : luaL_checkstring(L, idx);
 
-	if (modestr && !vertex::getConstant(modestr, def))
-		luax_enumerror(L, "mesh draw mode", vertex::getConstants(def), modestr);
+	if (modestr && !getConstant(modestr, def))
+		luax_enumerror(L, "mesh draw mode", getConstants(def), modestr);
 
 	return def;
 }
@@ -1555,7 +1802,9 @@ static Mesh *newStandardMesh(lua_State *L)
 	Mesh *t = nullptr;
 
 	PrimitiveType drawmode = luax_optmeshdrawmode(L, 2, PRIMITIVE_TRIANGLE_FAN);
-	vertex::Usage usage = luax_optmeshusage(L, 3, vertex::USAGE_DYNAMIC);
+	BufferUsage usage = luax_optbufferusage(L, 3, BUFFERUSAGE_DYNAMIC);
+
+	std::vector<Buffer::DataDeclaration> format = Mesh::getDefaultVertexFormat();
 
 	// First argument is a table of standard vertices, or the number of
 	// standard vertices.
@@ -1595,12 +1844,12 @@ static Mesh *newStandardMesh(lua_State *L)
 			vertices.push_back(v);
 		}
 
-		luax_catchexcept(L, [&](){ t = instance()->newMesh(vertices, drawmode, usage); });
+		luax_catchexcept(L, [&](){ t = instance()->newMesh(format, vertices.data(), vertices.size() * sizeof(Vertex), drawmode, usage); });
 	}
 	else
 	{
 		int count = (int) luaL_checkinteger(L, 1);
-		luax_catchexcept(L, [&](){ t = instance()->newMesh(count, drawmode, usage); });
+		luax_catchexcept(L, [&](){ t = instance()->newMesh(format, count, drawmode, usage); });
 	}
 
 	return t;
@@ -1612,10 +1861,10 @@ static Mesh *newCustomMesh(lua_State *L)
 
 	// First argument is the vertex format, second is a table of vertices or
 	// the number of vertices.
-	std::vector<Mesh::AttribFormat> vertexformat;
+	std::vector<Buffer::DataDeclaration> vertexformat;
 
 	PrimitiveType drawmode = luax_optmeshdrawmode(L, 3, PRIMITIVE_TRIANGLE_FAN);
-	vertex::Usage usage = luax_optmeshusage(L, 4, vertex::USAGE_DYNAMIC);
+	BufferUsage usage = luax_optbufferusage(L, 4, BUFFERUSAGE_DYNAMIC);
 
 	lua_rawgeti(L, 1, 1);
 	if (!lua_istable(L, -1))
@@ -1634,27 +1883,53 @@ static Mesh *newCustomMesh(lua_State *L)
 		for (int j = 1; j <= 3; j++)
 			lua_rawgeti(L, -j, j);
 
-		Mesh::AttribFormat format;
-		format.name = luaL_checkstring(L, -3);
+		const char *name = luaL_checkstring(L, -3);
 
+		DataFormat format = DATAFORMAT_MAX_ENUM;
 		const char *tname = luaL_checkstring(L, -2);
-		if (strcmp(tname, "byte") == 0) // Legacy name.
-			format.type = vertex::DATA_UNORM8;
-		else if (!vertex::getConstant(tname, format.type))
-		{
-			luax_enumerror(L, "Mesh vertex data type name", vertex::getConstants(format.type), tname);
-			return nullptr;
-		}
 
-		format.components = (int) luaL_checkinteger(L, -1);
-		if (format.components <= 0 || format.components > 4)
+		if (!lua_isnoneornil(L, -1))
 		{
-			luaL_error(L, "Number of vertex attribute components must be between 1 and 4 (got %d)", format.components);
-			return nullptr;
+			int components = (int) luaL_checkinteger(L, -1);
+
+			// Check deprecated format names.
+			if (strcmp(tname, "byte") == 0 || strcmp(tname, "unorm8") == 0)
+			{
+				if (components == 4)
+					format = DATAFORMAT_UNORM8_VEC4;
+				else
+					luaL_error(L, "Invalid component count (%d) for vertex data type %s", components, tname);
+			}
+			else if (strcmp(tname, "unorm16") == 0)
+			{
+				if (components == 2)
+					format = DATAFORMAT_UNORM16_VEC2;
+				else if (components == 4)
+					format = DATAFORMAT_UNORM16_VEC4;
+				else
+					luaL_error(L, "Invalid component count (%d) for vertex data type %s", components, tname);
+
+			}
+			else if (strcmp(tname, "float") == 0)
+			{
+				if (components == 1)
+					format = DATAFORMAT_FLOAT;
+				else if (components == 2)
+					format = DATAFORMAT_FLOAT_VEC2;
+				else if (components == 3)
+					format = DATAFORMAT_FLOAT_VEC3;
+				else if (components == 4)
+					format = DATAFORMAT_FLOAT_VEC4;
+				else
+					luaL_error(L, "Invalid component count (%d) for vertex data type %s", components, tname);
+			}
 		}
 
+		if (format == DATAFORMAT_MAX_ENUM && !getConstant(tname, format))
+			luax_enumerror(L, "vertex data format", getConstants(format), tname);
+
 		lua_pop(L, 4);
-		vertexformat.push_back(format);
+		vertexformat.emplace_back(name, format);
 	}
 
 	if (lua_isnumber(L, 2))
@@ -1679,10 +1954,6 @@ static Mesh *newCustomMesh(lua_State *L)
 		}
 		lua_pop(L, 1);
 
-		int vertexcomponents = 0;
-		for (const Mesh::AttribFormat &format : vertexformat)
-			vertexcomponents += format.components;
-
 		size_t numvertices = luax_objlen(L, 2);
 
 		luax_catchexcept(L, [&](){ t = instance()->newMesh(vertexformat, numvertices, drawmode, usage); });
@@ -1699,19 +1970,19 @@ static Mesh *newCustomMesh(lua_State *L)
 			int n = 0;
 			for (size_t i = 0; i < vertexformat.size(); i++)
 			{
-				int components = vertexformat[i].components;
+				const auto &info = getDataFormatInfo(vertexformat[i].format);
 
 				// get vertices[vertindex][n]
-				for (int c = 0; c < components; c++)
+				for (int c = 0; c < info.components; c++)
 				{
 					n++;
 					lua_rawgeti(L, -(c + 1), n);
 				}
 
 				// Fetch the values from Lua and store them in data buffer.
-				luax_writeAttributeData(L, -components, vertexformat[i].type, components, data);
+				luax_writebufferdata(L, -info.components, vertexformat[i].format, data);
 
-				lua_pop(L, components);
+				lua_pop(L, info.components);
 
 				luax_catchexcept(L,
 					[&](){ t->setVertexAttribute(vertindex, i, data, sizeof(float) * 4); },
@@ -2214,8 +2485,8 @@ int w_setMeshCullMode(lua_State *L)
 	const char *str = luaL_checkstring(L, 1);
 	CullMode mode;
 
-	if (!vertex::getConstant(str, mode))
-		return luax_enumerror(L, "cull mode", vertex::getConstants(mode), str);
+	if (!getConstant(str, mode))
+		return luax_enumerror(L, "cull mode", getConstants(mode), str);
 
 	luax_catchexcept(L, [&]() { instance()->setMeshCullMode(mode); });
 	return 0;
@@ -2225,7 +2496,7 @@ int w_getMeshCullMode(lua_State *L)
 {
 	CullMode mode = instance()->getMeshCullMode();
 	const char *str;
-	if (!vertex::getConstant(mode, str))
+	if (!getConstant(mode, str))
 		return luaL_error(L, "Unknown cull mode");
 	lua_pushstring(L, str);
 	return 1;
@@ -2234,10 +2505,10 @@ int w_getMeshCullMode(lua_State *L)
 int w_setFrontFaceWinding(lua_State *L)
 {
 	const char *str = luaL_checkstring(L, 1);
-	vertex::Winding winding;
+	Winding winding;
 
-	if (!vertex::getConstant(str, winding))
-		return luax_enumerror(L, "vertex winding", vertex::getConstants(winding), str);
+	if (!getConstant(str, winding))
+		return luax_enumerror(L, "vertex winding", getConstants(winding), str);
 
 	luax_catchexcept(L, [&]() { instance()->setFrontFaceWinding(winding); });
 	return 0;
@@ -2245,9 +2516,9 @@ int w_setFrontFaceWinding(lua_State *L)
 
 int w_getFrontFaceWinding(lua_State *L)
 {
-	vertex::Winding winding = instance()->getFrontFaceWinding();
+	Winding winding = instance()->getFrontFaceWinding();
 	const char *str;
-	if (!vertex::getConstant(winding, str))
+	if (!getConstant(winding, str))
 		return luaL_error(L, "Unknown vertex winding");
 	lua_pushstring(L, str);
 	return 1;
@@ -2289,46 +2560,6 @@ int w_getShader(lua_State *L)
 	return 1;
 }
 
-int w_setDefaultShaderCode(lua_State *L)
-{
-	for (int i = 0; i < 2; i++)
-	{
-		luaL_checktype(L, i + 1, LUA_TTABLE);
-
-		for (int lang = 0; lang < Shader::LANGUAGE_MAX_ENUM; lang++)
-		{
-			const char *langname;
-			if (!Shader::getConstant((Shader::Language) lang, langname))
-				continue;
-
-			lua_getfield(L, i + 1, langname);
-
-			lua_getfield(L, -1, "vertex");
-			lua_getfield(L, -2, "pixel");
-			lua_getfield(L, -3, "videopixel");
-			lua_getfield(L, -4, "arraypixel");
-
-			std::string vertex = luax_checkstring(L, -4);
-			std::string pixel = luax_checkstring(L, -3);
-			std::string videopixel = luax_checkstring(L, -2);
-			std::string arraypixel = luax_checkstring(L, -1);
-
-			lua_pop(L, 5);
-
-			Graphics::defaultShaderCode[Shader::STANDARD_DEFAULT][lang][i].source[ShaderStage::STAGE_VERTEX] = vertex;
-			Graphics::defaultShaderCode[Shader::STANDARD_DEFAULT][lang][i].source[ShaderStage::STAGE_PIXEL] = pixel;
-
-			Graphics::defaultShaderCode[Shader::STANDARD_VIDEO][lang][i].source[ShaderStage::STAGE_VERTEX] = vertex;
-			Graphics::defaultShaderCode[Shader::STANDARD_VIDEO][lang][i].source[ShaderStage::STAGE_PIXEL] = videopixel;
-
-			Graphics::defaultShaderCode[Shader::STANDARD_ARRAY][lang][i].source[ShaderStage::STAGE_VERTEX] = vertex;
-			Graphics::defaultShaderCode[Shader::STANDARD_ARRAY][lang][i].source[ShaderStage::STAGE_PIXEL] = arraypixel;
-		}
-	}
-
-	return 0;
-}
-
 int w_getSupported(lua_State *L)
 {
 	const Graphics::Capabilities &caps = instance()->getCapabilities();
@@ -3156,6 +3387,9 @@ static const luaL_Reg functions[] =
 	{ "newSpriteBatch", w_newSpriteBatch },
 	{ "newParticleSystem", w_newParticleSystem },
 	{ "newShader", w_newShader },
+	{ "newBuffer", w_newBuffer },
+	{ "newVertexBuffer", w_newVertexBuffer },
+	{ "newIndexBuffer", w_newIndexBuffer },
 	{ "newMesh", w_newMesh },
 	{ "newText", w_newText },
 	{ "_newVideo", w_newVideo },
@@ -3203,7 +3437,6 @@ static const luaL_Reg functions[] =
 
 	{ "setShader", w_setShader },
 	{ "getShader", w_getShader },
-	{ "_setDefaultShaderCode", w_setDefaultShaderCode },
 
 	{ "getSupported", w_getSupported },
 	{ "getTextureFormats", w_getTextureFormats },
@@ -3286,6 +3519,7 @@ static const lua_CFunction types[] =
 	luaopen_texture,
 	luaopen_font,
 	luaopen_quad,
+	luaopen_graphicsbuffer,
 	luaopen_spritebatch,
 	luaopen_particlesystem,
 	luaopen_shader,
@@ -3328,11 +3562,6 @@ extern "C" int luaopen_love_graphics(lua_State *L)
 	else
 		lua_error(L);
 
-	if (luaL_loadbuffer(L, (const char *)graphics_shader_lua, sizeof(graphics_shader_lua), "wrap_GraphicsShader.lua") == 0)
-		lua_call(L, 0, 0);
-	else
-		lua_error(L);
-
 	return n;
 }
 

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

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

+ 0 - 520
src/modules/graphics/wrap_GraphicsShader.lua

@@ -1,520 +0,0 @@
-R"luastring"--(
--- DO NOT REMOVE THE ABOVE LINE. It is used to load this file as a C++ string.
--- There is a matching delimiter at the bottom of the file.
-
---[[
-Copyright (c) 2006-2020 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.
---]]
-
-local table_concat, table_insert = table.concat, table.insert
-local ipairs = ipairs
-
-local GLSL = {}
-
-GLSL.VERSION = { -- index using [target][gles]
-	glsl1 = {[false]="#version 120",      [true]="#version 100"},
-	glsl3 = {[false]="#version 330 core", [true]="#version 300 es"},
-	glsl4 = {[false]="#version 430 core", [true]="#version 310 es"},
-}
-
-GLSL.SYNTAX = [[
-#if !defined(GL_ES) && __VERSION__ < 140
-	#define lowp
-	#define mediump
-	#define highp
-#endif
-#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
-	#define LOVE_HIGHP_OR_MEDIUMP highp
-#else
-	#define LOVE_HIGHP_OR_MEDIUMP mediump
-#endif
-#if __VERSION__ >= 300
-#define LOVE_IO_LOCATION(x) layout (location = x)
-#else
-#define LOVE_IO_LOCATION(x)
-#endif
-#define number float
-#define Image sampler2D
-#define ArrayImage sampler2DArray
-#define CubeImage samplerCube
-#define VolumeImage sampler3D
-#if __VERSION__ >= 300 && !defined(LOVE_GLSL1_ON_GLSL3)
-	#define DepthImage sampler2DShadow
-	#define DepthArrayImage sampler2DArrayShadow
-	#define DepthCubeImage samplerCubeShadow
-#endif
-#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
-#ifdef GL_OES_standard_derivatives
-#extension GL_OES_standard_derivatives : enable
-#endif
-]]
-
--- Uniforms shared by the vertex and pixel shader stages.
-GLSL.UNIFORMS = [[
-// According to the GLSL ES 1.0 spec, uniform precision must match between stages,
-// but we can't guarantee that highp is always supported in fragment shaders...
-// We *really* don't want to use mediump for these in vertex shaders though.
-#ifdef LOVE_USE_UNIFORM_BUFFERS
-layout (std140) uniform love_UniformsPerDrawBuffer {
-	highp vec4 love_UniformsPerDraw[13];
-};
-#else
-uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_UniformsPerDraw[13];
-#endif
-
-// These are initialized in love_initializeBuiltinUniforms below. GLSL ES can't
-// do it as an initializer.
-LOVE_HIGHP_OR_MEDIUMP mat4 TransformMatrix;
-LOVE_HIGHP_OR_MEDIUMP mat4 ProjectionMatrix;
-LOVE_HIGHP_OR_MEDIUMP mat3 NormalMatrix;
-
-LOVE_HIGHP_OR_MEDIUMP vec4 love_ScreenSize;
-LOVE_HIGHP_OR_MEDIUMP vec4 ConstantColor;
-
-#define TransformProjectionMatrix (ProjectionMatrix * TransformMatrix)
-
-// Alternate names
-#define ViewSpaceFromLocal TransformMatrix
-#define ClipSpaceFromView ProjectionMatrix
-#define ClipSpaceFromLocal TransformProjectionMatrix
-#define ViewNormalFromLocal NormalMatrix
-
-void love_initializeBuiltinUniforms() {
-	TransformMatrix = mat4(
-	   love_UniformsPerDraw[0],
-	   love_UniformsPerDraw[1],
-	   love_UniformsPerDraw[2],
-	   love_UniformsPerDraw[3]
-	);
-
-	ProjectionMatrix = mat4(
-	   love_UniformsPerDraw[4],
-	   love_UniformsPerDraw[5],
-	   love_UniformsPerDraw[6],
-	   love_UniformsPerDraw[7]
-	);
-
-	NormalMatrix = mat3(
-	   love_UniformsPerDraw[8].xyz,
-	   love_UniformsPerDraw[9].xyz,
-	   love_UniformsPerDraw[10].xyz
-	);
-
-	love_ScreenSize = love_UniformsPerDraw[11];
-	ConstantColor = love_UniformsPerDraw[12];
-}
-]]
-
-GLSL.FUNCTIONS = [[
-#ifdef GL_ES
-	#if __VERSION__ >= 300 || defined(GL_EXT_texture_array)
-		precision lowp sampler2DArray;
-	#endif
-	#if __VERSION__ >= 300 || defined(GL_OES_texture_3D)
-		precision lowp sampler3D;
-	#endif
-	#if __VERSION__ >= 300
-		precision lowp sampler2DShadow;
-		precision lowp samplerCubeShadow;
-		precision lowp sampler2DArrayShadow;
-	#endif
-#endif
-
-#if __VERSION__ >= 130 && !defined(LOVE_GLSL1_ON_GLSL3)
-	#define Texel texture
-#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
-
-float gammaToLinearPrecise(float c) {
-	return c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4);
-}
-vec3 gammaToLinearPrecise(vec3 c) {
-	bvec3 leq = lessThanEqual(c, vec3(0.04045));
-	c.r = leq.r ? c.r / 12.92 : pow((c.r + 0.055) / 1.055, 2.4);
-	c.g = leq.g ? c.g / 12.92 : pow((c.g + 0.055) / 1.055, 2.4);
-	c.b = leq.b ? c.b / 12.92 : pow((c.b + 0.055) / 1.055, 2.4);
-	return c;
-}
-vec4 gammaToLinearPrecise(vec4 c) { return vec4(gammaToLinearPrecise(c.rgb), c.a); }
-float linearToGammaPrecise(float c) {
-	return c < 0.0031308 ? c * 12.92 : 1.055 * pow(c, 1.0 / 2.4) - 0.055;
-}
-vec3 linearToGammaPrecise(vec3 c) {
-	bvec3 lt = lessThanEqual(c, vec3(0.0031308));
-	c.r = lt.r ? c.r * 12.92 : 1.055 * pow(c.r, 1.0 / 2.4) - 0.055;
-	c.g = lt.g ? c.g * 12.92 : 1.055 * pow(c.g, 1.0 / 2.4) - 0.055;
-	c.b = lt.b ? c.b * 12.92 : 1.055 * pow(c.b, 1.0 / 2.4) - 0.055;
-	return c;
-}
-vec4 linearToGammaPrecise(vec4 c) { return vec4(linearToGammaPrecise(c.rgb), c.a); }
-
-// http://chilliant.blogspot.com.au/2012/08/srgb-approximations-for-hlsl.html?m=1
-
-mediump float gammaToLinearFast(mediump float c) { return c * (c * (c * 0.305306011 + 0.682171111) + 0.012522878); }
-mediump vec3 gammaToLinearFast(mediump vec3 c) { return c * (c * (c * 0.305306011 + 0.682171111) + 0.012522878); }
-mediump vec4 gammaToLinearFast(mediump vec4 c) { return vec4(gammaToLinearFast(c.rgb), c.a); }
-
-mediump float linearToGammaFast(mediump float c) { return max(1.055 * pow(max(c, 0.0), 0.41666666) - 0.055, 0.0); }
-mediump vec3 linearToGammaFast(mediump vec3 c) { return max(1.055 * pow(max(c, vec3(0.0)), vec3(0.41666666)) - 0.055, vec3(0.0)); }
-mediump vec4 linearToGammaFast(mediump vec4 c) { return vec4(linearToGammaFast(c.rgb), c.a); }
-
-#define gammaToLinear gammaToLinearFast
-#define linearToGamma linearToGammaFast
-
-#ifdef LOVE_GAMMA_CORRECT
-	#define gammaCorrectColor gammaToLinear
-	#define unGammaCorrectColor linearToGamma
-	#define gammaCorrectColorPrecise gammaToLinearPrecise
-	#define unGammaCorrectColorPrecise linearToGammaPrecise
-	#define gammaCorrectColorFast gammaToLinearFast
-	#define unGammaCorrectColorFast linearToGammaFast
-#else
-	#define gammaCorrectColor
-	#define unGammaCorrectColor
-	#define gammaCorrectColorPrecise
-	#define unGammaCorrectColorPrecise
-	#define gammaCorrectColorFast
-	#define unGammaCorrectColorFast
-#endif]]
-
-GLSL.VERTEX = {
-	HEADER = [[
-#define love_Position gl_Position
-
-#if __VERSION__ >= 130
-	#define attribute in
-	#define varying out
-	#ifndef LOVE_GLSL1_ON_GLSL3
-		#define love_VertexID gl_VertexID
-		#define love_InstanceID gl_InstanceID
-	#endif
-#endif
-
-#ifdef GL_ES
-	uniform mediump float love_PointSize;
-#endif]],
-
-	FUNCTIONS = [[
-void setPointSize() {
-#ifdef GL_ES
-	gl_PointSize = love_PointSize;
-#endif
-}]],
-
-	MAIN = [[
-LOVE_IO_LOCATION(0) attribute vec4 VertexPosition;
-LOVE_IO_LOCATION(1) attribute vec4 VertexTexCoord;
-LOVE_IO_LOCATION(2) attribute vec4 VertexColor;
-
-varying vec4 VaryingTexCoord;
-varying vec4 VaryingColor;
-
-vec4 position(mat4 clipSpaceFromLocal, vec4 localPosition);
-
-void main() {
-	love_initializeBuiltinUniforms();
-	VaryingTexCoord = VertexTexCoord;
-	VaryingColor = gammaCorrectColor(VertexColor) * ConstantColor;
-	setPointSize();
-	love_Position = position(ClipSpaceFromLocal, VertexPosition);
-}]],
-}
-
-GLSL.PIXEL = {
-	HEADER = [[
-#ifdef GL_ES
-	precision mediump float;
-#endif
-
-#define love_MaxRenderTargets gl_MaxDrawBuffers
-
-#if __VERSION__ >= 130
-	#define varying in
-	// Some drivers seem to make the pixel shader do more work when multiple
-	// pixel shader outputs are defined, even when only one is actually used.
-	// TODO: We should use reflection or something instead of this, to determine
-	// how many outputs are actually used in the shader code.
-	#ifdef LOVE_MULTI_RENDER_TARGETS
-		LOVE_IO_LOCATION(0) out vec4 love_RenderTargets[love_MaxRenderTargets];
-		#define love_PixelColor love_RenderTargets[0]
-	#else
-		LOVE_IO_LOCATION(0) out vec4 love_PixelColor;
-	#endif
-#else
-	#ifdef LOVE_MULTI_RENDER_TARGETS
-		#define love_RenderTargets gl_FragData
-	#endif
-	#define love_PixelColor gl_FragColor
-#endif
-
-// Legacy
-#define love_MaxCanvases love_MaxRenderTargets
-#define love_Canvases love_RenderTargets
-#ifdef LOVE_MULTI_RENDER_TARGETS
-#define LOVE_MULTI_CANVASES 1
-#endif
-
-// See Shader::updateScreenParams in Shader.cpp.
-#define love_PixelCoord (vec2(gl_FragCoord.x, (gl_FragCoord.y * love_ScreenSize.z) + love_ScreenSize.w))]],
-
-	FUNCTIONS = [[
-uniform sampler2D love_VideoYChannel;
-uniform sampler2D love_VideoCbChannel;
-uniform sampler2D love_VideoCrChannel;
-
-vec4 VideoTexel(vec2 texcoords) {
-	vec3 yuv;
-	yuv[0] = Texel(love_VideoYChannel, texcoords).r;
-	yuv[1] = Texel(love_VideoCbChannel, texcoords).r;
-	yuv[2] = Texel(love_VideoCrChannel, texcoords).r;
-	yuv += vec3(-0.0627451017, -0.501960814, -0.501960814);
-
-	vec4 color;
-	color.r = dot(yuv, vec3(1.164,  0.000,  1.596));
-	color.g = dot(yuv, vec3(1.164, -0.391, -0.813));
-	color.b = dot(yuv, vec3(1.164,  2.018,  0.000));
-	color.a = 1.0;
-
-	return gammaCorrectColor(color);
-}]],
-
-	MAIN = [[
-uniform sampler2D MainTex;
-varying LOVE_HIGHP_OR_MEDIUMP vec4 VaryingTexCoord;
-varying mediump vec4 VaryingColor;
-
-vec4 effect(vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord);
-
-void main() {
-	love_initializeBuiltinUniforms();
-	love_PixelColor = effect(VaryingColor, MainTex, VaryingTexCoord.st, love_PixelCoord);
-}]],
-
-	MAIN_CUSTOM = [[
-varying LOVE_HIGHP_OR_MEDIUMP vec4 VaryingTexCoord;
-varying mediump vec4 VaryingColor;
-
-void effect();
-
-void main() {
-	love_initializeBuiltinUniforms();
-	effect();
-}]],
-}
-
-local function getLanguageTarget(code)
-	if not code then return nil end
-	return (code:match("^%s*#pragma language (%w+)")) or "glsl1"
-end
-
-local function createShaderStageCode(stage, code, lang, gles, glsl1on3, gammacorrect, custom, multirendertarget, useubo)
-	stage = stage:upper()
-	local lines = {
-		GLSL.VERSION[lang][gles],
-		"#define " ..stage .. " " .. stage,
-		glsl1on3 and "#define LOVE_GLSL1_ON_GLSL3 1" or "",
-		gammacorrect and "#define LOVE_GAMMA_CORRECT 1" or "",
-		multirendertarget and "#define LOVE_MULTI_RENDER_TARGETS 1" or "",
-		useubo and "#define LOVE_USE_UNIFORM_BUFFERS 1" or "",
-		GLSL.SYNTAX,
-		GLSL[stage].HEADER,
-		GLSL.UNIFORMS,
-		GLSL.FUNCTIONS,
-		GLSL[stage].FUNCTIONS,
-		custom and GLSL[stage].MAIN_CUSTOM or GLSL[stage].MAIN,
-		((lang == "glsl1" or glsl1on3) and not gles) and "#line 0" or "#line 1",
-		code,
-	}
-	return table_concat(lines, "\n")
-end
-
-local function isVertexCode(code)
-	return code:match("vec4%s+position%s*%(") ~= nil
-end
-
-local function isPixelCode(code)
-	if code:match("vec4%s+effect%s*%(") then
-		return true
-	elseif code:match("void%s+effect%s*%(") then -- custom effect function
-		local mrt = (code:match("love_RenderTargets") ~= nil) or (code:match("love_Canvases") ~= nil)
-		return true, true, mrt
-	else
-		return false
-	end
-end
-
-function love.graphics._shaderCodeToGLSL(gles, arg1, arg2)
-	local vertexcode, pixelcode
-	local is_custompixel = false -- whether pixel code has "effects" function instead of "effect"
-	local is_multicanvas = false
-
-	if arg1 then
-		if isVertexCode(arg1) then
-			vertexcode = arg1 -- first arg contains vertex shader code
-		end
-
-		local ispixel, isCustomPixel, isMultiCanvas = isPixelCode(arg1)
-		if ispixel then
-			pixelcode = arg1 -- first arg contains pixel shader code
-			is_custompixel, is_multicanvas = isCustomPixel, isMultiCanvas
-		end
-	end
-	
-	if arg2 then
-		if isVertexCode(arg2) then
-			vertexcode = arg2 -- second arg contains vertex shader code
-		end
-
-		local ispixel, isCustomPixel, isMultiCanvas = isPixelCode(arg2)
-		if ispixel then
-			pixelcode = arg2 -- second arg contains pixel shader code
-			is_custompixel, is_multicanvas = isCustomPixel, isMultiCanvas
-		end
-	end
-
-	local graphicsfeatures = love.graphics.getSupported()
-	local supportsGLSL3 = graphicsfeatures.glsl3
-	local supportsGLSL4 = graphicsfeatures.glsl4
-	local gammacorrect = love.graphics.isGammaCorrect()
-	local renderer = love.graphics.getRenderer()
-	local useubo = renderer == "Metal"
-
-	local targetlang = getLanguageTarget(pixelcode or vertexcode)
-	if getLanguageTarget(vertexcode or pixelcode) ~= targetlang then
-		error("vertex and pixel shader languages must match", 2)
-	end
-
-	if targetlang == "glsl3" and not supportsGLSL3 then
-		error("GLSL 3 shaders are not supported on this system.", 2)
-	end
-
-	if targetlang == "glsl4" and not supportsGLSL4 then
-		error("GLSL 4 shaders are not supported on this system.", 2)
-	end
-
-	if targetlang ~= nil and not GLSL.VERSION[targetlang] then
-		error("Invalid shader language: " .. targetlang, 2)
-	end
-
-	local lang = targetlang or "glsl1"
-	local glsl1on3 = false
-	if lang == "glsl1" and supportsGLSL3 then
-		lang = "glsl3"
-		glsl1on3 = true
-	end
-
-	if vertexcode then
-		vertexcode = createShaderStageCode("VERTEX", vertexcode, lang, gles, glsl1on3, gammacorrect, false, false, useubo)
-	end
-	if pixelcode then
-		pixelcode = createShaderStageCode("PIXEL", pixelcode, lang, gles, glsl1on3, gammacorrect, is_custompixel, is_multicanvas, useubo)
-	end
-
-	return vertexcode, pixelcode
-end
-
-local defaultcode = {
-	vertex = [[
-vec4 position(mat4 clipSpaceFromLocal, vec4 localPosition) {
-	return clipSpaceFromLocal * localPosition;
-}]],
-	pixel = [[
-vec4 effect(vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord) {
-	return Texel(tex, texcoord) * vcolor;
-}]],
-	videopixel = [[
-void effect() {
-	love_PixelColor = VideoTexel(VaryingTexCoord.xy) * VaryingColor;
-}]],
-	arraypixel = [[
-uniform ArrayImage MainTex;
-void effect() {
-	love_PixelColor = Texel(MainTex, VaryingTexCoord.xyz) * VaryingColor;
-}]],
-}
-
-local defaults = {}
-local defaults_gammacorrect = {}
-
-local langs = {
-	glsl1 = {target="glsl1", gles=false},
-	essl1 = {target="glsl1", gles=true},
-	glsl3 = {target="glsl3", gles=false},
-	essl3 = {target="glsl3", gles=true},
-}
-
--- FIXME: this is temporary until a glslang pull request is merged in.
-local useubo = true
-
-for lang, info in pairs(langs) do
-	for _, gammacorrect in ipairs{false, true} do
-		local t = gammacorrect and defaults_gammacorrect or defaults
-		t[lang] = {
-			vertex = createShaderStageCode("VERTEX", defaultcode.vertex, info.target, info.gles, false, gammacorrect, false, false, useubo),
-			pixel = createShaderStageCode("PIXEL", defaultcode.pixel, info.target, info.gles, false, gammacorrect, false, false, useubo),
-			videopixel = createShaderStageCode("PIXEL", defaultcode.videopixel, info.target, info.gles, false, gammacorrect, true, false, useubo),
-			arraypixel = createShaderStageCode("PIXEL", defaultcode.arraypixel, info.target, info.gles, false, gammacorrect, true, false, useubo),
-		}
-	end
-end
-
-love.graphics._setDefaultShaderCode(defaults, defaults_gammacorrect)
-
--- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string.
---)luastring"--"

+ 133 - 184
src/modules/graphics/wrap_Mesh.cpp

@@ -20,6 +20,7 @@
 
 // LOVE
 #include "wrap_Mesh.h"
+#include "wrap_Buffer.h"
 #include "Texture.h"
 #include "wrap_Texture.h"
 
@@ -36,140 +37,6 @@ Mesh *luax_checkmesh(lua_State *L, int idx)
 	return luax_checktype<Mesh>(L, idx);
 }
 
-static const double defaultComponents[] = {0.0, 0.0, 0.0, 1.0};
-
-template <typename T>
-static inline size_t writeData(lua_State *L, int startidx, int components, char *data)
-{
-	auto componentdata = (T *) data;
-
-	for (int i = 0; i < components; i++)
-		componentdata[i] = (T) (luaL_optnumber(L, startidx + i, defaultComponents[i]));
-
-	return sizeof(T) * components;
-}
-
-template <typename T>
-static inline size_t writeSNormData(lua_State *L, int startidx, int components, char *data)
-{
-	auto componentdata = (T *) data;
-	auto maxval = std::numeric_limits<T>::max();
-
-	for (int i = 0; i < components; i++)
-		componentdata[i] = (T) (luax_optnumberclamped(L, startidx + i, -1.0, 1.0, defaultComponents[i]) * maxval);
-
-	return sizeof(T) * components;
-}
-
-template <typename T>
-static inline size_t writeUNormData(lua_State *L, int startidx, int components, char *data)
-{
-	auto componentdata = (T *) data;
-	auto maxval = std::numeric_limits<T>::max();
-
-	for (int i = 0; i < components; i++)
-		componentdata[i] = (T) (luax_optnumberclamped01(L, startidx + i, 1.0) * maxval);
-
-	return sizeof(T) * components;
-}
-
-char *luax_writeAttributeData(lua_State *L, int startidx, vertex::DataType type, int components, char *data)
-{
-	switch (type)
-	{
-	case vertex::DATA_SNORM8:
-		return data + writeSNormData<int8>(L, startidx, components, data);
-	case vertex::DATA_UNORM8:
-		return data + writeUNormData<uint8>(L, startidx, components, data);
-	case vertex::DATA_INT8:
-		return data + writeData<int8>(L, startidx, components, data);
-	case vertex::DATA_UINT8:
-		return data + writeData<uint8>(L, startidx, components, data);
-	case vertex::DATA_SNORM16:
-		return data + writeSNormData<int16>(L, startidx, components, data);
-	case vertex::DATA_UNORM16:
-		return data + writeUNormData<uint16>(L, startidx, components, data);
-	case vertex::DATA_INT16:
-		return data + writeData<int16>(L, startidx, components, data);
-	case vertex::DATA_UINT16:
-		return data + writeData<uint16>(L, startidx, components, data);
-	case vertex::DATA_INT32:
-		return data + writeData<int32>(L, startidx, components, data);
-	case vertex::DATA_UINT32:
-		return data + writeData<uint32>(L, startidx, components, data);
-	case vertex::DATA_FLOAT:
-		return data + writeData<float>(L, startidx, components, data);
-	default:
-		return data;
-	}
-}
-
-template <typename T>
-static inline size_t readData(lua_State *L, int components, const char *data)
-{
-	auto componentdata = (const T *) data;
-
-	for (int i = 0; i < components; i++)
-		lua_pushnumber(L, (lua_Number) componentdata[i]);
-
-	return sizeof(T) * components;
-}
-
-template <typename T>
-static inline size_t readSNormData(lua_State *L, int components, const char *data)
-{
-	auto componentdata = (const T *) data;
-	auto maxval = std::numeric_limits<T>::max();
-
-	for (int i = 0; i < components; i++)
-		lua_pushnumber(L, std::max(-1.0, (lua_Number) componentdata[i] / (lua_Number)maxval));
-
-	return sizeof(T) * components;
-}
-
-template <typename T>
-static inline size_t readUNormData(lua_State *L, int components, const char *data)
-{
-	auto componentdata = (const T *) data;
-	auto maxval = std::numeric_limits<T>::max();
-
-	for (int i = 0; i < components; i++)
-		lua_pushnumber(L, (lua_Number) componentdata[i] / (lua_Number)maxval);
-
-	return sizeof(T) * components;
-}
-
-const char *luax_readAttributeData(lua_State *L, vertex::DataType type, int components, const char *data)
-{
-	switch (type)
-	{
-	case vertex::DATA_SNORM8:
-		return data + readSNormData<int8>(L, components, data);
-	case vertex::DATA_UNORM8:
-		return data + readUNormData<uint8>(L, components, data);
-	case vertex::DATA_INT8:
-		return data + readData<int8>(L, components, data);
-	case vertex::DATA_UINT8:
-		return data + readData<uint8>(L, components, data);
-	case vertex::DATA_SNORM16:
-		return data + readSNormData<int16>(L, components, data);
-	case vertex::DATA_UNORM16:
-		return data + readUNormData<uint16>(L, components, data);
-	case vertex::DATA_INT16:
-		return data + readData<int16>(L, components, data);
-	case vertex::DATA_UINT16:
-		return data + readData<uint16>(L, components, data);
-	case vertex::DATA_INT32:
-		return data + readData<int32>(L, components, data);
-	case vertex::DATA_UINT32:
-		return data + readData<uint32>(L, components, data);
-	case vertex::DATA_FLOAT:
-		return data + readData<float>(L, components, data);
-	default:
-		return data;
-	}
-}
-
 int w_Mesh_setVertices(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
@@ -188,7 +55,7 @@ int w_Mesh_setVertices(lua_State *L)
 	size_t byteoffset = vertstart * stride;
 	int totalverts = (int) t->getVertexCount();
 
-	if (vertstart >= totalverts)
+	if (vertstart >= totalverts || vertstart < 0)
 		return luaL_error(L, "Invalid vertex start index (must be between 1 and %d)", totalverts);
 
 	if (luax_istype(L, 2, Data::type))
@@ -215,11 +82,11 @@ int w_Mesh_setVertices(lua_State *L)
 	if (vertstart + vertcount > totalverts)
 		return luaL_error(L, "Too many vertices (expected at most %d, got %d)", totalverts - vertstart, vertcount);
 
-	const std::vector<Mesh::AttribFormat> &vertexformat = t->getVertexFormat();
+	const std::vector<Buffer::DataMember> &vertexformat = t->getVertexFormat();
 
 	int ncomponents = 0;
-	for (const Mesh::AttribFormat &format : vertexformat)
-		ncomponents += format.components;
+	for (const Buffer::DataMember &member : vertexformat)
+		ncomponents += member.info.components;
 
 	char *data = (char *) t->mapVertexData() + byteoffset;
 
@@ -235,14 +102,16 @@ int w_Mesh_setVertices(lua_State *L)
 
 		int idx = -ncomponents;
 
-		for (const Mesh::AttribFormat &format : vertexformat)
+		for (const Buffer::DataMember &member : vertexformat)
 		{
 			// Fetch the values from Lua and store them in data buffer.
-			data = luax_writeAttributeData(L, idx, format.type, format.components, data);
-			idx += format.components;
+			luax_writebufferdata(L, idx, member.decl.format, data + member.offset);
+			idx += member.info.components;
 		}
 
 		lua_pop(L, ncomponents + 1);
+
+		data += stride;
 	}
 
 	t->unmapVertexData(byteoffset, vertcount * stride);
@@ -256,34 +125,34 @@ int w_Mesh_setVertex(lua_State *L)
 
 	bool istable = lua_istable(L, 3);
 
-	const std::vector<Mesh::AttribFormat> &vertexformat = t->getVertexFormat();
-
+	const std::vector<Buffer::DataMember> &vertexformat = t->getVertexFormat();
 	char *data = (char *) t->getVertexScratchBuffer();
-	char *writtendata = data;
 
 	int idx = istable ? 1 : 3;
 
 	if (istable)
 	{
-		for (const Mesh::AttribFormat &format : vertexformat)
+		for (const Buffer::DataMember &member : vertexformat)
 		{
-			for (int i = idx; i < idx + format.components; i++)
+			int components = member.info.components;
+
+			for (int i = idx; i < idx + components; i++)
 				lua_rawgeti(L, 3, i);
 
 			// Fetch the values from Lua and store them in data buffer.
-			writtendata = luax_writeAttributeData(L, -format.components, format.type, format.components, writtendata);
+			luax_writebufferdata(L, -components, member.decl.format, data + member.offset);
 
-			idx += format.components;
-			lua_pop(L, format.components);
+			idx += components;
+			lua_pop(L, components);
 		}
 	}
 	else
 	{
-		for (const Mesh::AttribFormat &format : vertexformat)
+		for (const Buffer::DataMember &member : vertexformat)
 		{
 			// Fetch the values from Lua and store them in data buffer.
-			writtendata = luax_writeAttributeData(L, idx, format.type, format.components, writtendata);
-			idx += format.components;
+			luax_writebufferdata(L, idx, member.decl.format, data + member.offset);
+			idx += member.info.components;
 		}
 	}
 
@@ -296,19 +165,18 @@ int w_Mesh_getVertex(lua_State *L)
 	Mesh *t = luax_checkmesh(L, 1);
 	size_t index = (size_t) luaL_checkinteger(L, 2) - 1;
 
-	const std::vector<Mesh::AttribFormat> &vertexformat = t->getVertexFormat();
+	const std::vector<Buffer::DataMember> &vertexformat = t->getVertexFormat();
 
 	char *data = (char *) t->getVertexScratchBuffer();
-	const char *readdata = data;
 
 	luax_catchexcept(L, [&](){ t->getVertex(index, data, t->getVertexStride()); });
 
 	int n = 0;
 
-	for (const Mesh::AttribFormat &format : vertexformat)
+	for (const Buffer::DataMember &member : vertexformat)
 	{
-		readdata = luax_readAttributeData(L, format.type, format.components, readdata);
-		n += format.components;
+		luax_readbufferdata(L, member.decl.format, data + member.offset);
+		n += member.info.components;
 	}
 
 	return n;
@@ -320,15 +188,18 @@ int w_Mesh_setVertexAttribute(lua_State *L)
 	size_t vertindex = (size_t) luaL_checkinteger(L, 2) - 1;
 	int attribindex = (int) luaL_checkinteger(L, 3) - 1;
 
-	vertex::DataType type;
-	int components;
-	luax_catchexcept(L, [&](){ type = t->getAttributeInfo(attribindex, components); });
+	const auto &vertexformat = t->getVertexFormat();
+
+	if (attribindex < 0 || attribindex >= (int) vertexformat.size())
+		return luaL_error(L, "Invalid vertex attribute index: %d", attribindex + 1);
+
+	const Buffer::DataMember &member = vertexformat[attribindex];
 
 	// Maximum possible size for a single vertex attribute.
 	char data[sizeof(float) * 4];
 
 	// Fetch the values from Lua and store them in the data buffer.
-	luax_writeAttributeData(L, 4, type, components, data);
+	luax_writebufferdata(L, 4, member.decl.format, data);
 
 	luax_catchexcept(L, [&](){ t->setVertexAttribute(vertindex, attribindex, data, sizeof(float) * 4); });
 	return 0;
@@ -340,17 +211,20 @@ int w_Mesh_getVertexAttribute(lua_State *L)
 	size_t vertindex = (size_t) luaL_checkinteger(L, 2) - 1;
 	int attribindex = (int) luaL_checkinteger(L, 3) - 1;
 
-	vertex::DataType type;
-	int components;
-	luax_catchexcept(L, [&](){ type = t->getAttributeInfo(attribindex, components); });
+	const auto &vertexformat = t->getVertexFormat();
+
+	if (attribindex < 0 || attribindex >= (int) vertexformat.size())
+		return luaL_error(L, "Invalid vertex attribute index: %d", attribindex + 1);
+
+	const Buffer::DataMember &member = vertexformat[attribindex];
 
 	// Maximum possible size for a single vertex attribute.
 	char data[sizeof(float) * 4];
 
 	luax_catchexcept(L, [&](){ t->getVertexAttribute(vertindex, attribindex, data, sizeof(float) * 4); });
 
-	luax_readAttributeData(L, type, components, data);
-	return components;
+	luax_readbufferdata(L, member.decl.format, data);
+	return member.info.components;
 }
 
 int w_Mesh_getVertexCount(lua_State *L)
@@ -364,28 +238,27 @@ int w_Mesh_getVertexFormat(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
 
-	const std::vector<Mesh::AttribFormat> &vertexformat = t->getVertexFormat();
+	const std::vector<Buffer::DataMember> &vertexformat = t->getVertexFormat();
 	lua_createtable(L, (int) vertexformat.size(), 0);
 
 	const char *tname = nullptr;
 
 	for (size_t i = 0; i < vertexformat.size(); i++)
 	{
-		if (!vertex::getConstant(vertexformat[i].type, tname))
-			return luax_enumerror(L, "vertex attribute data type", vertex::getConstants(vertexformat[i].type), tname);
+		const auto &decl = vertexformat[i].decl;
+
+		if (!getConstant(decl.format, tname))
+			return luax_enumerror(L, "vertex attribute data type", getConstants(decl.format), tname);
 
 		lua_createtable(L, 3, 0);
 
-		lua_pushstring(L, vertexformat[i].name.c_str());
+		lua_pushstring(L, decl.name.c_str());
 		lua_rawseti(L, -2, 1);
 
 		lua_pushstring(L, tname);
 		lua_rawseti(L, -2, 2);
 
-		lua_pushinteger(L, vertexformat[i].components);
-		lua_rawseti(L, -2, 3);
-
-		// format[i] = {name, type, components}
+		// format[i] = {name, type}
 		lua_rawseti(L, -2, (int) i + 1);
 	}
 
@@ -415,16 +288,29 @@ int w_Mesh_attachAttribute(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
 	const char *name = luaL_checkstring(L, 2);
-	Mesh *mesh = luax_checkmesh(L, 3);
+
+	Buffer *buffer = nullptr;
+	if (luax_istype(L, 3, Buffer::type))
+	{
+		buffer = luax_checktype<Buffer>(L, 3);
+	}
+	else
+	{
+		Mesh *mesh = luax_checkmesh(L, 3);
+		buffer = mesh->getVertexBuffer();
+		if (buffer == nullptr)
+			return luaL_error(L, "Mesh does not have its own vertex buffer.");
+		luax_markdeprecated(L, "Mesh:attachAttribute(name, mesh, ...)", API_METHOD, DEPRECATED_REPLACED, "Mesh:attachAttribute(name, buffer, ...)");
+	}
 
 	AttributeStep step = STEP_PER_VERTEX;
 	const char *stepstr = lua_isnoneornil(L, 4) ? nullptr : luaL_checkstring(L, 4);
-	if (stepstr != nullptr && !vertex::getConstant(stepstr, step))
-		return luax_enumerror(L, "vertex attribute step", vertex::getConstants(step), stepstr);
+	if (stepstr != nullptr && !getConstant(stepstr, step))
+		return luax_enumerror(L, "vertex attribute step", getConstants(step), stepstr);
 
 	const char *attachname = luaL_optstring(L, 5, name);
 
-	luax_catchexcept(L, [&](){ t->attachAttribute(name, mesh, attachname, step); });
+	luax_catchexcept(L, [&](){ t->attachAttribute(name, buffer, attachname, step); });
 	return 0;
 }
 
@@ -438,6 +324,48 @@ int w_Mesh_detachAttribute(lua_State *L)
 	return 1;
 }
 
+int w_Mesh_getAttachedAttributes(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	const auto &attributes = t->getAttachedAttributes();
+
+	lua_createtable(L, (int) attributes.size(), 0);
+
+	for (int i = 0; i < (int) attributes.size(); i++)
+	{
+		const auto &attrib = attributes[i];
+
+		lua_createtable(L, 4, 0);
+
+		luax_pushstring(L, attrib.name);
+		lua_rawseti(L, -1, 1);
+
+		luax_pushtype(L, attrib.buffer.get());
+		lua_rawseti(L, -1, 2);
+
+		const char *stepstr = nullptr;
+		if (!getConstant(attrib.step, stepstr))
+			return luaL_error(L, "Invalid vertex attribute step.");
+		lua_pushstring(L, stepstr);
+		lua_rawseti(L, -1, 3);
+
+		const Buffer::DataMember &member = attrib.buffer->getDataMember(attrib.indexInBuffer);
+		luax_pushstring(L, member.decl.name);
+		lua_rawseti(L, -1, 4);
+
+		lua_rawseti(L, -1, i + 1);
+	}
+
+	return 1;
+}
+
+int w_Mesh_getVertexBuffer(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	luax_pushtype(L, t->getVertexBuffer());
+	return 1;
+}
+
 int w_Mesh_flush(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
@@ -462,10 +390,10 @@ int w_Mesh_setVertexMap(lua_State *L)
 
 		const char *indextypestr = luaL_checkstring(L, 3);
 		IndexDataType indextype;
-		if (!vertex::getConstant(indextypestr, indextype))
-			return luax_enumerror(L, "index data type", vertex::getConstants(indextype), indextypestr);
+		if (!getConstant(indextypestr, indextype))
+			return luax_enumerror(L, "index data type", getConstants(indextype), indextypestr);
 
-		size_t datatypesize = vertex::getIndexDataSize(indextype);
+		size_t datatypesize = getIndexDataSize(indextype);
 
 		int indexcount = (int) luaL_optinteger(L, 4, d->getSize() / datatypesize);
 
@@ -528,6 +456,23 @@ int w_Mesh_getVertexMap(lua_State *L)
 	return 1;
 }
 
+int w_Mesh_setIndexBuffer(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	Buffer *b = nullptr;
+	if (!lua_isnoneornil(L, 2))
+		b = luax_checkbuffer(L, 2);
+	luax_catchexcept(L, [&]() { t->setIndexBuffer(b); });
+	return 0;
+}
+
+int w_Mesh_getIndexBuffer(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	luax_pushtype(L, t->getIndexBuffer());
+	return 1;
+}
+
 int w_Mesh_setTexture(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
@@ -561,8 +506,8 @@ int w_Mesh_setDrawMode(lua_State *L)
 	const char *str = luaL_checkstring(L, 2);
 	PrimitiveType mode;
 
-	if (!vertex::getConstant(str, mode))
-		return luax_enumerror(L, "mesh draw mode", vertex::getConstants(mode), str);
+	if (!getConstant(str, mode))
+		return luax_enumerror(L, "mesh draw mode", getConstants(mode), str);
 
 	t->setDrawMode(mode);
 	return 0;
@@ -574,7 +519,7 @@ int w_Mesh_getDrawMode(lua_State *L)
 	PrimitiveType mode = t->getDrawMode();
 	const char *str;
 
-	if (!vertex::getConstant(mode, str))
+	if (!getConstant(mode, str))
 		return luaL_error(L, "Unknown mesh draw mode.");
 
 	lua_pushstring(L, str);
@@ -624,9 +569,13 @@ static const luaL_Reg w_Mesh_functions[] =
 	{ "isAttributeEnabled", w_Mesh_isAttributeEnabled },
 	{ "attachAttribute", w_Mesh_attachAttribute },
 	{ "detachAttribute", w_Mesh_detachAttribute },
+	{ "getAttachedAttributes", w_Mesh_getAttachedAttributes },
+	{ "getVertexBuffer", w_Mesh_getVertexBuffer },
 	{ "flush", w_Mesh_flush },
 	{ "setVertexMap", w_Mesh_setVertexMap },
 	{ "getVertexMap", w_Mesh_getVertexMap },
+	{ "setIndexBuffer", w_Mesh_setIndexBuffer },
+	{ "getIndexBuffer", w_Mesh_getIndexBuffer },
 	{ "setTexture", w_Mesh_setTexture },
 	{ "getTexture", w_Mesh_getTexture },
 	{ "setDrawMode", w_Mesh_setDrawMode },

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

@@ -30,9 +30,6 @@ namespace love
 namespace graphics
 {
 
-char *luax_writeAttributeData(lua_State *L, int startidx, vertex::DataType type, int components, char *data);
-const char *luax_readAttributeData(lua_State *L, vertex::DataType type, int components, const char *data);
-
 Mesh *luax_checkmesh(lua_State *L, int idx);
 extern "C" int luaopen_mesh(lua_State *L);
 

+ 52 - 17
src/modules/graphics/wrap_Shader.cpp

@@ -287,6 +287,23 @@ int w_Shader_sendTextures(lua_State *L, int startidx, Shader *shader, const Shad
 	return 0;
 }
 
+int w_Shader_sendBuffers(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info)
+{
+	int count = _getCount(L, startidx, info);
+
+	std::vector<Buffer *> buffers;
+	buffers.reserve(count);
+
+	for (int i = 0; i < count; i++)
+	{
+		Buffer *buffer = luax_checktype<Buffer>(L, startidx + i);
+		buffers.push_back(buffer);
+	}
+
+	luax_catchexcept(L, [&]() { shader->sendBuffers(info, buffers.data(), count); });
+	return 0;
+}
+
 static int w_Shader_sendLuaValues(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info, const char *name)
 {
 	switch (info->baseType)
@@ -303,6 +320,8 @@ static int w_Shader_sendLuaValues(lua_State *L, int startidx, Shader *shader, co
 		return w_Shader_sendBooleans(L, startidx, shader, info);
 	case Shader::UNIFORM_SAMPLER:
 		return w_Shader_sendTextures(L, startidx, shader, info);
+	case Shader::UNIFORM_TEXELBUFFER:
+		return w_Shader_sendBuffers(L, startidx, shader, info);
 	default:
 		return luaL_error(L, "Unknown variable type for shader uniform '%s", name);
 	}
@@ -313,20 +332,36 @@ static int w_Shader_sendData(lua_State *L, int startidx, Shader *shader, const S
 	if (info->baseType == Shader::UNIFORM_SAMPLER)
 		return luaL_error(L, "Uniform sampler values (textures) cannot be sent to Shaders via Data objects.");
 
-	bool columnmajor = false;
-	if (info->baseType == Shader::UNIFORM_MATRIX && lua_type(L, startidx + 1) == LUA_TSTRING)
+	math::Transform::MatrixLayout layout = math::Transform::MATRIX_ROW_MAJOR;
+	int dataidx = startidx;
+	if (info->baseType == Shader::UNIFORM_MATRIX)
 	{
-		const char *layoutstr = lua_tostring(L, startidx + 1);
-		math::Transform::MatrixLayout layout;
-		if (!math::Transform::getConstant(layoutstr, layout))
-			return luax_enumerror(L, "matrix layout", math::Transform::getConstants(layout), layoutstr);
+		if (lua_type(L, startidx) == LUA_TSTRING)
+		{
+			// (matrixlayout, data, ...)
+			const char *layoutstr = lua_tostring(L, startidx);
+			if (!math::Transform::getConstant(layoutstr, layout))
+				return luax_enumerror(L, "matrix layout", math::Transform::getConstants(layout), layoutstr);
 
-		columnmajor = (layout == math::Transform::MATRIX_COLUMN_MAJOR);
-		startidx++;
+			startidx++;
+			dataidx = startidx;
+		}
+		else if (lua_type(L, startidx + 1) == LUA_TSTRING)
+		{
+			// (data, matrixlayout, ...)
+			// Should be deprecated in the future (doesn't match the argument
+			// order of Shader:send(name, matrixlayout, table))
+			const char *layoutstr = lua_tostring(L, startidx + 1);
+			if (!math::Transform::getConstant(layoutstr, layout))
+				return luax_enumerror(L, "matrix layout", math::Transform::getConstants(layout), layoutstr);
+
+			startidx++;
+		}
 	}
 
-	Data *data = luax_checktype<Data>(L, startidx);
+	bool columnmajor = (layout == math::Transform::MATRIX_COLUMN_MAJOR);
 
+	Data *data = luax_checktype<Data>(L, dataidx);
 	size_t size = data->getSize();
 
 	ptrdiff_t offset = (ptrdiff_t) luaL_optinteger(L, startidx + 1, 0);
@@ -339,17 +374,17 @@ static int w_Shader_sendData(lua_State *L, int startidx, Shader *shader, const S
 
 	if (!lua_isnoneornil(L, startidx + 2))
 	{
-		lua_Integer datasize = luaL_checkinteger(L, startidx + 2);
-		if (datasize <= 0)
+		lua_Integer sizearg = luaL_checkinteger(L, startidx + 2);
+		if (sizearg <= 0)
 			return luaL_error(L, "Size must be greater than 0.");
-		else if ((size_t) datasize > size - offset)
+		else if ((size_t) sizearg > size - offset)
 			return luaL_error(L, "Size and offset must fit within the Data's bounds.");
-		else if (size % uniformstride != 0)
-			return luaL_error(L, "Size must be a multiple of the uniform's size in bytes.");
-		else if (size > info->dataSize)
+		else if (sizearg % uniformstride != 0)
+			return luaL_error(L, "Size (%d) must be a multiple of the uniform's size in bytes (%d).", sizearg, uniformstride);
+		else if ((size_t) sizearg > info->dataSize)
 			return luaL_error(L, "Size must not be greater than the uniform's total size in bytes.");
 
-		size = (size_t) datasize;
+		size = (size_t) sizearg;
 	}
 	else
 	{
@@ -416,7 +451,7 @@ int w_Shader_send(lua_State *L)
 		return 1;
 	}
 
-	if (luax_istype(L, 3, Data::type))
+	if (luax_istype(L, 3, Data::type) || (info->baseType == Shader::UNIFORM_MATRIX && luax_istype(L, 4, Data::type)))
 		w_Shader_sendData(L, 3, shader, info, false);
 	else
 		w_Shader_sendLuaValues(L, 3, shader, info, name);

+ 15 - 2
src/modules/graphics/wrap_SpriteBatch.cpp

@@ -216,9 +216,22 @@ int w_SpriteBatch_attachAttribute(lua_State *L)
 {
 	SpriteBatch *t = luax_checkspritebatch(L, 1);
 	const char *name = luaL_checkstring(L, 2);
-	Mesh *m = luax_checktype<Mesh>(L, 3);
 
-	luax_catchexcept(L, [&](){ t->attachAttribute(name, m); });
+	Buffer *buffer = nullptr;
+	if (luax_istype(L, 3, Buffer::type))
+	{
+		buffer = luax_checktype<Buffer>(L, 3);
+	}
+	else
+	{
+		Mesh *mesh = luax_checktype<Mesh>(L, 3);
+		buffer = mesh->getVertexBuffer();
+		if (buffer == nullptr)
+			return luaL_error(L, "Mesh does not have its own vertex buffer.");
+		luax_markdeprecated(L, "SpriteBatch:attachAttribute(name, mesh)", API_METHOD, DEPRECATED_REPLACED, "SpriteBatch:attachAttribute(name, buffer)");
+	}
+
+	luax_catchexcept(L, [&](){ t->attachAttribute(name, buffer); });
 	return 0;
 }
 

+ 2 - 1
src/modules/image/magpie/STBHandler.cpp

@@ -30,12 +30,13 @@ static void loveSTBIAssert(bool test, const char *teststr)
 }
 
 // stb_image
- #define STBI_ONLY_JPEG
+#define STBI_ONLY_JPEG
 // #define STBI_ONLY_PNG
 #define STBI_ONLY_BMP
 #define STBI_ONLY_TGA
 #define STBI_ONLY_HDR
 #define STBI_NO_STDIO
+#define STB_IMAGE_STATIC
 #define STB_IMAGE_IMPLEMENTATION
 #define STBI_ASSERT(A) loveSTBIAssert((A), #A)
 #include "libraries/stb/stb_image.h"

+ 0 - 5
src/modules/love/love.cpp

@@ -36,10 +36,6 @@
 
 #ifdef LOVE_ANDROID
 #include <SDL.h>
-extern "C"
-{
-#include "luajit.h"
-}
 #endif // LOVE_ANDROID
 
 #ifdef LOVE_LEGENDARY_CONSOLE_IO_HACK
@@ -383,7 +379,6 @@ int luaopen_love(lua_State *L)
 	lua_setfield(L, -2, "_version_codename");
 
 #ifdef LOVE_ANDROID
-	luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_OFF);
 	lua_register(L, "print", w_print_sdl_log);
 #endif
 

+ 68 - 37
src/modules/timer/Timer.cpp

@@ -24,6 +24,7 @@
 #include "common/delay.h"
 #include "Timer.h"
 
+#include <iostream>
 #if defined(LOVE_WINDOWS)
 #include <windows.h>
 #elif defined(LOVE_MACOS) || defined(LOVE_IOS)
@@ -35,15 +36,6 @@
 #include <sys/time.h>
 #endif
 
-#if defined(LOVE_LINUX)
-static inline double getTimeOfDay()
-{
-	timeval t;
-	gettimeofday(&t, NULL);
-	return (double) t.tv_sec + (double) t.tv_usec / 1000000.0;
-}
-#endif
-
 namespace love
 {
 namespace timer
@@ -109,52 +101,91 @@ double Timer::getAverageDelta() const
 	return averageDelta;
 }
 
-double Timer::getTimerPeriod()
+#if defined(LOVE_LINUX)
+
+static inline timespec getTimeOfDay()
 {
-#if defined(LOVE_MACOS) || defined(LOVE_IOS)
-	mach_timebase_info_data_t info;
-	mach_timebase_info(&info);
-	return (double) info.numer / (double) info.denom / 1000000000.0;
-#elif defined(LOVE_WINDOWS)
-	LARGE_INTEGER temp;
-	if (QueryPerformanceFrequency(&temp) != 0 && temp.QuadPart != 0)
-		return 1.0 / (double) temp.QuadPart;
-#endif
-	return 0;
+	timeval t;
+	gettimeofday(&t, NULL);
+	return timespec { t.tv_sec, t.tv_usec * 1000 };
 }
 
-double Timer::getTime()
+static timespec getTimeAbsolute()
 {
-	// The timer period (reciprocal of the frequency.)
-	static const double timerPeriod = getTimerPeriod();
-
-#if defined(LOVE_LINUX)
-	(void) timerPeriod; // Unused on linux
-
-	double mt;
 	// Check for POSIX timers and monotonic clocks. If not supported, use the gettimeofday fallback.
 #if _POSIX_TIMERS > 0 && defined(_POSIX_MONOTONIC_CLOCK) \
 && (defined(CLOCK_MONOTONIC_RAW) || defined(CLOCK_MONOTONIC))
-	timespec t;
+
 #ifdef CLOCK_MONOTONIC_RAW
 	clockid_t clk_id = CLOCK_MONOTONIC_RAW;
 #else
 	clockid_t clk_id = CLOCK_MONOTONIC;
 #endif
+
+	timespec t;
 	if (clock_gettime(clk_id, &t) == 0)
-		mt = (double) t.tv_sec + (double) t.tv_nsec / 1000000000.0;
+		return t;
 	else
+		return getTimeOfDay();
 #endif
-		mt = getTimeOfDay();
-	return mt;
+	return getTimeOfDay();
+}
+
+double Timer::getTime()
+{
+	static const timespec start = getTimeAbsolute();
+	const timespec now = getTimeAbsolute();
+	// tv_sec and tv_nsec should be signed on POSIX, so we are fine in just subtracting here.
+	const long sec = now.tv_sec - start.tv_sec;
+	const long nsec = now.tv_nsec - start.tv_nsec;
+	return (double) sec + (double) nsec / 1.0e9;
+}
+
 #elif defined(LOVE_MACOS) || defined(LOVE_IOS)
-	return (double) mach_absolute_time() * timerPeriod;
+
+static mach_timebase_info_data_t getTimebaseInfo()
+{
+	mach_timebase_info_data_t info;
+	mach_timebase_info(&info);
+	return info;
+}
+
+double Timer::getTime()
+{
+	static const mach_timebase_info_data_t info = getTimebaseInfo();
+	static const uint64_t start = mach_absolute_time();
+	const uint64_t rel = mach_absolute_time() - start;
+	return ((double) rel * 1.0e-9) * (double) info.numer / (double) info.denom;
+}
+
 #elif defined(LOVE_WINDOWS)
-	LARGE_INTEGER microTime;
-	QueryPerformanceCounter(&microTime);
-	return (double) microTime.QuadPart * timerPeriod;
-#endif
+
+static LARGE_INTEGER getTimeAbsolute()
+{
+	LARGE_INTEGER t;
+	QueryPerformanceCounter(&t);
+	return t;
+}
+
+static LARGE_INTEGER getFrequency()
+{
+	LARGE_INTEGER freq;
+	// "On systems that run Windows XP or later, the function will always succeed and will thus never return zero."
+	QueryPerformanceFrequency(&freq);
+	return freq;
 }
 
+double Timer::getTime()
+{
+	static const LARGE_INTEGER freq = getFrequency();
+	static const LARGE_INTEGER start = getTimeAbsolute();
+	const LARGE_INTEGER now = getTimeAbsolute();
+	LARGE_INTEGER rel;
+	rel.QuadPart = now.QuadPart - start.QuadPart;
+	return (double) rel.QuadPart / (double) freq.QuadPart;
+}
+
+#endif
+
 } // timer
 } // love

+ 5 - 6
src/modules/timer/Timer.h

@@ -72,9 +72,11 @@ public:
 	double getAverageDelta() const;
 
 	/**
-	 * Gets the amount of time passed since an unspecified time. Useful for
-	 * profiling code or measuring intervals. The time is microsecond-precise,
-	 * and increases monotonically.
+	 * Gets the amount of time in seconds passed since its first invocation
+	 * (which happens as part of the Timer constructor,
+	 * which is called when the module is first opened).
+	 * Useful for profiling code or measuring intervals.
+	 * The time is microsecond-precise, and increases monotonically.
 	 * @return The time (in seconds)
 	 **/
 	static double getTime();
@@ -99,9 +101,6 @@ private:
 	// The current timestep.
 	double dt;
 
-	// Returns the timer period on some platforms.
-	static double getTimerPeriod();
-
 }; // Timer
 
 } // timer

+ 1 - 1
src/scripts/boot.lua

@@ -532,6 +532,7 @@ function love.init()
 
 	-- Setup window here.
 	if c.window and c.modules.window then
+		love.window.setTitle(c.window.title or c.title)
 		assert(love.window.setMode(c.window.width, c.window.height,
 		{
 			fullscreen = c.window.fullscreen,
@@ -551,7 +552,6 @@ function love.init()
 			x = c.window.x,
 			y = c.window.y,
 		}), "Could not set window mode")
-		love.window.setTitle(c.window.title or c.title)
 		if c.window.icon then
 			assert(love.image, "If an icon is set in love.conf, love.image must be loaded!")
 			love.window.setIcon(love.image.newImageData(c.window.icon))

+ 3 - 3
src/scripts/boot.lua.h

@@ -1055,6 +1055,9 @@ const unsigned char boot_lua[] =
 	0x09, 0x69, 0x66, 0x20, 0x63, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x63, 
 	0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x74, 0x68, 
 	0x65, 0x6e, 0x0a,
+	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x73, 0x65, 0x74, 0x54, 
+	0x69, 0x74, 0x6c, 0x65, 0x28, 0x63, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x74, 0x69, 0x74, 0x6c, 
+	0x65, 0x20, 0x6f, 0x72, 0x20, 0x63, 0x2e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x29, 0x0a,
 	0x09, 0x09, 0x61, 0x73, 0x73, 0x65, 0x72, 0x74, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 
 	0x6f, 0x77, 0x2e, 0x73, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x28, 0x63, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 
 	0x77, 0x2e, 0x77, 0x69, 0x64, 0x74, 0x68, 0x2c, 0x20, 0x63, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 
@@ -1094,9 +1097,6 @@ const unsigned char boot_lua[] =
 	0x09, 0x09, 0x09, 0x79, 0x20, 0x3d, 0x20, 0x63, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x79, 0x2c, 0x0a,
 	0x09, 0x09, 0x7d, 0x29, 0x2c, 0x20, 0x22, 0x43, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 
 	0x65, 0x74, 0x20, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0x29, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x73, 0x65, 0x74, 0x54, 
-	0x69, 0x74, 0x6c, 0x65, 0x28, 0x63, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x74, 0x69, 0x74, 0x6c, 
-	0x65, 0x20, 0x6f, 0x72, 0x20, 0x63, 0x2e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x29, 0x0a,
 	0x09, 0x09, 0x69, 0x66, 0x20, 0x63, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x69, 0x63, 0x6f, 0x6e, 
 	0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
 	0x09, 0x09, 0x09, 0x61, 0x73, 0x73, 0x65, 0x72, 0x74, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x69, 0x6d, 0x61, 

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