Browse Source

Merged default into minor

--HG--
branch : minor
Alex Szpakowski 11 years ago
parent
commit
60cd1456ea
100 changed files with 2698 additions and 784 deletions
  1. 1 0
      CMakeLists.txt
  2. 26 1
      changes.txt
  3. 2 2
      platform/macosx/Info-Framework.plist
  4. 1 1
      platform/macosx/love-Info.plist
  5. 67 1
      platform/macosx/love-framework.xcodeproj/project.pbxproj
  6. 76 1
      platform/macosx/love.xcodeproj/project.pbxproj
  7. 5 0
      platform/unix/debian/love.install
  8. 12 2
      platform/unix/love.1
  9. 1 1
      src/common/Data.h
  10. 6 2
      src/common/Memoizer.cpp
  11. 17 7
      src/common/Reference.cpp
  12. 17 1
      src/common/Reference.h
  13. 1 1
      src/common/Vector.cpp
  14. 1 1
      src/common/b64.h
  15. 1 1
      src/common/runtime.cpp
  16. 1 1
      src/common/runtime.h
  17. 1 1
      src/common/utf8.h
  18. 3 3
      src/common/version.h
  19. 16 15
      src/libraries/ddsparse/ddsparse.cpp
  20. 1 1
      src/libraries/ddsparse/ddsparse.h
  21. 0 4
      src/libraries/enet/enet.cpp
  22. 5 0
      src/libraries/luasocket/libluasocket/lua.h
  23. 2 2
      src/modules/audio/Audio.h
  24. 1 1
      src/modules/audio/Source.h
  25. 2 5
      src/modules/audio/null/Audio.cpp
  26. 1 2
      src/modules/audio/null/Audio.h
  27. 2 1
      src/modules/audio/null/Source.cpp
  28. 1 1
      src/modules/audio/null/Source.h
  29. 28 14
      src/modules/audio/openal/Audio.cpp
  30. 1 2
      src/modules/audio/openal/Audio.h
  31. 32 12
      src/modules/audio/openal/Pool.cpp
  32. 8 3
      src/modules/audio/openal/Pool.h
  33. 13 3
      src/modules/audio/openal/Source.cpp
  34. 2 2
      src/modules/audio/openal/Source.h
  35. 2 2
      src/modules/audio/wrap_Audio.cpp
  36. 2 2
      src/modules/audio/wrap_Source.cpp
  37. 56 9
      src/modules/event/sdl/Event.cpp
  38. 1 1
      src/modules/font/Font.h
  39. 0 1
      src/modules/font/GlyphData.h
  40. 1 1
      src/modules/font/ImageRasterizer.h
  41. 1 1
      src/modules/font/Rasterizer.cpp
  42. 1 1
      src/modules/font/Rasterizer.h
  43. 1 1
      src/modules/font/freetype/TrueTypeRasterizer.cpp
  44. 1 1
      src/modules/graphics/Drawable.h
  45. 22 0
      src/modules/graphics/Graphics.cpp
  46. 18 1
      src/modules/graphics/Graphics.h
  47. 19 0
      src/modules/graphics/Texture.cpp
  48. 15 1
      src/modules/graphics/Texture.h
  49. 347 83
      src/modules/graphics/opengl/Canvas.cpp
  50. 29 22
      src/modules/graphics/opengl/Canvas.h
  51. 35 4
      src/modules/graphics/opengl/Font.cpp
  52. 8 3
      src/modules/graphics/opengl/Font.h
  53. 182 96
      src/modules/graphics/opengl/Graphics.cpp
  54. 28 12
      src/modules/graphics/opengl/Graphics.h
  55. 46 15
      src/modules/graphics/opengl/Image.cpp
  56. 15 8
      src/modules/graphics/opengl/Image.h
  57. 193 41
      src/modules/graphics/opengl/Mesh.cpp
  58. 35 16
      src/modules/graphics/opengl/Mesh.h
  59. 193 4
      src/modules/graphics/opengl/OpenGL.cpp
  60. 60 0
      src/modules/graphics/opengl/OpenGL.h
  61. 78 44
      src/modules/graphics/opengl/ParticleSystem.cpp
  62. 39 19
      src/modules/graphics/opengl/ParticleSystem.h
  63. 52 9
      src/modules/graphics/opengl/Shader.cpp
  64. 12 1
      src/modules/graphics/opengl/Shader.h
  65. 5 1
      src/modules/graphics/opengl/SpriteBatch.cpp
  66. 1 3
      src/modules/graphics/opengl/SpriteBatch.h
  67. 2 2
      src/modules/graphics/opengl/Texture.h
  68. 25 13
      src/modules/graphics/opengl/wrap_Canvas.cpp
  69. 2 1
      src/modules/graphics/opengl/wrap_Canvas.h
  70. 106 49
      src/modules/graphics/opengl/wrap_Graphics.cpp
  71. 3 0
      src/modules/graphics/opengl/wrap_Graphics.h
  72. 57 22
      src/modules/graphics/opengl/wrap_Mesh.cpp
  73. 4 2
      src/modules/graphics/opengl/wrap_Mesh.h
  74. 28 2
      src/modules/graphics/opengl/wrap_ParticleSystem.cpp
  75. 3 0
      src/modules/graphics/opengl/wrap_ParticleSystem.h
  76. 2 2
      src/modules/image/Image.h
  77. 14 18
      src/modules/image/magpie/DevilHandler.cpp
  78. 12 7
      src/modules/image/magpie/DevilHandler.h
  79. 62 0
      src/modules/image/magpie/FormatHandler.cpp
  80. 18 10
      src/modules/image/magpie/FormatHandler.h
  81. 8 5
      src/modules/image/magpie/Image.cpp
  82. 9 0
      src/modules/image/magpie/Image.h
  83. 41 14
      src/modules/image/magpie/ImageData.cpp
  84. 10 3
      src/modules/image/magpie/ImageData.h
  85. 11 0
      src/modules/keyboard/Keyboard.cpp
  86. 11 0
      src/modules/keyboard/Keyboard.h
  87. 11 0
      src/modules/keyboard/sdl/Keyboard.cpp
  88. 12 0
      src/modules/love/love.cpp
  89. 30 0
      src/modules/math/MathModule.cpp
  90. 30 20
      src/modules/math/MathModule.h
  91. 76 20
      src/modules/math/RandomGenerator.cpp
  92. 31 43
      src/modules/math/RandomGenerator.h
  93. 93 14
      src/modules/math/wrap_Math.cpp
  94. 6 2
      src/modules/math/wrap_Math.h
  95. 32 27
      src/modules/math/wrap_RandomGenerator.cpp
  96. 4 2
      src/modules/math/wrap_RandomGenerator.h
  97. 1 1
      src/modules/mouse/Mouse.h
  98. 47 4
      src/modules/mouse/sdl/Mouse.cpp
  99. 2 2
      src/modules/mouse/wrap_Cursor.cpp
  100. 43 1
      src/modules/physics/box2d/Body.cpp

+ 1 - 0
CMakeLists.txt

@@ -341,6 +341,7 @@ set(LOVE_SRC_MODULE_IMAGE_MAGPIE
 	src/modules/image/magpie/ddsHandler.h
 	src/modules/image/magpie/DevilHandler.cpp
 	src/modules/image/magpie/DevilHandler.h
+	src/modules/image/magpie/FormatHandler.cpp
 	src/modules/image/magpie/FormatHandler.h
 	src/modules/image/magpie/Image.cpp
 	src/modules/image/magpie/Image.h

+ 26 - 1
changes.txt

@@ -5,11 +5,27 @@ LOVE 0.9.1 [Baby Inspector]
 
   * Added Source:clone.
   * Added ParticleSystem:clone.
-  * Added Mesh:setWireframe and Mesh:isWireframe for debugging.
+  * Added ParticleSystem:moveTo, has smoother emitter movement compared to setPosition.
+  * Added ParticleSystem:setRelativeRotation.
+  * Added love.graphics.setWireframe for debugging.
+  * Added Mesh:setDrawRange and Mesh:getDrawRange.
+  * Added instancing support to Meshes with Mesh:setInstanceCount.
   * Added CircleShape:getPoint and CircleShape:setPoint.
   * Added Mesh/SpriteBatch/ParticleSystem:setTexture, accepts Canvases and Images.
+  * Added high-dpi window support for Retina displays in OS X, via the 'highdpi' window flag.
+  * Added love.window.getPixelScale.
+  * Added love.graphics.getSystemLimit.
+  * Added antialiasing support to Canvases.
+  * Added Canvas:getFSAA.
+  * Added 'love_ScreenSize' built-in variable in shaders.
+  * Added love.getVersion.
+  * Added support for gamma-correct rendering.
+  * Added love.graphics.isSupported("srgb").
+  * Added love.math.gammaToLinear and love.math.linearToGamma.
+  * Added RandomGenerator:getState and RandomGenerator:setState.
 
   * Deprecated Mesh/SpriteBatch/ParticleSystem:setImage.
+  * Deprecated love.graphics.getMaxImageSize and love.graphics.getMaxPointSize.
 
   * Fixed love.graphics.scale with negative values causing incorrect line widths.
   * Fixed Joystick:isDown using 0-based button index arguments.
@@ -17,11 +33,20 @@ LOVE 0.9.1 [Baby Inspector]
   * Fixed love.graphics.setCanvas() to restore the proper viewport and scissor rectangles.
   * Fixed TrueType font glyphs which request a monochrome bitmap pixel mode.
   * Fixed love.graphics.reset causing crashes when called in between love.graphics.push/pop.
+  * Fixed tab characters ("\t") to display properly with love.graphics.print.
+  * Fixed World:getBodyList and World:getJointList causing hard crashes.
+
+  * Renamed love.graphics.getMaxImageSize to love.graphics.getMaxTextureSize (old function still exists.)
 
   * Updated the error text for love.filesystem’s module searchers when require fails.
   * Updated the love.filesystem module searchers to be tried after package.preload instead of before.
   * Updated love.graphics.newParticleSystem, newSpriteBatch, and newMesh to accept Canvases.
   * Updated Canvas drawing code, texture coordinates are no longer flipped vertically.
+  * Updated Canvas:renderTo to work properly if a Canvas is currently active.
+  * Updated ParticleSystem:setEmissionRate to accept non-integer numbers.
+  * Updated Source:play to return a boolean indicating success.
+  * Updated t.console in conf.lua to create the console before modules are loaded in Windows.
+  * Updated Mesh vertex maps (index buffers) to use less space in VRAM.
 
 LOVE 0.9.0 [Baby Inspector]
 ---------------------------

+ 2 - 2
platform/macosx/Info-Framework.plist

@@ -17,11 +17,11 @@
 	<key>CFBundlePackageType</key>
 	<string>FMWK</string>
 	<key>CFBundleShortVersionString</key>
-	<string>0.9.0</string>
+	<string>0.9.1</string>
 	<key>CFBundleSignature</key>
 	<string>LoVe</string>
 	<key>CFBundleVersion</key>
-	<string>0.9.0</string>
+	<string>0.9.1</string>
 	<key>NSPrincipalClass</key>
 	<string></string>
 </dict>

+ 1 - 1
platform/macosx/love-Info.plist

@@ -46,7 +46,7 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>0.9.0</string>
+	<string>0.9.1</string>
 	<key>CFBundleSignature</key>
 	<string>LoVe</string>
 	<key>LSApplicationCategoryType</key>

+ 67 - 1
platform/macosx/love-framework.xcodeproj/project.pbxproj

@@ -289,6 +289,7 @@
 		FAC86E641724552C00EED715 /* wrap_Quad.h in Headers */ = {isa = PBXBuildFile; fileRef = FAC86E621724552C00EED715 /* wrap_Quad.h */; };
 		FAC86E6B1724555D00EED715 /* Quad.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAC86E671724555D00EED715 /* Quad.cpp */; };
 		FAC86E6C1724555D00EED715 /* Quad.h in Headers */ = {isa = PBXBuildFile; fileRef = FAC86E681724555D00EED715 /* Quad.h */; };
+		FADD58DD18C30367005FC3BF /* FormatHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FADD58DC18C30367005FC3BF /* FormatHandler.cpp */; };
 		FAE010DB170DDE99006F29D0 /* ddsinfo.h in Headers */ = {isa = PBXBuildFile; fileRef = FAE010D8170DDE99006F29D0 /* ddsinfo.h */; };
 		FAE010DC170DDE99006F29D0 /* ddsparse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAE010D9170DDE99006F29D0 /* ddsparse.cpp */; };
 		FAE010DD170DDE99006F29D0 /* ddsparse.h in Headers */ = {isa = PBXBuildFile; fileRef = FAE010DA170DDE99006F29D0 /* ddsparse.h */; };
@@ -847,6 +848,7 @@
 		FAC86E621724552C00EED715 /* wrap_Quad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Quad.h; sourceTree = "<group>"; };
 		FAC86E671724555D00EED715 /* Quad.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Quad.cpp; sourceTree = "<group>"; };
 		FAC86E681724555D00EED715 /* Quad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Quad.h; sourceTree = "<group>"; };
+		FADD58DC18C30367005FC3BF /* FormatHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FormatHandler.cpp; sourceTree = "<group>"; };
 		FAE010D8170DDE99006F29D0 /* ddsinfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ddsinfo.h; sourceTree = "<group>"; };
 		FAE010D9170DDE99006F29D0 /* ddsparse.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ddsparse.cpp; sourceTree = "<group>"; };
 		FAE010DA170DDE99006F29D0 /* ddsparse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ddsparse.h; sourceTree = "<group>"; };
@@ -1088,6 +1090,7 @@
 				FAE010DF170DE25E006F29D0 /* ddsHandler.h */,
 				1AA7781A230065F346E2313A /* DevilHandler.cpp */,
 				283342E174613897621A43F1 /* DevilHandler.h */,
+				FADD58DC18C30367005FC3BF /* FormatHandler.cpp */,
 				FA0CDE3B1710F9A50056E8D7 /* FormatHandler.h */,
 				505F23A73BFE250833D650E4 /* Image.cpp */,
 				68616BD516DB124312B47EB3 /* Image.h */,
@@ -2045,7 +2048,7 @@
 		08FB7793FE84155DC02AAC07 /* Project object */ = {
 			isa = PBXProject;
 			attributes = {
-				LastUpgradeCheck = 0500;
+				LastUpgradeCheck = 0510;
 			};
 			buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "love-framework" */;
 			compatibilityVersion = "Xcode 3.2";
@@ -2267,6 +2270,7 @@
 				FA08F66816C7548200F007B5 /* wrap_WheelJoint.cpp in Sources */,
 				FA08F66916C7548200F007B5 /* wrap_World.cpp in Sources */,
 				FA08F66A16C7549200F007B5 /* Sound.cpp in Sources */,
+				FADD58DD18C30367005FC3BF /* FormatHandler.cpp in Sources */,
 				FA08F66B16C7549200F007B5 /* SoundData.cpp in Sources */,
 				FA08F66C16C7549200F007B5 /* wrap_Decoder.cpp in Sources */,
 				FA08F66D16C7549200F007B5 /* wrap_Sound.cpp in Sources */,
@@ -2407,6 +2411,66 @@
 			};
 			name = Debug;
 		};
+		FA5326C4189719C700F7BBF4 /* Distribution */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
+				CLANG_ENABLE_MODULES = YES;
+				DEAD_CODE_STRIPPING = YES;
+				FRAMEWORK_SEARCH_PATHS = /Library/Frameworks;
+				GCC_OPTIMIZATION_LEVEL = 3;
+				GCC_PREPROCESSOR_DEFINITIONS = LOVE_MACOSX_USE_FRAMEWORKS;
+				GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES;
+				GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
+				GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
+				GCC_WARN_SIGN_COMPARE = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES;
+				GCC_WARN_UNUSED_PARAMETER = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				HEADER_SEARCH_PATHS = (
+					"\"$(SRCROOT)/../../src\"",
+					"\"$(SRCROOT)/../../src/libraries\"",
+					"\"$(SRCROOT)/../../src/modules\"",
+					"\"$(SRCROOT)/../../src/libraries/enet/libenet/include\"",
+					/Library/Frameworks/FreeType.framework/Headers,
+					/Library/Frameworks/Lua.framework/Headers,
+					/Library/Frameworks/SDL2.framework/Headers,
+				);
+				LD_RUNPATH_SEARCH_PATHS = "@rpath";
+				LIBRARY_SEARCH_PATHS = "";
+				LLVM_LTO = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.6;
+				ONLY_ACTIVE_ARCH = NO;
+				USE_HEADERMAP = NO;
+				WARNING_CFLAGS = "-Wall";
+			};
+			name = Distribution;
+		};
+		FA5326C5189719C700F7BBF4 /* Distribution */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ENABLE_MODULES = NO;
+				COMBINE_HIDPI_IMAGES = YES;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				DYLIB_COMPATIBILITY_VERSION = 9.0;
+				DYLIB_CURRENT_VERSION = 9.0;
+				FRAMEWORK_VERSION = A;
+				GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+				INFOPLIST_FILE = "Info-Framework.plist";
+				LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
+				OTHER_LDFLAGS = (
+					"-undefined",
+					dynamic_lookup,
+				);
+				PRODUCT_NAME = love;
+				SKIP_INSTALL = YES;
+				WRAPPER_EXTENSION = framework;
+			};
+			name = Distribution;
+		};
 		FA577AC016C7507900860150 /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
@@ -2464,6 +2528,7 @@
 			buildConfigurations = (
 				64274E785071353E1A1D0D4B /* Debug */,
 				10D5479E63C26BB35EB5482E /* Release */,
+				FA5326C4189719C700F7BBF4 /* Distribution */,
 			);
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Debug;
@@ -2473,6 +2538,7 @@
 			buildConfigurations = (
 				FA577AC016C7507900860150 /* Debug */,
 				FA577AC116C7507900860150 /* Release */,
+				FA5326C5189719C700F7BBF4 /* Distribution */,
 			);
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Debug;

+ 76 - 1
platform/macosx/love.xcodeproj/project.pbxproj

@@ -198,7 +198,7 @@
 		29B97313FDCFA39411CA2CEA /* Project object */ = {
 			isa = PBXProject;
 			attributes = {
-				LastUpgradeCheck = 0500;
+				LastUpgradeCheck = 0510;
 			};
 			buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "love" */;
 			compatibilityVersion = "Xcode 3.2";
@@ -402,6 +402,79 @@
 			};
 			name = Release;
 		};
+		FA5326C618971A0900F7BBF4 /* Distribution */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
+				CLANG_ENABLE_MODULES = YES;
+				DEPLOYMENT_POSTPROCESSING = NO;
+				FRAMEWORK_SEARCH_PATHS = /Library/Frameworks;
+				GCC_INPUT_FILETYPE = automatic;
+				GCC_OPTIMIZATION_LEVEL = 3;
+				GCC_PREPROCESSOR_DEFINITIONS = LOVE_MACOSX_USE_FRAMEWORKS;
+				GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = NO;
+				GCC_WARN_ABOUT_MISSING_NEWLINE = NO;
+				GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO;
+				GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = NO;
+				GCC_WARN_CHECK_SWITCH_STATEMENTS = YES;
+				GCC_WARN_FOUR_CHARACTER_CONSTANTS = NO;
+				GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = NO;
+				GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = NO;
+				GCC_WARN_MISSING_PARENTHESES = NO;
+				GCC_WARN_NON_VIRTUAL_DESTRUCTOR = NO;
+				GCC_WARN_PEDANTIC = NO;
+				GCC_WARN_SHADOW = NO;
+				GCC_WARN_SIGN_COMPARE = YES;
+				GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO;
+				GCC_WARN_UNUSED_PARAMETER = NO;
+				GCC_WARN_UNUSED_VALUE = NO;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				HEADER_SEARCH_PATHS = (
+					"\"$(SRCROOT)/../../src\"",
+					"\"$(SRCROOT)/../../src/libraries\"",
+					"\"$(SRCROOT)/../../src/modules\"",
+					/Library/Frameworks/Lua.framework/Headers,
+					/Library/Frameworks/SDL2.framework/Headers,
+				);
+				INFOPLIST_FILE = "love-Info.plist";
+				LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
+				LLVM_LTO = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.6;
+				ONLY_ACTIVE_ARCH = NO;
+				OTHER_LDFLAGS = "";
+				"OTHER_LDFLAGS[arch=x86_64]" = (
+					"-pagezero_size",
+					10000,
+					"-image_base",
+					100000000,
+				);
+				PRODUCT_NAME = love;
+				SCAN_ALL_SOURCE_FILES_FOR_INCLUDES = YES;
+				WARNING_CFLAGS = (
+					"-Wall",
+					"-W",
+				);
+			};
+			name = Distribution;
+		};
+		FA5326C718971A0900F7BBF4 /* Distribution */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				COMBINE_HIDPI_IMAGES = YES;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(inherited)",
+					"\"$(SRCROOT)/build/Release\"",
+					"\"$(SRCROOT)/build/Debug\"",
+				);
+				INSTALL_PATH = /Applications;
+				PRODUCT_NAME = love;
+			};
+			name = Distribution;
+		};
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
@@ -410,6 +483,7 @@
 			buildConfigurations = (
 				C01FCF4B08A954540054247B /* Debug */,
 				C01FCF4C08A954540054247B /* Release */,
+				FA5326C718971A0900F7BBF4 /* Distribution */,
 			);
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
@@ -419,6 +493,7 @@
 			buildConfigurations = (
 				C01FCF4F08A954540054247B /* Debug */,
 				C01FCF5008A954540054247B /* Release */,
+				FA5326C618971A0900F7BBF4 /* Distribution */,
 			);
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;

+ 5 - 0
platform/unix/debian/love.install

@@ -1 +1,6 @@
 usr/bin/love
+usr/share/man/man1/love.1
+usr/share/pixmaps/love.svg
+usr/share/mime/packages/love.xml
+usr/share/icons/hicolor/scalable/mimetypes/application-x-love-game.svg
+usr/share/applications/love.desktop

+ 12 - 2
platform/unix/love.1

@@ -1,4 +1,5 @@
 .\" (c) 2008-2011 Miriam Ruiz <[email protected]>
+.\" (c) 2013 Bart van Strien <[email protected]>
 .\"
 .\" This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damagesarising from the use of this software.
 .\"
@@ -11,16 +12,25 @@
 .\" 3. This notice may not be removed or altered from any source distribution.
 .\"
 .\" Modifications:
-.\" - Update version to 0.9 and remove reference to doc dir - Bart van Strien, 2013
+.\" - Update version to 0.9 and remove reference to doc dir
+.\" - Add fused and version flags
+
 .TH "LÖVE" "1" "0.9" "" ""
 .SH "NAME"
 love \- 2D game development framework
+
 .SH "SYNOPSIS"
 .B love
-<\fIgame.love\fR>
+[--fused] <\fIgame.love\fR>
+.PP
+.B love
+--version
+.PP
+
 .SH "DESCRIPTION"
 LÖVE was created to be a user\-friendly engine in which simple (or complicated) games could be made without having extensive knowledge of system or graphics functions and without having to dedicate time towards developing the same engine features time and time again.
 .P
 Developed with cross\-platform implementation in mind, it utilizes the latest open source libraries to deliver a similar game experience, independent of operating system. By relying on the Lua scripting language for game\-specific programming, it allows even the novice game creator to quickly and efficiently develop an idea into a fully working game.
+
 .SH "SEE ALSO"
 You can find more information at \fIhttp://love2d.org/\fR

+ 1 - 1
src/common/Data.h

@@ -38,7 +38,7 @@ public:
 	/**
 	 * Destructor.
 	 **/
-	virtual ~Data() {};
+	virtual ~Data() {}
 
 	/**
 	 * Gets a pointer to the data. This pointer will obviously not

+ 6 - 2
src/common/Memoizer.cpp

@@ -38,8 +38,12 @@ void Memoizer::remove(void *key)
 
 void *Memoizer::find(void *key)
 {
-	if (objectMap.count(key)) return objectMap[key];
-	return NULL;
+	auto it = objectMap.find(key);
+
+	if (it != objectMap.end())
+		return it->second;
+	else
+		return nullptr;
 }
 
 } // love

+ 17 - 7
src/common/Reference.cpp

@@ -26,7 +26,7 @@ namespace love
 const char REFERENCE_TABLE_NAME[] = "love-references";
 
 Reference::Reference()
-	: L(0)
+	: L(nullptr)
 	, idx(LUA_REFNIL)
 {
 }
@@ -64,21 +64,31 @@ void Reference::unref()
 	}
 }
 
-void Reference::push()
+void Reference::push(lua_State *newL)
 {
 	if (idx != LUA_REFNIL)
 	{
-		luax_insist(L, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME);
-		lua_rawgeti(L, -1, idx);
-		lua_remove(L, -2);
+		luax_insist(newL, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME);
+		lua_rawgeti(newL, -1, idx);
+		lua_remove(newL, -2);
 	}
 	else
-		lua_pushnil(L);
+		lua_pushnil(newL);
 }
 
-lua_State *Reference::getL()
+void Reference::push()
+{
+	push(L);
+}
+
+lua_State *Reference::getL() const
 {
 	return L;
 }
 
+void Reference::setL(lua_State *newL)
+{
+	L = newL;
+}
+
 } // love

+ 17 - 1
src/common/Reference.h

@@ -63,6 +63,14 @@ public:
 	 **/
 	void unref();
 
+	/**
+	 * Pushes the referred value onto the stack of a different coroutine
+	 * in the same main Lua state.
+	 * THIS SHOULD NOT BE USED FOR DIFFERENT LUA STATES (created with
+	 * luaL_newstate)! Only with different coroutines!
+	 **/
+	void push(lua_State *newL);
+
 	/**
 	 * Pushes the referred value onto the stack.
 	 **/
@@ -72,7 +80,15 @@ public:
 	 * Gets the Lua state associated with this
 	 * reference.
 	 **/
-	lua_State *getL();
+	lua_State *getL() const;
+
+	/**
+	 * Associates a new Lua state with this reference.
+	 * THIS IS DANGEROUS! It is only designed to be
+	 * used with different coroutines from the same
+	 * main Lua state!
+	 **/
+	void setL(lua_State *newL);
 
 private:
 

+ 1 - 1
src/common/Vector.cpp

@@ -23,4 +23,4 @@
 namespace love
 {
 // Implementation in header.
-}
+}

+ 1 - 1
src/common/b64.h

@@ -38,4 +38,4 @@ char *b64_decode(const char *src, int slen, int &size);
 
 } // love
 
-#endif // LOVE_B64_H
+#endif // LOVE_B64_H

+ 1 - 1
src/common/runtime.cpp

@@ -464,7 +464,7 @@ void luax_pushtype(lua_State *L, const char *name, bits flags, love::Object *dat
 
 bool luax_istype(lua_State *L, int idx, love::bits type)
 {
-	if (lua_isuserdata(L, idx) == 0)
+	if (lua_type(L, idx) != LUA_TUSERDATA)
 		return false;
 
 	return ((((Proxy *)lua_touserdata(L, idx))->flags & type) == type);

+ 1 - 1
src/common/runtime.h

@@ -401,7 +401,7 @@ extern "C" { // Also called from luasocket
 template <typename T>
 T *luax_checktype(lua_State *L, int idx, const char *name, love::bits type)
 {
-	if (lua_isuserdata(L, idx) == 0)
+	if (lua_type(L, idx) != LUA_TUSERDATA)
 		luax_typerror(L, idx, name);
 
 	Proxy *u = (Proxy *)lua_touserdata(L, idx);

+ 1 - 1
src/common/utf8.h

@@ -45,4 +45,4 @@ void replace_char(std::string &str, char find, char replace);
 
 } // love
 
-#endif // LOVE_WINDOWS
+#endif // LOVE_WINDOWS

+ 3 - 3
src/common/version.h

@@ -27,9 +27,9 @@ namespace love
 // Version stuff.
 const int VERSION_MAJOR = 0;
 const int VERSION_MINOR = 9;
-const int VERSION_REV = 0;
-const char *VERSION = "0.9.0";
-const char *VERSION_COMPATIBILITY[] =  { VERSION, 0 };
+const int VERSION_REV = 1;
+const char *VERSION = "0.9.1";
+const char *VERSION_COMPATIBILITY[] =  { VERSION, "0.9.0", 0 };
 const char *VERSION_CODENAME = "Baby Inspector";
 
 } // love

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

@@ -237,36 +237,37 @@ size_t Parser::getMipmapCount() const
 
 size_t Parser::parseImageSize(Format fmt, int width, int height) const
 {
-	size_t size = 0;
+	size_t numBlocksWide = 0;
+	size_t numBlocksHigh = 0;
+	size_t numBytesPerBlock = 0;
 
 	switch (fmt)
 	{
 	case FORMAT_DXT1:
+	case FORMAT_BC4:
+	case FORMAT_BC4s:
+		numBytesPerBlock = 8;
+		break;
 	case FORMAT_DXT3:
 	case FORMAT_DXT5:
 	case FORMAT_BC5s:
 	case FORMAT_BC5:
+	case FORMAT_BC6H:
 	case FORMAT_BC7:
 	case FORMAT_BC7srgb:
-		{
-			int numBlocksWide = 0;
-			if (width > 0)
-				numBlocksWide = std::max(1, (width + 3) / 4);
-
-			int numBlocksHigh = 0;
-			if (height > 0)
-				numBlocksHigh = std::max(1, (height + 3) / 4);
-
-			int numBytesPerBlock = (fmt == FORMAT_DXT1 ? 8 : 16);
-
-			size = numBlocksWide * numBytesPerBlock * numBlocksHigh;
-		}
+		numBytesPerBlock = 16;
 		break;
 	default:
 		break;
 	}
 
-	return size;
+	if (width > 0)
+		numBlocksWide = std::max(1, (width + 3) / 4);
+
+	if (height > 0)
+		numBlocksHigh = std::max(1, (height + 3) / 4);
+
+	return numBlocksWide * numBytesPerBlock * numBlocksHigh;
 }
 
 bool Parser::parseTexData(const uint8_t *data, size_t dataSize, Format fmt, int w, int h, int mips)

+ 1 - 1
src/libraries/ddsparse/ddsparse.h

@@ -60,7 +60,7 @@ struct Image
 	const uint8_t *data;
 
 	Image() : width(0), height(0), dataSize(0), data(0)
-	{};
+	{}
 };
 
 /**

+ 0 - 4
src/libraries/enet/enet.cpp

@@ -716,10 +716,6 @@ static const struct luaL_Reg enet_peer_funcs [] = {
 	{NULL, NULL}
 };
 
-static const struct luaL_Reg enet_event_funcs [] = {
-	{NULL, NULL}
-};
-
 int luaopen_enet(lua_State *l) {
 	enet_initialize();
 	atexit(enet_deinitialize);

+ 5 - 0
src/libraries/luasocket/libluasocket/lua.h

@@ -1,3 +1,6 @@
+#ifndef LUA_WRAP_H
+#define LUA_WRAP_H
+
 #define LUA_COMPAT_ALL
 #include <lua.h>
 #include <lualib.h>
@@ -10,3 +13,5 @@
 
 extern int luax_typerror(lua_State *L, int narg, const char *type);
 #endif
+
+#endif // LUA_WRAP_H

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

@@ -67,7 +67,7 @@ public:
 	/**
 	 * Destructor.
 	 **/
-	virtual ~Audio() {};
+	virtual ~Audio() {}
 
 	virtual Source *newSource(love::sound::Decoder *decoder) = 0;
 	virtual Source *newSource(love::sound::SoundData *soundData) = 0;
@@ -88,7 +88,7 @@ public:
 	 * Play the specified Source.
 	 * @param source The Source to play.
 	 **/
-	virtual void play(Source *source) = 0;
+	virtual bool play(Source *source) = 0;
 
 	/**
 	 * Stops playback on the specified source.

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

@@ -53,7 +53,7 @@ public:
 
 	virtual Source *clone() = 0;
 
-	virtual void play() = 0;
+	virtual bool play() = 0;
 	virtual void stop() = 0;
 	virtual void pause() = 0;
 	virtual void resume() = 0;

+ 2 - 5
src/modules/audio/null/Audio.cpp

@@ -61,12 +61,9 @@ int Audio::getMaxSources() const
 	return 0;
 }
 
-void Audio::play(love::audio::Source *)
-{
-}
-
-void Audio::play()
+bool Audio::play(love::audio::Source *)
 {
+	return false;
 }
 
 void Audio::stop(love::audio::Source *)

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

@@ -48,8 +48,7 @@ public:
 	love::audio::Source *newSource(love::sound::SoundData *soundData);
 	int getSourceCount() const;
 	int getMaxSources() const;
-	void play(love::audio::Source *source);
-	void play();
+	bool play(love::audio::Source *source);
 	void stop(love::audio::Source *source);
 	void stop();
 	void pause(love::audio::Source *source);

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

@@ -42,8 +42,9 @@ love::audio::Source *Source::clone()
 	return this;
 }
 
-void Source::play()
+bool Source::play()
 {
+	return false;
 }
 
 void Source::stop()

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

@@ -39,7 +39,7 @@ public:
 	virtual ~Source();
 
 	virtual love::audio::Source *clone();
-	virtual void play();
+	virtual bool play();
 	virtual void stop();
 	virtual void pause();
 	virtual void resume();

+ 28 - 14
src/modules/audio/openal/Audio.cpp

@@ -69,22 +69,25 @@ void Audio::PoolThread::setFinish()
 }
 
 Audio::Audio()
-	: distanceModel(DISTANCE_INVERSE_CLAMPED)
+	: device(nullptr)
+	, capture(nullptr)
+	, context(nullptr)
+	, pool(nullptr)
+	, poolThread(nullptr)
+	, distanceModel(DISTANCE_INVERSE_CLAMPED)
 {
-	// Passing zero for default device.
-	device = alcOpenDevice(0);
+	// Passing null for default device.
+	device = alcOpenDevice(nullptr);
 
-	if (device == 0)
+	if (device == nullptr)
 		throw love::Exception("Could not open device.");
 
-	context = alcCreateContext(device, 0);
+	context = alcCreateContext(device, nullptr);
 
-	if (context == 0)
+	if (context == nullptr)
 		throw love::Exception("Could not create context.");
 
-	alcMakeContextCurrent(context);
-
-	if (alcGetError(device) != ALC_NO_ERROR)
+	if (!alcMakeContextCurrent(context) || alcGetError(device) != ALC_NO_ERROR)
 		throw love::Exception("Could not make context current.");
 
 	/*std::string captureName(alcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER));
@@ -108,7 +111,18 @@ Audio::Audio()
 	}*/
 
 	// pool must be allocated after AL context.
-	pool = new Pool();
+	try
+	{
+		pool = new Pool();
+	}
+	catch (love::Exception &)
+	{
+		alcMakeContextCurrent(nullptr);
+		alcDestroyContext(context);
+		//if (capture) alcCaptureCloseDevice(capture);
+		alcCloseDevice(device);
+		throw;
+	}
 
 	poolThread = new PoolThread(pool);
 	poolThread->start();
@@ -122,7 +136,7 @@ Audio::~Audio()
 	delete poolThread;
 	delete pool;
 
-	alcMakeContextCurrent(0);
+	alcMakeContextCurrent(nullptr);
 	alcDestroyContext(context);
 	//if (capture) alcCaptureCloseDevice(capture);
 	alcCloseDevice(device);
@@ -154,9 +168,9 @@ int Audio::getMaxSources() const
 	return pool->getMaxSources();
 }
 
-void Audio::play(love::audio::Source *source)
+bool Audio::play(love::audio::Source *source)
 {
-	source->play();
+	return source->play();
 }
 
 void Audio::stop(love::audio::Source *source)
@@ -281,7 +295,7 @@ bool Audio::canRecord()
 
 Audio::DistanceModel Audio::getDistanceModel() const
 {
-	return this->distanceModel;
+	return distanceModel;
 }
 
 void Audio::setDistanceModel(DistanceModel distanceModel)

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

@@ -67,8 +67,7 @@ public:
 	love::audio::Source *newSource(love::sound::SoundData *soundData);
 	int getSourceCount() const;
 	int getMaxSources() const;
-	void play(love::audio::Source *source);
-	void play();
+	bool play(love::audio::Source *source);
 	void stop(love::audio::Source *source);
 	void stop();
 	void pause(love::audio::Source *source);

+ 32 - 12
src/modules/audio/openal/Pool.cpp

@@ -30,23 +30,45 @@ namespace openal
 {
 
 Pool::Pool()
+	: sources()
+	, totalSources(0)
+	, mutex(nullptr)
 {
+	// Clear errors.
+	alGetError();
+
 	// Generate sources.
-	alGenSources(NUM_SOURCES, sources);
+	for (int i = 0; i < MAX_SOURCES; i++)
+	{
+		alGenSources(1, &sources[i]);
+
+		// We might hit an implementation-dependent limit on the total number
+		// of sources before reaching MAX_SOURCES.
+		if (alGetError() != AL_NO_ERROR)
+			break;
+
+		totalSources++;
+	}
+
+	if (totalSources < 4)
+		throw love::Exception("Could not generate sources.");
 
 	// Create the mutex.
 	mutex = thread::newMutex();
 
-	if (alGetError() != AL_NO_ERROR)
-		throw love::Exception("Could not generate sources.");
+	ALboolean hasext = alIsExtensionPresent("AL_SOFT_direct_channels");
 
 	// Make all sources available initially.
-	for (int i = 0; i < NUM_SOURCES; i++)
+	for (int i = 0; i < totalSources; i++)
 	{
-#ifdef AL_DIRECT_CHANNELS_SOFT
-		// Bypassing virtualization of speakers for multi-channel sources in OpenAL Soft.
-		alSourcei(sources[i], AL_DIRECT_CHANNELS_SOFT, AL_TRUE);
+#ifdef AL_SOFT_direct_channels
+		if (hasext)
+		{
+			// Bypass virtualization of speakers for multi-channel sources in OpenAL Soft.
+			alSourcei(sources[i], AL_DIRECT_CHANNELS_SOFT, AL_TRUE);
+		}
 #endif
+
 		available.push(sources[i]);
 	}
 }
@@ -58,7 +80,7 @@ Pool::~Pool()
 	delete mutex;
 
 	// Free all sources.
-	alDeleteSources(NUM_SOURCES, sources);
+	alDeleteSources(totalSources, sources);
 }
 
 bool Pool::isAvailable() const
@@ -113,7 +135,7 @@ int Pool::getSourceCount() const
 
 int Pool::getMaxSources() const
 {
-	return NUM_SOURCES;
+	return totalSources;
 }
 
 bool Pool::play(Source *source, ALuint &out)
@@ -141,9 +163,7 @@ bool Pool::play(Source *source, ALuint &out)
 
 			source->retain();
 
-			source->playAtomic();
-
-			ok = true;
+			ok = source->playAtomic();
 		}
 		else
 		{

+ 8 - 3
src/modules/audio/openal/Pool.h

@@ -36,6 +36,7 @@
 #ifdef LOVE_MACOSX_USE_FRAMEWORKS
 #include <OpenAL-Soft/alc.h>
 #include <OpenAL-Soft/al.h>
+#include <OpenAL-Soft/alext.h>
 #else
 #include <AL/alc.h>
 #include <AL/al.h>
@@ -99,11 +100,15 @@ private:
 
 	bool findSource(Source *source, ALuint &out);
 	bool removeSource(Source *source);
-	// Number of OpenAL sources.
-	static const int NUM_SOURCES = 64;
+
+	// Maximum possible number of OpenAL sources the pool attempts to generate.
+	static const int MAX_SOURCES = 64;
 
 	// OpenAL sources
-	ALuint sources[NUM_SOURCES];
+	ALuint sources[MAX_SOURCES];
+
+	// Total number of created sources in the pool.
+	int totalSources;
 
 	// A queue of available sources.
 	std::queue<ALuint> available;

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

@@ -165,15 +165,16 @@ love::audio::Source *Source::clone()
 	return new Source(*this);
 }
 
-void Source::play()
+bool Source::play()
 {
 	if (valid && paused)
 	{
 		pool->resume(this);
-		return;
+		return true;
 	}
 
 	valid = pool->play(this, source);
+	return valid;
 }
 
 void Source::stop()
@@ -512,7 +513,7 @@ bool Source::isLooping() const
 	return looping;
 }
 
-void Source::playAtomic()
+bool Source::playAtomic()
 {
 	if (type == TYPE_STATIC)
 	{
@@ -539,10 +540,19 @@ void Source::playAtomic()
 	// of the new one.
 	reset();
 
+	// Clear errors.
+	alGetError();
+
 	alSourcePlay(source);
 
+	// alSourcePlay may fail if the system has reached its limit of simultaneous
+	// playing sources.
+	bool success = alGetError() == AL_NO_ERROR;
+
 	valid = true; //if it fails it will be set to false again
 	//but this prevents a horrible, horrible bug
+
+	return success;
 }
 
 void Source::stopAtomic()

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

@@ -76,7 +76,7 @@ public:
 	virtual ~Source();
 
 	virtual love::audio::Source *clone();
-	virtual void play();
+	virtual bool play();
 	virtual void stop();
 	virtual void pause();
 	virtual void resume();
@@ -117,7 +117,7 @@ public:
 	virtual float getMaxDistance() const;
 	virtual int getChannels() const;
 
-	void playAtomic();
+	bool playAtomic();
 	void stopAtomic();
 	void pauseAtomic();
 	void resumeAtomic();

+ 2 - 2
src/modules/audio/wrap_Audio.cpp

@@ -75,8 +75,8 @@ int w_newSource(lua_State *L)
 int w_play(lua_State *L)
 {
 	Source *s = luax_checksource(L, 1);
-	instance->play(s);
-	return 0;
+	luax_pushboolean(L, instance->play(s));
+	return 1;
 }
 
 int w_stop(lua_State *L)

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

@@ -44,8 +44,8 @@ int w_Source_clone(lua_State *L)
 int w_Source_play(lua_State *L)
 {
 	Source *t = luax_checksource(L, 1);
-	t->play();
-	return 0;
+	luax_pushboolean(L, t->play());
+	return 1;
 }
 
 int w_Source_stop(lua_State *L)

+ 56 - 9
src/modules/event/sdl/Event.cpp

@@ -37,6 +37,24 @@ namespace event
 namespace sdl
 {
 
+// SDL reports mouse coordinates in the window coordinate system in OS X, but
+// we want them in pixel coordinates (may be different with high-DPI enabled.)
+static void windowToPixelCoords(int *x, int *y)
+{
+	double scale = 1.0;
+
+	window::Window *window = (window::Window *) Module::findInstance("love.window.");
+	if (window != nullptr)
+		scale = window->getPixelScale();
+
+	if (x != nullptr)
+		*x = int(double(*x) * scale);
+
+	if (y != nullptr)
+		*y = int(double(*y) * scale);
+}
+
+
 const char *Event::getName() const
 {
 	return "love.event.sdl";
@@ -162,8 +180,11 @@ Message *Event::convert(const SDL_Event &e) const
 	case SDL_MOUSEBUTTONUP:
 		if (buttons.find(e.button.button, button) && mouse::Mouse::getConstant(button, txt))
 		{
-			arg1 = new Variant((double) e.button.x);
-			arg2 = new Variant((double) e.button.y);
+			int x = e.button.x;
+			int y = e.button.y;
+			windowToPixelCoords(&x, &y);
+			arg1 = new Variant((double) x);
+			arg2 = new Variant((double) y);
 			arg3 = new Variant(txt, strlen(txt));
 			msg = new Message((e.type == SDL_MOUSEBUTTONDOWN) ?
 							  "mousepressed" : "mousereleased",
@@ -182,6 +203,7 @@ Message *Event::convert(const SDL_Event &e) const
 
 			int mx, my;
 			SDL_GetMouseState(&mx, &my);
+			windowToPixelCoords(&mx, &my);
 
 			arg1 = new Variant((double) mx);
 			arg2 = new Variant((double) my);
@@ -366,7 +388,7 @@ Message *Event::convertJoystickEvent(const SDL_Event &e) const
 Message *Event::convertWindowEvent(const SDL_Event &e) const
 {
 	Message *msg = 0;
-	Variant *arg1, *arg2;
+	Variant *arg1, *arg2, *arg3, *arg4;
 	window::Window *win = 0;
 
 	if (e.type != SDL_WINDOWEVENT)
@@ -402,17 +424,31 @@ Message *Event::convertWindowEvent(const SDL_Event &e) const
 		win = (window::Window *) Module::findInstance("love.window.");
 		if (win)
 		{
+			int px_w = e.window.data1;
+			int px_h = e.window.data2;
+
+#if SDL_VERSION_ATLEAST(2,0,1)
+			SDL_Window *sdlwin = SDL_GetWindowFromID(e.window.windowID);
+			if (sdlwin)
+				SDL_GL_GetDrawableSize(sdlwin, &px_w, &px_h);
+#endif
+
 			win->onWindowResize(e.window.data1, e.window.data2);
 
 			graphics::Graphics *gfx = (graphics::Graphics *) Module::findInstance("love.graphics.");
 			if (gfx)
-				gfx->setViewportSize(e.window.data1, e.window.data2);
+				gfx->setViewportSize(px_w, px_h);
+
+			arg1 = new Variant((double) px_w);
+			arg2 = new Variant((double) px_h);
+			arg3 = new Variant((double) e.window.data1);
+			arg4 = new Variant((double) e.window.data2);
+			msg = new Message("resize", arg1, arg2, arg3, arg4);
+			arg1->release();
+			arg2->release();
+			arg3->release();
+			arg4->release();
 		}
-		arg1 = new Variant((double) e.window.data1);
-		arg2 = new Variant((double) e.window.data2);
-		msg = new Message("resize", arg1, arg2);
-		arg1->release();
-		arg2->release();
 		break;
 	}
 
@@ -608,6 +644,17 @@ std::map<SDL_Keycode, love::keyboard::Keyboard::Key> Event::createKeyMap()
 	k[SDLK_AUDIOPLAY] = Keyboard::KEY_AUDIOPLAY;
 	k[SDLK_AUDIOMUTE] = Keyboard::KEY_AUDIOMUTE;
 	k[SDLK_MEDIASELECT] = Keyboard::KEY_MEDIASELECT;
+	k[SDLK_WWW] = Keyboard::KEY_WWW;
+	k[SDLK_MAIL] = Keyboard::KEY_MAIL;
+	k[SDLK_CALCULATOR] = Keyboard::KEY_CALCULATOR;
+	k[SDLK_COMPUTER] = Keyboard::KEY_COMPUTER;
+	k[SDLK_AC_SEARCH] = Keyboard::KEY_APP_SEARCH;
+	k[SDLK_AC_HOME] = Keyboard::KEY_APP_HOME;
+	k[SDLK_AC_BACK] = Keyboard::KEY_APP_BACK;
+	k[SDLK_AC_FORWARD] = Keyboard::KEY_APP_FORWARD;
+	k[SDLK_AC_STOP] = Keyboard::KEY_APP_STOP;
+	k[SDLK_AC_REFRESH] = Keyboard::KEY_APP_REFRESH;
+	k[SDLK_AC_BOOKMARKS] = Keyboard::KEY_APP_BOOKMARKS;
 
 	k[SDLK_BRIGHTNESSDOWN] = Keyboard::KEY_BRIGHTNESSDOWN;
 	k[SDLK_BRIGHTNESSUP] = Keyboard::KEY_BRIGHTNESSUP;

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

@@ -54,4 +54,4 @@ public:
 } // font
 } // love
 
-#endif // LOVE_FONT_FONT_H
+#endif // LOVE_FONT_FONT_H

+ 0 - 1
src/modules/font/GlyphData.h

@@ -46,7 +46,6 @@ struct GlyphMetrics
 	int advance;
 	int bearingX;
 	int bearingY;
-	int spacing;
 };
 
 /**

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

@@ -79,4 +79,4 @@ private:
 } // font
 } // love
 
-#endif // LOVE_FONT_IMAGE_RASTERIZER_H
+#endif // LOVE_FONT_IMAGE_RASTERIZER_H

+ 1 - 1
src/modules/font/Rasterizer.cpp

@@ -96,4 +96,4 @@ bool Rasterizer::hasGlyphs(const std::string &text) const
 }
 
 } // font
-} // love
+} // love

+ 1 - 1
src/modules/font/Rasterizer.h

@@ -114,4 +114,4 @@ protected:
 } // font
 } // love
 
-#endif // LOVE_FONT_RASTERIZER_H
+#endif // LOVE_FONT_RASTERIZER_H

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

@@ -146,4 +146,4 @@ bool TrueTypeRasterizer::hasGlyph(uint32 glyph) const
 
 } // freetype
 } // font
-} // love
+} // love

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

@@ -55,7 +55,7 @@ public:
 	 * @param kx Shear along the x-axis.
 	 * @param ky Shear along the y-axis.
 	 **/
-	virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const = 0;
+	virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) = 0;
 };
 
 } // graphics

+ 22 - 0
src/modules/graphics/Graphics.cpp

@@ -99,6 +99,16 @@ bool Graphics::getConstant(RendererInfo in, const char *&out)
 	return rendererInfo.find(in, out);
 }
 
+bool Graphics::getConstant(const char *in, SystemLimit &out)
+{
+	return systemLimits.find(in, out);
+}
+
+bool Graphics::getConstant(SystemLimit in, const char *&out)
+{
+	return systemLimits.find(in, out);
+}
+
 StringMap<Graphics::DrawMode, Graphics::DRAW_MAX_ENUM>::Entry Graphics::drawModeEntries[] =
 {
 	{ "line", Graphics::DRAW_LINE },
@@ -157,6 +167,8 @@ StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM>::Entry Graphics::suppor
 	{ "mipmap", Graphics::SUPPORT_MIPMAP },
 	{ "dxt", Graphics::SUPPORT_DXT },
 	{ "bc5", Graphics::SUPPORT_BC5 },
+	{ "instancing", Graphics::SUPPORT_INSTANCING },
+	{ "srgb", Graphics::SUPPORT_SRGB },
 };
 
 StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM> Graphics::support(Graphics::supportEntries, sizeof(Graphics::supportEntries));
@@ -171,5 +183,15 @@ StringMap<Graphics::RendererInfo, Graphics::RENDERER_INFO_MAX_ENUM>::Entry Graph
 
 StringMap<Graphics::RendererInfo, Graphics::RENDERER_INFO_MAX_ENUM> Graphics::rendererInfo(Graphics::rendererInfoEntries, sizeof(Graphics::rendererInfoEntries));
 
+StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM>::Entry Graphics::systemLimitEntries[] =
+{
+	{"pointsize", Graphics::LIMIT_POINT_SIZE},
+	{"texturesize", Graphics::LIMIT_TEXTURE_SIZE},
+	{"multicanvas", Graphics::LIMIT_MULTI_CANVAS},
+	{"canvasfsaa", Graphics::LIMIT_CANVAS_FSAA},
+};
+
+StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM> Graphics::systemLimits(Graphics::systemLimitEntries, sizeof(Graphics::systemLimitEntries));
+
 } // graphics
 } // love

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

@@ -87,6 +87,8 @@ public:
 		SUPPORT_MIPMAP,
 		SUPPORT_DXT,
 		SUPPORT_BC5,
+		SUPPORT_INSTANCING,
+		SUPPORT_SRGB,
 		SUPPORT_MAX_ENUM
 	};
 
@@ -99,6 +101,15 @@ public:
 		RENDERER_INFO_MAX_ENUM
 	};
 
+	enum SystemLimit
+	{
+		LIMIT_POINT_SIZE,
+		LIMIT_TEXTURE_SIZE,
+		LIMIT_MULTI_CANVAS,
+		LIMIT_CANVAS_FSAA,
+		LIMIT_MAX_ENUM
+	};
+
 	virtual ~Graphics();
 
 	/**
@@ -111,7 +122,7 @@ public:
 	 * @param width The viewport width.
 	 * @param height The viewport height.
 	 **/
-	virtual bool setMode(int width, int height) = 0;
+	virtual bool setMode(int width, int height, bool &sRGB) = 0;
 
 	/**
 	 * Un-sets the current graphics display mode (uninitializing objects if
@@ -140,6 +151,9 @@ public:
 	static bool getConstant(const char *in, RendererInfo &out);
 	static bool getConstant(RendererInfo in, const char *&out);
 
+	static bool getConstant(const char *in, SystemLimit &out);
+	static bool getConstant(SystemLimit in, const char *&out);
+
 private:
 
 	static StringMap<DrawMode, DRAW_MAX_ENUM>::Entry drawModeEntries[];
@@ -163,6 +177,9 @@ private:
 	static StringMap<RendererInfo, RENDERER_INFO_MAX_ENUM>::Entry rendererInfoEntries[];
 	static StringMap<RendererInfo, RENDERER_INFO_MAX_ENUM> rendererInfo;
 
+	static StringMap<SystemLimit, LIMIT_MAX_ENUM>::Entry systemLimitEntries[];
+	static StringMap<SystemLimit, LIMIT_MAX_ENUM> systemLimits;
+
 }; // Graphics
 
 } // graphics

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

@@ -109,6 +109,16 @@ bool Texture::getConstant(WrapMode in, const char  *&out)
 	return wrapModes.find(in, out);
 }
 
+bool Texture::getConstant(const char *in, Format &out)
+{
+	return formats.find(in, out);
+}
+
+bool Texture::getConstant(Format in, const char *&out)
+{
+	return formats.find(in, out);
+}
+
 StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM>::Entry Texture::filterModeEntries[] =
 {
 	{ "linear", Texture::FILTER_LINEAR },
@@ -125,6 +135,15 @@ StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM>::Entry Texture::wrapModeEnt
 
 StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM> Texture::wrapModes(Texture::wrapModeEntries, sizeof(Texture::wrapModeEntries));
 
+StringMap<Texture::Format, Texture::FORMAT_MAX_ENUM>::Entry Texture::formatEntries[] =
+{
+	{"normal", Texture::FORMAT_NORMAL},
+	{"hdr", Texture::FORMAT_HDR},
+	{"srgb", Texture::FORMAT_SRGB},
+};
+
+StringMap<Texture::Format, Texture::FORMAT_MAX_ENUM> Texture::formats(Texture::formatEntries, sizeof(Texture::formatEntries));
+
 
 } // graphics
 } // love

+ 15 - 1
src/modules/graphics/Texture.h

@@ -55,6 +55,14 @@ public:
 		FILTER_MAX_ENUM
 	};
 
+	enum Format
+	{
+		FORMAT_NORMAL,
+		FORMAT_HDR,
+		FORMAT_SRGB,
+		FORMAT_MAX_ENUM
+	};
+
 	struct Filter
 	{
 		Filter();
@@ -88,7 +96,7 @@ public:
 	 * @param kx Shear along the x-axis.
 	 * @param ky Shear along the y-axis.
 	 **/
-	virtual void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const = 0;
+	virtual void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) = 0;
 
 	virtual int getWidth() const;
 	virtual int getHeight() const;
@@ -111,6 +119,9 @@ public:
 	static bool getConstant(const char *in, WrapMode &out);
 	static bool getConstant(WrapMode in, const char  *&out);
 
+	static bool getConstant(const char *in, Format &out);
+	static bool getConstant(Format in, const char *&out);
+
 protected:
 
 	int width;
@@ -132,6 +143,9 @@ private:
 	static StringMap<WrapMode, WRAP_MAX_ENUM>::Entry wrapModeEntries[];
 	static StringMap<WrapMode, WRAP_MAX_ENUM> wrapModes;
 
+	static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
+	static StringMap<Format, FORMAT_MAX_ENUM> formats;
+
 }; // Texture
 
 } // graphics

+ 347 - 83
src/modules/graphics/opengl/Canvas.cpp

@@ -55,10 +55,25 @@ struct FramebufferStrategy
 	/**
 	 * @param[in]  width   Width of the stencil buffer
 	 * @param[in]  height  Height of the stencil buffer
+	 * @param[in]  samples Number of samples to use
 	 * @param[out] stencil Name for stencil buffer
 	 * @return Whether the stencil buffer was successfully created
 	 **/
-	virtual bool createStencil(int, int, GLuint &)
+	virtual bool createStencil(int, int, int, GLuint &)
+	{
+		return false;
+	}
+
+	/// Create a MSAA renderbuffer and attach it to the active FBO.
+	/**
+	 * @param[in]    width Width of the MSAA buffer
+	 * @param[in]    height Height of the MSAA buffer
+	 * @param[inout] samples Number of samples to use
+	 * @param[in]    internalformat The internal format to use for the buffer
+	 * @param[out]   buffer Name for the MSAA buffer
+	 * @return Whether the MSAA buffer was successfully created and attached
+	 **/
+	virtual bool createMSAABuffer(int, int, int &, GLenum, GLuint &)
 	{
 		return false;
 	}
@@ -68,7 +83,7 @@ struct FramebufferStrategy
 	 * @param[in] framebuffer   Framebuffer name
 	 * @param[in] depth_stencil Name for packed depth and stencil buffer
 	 */
-	virtual void deleteFBO(GLuint, GLuint) {}
+	virtual void deleteFBO(GLuint, GLuint, GLuint) {}
 	virtual void bindFBO(GLuint) {}
 
 	/// attach additional canvases to the active framebuffer for rendering
@@ -93,8 +108,11 @@ struct FramebufferStrategyGL3 : public FramebufferStrategy
 		glGenFramebuffers(1, &framebuffer);
 		glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
 
-		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
-			GL_TEXTURE_2D, texture, 0);
+		if (texture != 0)
+		{
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+			                       GL_TEXTURE_2D, texture, 0);
+		}
 
 		// check status
 		GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
@@ -104,27 +122,62 @@ struct FramebufferStrategyGL3 : public FramebufferStrategy
 		return status;
 	}
 
-	virtual bool createStencil(int width, int height, GLuint &stencil)
+	virtual bool createStencil(int width, int height, int samples, GLuint &stencil)
 	{
 		// create combined depth/stencil buffer
 		glDeleteRenderbuffers(1, &stencil);
 		glGenRenderbuffers(1, &stencil);
 		glBindRenderbuffer(GL_RENDERBUFFER, stencil);
-		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, width, height);
+
+		if (samples > 1)
+			glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH_STENCIL, width, height);
+		else
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, width, height);
 
 		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
-								  GL_RENDERBUFFER, stencil);
+		                          GL_RENDERBUFFER, stencil);
 
 		glBindRenderbuffer(GL_RENDERBUFFER, 0);
 
 		// check status
-		return glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
+		if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+		{
+			glDeleteRenderbuffers(1, &stencil);
+			stencil = 0;
+			return false;
+		}
+
+		return true;
 	}
 
-	virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil)
+	virtual bool createMSAABuffer(int width, int height, int &samples, GLenum internalformat, GLuint &buffer)
+	{
+		glGenRenderbuffers(1, &buffer);
+		glBindRenderbuffer(GL_RENDERBUFFER, buffer);
+		glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internalformat,
+		                                 width, height);
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+		                          GL_RENDERBUFFER, buffer);
+		glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples);
+
+		glBindRenderbuffer(GL_RENDERBUFFER, 0);
+
+		if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+		{
+			glDeleteRenderbuffers(1, &buffer);
+			buffer = 0;
+			return false;
+		}
+
+		return true;
+	}
+
+	virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil, GLuint msaa_buffer)
 	{
 		if (depth_stencil != 0)
 			glDeleteRenderbuffers(1, &depth_stencil);
+		if (msaa_buffer != 0)
+			glDeleteRenderbuffers(1, &msaa_buffer);
 		if (framebuffer != 0)
 			glDeleteFramebuffers(1, &framebuffer);
 	}
@@ -189,27 +242,71 @@ struct FramebufferStrategyPackedEXT : public FramebufferStrategy
 		return status;
 	}
 
-	virtual bool createStencil(int width, int height, GLuint &stencil)
+	virtual bool createStencil(int width, int height, int samples, GLuint &stencil)
 	{
 		// create combined depth/stencil buffer
-		glDeleteRenderbuffers(1, &stencil);
+		glDeleteRenderbuffersEXT(1, &stencil);
 		glGenRenderbuffersEXT(1, &stencil);
 		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, stencil);
-		glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_STENCIL_EXT,
-								 width, height);
+
+		if (samples > 1)
+		{
+			glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples,
+			                                    GL_DEPTH_STENCIL, width, height);
+		}
+		else
+		{
+			glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_STENCIL_EXT,
+			                         width, height);
+		}
+
 		glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT,
 									 GL_RENDERBUFFER_EXT, stencil);
 
 		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
 
 		// check status
-		return glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT;
+		if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+		{
+			glDeleteRenderbuffersEXT(1, &stencil);
+			stencil = 0;
+			return false;
+		}
+
+		return true;
+	}
+
+	virtual bool createMSAABuffer(int width, int height, int &samples, GLenum internalformat, GLuint &buffer)
+	{
+		if (!GLEE_EXT_framebuffer_multisample)
+			return false;
+
+		glGenRenderbuffersEXT(1, &buffer);
+		glBindRenderbufferEXT(GL_RENDERBUFFER, buffer);
+		glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples,
+		                                    internalformat, width, height);
+		glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+		                             GL_RENDERBUFFER, buffer);
+		glGetRenderbufferParameterivEXT(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples);
+
+		glBindRenderbufferEXT(GL_RENDERBUFFER, 0);
+
+		if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+		{
+			glDeleteRenderbuffersEXT(1, &buffer);
+			buffer = 0;
+			return false;
+		}
+
+		return true;
 	}
 
-	virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil)
+	virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil, GLuint msaa_buffer)
 	{
 		if (depth_stencil != 0)
 			glDeleteRenderbuffersEXT(1, &depth_stencil);
+		if (msaa_buffer != 0)
+			glDeleteRenderbuffersEXT(1, &msaa_buffer);
 		if (framebuffer != 0)
 			glDeleteFramebuffersEXT(1, &framebuffer);
 	}
@@ -254,33 +351,50 @@ struct FramebufferStrategyPackedEXT : public FramebufferStrategy
 
 struct FramebufferStrategyEXT : public FramebufferStrategyPackedEXT
 {
-	virtual bool createStencil(int width, int height, GLuint &stencil)
+	virtual bool createStencil(int width, int height, int samples, GLuint &stencil)
 	{
 		// create stencil buffer
-		glDeleteRenderbuffers(1, &stencil);
+		glDeleteRenderbuffersEXT(1, &stencil);
 		glGenRenderbuffersEXT(1, &stencil);
 		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, stencil);
-		glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_STENCIL_INDEX,
-								 width, height);
+
+		if (samples > 1)
+		{
+			glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples,
+			                                    GL_STENCIL_INDEX, width, height);
+		}
+		else
+		{
+			glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_STENCIL_INDEX,
+			                         width, height);
+		}
+
 		glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT,
 									 GL_RENDERBUFFER_EXT, stencil);
 
 		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
 
 		// check status
-		return glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT;
+		if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+		{
+			glDeleteRenderbuffersEXT(1, &stencil);
+			stencil = 0;
+			return false;
+		}
+
+		return true;
 	}
 
 	bool isSupported()
 	{
 		GLuint fb = 0, stencil = 0;
 		GLenum status = createFBO(fb, 0);
-		deleteFBO(fb, stencil);
+		deleteFBO(fb, stencil, 0);
 		return status == GL_FRAMEBUFFER_COMPLETE;
 	}
 };
 
-FramebufferStrategy *strategy = NULL;
+FramebufferStrategy *strategy = nullptr;
 
 FramebufferStrategy strategyNone;
 
@@ -290,8 +404,9 @@ FramebufferStrategyPackedEXT strategyPackedEXT;
 
 FramebufferStrategyEXT strategyEXT;
 
-Canvas *Canvas::current = NULL;
+Canvas *Canvas::current = nullptr;
 OpenGL::Viewport Canvas::systemViewport = OpenGL::Viewport();
+bool Canvas::screenHasSRGB = false;
 
 static void getStrategy()
 {
@@ -308,14 +423,15 @@ static void getStrategy()
 	}
 }
 
-static int maxFBOColorAttachments = 0;
-static int maxDrawBuffers = 0;
-
-Canvas::Canvas(int width, int height, TextureType texture_type)
+Canvas::Canvas(int width, int height, Texture::Format format, int fsaa)
 	: fbo(0)
+    , resolve_fbo(0)
 	, texture(0)
+    , fsaa_buffer(0)
 	, depth_stencil(0)
-	, texture_type(texture_type)
+	, format(format)
+    , fsaa_samples(fsaa)
+	, fsaa_dirty(false)
 {
 	this->width = width;
 	this->height = height;
@@ -357,9 +473,50 @@ Canvas::~Canvas()
 	unloadVolatile();
 }
 
+bool Canvas::createFSAAFBO(GLenum internalformat)
+{
+	// Create our FBO without a texture.
+	status = strategy->createFBO(fbo, 0);
+
+	GLuint previous = 0;
+	if (current != this)
+	{
+		if (current != nullptr)
+			previous = current->fbo;
+
+		strategy->bindFBO(fbo);
+	}
+
+	// Create and attach the MSAA buffer for our FBO.
+	if (strategy->createMSAABuffer(width, height, fsaa_samples, internalformat, fsaa_buffer))
+		status = GL_FRAMEBUFFER_COMPLETE;
+	else
+		status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
+
+	// Create the FBO used for the MSAA resolve, and attach the texture.
+	if (status == GL_FRAMEBUFFER_COMPLETE)
+		status = strategy->createFBO(resolve_fbo, texture);
+
+	if (status != GL_FRAMEBUFFER_COMPLETE)
+	{
+		// Clean up.
+		strategy->deleteFBO(fbo, 0, fsaa_buffer);
+		strategy->deleteFBO(resolve_fbo, 0, 0);
+		fbo = fsaa_buffer = resolve_fbo = 0;
+		fsaa_samples = 0;
+	}
+
+	if (current != this)
+		strategy->bindFBO(previous);
+
+	return status == GL_FRAMEBUFFER_COMPLETE;
+}
+
 bool Canvas::loadVolatile()
 {
 	fbo = depth_stencil = texture = 0;
+	resolve_fbo = fsaa_buffer = 0;
+	status = GL_FRAMEBUFFER_COMPLETE;
 
 	// glTexImage2D is guaranteed to error in this case.
 	if (width > gl.getMaxTextureSize() || height > gl.getMaxTextureSize())
@@ -376,13 +533,17 @@ bool Canvas::loadVolatile()
 
 	GLint internalformat;
 	GLenum textype;
-	switch (texture_type)
+	switch (format)
 	{
-	case TYPE_HDR:
+	case Texture::FORMAT_HDR:
 		internalformat = GL_RGBA16F;
 		textype = GL_FLOAT;
 		break;
-	case TYPE_NORMAL:
+	case Texture::FORMAT_SRGB:
+		internalformat = GL_SRGB8_ALPHA8;
+		textype = GL_UNSIGNED_BYTE;
+		break;
+	case Texture::FORMAT_NORMAL:
 	default:
 		internalformat = GL_RGBA8;
 		textype = GL_UNSIGNED_BYTE;
@@ -406,21 +567,44 @@ bool Canvas::loadVolatile()
 		return false;
 	}
 
-	status = strategy->createFBO(fbo, texture);
+	int max_samples = 0;
+	if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object
+		|| GLEE_EXT_framebuffer_multisample)
+	{
+		glGetIntegerv(GL_MAX_SAMPLES, &max_samples);
+	}
+
+	if (fsaa_samples > max_samples)
+		fsaa_samples = max_samples;
+
+	// Try to create a FSAA FBO if requested.
+	bool fsaasuccess = false;
+	if (fsaa_samples > 1)
+		fsaasuccess = createFSAAFBO(internalformat);
+
+	// On failure (or no requested FSAA), fall back to a regular FBO.
+	if (!fsaasuccess)
+		status = strategy->createFBO(fbo, texture);
+
 	if (status != GL_FRAMEBUFFER_COMPLETE)
 		return false;
 
 	clear(Color(0, 0, 0, 0));
+
+	fsaa_dirty = (fsaa_buffer != 0);
+
 	return true;
 }
 
 void Canvas::unloadVolatile()
 {
-	strategy->deleteFBO(fbo, depth_stencil);
+	strategy->deleteFBO(fbo, depth_stencil, fsaa_buffer);
+	strategy->deleteFBO(resolve_fbo, 0, 0);
 
 	gl.deleteTexture(texture);
 
 	fbo = depth_stencil = texture = 0;
+	resolve_fbo = fsaa_buffer = 0;
 
 	for (size_t i = 0; i < attachedCanvases.size(); i++)
 		attachedCanvases[i]->release();
@@ -428,13 +612,12 @@ void Canvas::unloadVolatile()
 	attachedCanvases.clear();
 }
 
-void Canvas::drawv(const Matrix &t, const Vertex *v) const
+void Canvas::drawv(const Matrix &t, const Vertex *v)
 {
 	glPushMatrix();
-
 	glMultMatrixf((const GLfloat *)t.getElements());
 
-	gl.bindTexture(texture);
+	predraw();
 
 	glEnableClientState(GL_VERTEX_ARRAY);
 	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
@@ -447,11 +630,13 @@ void Canvas::drawv(const Matrix &t, const Vertex *v) const
 
 	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 	glDisableClientState(GL_VERTEX_ARRAY);
+
+	postdraw();
 	
 	glPopMatrix();
 }
 
-void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
+void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	static Matrix t;
 	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
@@ -459,7 +644,7 @@ void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, f
 	drawv(t, vertices);
 }
 
-void Canvas::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
+void Canvas::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	static Matrix t;
 	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
@@ -487,8 +672,12 @@ GLuint Canvas::getGLTexture() const
 	return texture;
 }
 
-void Canvas::predraw() const
+void Canvas::predraw()
 {
+	// We need to make sure the texture is up-to-date by resolving the MSAA
+	// buffer (which we render to when the canvas is active) to it.
+	resolveMSAA();
+
 	gl.bindTexture(texture);
 }
 
@@ -498,12 +687,19 @@ void Canvas::setupGrab()
 	if (current == this)
 		return;
 
-	// cleanup after previous fbo
-	if (current != NULL)
-		current->stopGrab();
+	// cleanup after previous Canvas
+	if (current != nullptr)
+	{
+		systemViewport = current->systemViewport;
+		current->stopGrab(true);
+	}
+	else
+		systemViewport = gl.getViewport();
+
+	// indicate we are using this Canvas.
+	current = this;
 
 	// bind the framebuffer object.
-	systemViewport = gl.getViewport();
 	strategy->bindFBO(fbo);
 	gl.setViewport(OpenGL::Viewport(0, 0, width, height));
 
@@ -518,8 +714,14 @@ void Canvas::setupGrab()
 	// Switch back to modelview matrix
 	glMatrixMode(GL_MODELVIEW);
 
-	// indicate we are using this fbo
-	current = this;
+	// Make sure the correct sRGB setting is used when drawing to the canvas.
+	if (format == FORMAT_SRGB)
+		glEnable(GL_FRAMEBUFFER_SRGB);
+	else if (screenHasSRGB)
+		glDisable(GL_FRAMEBUFFER_SRGB);
+
+	if (fsaa_buffer != 0)
+		fsaa_dirty = true;
 }
 
 void Canvas::startGrab(const std::vector<Canvas *> &canvases)
@@ -533,8 +735,11 @@ void Canvas::startGrab(const std::vector<Canvas *> &canvases)
 		if (!isMultiCanvasSupported())
 			throw love::Exception("Multi-canvas rendering is not supported on this system.");
 
-		if (canvases.size()+1 > size_t(maxDrawBuffers) || canvases.size()+1 > size_t(maxFBOColorAttachments))
+		if ((int) canvases.size() + 1 > gl.getMaxRenderTargets())
 			throw love::Exception("This system can't simultaniously render to %d canvases.", canvases.size()+1);
+
+		if (fsaa_samples != 0)
+			throw love::Exception("Multi-canvas rendering is not supported with FSAA.");
 	}
 
 	for (size_t i = 0; i < canvases.size(); i++)
@@ -542,8 +747,11 @@ void Canvas::startGrab(const std::vector<Canvas *> &canvases)
 		if (canvases[i]->getWidth() != width || canvases[i]->getHeight() != height)
 			throw love::Exception("All canvas arguments must have the same dimensions.");
 
-		if (canvases[i]->getTextureType() != texture_type)
-			throw love::Exception("All canvas arguments must have the same texture type.");
+		if (canvases[i]->getTextureFormat() != format)
+			throw love::Exception("All canvas arguments must have the same texture format.");
+
+		if (canvases[i]->getFSAA() != 0)
+			throw love::Exception("Multi-canvas rendering is not supported with FSAA.");
 
 		if (!canvaseschanged && canvases[i] != attachedCanvases[i])
 			canvaseschanged = true;
@@ -584,21 +792,33 @@ void Canvas::startGrab()
 	attachedCanvases.clear();
 }
 
-void Canvas::stopGrab()
+void Canvas::stopGrab(bool switchingToOtherCanvas)
 {
 	// i am not grabbing. leave me alone
 	if (current != this)
 		return;
 
-	// bind default
-	strategy->bindFBO(0);
 	glMatrixMode(GL_PROJECTION);
 	glPopMatrix();
 	glMatrixMode(GL_MODELVIEW);
 
-	current = nullptr;
+	if (switchingToOtherCanvas)
+	{
+		if (format == FORMAT_SRGB)
+			glDisable(GL_FRAMEBUFFER_SRGB);
+	}
+	else
+	{
+		// bind system framebuffer.
+		strategy->bindFBO(0);
+		current = nullptr;
+		gl.setViewport(systemViewport);
 
-	gl.setViewport(systemViewport);
+		if (format == FORMAT_SRGB && !screenHasSRGB)
+			glDisable(GL_FRAMEBUFFER_SRGB);
+		else if (format != FORMAT_SRGB && screenHasSRGB)
+			glEnable(GL_FRAMEBUFFER_SRGB);
+	}
 }
 
 void Canvas::clear(Color c)
@@ -651,6 +871,9 @@ void Canvas::clear(Color c)
 
 	if (current != this)
 		strategy->bindFBO(previous);
+
+	if (fsaa_buffer != 0)
+		fsaa_dirty = true;
 }
 
 bool Canvas::checkCreateStencil()
@@ -662,7 +885,7 @@ bool Canvas::checkCreateStencil()
 	if (current != this)
 		strategy->bindFBO(fbo);
 
-	bool success = strategy->createStencil(width, height, depth_stencil);
+	bool success = strategy->createStencil(width, height, fsaa_samples, depth_stencil);
 
 	if (current && current != this)
 		strategy->bindFBO(current->fbo);
@@ -674,12 +897,22 @@ bool Canvas::checkCreateStencil()
 
 love::image::ImageData *Canvas::getImageData(love::image::Image *image)
 {
+	resolveMSAA();
+
 	int row = 4 * width;
 	int size = row * height;
 	GLubyte *pixels  = new GLubyte[size];
 
-	strategy->bindFBO(fbo);
+	// Our texture is attached to 'resolve_fbo' when we use MSAA.
+	if (fsaa_samples > 1 && (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object))
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo);
+	else if (fsaa_samples > 1 && GLEE_EXT_framebuffer_multisample)
+		glBindFramebufferEXT(GL_READ_FRAMEBUFFER, resolve_fbo);
+	else
+		strategy->bindFBO(fbo);
+
 	glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+
 	if (current)
 		strategy->bindFBO(current->fbo);
 	else
@@ -692,10 +925,17 @@ love::image::ImageData *Canvas::getImageData(love::image::Image *image)
 
 void Canvas::getPixel(unsigned char* pixel_rgba, int x, int y)
 {
-	if (current != this)
+	resolveMSAA();
+
+	// Our texture is attached to 'resolve_fbo' when we use MSAA.
+	if (fsaa_samples > 1 && (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object))
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo);
+	else if (fsaa_samples > 1 && GLEE_EXT_framebuffer_multisample)
+		glBindFramebufferEXT(GL_READ_FRAMEBUFFER, resolve_fbo);
+	else if (current != this)
 		strategy->bindFBO(fbo);
 
-	glReadPixels(x, height - y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel_rgba);
+	glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel_rgba);
 
 	if (current && current != this)
 		strategy->bindFBO(current->fbo);
@@ -703,6 +943,44 @@ void Canvas::getPixel(unsigned char* pixel_rgba, int x, int y)
 		strategy->bindFBO(0);
 }
 
+bool Canvas::resolveMSAA()
+{
+	if (resolve_fbo == 0 || fsaa_buffer == 0)
+		return false;
+
+	if (!fsaa_dirty)
+		return true;
+
+	GLuint previous = 0;
+	if (current != nullptr)
+		previous = current->fbo;
+
+	// Do the MSAA resolve by blitting the MSAA renderbuffer to the texture.
+	if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object)
+	{
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo);
+		glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
+						  GL_COLOR_BUFFER_BIT, GL_NEAREST);
+	}
+	else if (GLEE_EXT_framebuffer_multisample && GLEE_EXT_framebuffer_blit)
+	{
+		glBindFramebufferEXT(GL_READ_FRAMEBUFFER, fbo);
+		glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, resolve_fbo);
+		glBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height,
+							 GL_COLOR_BUFFER_BIT, GL_NEAREST);
+	}
+	else
+		return false;
+
+	strategy->bindFBO(previous);
+
+	if (current != this)
+		fsaa_dirty = false;
+
+	return true;
+}
+
 bool Canvas::isSupported()
 {
 	getStrategy();
@@ -714,19 +992,22 @@ bool Canvas::isHDRSupported()
 	return GLEE_VERSION_3_0 || (isSupported() && GLEE_ARB_texture_float);
 }
 
-bool Canvas::isMultiCanvasSupported()
+bool Canvas::isSRGBSupported()
 {
-	if (!(isSupported() && (GLEE_VERSION_2_0 || GLEE_ARB_draw_buffers)))
+	if (GLEE_VERSION_3_0)
+		return true;
+
+	if (!isSupported())
 		return false;
 
-	if (maxFBOColorAttachments == 0 || maxDrawBuffers == 0)
-	{
-		glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxFBOColorAttachments);
-		glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
-	}
+	return (GLEE_ARB_framebuffer_sRGB || GLEE_EXT_framebuffer_sRGB)
+		&& GLEE_EXT_texture_sRGB;
+}
 
-	// system must support at least 4 simultanious active canvases
-	return maxFBOColorAttachments >= 4 && maxDrawBuffers >= 4;
+bool Canvas::isMultiCanvasSupported()
+{
+	// system must support at least 4 simultanious active canvases.
+	return gl.getMaxRenderTargets() >= 4;
 }
 
 void Canvas::bindDefaultCanvas()
@@ -735,23 +1016,6 @@ void Canvas::bindDefaultCanvas()
 		current->stopGrab();
 }
 
-bool Canvas::getConstant(const char *in, Canvas::TextureType &out)
-{
-	return textureTypes.find(in, out);
-}
-
-bool Canvas::getConstant(Canvas::TextureType in, const char *&out)
-{
-	return textureTypes.find(in, out);
-}
-
-StringMap<Canvas::TextureType, Canvas::TYPE_MAX_ENUM>::Entry Canvas::textureTypeEntries[] =
-{
-	{"normal", Canvas::TYPE_NORMAL},
-	{"hdr",    Canvas::TYPE_HDR},
-};
-StringMap<Canvas::TextureType, Canvas::TYPE_MAX_ENUM> Canvas::textureTypes(Canvas::textureTypeEntries, sizeof(Canvas::textureTypeEntries));
-
 } // opengl
 } // graphics
 } // love

+ 29 - 22
src/modules/graphics/opengl/Canvas.h

@@ -39,14 +39,7 @@ class Canvas : public Texture
 {
 public:
 
-	enum TextureType
-	{
-		TYPE_NORMAL,
-		TYPE_HDR,
-		TYPE_MAX_ENUM
-	};
-
-	Canvas(int width, int height, TextureType texture_type = TYPE_NORMAL);
+	Canvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0);
 	virtual ~Canvas();
 
 	// Implements Volatile.
@@ -54,14 +47,14 @@ public:
 	virtual void unloadVolatile();
 
 	// Implements Drawable.
-	virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
+	virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 	// Implements Texture.
-	virtual void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
+	virtual void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 	virtual void setFilter(const Texture::Filter &f);
 	virtual void setWrap(const Texture::Wrap &w);
 	virtual GLuint getGLTexture() const;
-	virtual void predraw() const;
+	virtual void predraw();
 
 	/**
 	 * @param canvases A list of other canvases to temporarily attach to this one,
@@ -69,7 +62,7 @@ public:
 	 **/
 	void startGrab(const std::vector<Canvas *> &canvases);
 	void startGrab();
-	void stopGrab();
+	void stopGrab(bool switchingToOtherCanvas = false);
 
 	void clear(Color c);
 
@@ -92,42 +85,56 @@ public:
 		return status;
 	}
 
-	inline TextureType getTextureType() const
+	inline Texture::Format getTextureFormat() const
+	{
+		return format;
+	}
+
+	inline int getFSAA() const
 	{
-		return texture_type;
+		return fsaa_samples;
 	}
 
+	bool resolveMSAA();
+
 	static bool isSupported();
 	static bool isHDRSupported();
+	static bool isSRGBSupported();
 	static bool isMultiCanvasSupported();
 
-	static bool getConstant(const char *in, TextureType &out);
-	static bool getConstant(TextureType in, const char *&out);
-
 	static Canvas *current;
 	static void bindDefaultCanvas();
 
 	// The viewport dimensions of the system (default) framebuffer.
 	static OpenGL::Viewport systemViewport;
 
+	// Whether the main screen should have linear -> sRGB conversions enabled.
+	static bool screenHasSRGB;
+
 private:
 
+	bool createFSAAFBO(GLenum internalformat);
+
 	GLuint fbo;
+	GLuint resolve_fbo;
+
 	GLuint texture;
+	GLuint fsaa_buffer;
 	GLuint depth_stencil;
 
-	TextureType texture_type;
+	Format format;
 
 	GLenum status;
 
 	std::vector<Canvas *> attachedCanvases;
 
+	int fsaa_samples;
+	bool fsaa_dirty;
+
 	void setupGrab();
-	void drawv(const Matrix &t, const Vertex *v) const;
+	void drawv(const Matrix &t, const Vertex *v);
 
-	static StringMap<TextureType, TYPE_MAX_ENUM>::Entry textureTypeEntries[];
-	static StringMap<TextureType, TYPE_MAX_ENUM> textureTypes;
-};
+}; // Canvas
 
 } // opengl
 } // graphics

+ 35 - 4
src/modules/graphics/opengl/Font.cpp

@@ -46,6 +46,7 @@ Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
 	, lineHeight(1)
 	, mSpacing(1)
 	, filter(filter)
+	, useSpacesAsTab(false)
 {
 	this->filter.mipmap = Texture::FILTER_NONE;
 
@@ -67,13 +68,16 @@ Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
 	textureWidth = TEXTURE_WIDTHS[textureSizeIndex];
 	textureHeight = TEXTURE_HEIGHTS[textureSizeIndex];
 
-	love::font::GlyphData *gd = 0;
+	love::font::GlyphData *gd = nullptr;
 
 	try
 	{
-		gd = r->getGlyphData(32);
+		gd = r->getGlyphData(32); // Space character.
 		type = (gd->getFormat() == love::font::GlyphData::FORMAT_LUMINANCE_ALPHA) ? FONT_TRUETYPE : FONT_IMAGE;
 
+		if (!r->hasGlyph(9)) // No tab character in the Rasterizer.
+			useSpacesAsTab = true;
+
 		loadVolatile();
 	}
 	catch (love::Exception &)
@@ -172,7 +176,26 @@ void Font::createTexture()
 
 Font::Glyph *Font::addGlyph(uint32 glyph)
 {
-	love::font::GlyphData *gd = rasterizer->getGlyphData(glyph);
+	love::font::GlyphData *gd = nullptr;
+
+	// Use spaces for the tab 'glyph'.
+	if (glyph == 9 && useSpacesAsTab)
+	{
+		love::font::GlyphData *spacegd = rasterizer->getGlyphData(32);
+
+		love::font::GlyphMetrics gm = {};
+		gm.advance = spacegd->getAdvance() * SPACES_PER_TAB;
+		gm.bearingX = spacegd->getBearingX();
+		gm.bearingY = spacegd->getBearingY();
+		love::font::GlyphData::Format f = spacegd->getFormat();
+
+		spacegd->release();
+
+		gd = new love::font::GlyphData(glyph, gm, f);
+	}
+	else
+		gd = rasterizer->getGlyphData(glyph);
+
 	int w = gd->getWidth();
 	int h = gd->getHeight();
 
@@ -186,7 +209,15 @@ Font::Glyph *Font::addGlyph(uint32 glyph)
 	if (textureY + h + TEXTURE_PADDING > textureHeight)
 	{
 		// totally out of space - new texture!
-		createTexture();
+		try
+		{
+			createTexture();
+		}
+		catch (love::Exception &)
+		{
+			gd->release();
+			throw;
+		}
 	}
 
 	Glyph *g = new Glyph;

+ 8 - 3
src/modules/graphics/opengl/Font.h

@@ -175,7 +175,7 @@ private:
 				return texture < other.texture;
 			else
 				return startvertex < other.startvertex;
-		};
+		}
 	};
 
 	bool initializeTexture(GLenum format);
@@ -202,14 +202,19 @@ private:
 	FontType type;
 	Texture::Filter filter;
 
+	int textureX, textureY;
+	int rowHeight;
+
+	bool useSpacesAsTab;
+
 	static const int NUM_TEXTURE_SIZES = 7;
 	static const int TEXTURE_WIDTHS[NUM_TEXTURE_SIZES];
 	static const int TEXTURE_HEIGHTS[NUM_TEXTURE_SIZES];
 
 	static const int TEXTURE_PADDING = 1;
 
-	int textureX, textureY;
-	int rowHeight;
+	// This will be used if the Rasterizer doesn't have a tab character itself.
+	static const int SPACES_PER_TAB = 4;
 
 }; // Font
 

+ 182 - 96
src/modules/graphics/opengl/Graphics.cpp

@@ -53,12 +53,18 @@ Graphics::Graphics()
 	, width(0)
 	, height(0)
 	, created(false)
+	, activeStencil(false)
 	, savedState()
 {
 	currentWindow = love::window::sdl::Window::createSingleton();
 
+	int w, h;
+	love::window::WindowSettings wsettings;
+
+	currentWindow->getWindow(w, h, wsettings);
+
 	if (currentWindow->isCreated())
-		setMode(currentWindow->getWidth(), currentWindow->getHeight());
+		setMode(w, h, wsettings.sRGB);
 }
 
 Graphics::~Graphics()
@@ -95,6 +101,8 @@ DisplayState Graphics::saveState()
 	for (int i = 0; i < 4; i++)
 		s.colorMask[i] = colorMask[i];
 
+	wireframe = isWireframe();
+
 	return s;
 }
 
@@ -111,6 +119,7 @@ void Graphics::restoreState(const DisplayState &s)
 	else
 		setScissor();
 	setColorMask(s.colorMask[0], s.colorMask[1], s.colorMask[2], s.colorMask[3]);
+	setWireframe(s.wireframe);
 }
 
 void Graphics::setViewportSize(int width, int height)
@@ -147,7 +156,7 @@ void Graphics::setViewportSize(int width, int height)
 		c->startGrab(c->getAttachedCanvases());
 }
 
-bool Graphics::setMode(int width, int height)
+bool Graphics::setMode(int width, int height, bool &sRGB)
 {
 	this->width = width;
 	this->height = height;
@@ -166,9 +175,6 @@ bool Graphics::setMode(int width, int height)
 	// Enable blending
 	glEnable(GL_BLEND);
 
-	// "Normal" blending
-	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
 	// Enable all color component writes.
 	setColorMask(true, true, true, true);
 
@@ -207,6 +213,31 @@ bool Graphics::setMode(int width, int height)
 	glGetIntegerv(GL_MAX_MODELVIEW_STACK_DEPTH, &matrixLimit);
 	matrixLimit -= 5;
 
+	// Set whether drawing converts input from linear -> sRGB colorspace.
+	if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_sRGB || GLEE_EXT_framebuffer_sRGB)
+	{
+		if (sRGB)
+			glEnable(GL_FRAMEBUFFER_SRGB);
+		else
+			glDisable(GL_FRAMEBUFFER_SRGB);
+	}
+	else
+		sRGB = false;
+
+	Canvas::screenHasSRGB = sRGB;
+
+	bool enabledebug = false;
+
+	if (GLEE_VERSION_3_0)
+	{
+		// Enable OpenGL's debug output if a debug context has been created.
+		GLint flags = 0;
+		glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
+		enabledebug = (flags & GL_CONTEXT_FLAG_DEBUG_BIT) != 0;
+	}
+
+	setDebug(enabledebug);
+
 	return true;
 }
 
@@ -223,6 +254,61 @@ void Graphics::unSetMode()
 	gl.deInitContext();
 }
 
+static void APIENTRY debugCB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei /*len*/, const GLchar *msg, GLvoid* /*usr*/)
+{
+	// Human-readable strings for the debug info.
+	const char *sourceStr = OpenGL::debugSourceString(source);
+	const char *typeStr = OpenGL::debugTypeString(type);
+	const char *severityStr = OpenGL::debugSeverityString(severity);
+
+	const char *fmt = "OpenGL: %s [source=%s, type=%s, severity=%s, id=%d]\n";
+	printf(fmt, msg, sourceStr, typeStr, severityStr, id);
+}
+
+void Graphics::setDebug(bool enable)
+{
+	// Make sure debug output is supported. The AMD ext. is a bit different
+	// so we don't make use of it, since AMD drivers now support KHR_debug.
+	if (!(GLEE_VERSION_4_3 || GLEE_KHR_debug || GLEE_ARB_debug_output))
+		return;
+
+	// Ugly hack to reduce code duplication.
+	if (GLEE_ARB_debug_output && !(GLEE_VERSION_4_3 || GLEE_KHR_debug))
+	{
+		glDebugMessageCallback = (GLEEPFNGLDEBUGMESSAGECALLBACKPROC) glDebugMessageCallbackARB;
+		glDebugMessageControl = (GLEEPFNGLDEBUGMESSAGECONTROLPROC) glDebugMessageControlARB;
+	}
+
+	if (!enable)
+	{
+		// Disable the debug callback function.
+		glDebugMessageCallback(nullptr, nullptr);
+
+		// We can disable debug output entirely with KHR_debug.
+		if (GLEE_VERSION_4_3 || GLEE_KHR_debug)
+			glDisable(GL_DEBUG_OUTPUT);
+
+		return;
+	}
+
+	// We don't want asynchronous debug output.
+	glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+
+	glDebugMessageCallback(debugCB, nullptr);
+
+	// Initially, enable everything.
+	glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, 0, GL_TRUE);
+
+	// Disable messages about deprecated OpenGL functionality.
+	glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR, GL_DONT_CARE, 0, 0, GL_FALSE);
+	glDebugMessageControl(GL_DEBUG_SOURCE_SHADER_COMPILER, GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR, GL_DONT_CARE, 0, 0, GL_FALSE);
+
+	if (GLEE_VERSION_4_3 || GLEE_KHR_debug)
+		glEnable(GL_DEBUG_OUTPUT);
+
+	::printf("OpenGL debug output enabled (LOVE_GRAPHICS_DEBUG=1)\n");
+}
+
 void Graphics::reset()
 {
 	DisplayState s;
@@ -295,6 +381,8 @@ void Graphics::defineStencil()
 	glEnable(GL_STENCIL_TEST);
 	glStencilFunc(GL_ALWAYS, 1, 1);
 	glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+
+	activeStencil = true;
 }
 
 void Graphics::useStencil(bool invert)
@@ -306,19 +394,18 @@ void Graphics::useStencil(bool invert)
 
 void Graphics::discardStencil()
 {
+	if (!activeStencil)
+		return;
+
 	setColorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]);
 	glDisable(GL_STENCIL_TEST);
+	activeStencil = false;
 }
 
-int Graphics::getMaxTextureSize() const
-{
-	return gl.getMaxTextureSize();
-}
-
-Image *Graphics::newImage(love::image::ImageData *data)
+Image *Graphics::newImage(love::image::ImageData *data, Texture::Format format)
 {
 	// Create the image.
-	Image *image = new Image(data);
+	Image *image = new Image(data, format);
 
 	if (!isCreated())
 		return image;
@@ -342,10 +429,10 @@ Image *Graphics::newImage(love::image::ImageData *data)
 	return image;
 }
 
-Image *Graphics::newImage(love::image::CompressedData *cdata)
+Image *Graphics::newImage(love::image::CompressedData *cdata, Texture::Format format)
 {
 	// Create the image.
-	Image *image = new Image(cdata);
+	Image *image = new Image(cdata, format);
 
 	if (!isCreated())
 		return image;
@@ -389,11 +476,14 @@ ParticleSystem *Graphics::newParticleSystem(Texture *texture, int size)
 	return new ParticleSystem(texture, size);
 }
 
-Canvas *Graphics::newCanvas(int width, int height, Canvas::TextureType texture_type)
+Canvas *Graphics::newCanvas(int width, int height, Texture::Format format, int fsaa)
 {
-	if (texture_type == Canvas::TYPE_HDR && !Canvas::isHDRSupported())
+	if (format == Texture::FORMAT_HDR && !Canvas::isHDRSupported())
 		throw Exception("HDR Canvases are not supported by your OpenGL implementation");
 
+	if (format == Texture::FORMAT_SRGB && !Canvas::isSRGBSupported())
+		throw Exception("sRGB Canvases are not supported by your OpenGL implementation");
+
 	if (width > gl.getMaxTextureSize())
 		throw Exception("Cannot create canvas: width of %d pixels is too large for this system.", width);
 	else if (height > gl.getMaxTextureSize())
@@ -402,7 +492,7 @@ Canvas *Graphics::newCanvas(int width, int height, Canvas::TextureType texture_t
 	while (GL_NO_ERROR != glGetError())
 		/* clear opengl error flag */;
 
-	Canvas *canvas = new Canvas(width, height, texture_type);
+	Canvas *canvas = new Canvas(width, height, format, fsaa);
 	GLenum err = canvas->getStatus();
 
 	// everything ok, return canvas (early out)
@@ -457,6 +547,11 @@ Mesh *Graphics::newMesh(const std::vector<Vertex> &vertices, Mesh::DrawMode mode
 	return new Mesh(vertices, mode);
 }
 
+Mesh *Graphics::newMesh(int vertexcount, Mesh::DrawMode mode)
+{
+	return new Mesh(vertexcount, mode);
+}
+
 void Graphics::setColor(const Color &c)
 {
 	gl.setColor(c);
@@ -509,22 +604,16 @@ const bool *Graphics::getColorMask() const
 
 void Graphics::setBlendMode(Graphics::BlendMode mode)
 {
-	const int gl_1_4 = GLEE_VERSION_1_4;
-
-	GLenum func = GL_FUNC_ADD;
-	GLenum src_rgb = GL_ONE;
-	GLenum src_a = GL_ONE;
-	GLenum dst_rgb = GL_ZERO;
-	GLenum dst_a = GL_ZERO;
+	OpenGL::BlendState state = {GL_ONE, GL_ONE, GL_ZERO, GL_ZERO, GL_FUNC_ADD};
 
 	switch (mode)
 	{
 	case BLEND_ALPHA:
-		if (gl_1_4 || GLEE_EXT_blend_func_separate)
+		if (GLEE_VERSION_1_4 || GLEE_EXT_blend_func_separate)
 		{
-			src_rgb = GL_SRC_ALPHA;
-			src_a = GL_ONE;
-			dst_rgb = dst_a = GL_ONE_MINUS_SRC_ALPHA;
+			state.srcRGB = GL_SRC_ALPHA;
+			state.srcA = GL_ONE;
+			state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA;
 		}
 		else
 		{
@@ -532,98 +621,56 @@ void Graphics::setBlendMode(Graphics::BlendMode mode)
 			// This will most likely only be used for the Microsoft software renderer and
 			// since it's still stuck with OpenGL 1.1, the only expected difference is a
 			// different alpha value when reading back the default framebuffer (newScreenshot).
-			src_rgb = src_a = GL_SRC_ALPHA;
-			dst_rgb = dst_a = GL_ONE_MINUS_SRC_ALPHA;
+			state.srcRGB = state.srcA = GL_SRC_ALPHA;
+			state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA;
 		}
 		break;
 	case BLEND_MULTIPLICATIVE:
-		src_rgb = src_a = GL_DST_COLOR;
-		dst_rgb = dst_a = GL_ZERO;
+		state.srcRGB = state.srcA = GL_DST_COLOR;
+		state.dstRGB = state.dstA = GL_ZERO;
 		break;
 	case BLEND_PREMULTIPLIED:
-		src_rgb = src_a = GL_ONE;
-		dst_rgb = dst_a = GL_ONE_MINUS_SRC_ALPHA;
+		state.srcRGB = state.srcA = GL_ONE;
+		state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA;
 		break;
 	case BLEND_SUBTRACTIVE:
-		func = GL_FUNC_REVERSE_SUBTRACT;
+		state.func = GL_FUNC_REVERSE_SUBTRACT;
 	case BLEND_ADDITIVE:
-		src_rgb = src_a = GL_SRC_ALPHA;
-		dst_rgb = dst_a = GL_ONE;
+		state.srcRGB = state.srcA = GL_SRC_ALPHA;
+		state.dstRGB = state.dstA = GL_ONE;
 		break;
 	case BLEND_REPLACE:
 	default:
-		src_rgb = src_a = GL_ONE;
-		dst_rgb = dst_a = GL_ZERO;
+		state.srcRGB = state.srcA = GL_ONE;
+		state.dstRGB = state.dstA = GL_ZERO;
 		break;
 	}
 
-	if (gl_1_4 || GLEE_ARB_imaging)
-		glBlendEquation(func);
-	else if (GLEE_EXT_blend_minmax && GLEE_EXT_blend_subtract)
-		glBlendEquationEXT(func);
-	else
-	{
-		if (func == GL_FUNC_REVERSE_SUBTRACT)
-			throw Exception("This graphics card does not support the subtractive blend mode!");
-		// GL_FUNC_ADD is the default even without access to glBlendEquation, so that'll still work.
-	}
-
-	if (src_rgb == src_a && dst_rgb == dst_a)
-		glBlendFunc(src_rgb, dst_rgb);
-	else
-	{
-		if (gl_1_4)
-			glBlendFuncSeparate(src_rgb, dst_rgb, src_a, dst_a);
-		else if (GLEE_EXT_blend_func_separate)
-			glBlendFuncSeparateEXT(src_rgb, dst_rgb, src_a, dst_a);
-		else
-			throw Exception("This graphics card does not support separated rgb and alpha blend functions!");
-	}
+	gl.setBlendState(state);
 }
 
 Graphics::BlendMode Graphics::getBlendMode() const
 {
-	const int gl_1_4 = GLEE_VERSION_1_4;
-
-	GLint src_rgb, src_a, dst_rgb, dst_a;
-	GLint equation = GL_FUNC_ADD;
-
-	if (gl_1_4 || GLEE_EXT_blend_func_separate)
-	{
-		glGetIntegerv(GL_BLEND_SRC_RGB, &src_rgb);
-		glGetIntegerv(GL_BLEND_SRC_ALPHA, &src_a);
-		glGetIntegerv(GL_BLEND_DST_RGB, &dst_rgb);
-		glGetIntegerv(GL_BLEND_DST_ALPHA, &dst_a);
-	}
-	else
-	{
-		glGetIntegerv(GL_BLEND_SRC, &src_rgb);
-		glGetIntegerv(GL_BLEND_DST, &dst_rgb);
-		src_a = src_rgb;
-		dst_a = dst_rgb;
-	}
+	OpenGL::BlendState state = gl.getBlendState();
 
-	if (gl_1_4 || GLEE_ARB_imaging || (GLEE_EXT_blend_minmax && GLEE_EXT_blend_subtract))
-		glGetIntegerv(GL_BLEND_EQUATION, &equation);
-
-	if (equation == GL_FUNC_REVERSE_SUBTRACT)  // && src == GL_SRC_ALPHA && dst == GL_ONE
+	if (state.func == GL_FUNC_REVERSE_SUBTRACT)  // && src == GL_SRC_ALPHA && dst == GL_ONE
 		return BLEND_SUBTRACTIVE;
 	// Everything else has equation == GL_FUNC_ADD.
-	else if (src_rgb == src_a && dst_rgb == dst_a)
+	else if (state.srcRGB == state.srcA && state.dstRGB == state.dstA)
 	{
-		if (src_rgb == GL_SRC_ALPHA && dst_rgb == GL_ONE)
+		if (state.srcRGB == GL_SRC_ALPHA && state.dstRGB == GL_ONE)
 			return BLEND_ADDITIVE;
-		else if (src_rgb == GL_SRC_ALPHA && dst_rgb == GL_ONE_MINUS_SRC_ALPHA)
+		else if (state.srcRGB == GL_SRC_ALPHA && state.dstRGB == GL_ONE_MINUS_SRC_ALPHA)
 			return BLEND_ALPHA; // alpha blend mode fallback for very old OpenGL versions.
-		else if (src_rgb == GL_DST_COLOR && dst_rgb == GL_ZERO)
+		else if (state.srcRGB == GL_DST_COLOR && state.dstRGB == GL_ZERO)
 			return BLEND_MULTIPLICATIVE;
-		else if (src_rgb == GL_ONE && dst_rgb == GL_ONE_MINUS_SRC_ALPHA)
+		else if (state.srcRGB == GL_ONE && state.dstRGB == GL_ONE_MINUS_SRC_ALPHA)
 			return BLEND_PREMULTIPLIED;
-		else if (src_rgb == GL_ONE && dst_rgb == GL_ZERO)
+		else if (state.srcRGB == GL_ONE && state.dstRGB == GL_ZERO)
 			return BLEND_REPLACE;
 	}
-	else if (src_rgb == GL_SRC_ALPHA && src_a == GL_ONE &&
-		dst_rgb == GL_ONE_MINUS_SRC_ALPHA && dst_a == GL_ONE_MINUS_SRC_ALPHA)
+	else if (state.srcRGB == GL_SRC_ALPHA && state.srcA == GL_ONE &&
+		state.dstRGB == GL_ONE_MINUS_SRC_ALPHA && state.dstA == GL_ONE_MINUS_SRC_ALPHA)
 		return BLEND_ALPHA;
 
 	throw Exception("Unknown blend mode");
@@ -693,11 +740,15 @@ float Graphics::getPointSize() const
 	return (float)size;
 }
 
-int Graphics::getMaxPointSize() const
+void Graphics::setWireframe(bool enable)
+{
+	wireframe = enable;
+	glPolygonMode(GL_FRONT_AND_BACK, enable ? GL_LINE : GL_FILL);
+}
+
+bool Graphics::isWireframe() const
 {
-	GLint max;
-	glGetIntegerv(GL_POINT_SIZE_MAX, &max);
-	return (int)max;
+	return wireframe;
 }
 
 void Graphics::print(const std::string &str, float x, float y , float angle, float sx, float sy, float ox, float oy, float kx, float ky)
@@ -1007,6 +1058,41 @@ std::string Graphics::getRendererInfo(Graphics::RendererInfo infotype) const
 	return std::string(infostr);
 }
 
+double Graphics::getSystemLimit(SystemLimit limittype) const
+{
+	double limit = 0.0;
+
+	switch (limittype)
+	{
+	case Graphics::LIMIT_POINT_SIZE:
+		{
+			GLfloat limits[2];
+			glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, limits);
+			limit = limits[1];
+		}
+		break;
+	case Graphics::LIMIT_TEXTURE_SIZE:
+		limit = (double) gl.getMaxTextureSize();
+		break;
+	case Graphics::LIMIT_MULTI_CANVAS:
+		limit = (double) gl.getMaxRenderTargets();
+		break;
+	case Graphics::LIMIT_CANVAS_FSAA:
+		if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object
+			|| GLEE_EXT_framebuffer_multisample)
+		{
+			GLint intlimit = 0;
+			glGetIntegerv(GL_MAX_SAMPLES, &intlimit);
+			limit = (double) intlimit;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return limit;
+}
+
 void Graphics::push()
 {
 	if (userMatrices == matrixLimit)

+ 28 - 12
src/modules/graphics/opengl/Graphics.h

@@ -80,6 +80,8 @@ struct DisplayState
 	// Color mask.
 	bool colorMask[4];
 
+	bool wireframe;
+
 	// Default values.
 	DisplayState()
 	{
@@ -91,6 +93,7 @@ struct DisplayState
 		pointSize = 1.0f;
 		scissor = false;
 		colorMask[0] = colorMask[1] = colorMask[2] = colorMask[3] = true;
+		wireframe = false;
 	}
 
 };
@@ -110,9 +113,11 @@ public:
 	void restoreState(const DisplayState &s);
 
 	virtual void setViewportSize(int width, int height);
-	virtual bool setMode(int width, int height);
+	virtual bool setMode(int width, int height, bool &sRGB);
 	virtual void unSetMode();
 
+	void setDebug(bool enable);
+
 	/**
 	 * Resets the current color, background color,
 	 * line style, and so forth. (This will be called
@@ -184,16 +189,11 @@ public:
 	 */
 	void discardStencil();
 
-	/**
-	 * Gets the maximum supported width or height of Textures on this system.
-	 **/
-	int getMaxTextureSize() const;
-
 	/**
 	 * Creates an Image object with padding and/or optimization.
 	 **/
-	Image *newImage(love::image::ImageData *data);
-	Image *newImage(love::image::CompressedData *cdata);
+	Image *newImage(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL);
+	Image *newImage(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL);
 
 	Quad *newQuad(Quad::Viewport v, float sw, float sh);
 
@@ -206,11 +206,12 @@ public:
 
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 
-	Canvas *newCanvas(int width, int height, Canvas::TextureType texture_type = Canvas::TYPE_NORMAL);
+	Canvas *newCanvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0);
 
 	Shader *newShader(const Shader::ShaderSources &sources);
 
 	Mesh *newMesh(const std::vector<Vertex> &vertices, Mesh::DrawMode mode = Mesh::DRAW_MODE_FAN);
+	Mesh *newMesh(int vertexcount, Mesh::DrawMode mode = Mesh::DRAW_MODE_FAN);
 
 	/**
 	 * Sets the foreground color.
@@ -324,10 +325,17 @@ public:
 	float getPointSize() const;
 
 	/**
-	 * Gets the maximum point size supported.
-	 * This may vary from computer to computer.
+	 * Sets whether graphics will be drawn as wireframe lines instead of filled
+	 * triangles (has no effect for drawn points.)
+	 * This should only be used as a debugging tool. The wireframe lines do not
+	 * behave the same as regular love.graphics lines.
+	 **/
+	void setWireframe(bool enable);
+
+	/**
+	 * Gets whether wireframe drawing mode is enabled.
 	 **/
-	int getMaxPointSize() const;
+	bool isWireframe() const;
 
 	/**
 	 * Draws text at the specified coordinates, with rotation and
@@ -430,6 +438,11 @@ public:
 	 **/
 	std::string getRendererInfo(Graphics::RendererInfo infotype) const;
 
+	/**
+	 * Gets the system-dependent numeric limit for the specified parameter.
+	 **/
+	double getSystemLimit(SystemLimit limittype) const;
+
 	void push();
 	void pop();
 	void rotate(float r);
@@ -450,11 +463,14 @@ private:
 	GLint matrixLimit;
 	GLint userMatrices;
 	bool colorMask[4];
+	bool wireframe;
 
 	int width;
 	int height;
 	bool created;
 
+	bool activeStencil;
+
 	DisplayState savedState;
 
 }; // Graphics

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

@@ -36,7 +36,7 @@ float Image::maxMipmapSharpness = 0.0f;
 Texture::FilterMode Image::defaultMipmapFilter = Texture::FILTER_NONE;
 float Image::defaultMipmapSharpness = 0.0f;
 
-Image::Image(love::image::ImageData *data)
+Image::Image(love::image::ImageData *data, Texture::Format format)
 	: data(data)
 	, cdata(nullptr)
 	, paddedWidth(width)
@@ -45,6 +45,7 @@ Image::Image(love::image::ImageData *data)
 	, mipmapSharpness(defaultMipmapSharpness)
 	, mipmapsCreated(false)
 	, compressed(false)
+	, format(format)
 	, usingDefaultTexture(false)
 {
 	width = data->getWidth();
@@ -54,7 +55,7 @@ Image::Image(love::image::ImageData *data)
 	preload();
 }
 
-Image::Image(love::image::CompressedData *cdata)
+Image::Image(love::image::CompressedData *cdata, Texture::Format format)
 	: data(nullptr)
 	, cdata(cdata)
 	, paddedWidth(width)
@@ -63,6 +64,7 @@ Image::Image(love::image::CompressedData *cdata)
 	, mipmapSharpness(defaultMipmapSharpness)
 	, mipmapsCreated(false)
 	, compressed(true)
+	, format(format)
 	, usingDefaultTexture(false)
 {
 	width = cdata->getWidth(0);
@@ -91,7 +93,7 @@ love::image::CompressedData *Image::getCompressedData() const
 	return cdata;
 }
 
-void Image::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
+void Image::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	Matrix t;
 	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
@@ -99,7 +101,7 @@ void Image::draw(float x, float y, float angle, float sx, float sy, float ox, fl
 	drawv(t, vertices);
 }
 
-void Image::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
+void Image::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	Matrix t;
 	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
@@ -107,7 +109,7 @@ void Image::drawq(Quad *quad, float x, float y, float angle, float sx, float sy,
 	drawv(t, quad->getVertices());
 }
 
-void Image::predraw() const
+void Image::predraw()
 {
 	bind();
 
@@ -121,7 +123,7 @@ void Image::predraw() const
 	}
 }
 
-void Image::postdraw() const
+void Image::postdraw()
 {
 	if (width != paddedWidth || height != paddedHeight)
 	{
@@ -328,6 +330,9 @@ void Image::unload()
 
 bool Image::loadVolatile()
 {
+	if (format == FORMAT_SRGB && !hasSRGBSupport())
+		throw love::Exception("sRGB images are not supported on this system.");
+
 	if (isCompressed() && cdata && !hasCompressedTextureSupport(cdata->getFormat()))
 	{
 		const char *str;
@@ -400,9 +405,10 @@ void Image::uploadTexturePadded()
 	}
 	else if (data)
 	{
+		GLenum iformat = (format == FORMAT_SRGB) ? GL_SRGB8_ALPHA8 : GL_RGBA8;
 		glTexImage2D(GL_TEXTURE_2D,
 		             0,
-		             GL_RGBA8,
+		             iformat,
 		             (GLsizei)paddedWidth,
 		             (GLsizei)paddedHeight,
 		             0,
@@ -437,9 +443,10 @@ void Image::uploadTexture()
 	}
 	else if (data)
 	{
+		GLenum iformat = (format == FORMAT_SRGB) ? GL_SRGB8_ALPHA8 : GL_RGBA8;
 		glTexImage2D(GL_TEXTURE_2D,
 		             0,
-		             GL_RGBA8,
+		             iformat,
 		             (GLsizei)width,
 		             (GLsizei)height,
 		             0,
@@ -497,6 +504,11 @@ bool Image::refresh()
 	return true;
 }
 
+Texture::Format Image::getFormat() const
+{
+	return format;
+}
+
 void Image::uploadDefaultTexture()
 {
 	usingDefaultTexture = true;
@@ -511,7 +523,7 @@ void Image::uploadDefaultTexture()
 	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, px);
 }
 
-void Image::drawv(const Matrix &t, const Vertex *v) const
+void Image::drawv(const Matrix &t, const Vertex *v)
 {
 	predraw();
 
@@ -561,16 +573,27 @@ bool Image::isCompressed() const
 	return compressed;
 }
 
-GLenum Image::getCompressedFormat(image::CompressedData::Format format) const
+GLenum Image::getCompressedFormat(image::CompressedData::Format cformat) const
 {
-	switch (format)
+	bool srgb = format == FORMAT_SRGB;
+
+	switch (cformat)
 	{
 	case image::CompressedData::FORMAT_DXT1:
-		return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
+		if (srgb)
+			return GL_COMPRESSED_SRGB_S3TC_DXT1_EXT;
+		else
+			return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
 	case image::CompressedData::FORMAT_DXT3:
-		return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+		if (srgb)
+			return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
+		else
+			return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
 	case image::CompressedData::FORMAT_DXT5:
-		return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+		if (srgb)
+			return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
+		else
+			return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
 	case image::CompressedData::FORMAT_BC4:
 		return GL_COMPRESSED_RED_RGTC1;
 	case image::CompressedData::FORMAT_BC4s:
@@ -580,7 +603,10 @@ GLenum Image::getCompressedFormat(image::CompressedData::Format format) const
 	case image::CompressedData::FORMAT_BC5s:
 		return GL_COMPRESSED_SIGNED_RG_RGTC2;
 	default:
-		return GL_RGBA8;
+		if (srgb)
+			return GL_SRGB8_ALPHA8;
+		else
+			return GL_RGBA8;
 	}
 }
 
@@ -632,6 +658,11 @@ bool Image::hasCompressedTextureSupport(image::CompressedData::Format format)
 	return false;
 }
 
+bool Image::hasSRGBSupport()
+{
+	return GLEE_VERSION_2_1 || GLEE_EXT_texture_sRGB;
+}
+
 } // opengl
 } // graphics
 } // love

+ 15 - 8
src/modules/graphics/opengl/Image.h

@@ -56,14 +56,14 @@ public:
 	 *
 	 * @param data The data from which to load the image.
 	 **/
-	Image(love::image::ImageData *data);
+	Image(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL);
 
 	/**
 	 * Creates a new Image with compressed image data.
 	 *
 	 * @param cdata The compressed data from which to load the image.
 	 **/
-	Image(love::image::CompressedData *cdata);
+	Image(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL);
 
 	/**
 	 * Destructor. Deletes the hardware texture and other resources.
@@ -76,20 +76,20 @@ public:
 	/**
 	 * @copydoc Drawable::draw()
 	 **/
-	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
+	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 	/**
 	 * @copydoc Texture::drawq()
 	 **/
-	void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
+	void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 	/**
 	 * Call before using this Image's texture to draw. Binds the texture,
 	 * globally scales texture coordinates if the Image has NPOT dimensions and
 	 * NPOT isn't supported, etc.
 	 **/
-	virtual void predraw() const;
-	virtual void postdraw() const;
+	virtual void predraw();
+	virtual void postdraw();
 
 	virtual GLuint getGLTexture() const;
 
@@ -120,6 +120,8 @@ public:
 	 **/
 	bool refresh();
 
+	Texture::Format getFormat() const;
+
 	static void setDefaultMipmapSharpness(float sharpness);
 	static float getDefaultMipmapSharpness();
 	static void setDefaultMipmapFilter(FilterMode f);
@@ -133,11 +135,13 @@ public:
 	static bool hasCompressedTextureSupport();
 	static bool hasCompressedTextureSupport(image::CompressedData::Format format);
 
+	static bool hasSRGBSupport();
+
 private:
 
 	void uploadDefaultTexture();
 
-	void drawv(const Matrix &t, const Vertex *v) const;
+	void drawv(const Matrix &t, const Vertex *v);
 
 	// The ImageData from which the texture is created. May be null if
 	// Compressed image data was used to create the texture.
@@ -162,6 +166,9 @@ private:
 	// Whether this Image is using a compressed texture.
 	bool compressed;
 
+	// The format to interpret the texture's data as.
+	Texture::Format format;
+
 	// True if the image wasn't able to be properly created and it had to fall
 	// back to a default texture.
 	bool usingDefaultTexture;
@@ -180,7 +187,7 @@ private:
 	static FilterMode defaultMipmapFilter;
 	static float defaultMipmapSharpness;
 
-	GLenum getCompressedFormat(image::CompressedData::Format format) const;
+	GLenum getCompressedFormat(image::CompressedData::Format cformat) const;
 
 }; // Image
 

+ 193 - 41
src/modules/graphics/opengl/Mesh.cpp

@@ -23,6 +23,9 @@
 #include "common/Matrix.h"
 #include "common/Exception.h"
 
+// C++
+#include <algorithm>
+
 namespace love
 {
 namespace graphics
@@ -35,14 +38,46 @@ Mesh::Mesh(const std::vector<Vertex> &verts, Mesh::DrawMode mode)
 	, vertex_count(0)
 	, ibo(nullptr)
 	, element_count(0)
+	, element_data_type(getGLDataTypeFromMax(verts.size()))
+	, instance_count(1)
 	, draw_mode(mode)
+	, range_min(-1)
+	, range_max(-1)
 	, texture(nullptr)
 	, colors_enabled(false)
-	, wireframe(false)
 {
 	setVertices(verts);
 }
 
+Mesh::Mesh(int vertexcount, Mesh::DrawMode mode)
+	: vbo(nullptr)
+	, vertex_count(0)
+	, ibo(nullptr)
+	, element_count(0)
+	, element_data_type(getGLDataTypeFromMax(vertexcount))
+	, draw_mode(mode)
+	, range_min(-1)
+	, range_max(-1)
+	, texture(nullptr)
+	, colors_enabled(false)
+{
+	if (vertexcount < 1)
+		throw love::Exception("Invalid number of vertices.");
+
+	std::vector<Vertex> verts(vertexcount);
+
+	// Default-initialized vertices should have a white opaque color.
+	for (size_t i = 0; i < verts.size(); i++)
+	{
+		verts[i].r = 255;
+		verts[i].g = 255;
+		verts[i].b = 255;
+		verts[i].a = 255;
+	}
+
+	setVertices(verts);
+}
+
 Mesh::~Mesh()
 {
 	delete vbo;
@@ -51,8 +86,8 @@ Mesh::~Mesh()
 
 void Mesh::setVertices(const std::vector<Vertex> &verts)
 {
-	if (verts.size() < 3)
-		throw love::Exception("At least 3 vertices are required.");
+	if (verts.size() == 0)
+		throw love::Exception("At least one vertex is required.");
 
 	size_t size = sizeof(Vertex) * verts.size();
 
@@ -118,15 +153,42 @@ size_t Mesh::getVertexCount() const
 	return vertex_count;
 }
 
-void Mesh::setVertexMap(const std::vector<uint32> &map)
+/**
+ * Copies index data from a vector to a mapped index buffer.
+ **/
+template <typename T>
+static void copyToIndexBuffer(const std::vector<uint32> &indices, VertexBuffer::Mapper &buffermap, size_t maxval)
 {
-	for (size_t i = 0; i < map.size(); i++)
+	T *elems = (T *) buffermap.get();
+
+	for (size_t i = 0; i < indices.size(); i++)
 	{
-		if (map[i] >= vertex_count)
-			throw love::Exception("Invalid vertex map value: %d", map[i] + 1);
+		if (indices[i] >= maxval)
+			throw love::Exception("Invalid vertex map value: %d", indices[i] + 1);
+
+		elems[i] = (T) indices[i];
 	}
+}
 
-	size_t size = sizeof(uint32) * map.size();
+void Mesh::setVertexMap(const std::vector<uint32> &map)
+{
+	GLenum datatype = getGLDataTypeFromMax(vertex_count);
+
+	// Calculate the size in bytes of the index buffer data.
+	size_t size = map.size();
+	switch (datatype)
+	{
+	case GL_UNSIGNED_BYTE:
+		size *= sizeof(uint8);
+		break;
+	case GL_UNSIGNED_SHORT:
+		size *= sizeof(uint16);
+		break;
+	case GL_UNSIGNED_INT:
+	default:
+		size *= sizeof(uint32);
+		break;
+	}
 
 	if (ibo && size > ibo->getSize())
 	{
@@ -142,27 +204,68 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
 
 	element_count = map.size();
 
-	if (ibo && element_count > 0)
-	{
-		VertexBuffer::Bind ibo_bind(*ibo);
-		VertexBuffer::Mapper ibo_map(*ibo);
+	if (!ibo || element_count == 0)
+		return;
+
+	VertexBuffer::Bind ibo_bind(*ibo);
+	VertexBuffer::Mapper ibo_map(*ibo);
 
-		// Fill the buffer.
-		memcpy(ibo_map.get(), &map[0], size);
+	// Fill the buffer with the index values from the vector.
+	switch (datatype)
+	{
+	case GL_UNSIGNED_BYTE:
+		copyToIndexBuffer<uint8>(map, ibo_map, vertex_count);
+		break;
+	case GL_UNSIGNED_SHORT:
+		copyToIndexBuffer<uint16>(map, ibo_map, vertex_count);
+		break;
+	case GL_UNSIGNED_INT:
+	default:
+		copyToIndexBuffer<uint32>(map, ibo_map, vertex_count);
+		break;
 	}
+
+	element_data_type = datatype;
+}
+
+/**
+ * Copies index data from a mapped buffer to a vector.
+ **/
+template <typename T>
+static void copyFromIndexBuffer(void *buffer, std::vector<uint32> &indices, size_t maxval)
+{
+	T *elems = (T *) buffer;
+	for (size_t i = 0; i < maxval; i++)
+		indices.push_back((uint32) elems[i]);
 }
 
-const uint32 *Mesh::getVertexMap() const
+void Mesh::getVertexMap(std::vector<uint32> &map) const
 {
-	if (ibo && element_count > 0)
-	{
-		VertexBuffer::Bind ibo_bind(*ibo);
+	if (!ibo || element_count == 0)
+		return;
 
-		// We unmap the buffer in Mesh::draw and Mesh::setVertexMap.
-		return (uint32 *) ibo->map();
-	}
+	map.clear();
+	map.reserve(element_count);
+
+	VertexBuffer::Bind ibo_bind(*ibo);
 
-	return 0;
+	// We unmap the buffer in Mesh::draw and Mesh::setVertexMap.
+	void *buffer = ibo->map();
+
+	// Fill the vector from the buffer.
+	switch (element_data_type)
+	{
+	case GL_UNSIGNED_BYTE:
+		copyFromIndexBuffer<uint8>(buffer, map, vertex_count);
+		break;
+	case GL_UNSIGNED_SHORT:
+		copyFromIndexBuffer<uint16>(buffer, map, vertex_count);
+		break;
+	case GL_UNSIGNED_INT:
+	default:
+		copyFromIndexBuffer<uint32>(buffer, map, vertex_count);
+		break;
+	}
 }
 
 size_t Mesh::getVertexMapCount() const
@@ -170,6 +273,16 @@ size_t Mesh::getVertexMapCount() const
 	return element_count;
 }
 
+void Mesh::setInstanceCount(int count)
+{
+	instance_count = std::max(count, 1);
+}
+
+int Mesh::getInstanceCount() const
+{
+	return instance_count;
+}
+
 void Mesh::setTexture(Texture *tex)
 {
 	tex->retain();
@@ -203,27 +316,37 @@ Mesh::DrawMode Mesh::getDrawMode() const
 	return draw_mode;
 }
 
-void Mesh::setVertexColors(bool enable)
+void Mesh::setDrawRange(int min, int max)
 {
-	colors_enabled = enable;
+	if (min < 0 || max < 0 || min > max)
+		throw love::Exception("Invalid draw range.");
+
+	range_min = min;
+	range_max = max;
 }
 
-bool Mesh::hasVertexColors() const
+void Mesh::setDrawRange()
 {
-	return colors_enabled;
+	range_min = range_max = -1;
 }
 
-void Mesh::setWireframe(bool enable)
+void Mesh::getDrawRange(int &min, int &max) const
 {
-	wireframe = enable;
+	min = range_min;
+	max = range_max;
 }
 
-bool Mesh::isWireframe() const
+void Mesh::setVertexColors(bool enable)
 {
-	return wireframe;
+	colors_enabled = enable;
+}
+
+bool Mesh::hasVertexColors() const
+{
+	return colors_enabled;
 }
 
-void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
+void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	const size_t pos_offset   = offsetof(Vertex, x);
 	const size_t tex_offset   = offsetof(Vertex, s);
@@ -261,32 +384,51 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 		glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), vbo->getPointer(color_offset));
 	}
 
-	if (wireframe)
-		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
-
 	GLenum mode = getGLDrawMode(draw_mode);
 
 	gl.prepareDraw();
 
 	if (ibo && element_count > 0)
 	{
+		// Use the custom vertex map (index buffer) to draw the vertices.
 		VertexBuffer::Bind ibo_bind(*ibo);
 
 		// Make sure the index buffer isn't mapped (sends data to GPU if needed.)
 		ibo->unmap();
 
-		// Use the custom vertex map to draw the vertices.
-		glDrawElements(mode, element_count, GL_UNSIGNED_INT, ibo->getPointer(0));
+		int max = element_count - 1;
+		if (range_max >= 0)
+			max = std::min(std::max(range_max, 0), (int) element_count - 1);
+
+		int min = 0;
+		if (range_min >= 0)
+			min = std::min(std::max(range_min, 0), max);
+
+		const void *indices = ibo->getPointer(min * sizeof(uint32));
+		GLenum type = element_data_type;
+
+		if (instance_count > 1)
+			gl.drawElementsInstanced(mode, max - min + 1, type, indices, instance_count);
+		else
+			glDrawElements(mode, max - min + 1, type, indices);
 	}
 	else
 	{
+		int max = vertex_count - 1;
+		if (range_max >= 0)
+			max = std::min(std::max(range_max, 0), (int) vertex_count - 1);
+
+		int min = 0;
+		if (range_min >= 0)
+			min = std::min(std::max(range_min, 0), max);
+
 		// Normal non-indexed drawing (no custom vertex map.)
-		glDrawArrays(mode, 0, vertex_count);
+		if (instance_count > 1)
+			gl.drawArraysInstanced(mode, min, max - min + 1, instance_count);
+		else
+			glDrawArrays(mode, min, max - min + 1);
 	}
 
-	if (wireframe)
-		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
-
 	glDisableClientState(GL_VERTEX_ARRAY);
 	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 
@@ -303,7 +445,7 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 		texture->postdraw();
 }
 
-GLenum Mesh::getGLDrawMode(Mesh::DrawMode mode) const
+GLenum Mesh::getGLDrawMode(DrawMode mode) const
 {
 	switch (mode)
 	{
@@ -322,6 +464,16 @@ GLenum Mesh::getGLDrawMode(Mesh::DrawMode mode) const
 	return GL_TRIANGLES;
 }
 
+GLenum Mesh::getGLDataTypeFromMax(size_t maxvalue) const
+{
+	if (maxvalue > LOVE_UINT16_MAX)
+		return GL_UNSIGNED_INT;
+	else if (maxvalue > LOVE_UINT8_MAX)
+		return GL_UNSIGNED_SHORT;
+	else
+		return GL_UNSIGNED_BYTE;
+}
+
 bool Mesh::getConstant(const char *in, Mesh::DrawMode &out)
 {
 	return drawModes.find(in, out);

+ 35 - 16
src/modules/graphics/opengl/Mesh.h

@@ -22,6 +22,7 @@
 #define LOVE_GRAPHICS_OPENGL_MESH_H
 
 // LOVE
+#include "common/config.h"
 #include "common/int.h"
 #include "common/math.h"
 #include "common/StringMap.h"
@@ -64,6 +65,16 @@ public:
 	 * @param mode The draw mode to use when drawing the Mesh.
 	 **/
 	Mesh(const std::vector<Vertex> &verts, DrawMode mode = DRAW_MODE_FAN);
+
+	/**
+	 * Constructor.
+	 * Creates a Mesh with a certain number of default-initialized (hidden)
+	 * vertices.
+	 * @param vertexcount The number of vertices to use in the Mesh.
+	 * @param mode The draw mode to use when drawing the Mesh.
+	 **/
+	Mesh(int vertexcount, DrawMode mode = DRAW_MODE_FAN);
+
 	virtual ~Mesh();
 
 	/**
@@ -98,17 +109,25 @@ public:
 	void setVertexMap(const std::vector<uint32> &map);
 
 	/**
-	 * Gets a pointer to the vertex map array. The pointer is only valid until
-	 * the next function call in the graphics module.
-	 * May return null if the vertex map is empty.
+	 * Fills the uint32 vector passed into the method with the previously set
+	 * vertex map (index buffer) values.
 	 **/
-	const uint32 *getVertexMap() const;
+	void getVertexMap(std::vector<uint32> &map) const;
 
 	/**
 	 * Gets the total number of elements in the vertex map array.
 	 **/
 	size_t getVertexMapCount() const;
 
+	/**
+	 * Sets the number of instances of this Mesh to draw (uses hardware
+	 * instancing when possible.)
+	 * A custom vertex shader is necessary in order to introduce differences
+	 * in each instance.
+	 **/
+	void setInstanceCount(int count);
+	int getInstanceCount() const;
+
 	/**
 	 * Sets the texture used when drawing the Mesh.
 	 **/
@@ -131,6 +150,10 @@ public:
 	void setDrawMode(DrawMode mode);
 	DrawMode getDrawMode() const;
 
+	void setDrawRange(int min, int max);
+	void setDrawRange();
+	void getDrawRange(int &min, int &max) const;
+
 	/**
 	 * Sets whether per-vertex colors are enabled. If this is disabled, the
 	 * global color (love.graphics.setColor) will be used for the entire Mesh.
@@ -138,17 +161,8 @@ public:
 	void setVertexColors(bool enable);
 	bool hasVertexColors() const;
 
-	/**
-	 * Sets whether the Mesh will be drawn as wireframe lines instead of filled
-	 * triangles (has no effect for DRAW_MODE_POINTS.)
-	 * This should only be used as a debugging tool. The wireframe lines do not
-	 * behave the same as regular love.graphics lines.
-	 **/
-	void setWireframe(bool enable);
-	bool isWireframe() const;
-
 	// Implements Drawable.
-	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
+	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 	static bool getConstant(const char *in, DrawMode &out);
 	static bool getConstant(DrawMode in, const char *&out);
@@ -156,6 +170,7 @@ public:
 private:
 
 	GLenum getGLDrawMode(DrawMode mode) const;
+	GLenum getGLDataTypeFromMax(size_t maxvalue) const;
 
 	// Vertex buffer.
 	VertexBuffer *vbo;
@@ -164,16 +179,20 @@ private:
 	// Element (vertex index) buffer, for the vertex map.
 	VertexBuffer *ibo;
 	size_t element_count;
+	GLenum element_data_type;
+
+	int instance_count;
 
 	DrawMode draw_mode;
 
+	int range_min;
+	int range_max;
+
 	Texture *texture;
 
 	// Whether the per-vertex colors are used when drawing.
 	bool colors_enabled;
 
-	bool wireframe;
-
 	static StringMap<DrawMode, DRAW_MODE_MAX_ENUM>::Entry drawModeEntries[];
 	static StringMap<DrawMode, DRAW_MODE_MAX_ENUM> drawModes;
 

+ 193 - 4
src/modules/graphics/opengl/OpenGL.cpp

@@ -43,6 +43,7 @@ OpenGL::OpenGL()
 	: contextInitialized(false)
 	, maxAnisotropy(1.0f)
 	, maxTextureSize(0)
+	, maxRenderTargets(0)
 	, vendor(VENDOR_UNKNOWN)
 	, state()
 {
@@ -110,9 +111,14 @@ void OpenGL::initContext()
 		glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint *) &state.textureUnits[0]);
 	}
 
+	BlendState blend = {GL_ONE, GL_ONE, GL_ZERO, GL_ZERO, GL_FUNC_ADD};
+	setBlendState(blend);
+
 	initMaxValues();
 	createDefaultTexture();
 
+	state.lastPseudoInstanceID = -1;
+
 	contextInitialized = true;
 }
 
@@ -188,6 +194,19 @@ void OpenGL::initMaxValues()
 		maxAnisotropy = 1.0f;
 
 	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+
+	if (Canvas::isSupported() && (GLEE_VERSION_2_0 || GLEE_ARB_draw_buffers))
+	{
+		int maxattachments = 0;
+		glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxattachments);
+
+		int maxdrawbuffers = 0;
+		glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxdrawbuffers);
+
+		maxRenderTargets = std::min(maxattachments, maxdrawbuffers);
+	}
+	else
+		maxRenderTargets = 0;
 }
 
 void OpenGL::createDefaultTexture()
@@ -214,10 +233,81 @@ void OpenGL::createDefaultTexture()
 
 void OpenGL::prepareDraw()
 {
-	// Make sure the active shader has the correct values for the built-in
-	// screen params uniform.
-	if (Shader::current)
-		Shader::current->checkSetScreenParams();
+	Shader *shader = Shader::current;
+	if (shader != nullptr)
+	{
+		// Make sure the active shader has the correct values for its
+		// love-provided uniforms.
+		shader->checkSetScreenParams();
+
+		// Make sure the Instance ID variable is up-to-date when
+		// pseudo-instancing is used.
+		if (state.lastPseudoInstanceID != 0 && shader->hasVertexAttrib(ATTRIB_PSEUDO_INSTANCE_ID))
+		{
+			glVertexAttrib1f((GLuint) ATTRIB_PSEUDO_INSTANCE_ID, 0.0f);
+			state.lastPseudoInstanceID = 0;
+		}
+
+		// We need to make sure antialiased Canvases are properly resolved
+		// before sampling from their textures in a shader.
+		// This is kind of a big hack. :(
+		const std::map<std::string, Object *> &r = shader->getBoundRetainables();
+		for (auto it = r.begin(); it != r.end(); ++it)
+		{
+			// Even bigger hack! D:
+			Canvas *canvas = dynamic_cast<Canvas *>(it->second);
+			if (canvas != nullptr)
+				canvas->resolveMSAA();
+		}
+	}
+}
+
+void OpenGL::drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount)
+{
+	Shader *shader = Shader::current;
+
+	if (GLEE_ARB_draw_instanced)
+		glDrawArraysInstancedARB(mode, first, count, primcount);
+	else
+	{
+		bool shaderHasID = shader && shader->hasVertexAttrib(ATTRIB_PSEUDO_INSTANCE_ID);
+
+		// Pseudo-instancing fallback.
+		for (int i = 0; i < primcount; i++)
+		{
+			if (shaderHasID)
+				glVertexAttrib1f((GLuint) ATTRIB_PSEUDO_INSTANCE_ID, (GLfloat) i);
+
+			glDrawArrays(mode, first, count);
+		}
+
+		if (shaderHasID)
+			state.lastPseudoInstanceID = primcount - 1;
+	}
+}
+
+void OpenGL::drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount)
+{
+	Shader *shader = Shader::current;
+
+	if (GLEE_ARB_draw_instanced)
+		glDrawElementsInstancedARB(mode, count, type, indices, primcount);
+	else
+	{
+		bool shaderHasID = shader && shader->hasVertexAttrib(ATTRIB_PSEUDO_INSTANCE_ID);
+
+		// Pseudo-instancing fallback.
+		for (int i = 0; i < primcount; i++)
+		{
+			if (shaderHasID)
+				glVertexAttrib1f((GLuint) ATTRIB_PSEUDO_INSTANCE_ID, (GLfloat) i);
+
+			glDrawElements(mode, count, type, indices);
+		}
+
+		if (shaderHasID)
+			state.lastPseudoInstanceID = primcount - 1;
+	}
 }
 
 void OpenGL::setColor(const Color &c)
@@ -277,6 +367,39 @@ OpenGL::Viewport OpenGL::getScissor() const
 	return state.scissor;
 }
 
+void OpenGL::setBlendState(const BlendState &blend)
+{
+	if (GLEE_VERSION_1_4 || GLEE_ARB_imaging)
+		glBlendEquation(blend.func);
+	else if (GLEE_EXT_blend_minmax && GLEE_EXT_blend_subtract)
+		glBlendEquationEXT(blend.func);
+	else
+	{
+		if (blend.func == GL_FUNC_REVERSE_SUBTRACT)
+			throw love::Exception("This graphics card does not support the subtractive blend mode!");
+		// GL_FUNC_ADD is the default even without access to glBlendEquation, so that'll still work.
+	}
+
+	if (blend.srcRGB == blend.srcA && blend.dstRGB == blend.dstA)
+		glBlendFunc(blend.srcRGB, blend.dstRGB);
+	else
+	{
+		if (GLEE_VERSION_1_4)
+			glBlendFuncSeparate(blend.srcRGB, blend.dstRGB, blend.srcA, blend.dstA);
+		else if (GLEE_EXT_blend_func_separate)
+			glBlendFuncSeparateEXT(blend.srcRGB, blend.dstRGB, blend.srcA, blend.dstA);
+		else
+			throw love::Exception("This graphics card does not support separated rgb and alpha blend functions!");
+	}
+
+	state.blend = blend;
+}
+
+OpenGL::BlendState OpenGL::getBlendState() const
+{
+	return state.blend;
+}
+
 void OpenGL::setTextureUnit(int textureunit)
 {
 	if (textureunit < 0 || (size_t) textureunit >= state.textureUnits.size())
@@ -504,11 +627,77 @@ int OpenGL::getMaxTextureSize() const
 	return maxTextureSize;
 }
 
+int OpenGL::getMaxRenderTargets() const
+{
+	return maxRenderTargets;
+}
+
 OpenGL::Vendor OpenGL::getVendor() const
 {
 	return vendor;
 }
 
+const char *OpenGL::debugSeverityString(GLenum severity)
+{
+	switch (severity)
+	{
+	case GL_DEBUG_SEVERITY_HIGH:
+		return "high";
+	case GL_DEBUG_SEVERITY_MEDIUM:
+		return "medium";
+	case GL_DEBUG_SEVERITY_LOW:
+		return "low";
+	default:
+		break;
+	}
+	return "unknown";
+}
+
+const char *OpenGL::debugSourceString(GLenum source)
+{
+	switch (source)
+	{
+	case GL_DEBUG_SOURCE_API:
+		return "API";
+	case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
+		return "window";
+	case GL_DEBUG_SOURCE_SHADER_COMPILER:
+		return "shader";
+	case GL_DEBUG_SOURCE_THIRD_PARTY:
+		return "external";
+	case GL_DEBUG_SOURCE_APPLICATION:
+		return "LOVE";
+	case GL_DEBUG_SOURCE_OTHER:
+		return "other";
+	default:
+		break;
+	}
+	return "unknown";
+}
+
+const char *OpenGL::debugTypeString(GLenum type)
+{
+	switch (type)
+	{
+	case GL_DEBUG_TYPE_ERROR:
+		return "error";
+	case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
+		return "deprecated behavior";
+	case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
+		return "undefined behavior";
+	case GL_DEBUG_TYPE_PERFORMANCE:
+		return "performance";
+	case GL_DEBUG_TYPE_PORTABILITY:
+		return "portability";
+	case GL_DEBUG_TYPE_OTHER:
+		return "other";
+	default:
+		break;
+	}
+	return "unknown";
+}
+
+
 // OpenGL class instance singleton.
 OpenGL gl;
 

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

@@ -63,6 +63,17 @@ public:
 		VENDOR_UNKNOWN
 	};
 
+	// Vertex attributes used in shaders by LOVE. The values map to OpenGL
+	// generic vertex attribute indices, when applicable.
+	// LOVE uses the old hard-coded attribute APIs for positions, colors, etc.
+	// (for now.)
+	enum VertexAttrib
+	{
+		// Instance ID when pseudo-instancing is used.
+		ATTRIB_PSEUDO_INSTANCE_ID = 1,
+		ATTRIB_MAX_ENUM
+	};
+
 	// A rectangle representing an OpenGL viewport or a scissor box.
 	struct Viewport
 	{
@@ -76,6 +87,18 @@ public:
 		Viewport(int _x, int _y, int _w, int _h)
 			: x(_x), y(_y), w(_w), h(_h)
 		{}
+
+		bool operator == (const Viewport &rhs) const
+		{
+			return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h;
+		}
+	};
+
+	struct BlendState
+	{
+		GLenum srcRGB, srcA;
+		GLenum dstRGB, dstA;
+		GLenum func;
 	};
 
 	OpenGL();
@@ -99,6 +122,16 @@ public:
 	 **/
 	void prepareDraw();
 
+	/**
+	 * glDrawArraysInstanced with a pseudo-instancing fallback.
+	 **/
+	void drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount);
+
+	/**
+	 * glDrawElementsInstanced with a pseudo-instancing fallback.
+	 **/
+	void drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount);
+
 	/**
 	 * Sets the current constant color.
 	 **/
@@ -141,6 +174,17 @@ public:
 	 **/
 	Viewport getScissor() const;
 
+	/**
+	 * Sets blending functionality.
+	 * Note: This does not globally enable or disable blending.
+	 **/
+	void setBlendState(const BlendState &blend);
+
+	/**
+	 * Gets the currently set blending functionality.
+	 **/
+	BlendState getBlendState() const;
+
 	/**
 	 * Helper for setting the active texture unit.
 	 *
@@ -194,11 +238,21 @@ public:
 	 **/
 	int getMaxTextureSize() const;
 
+	/**
+	 * Returns the maximum supported number of simultaneous render targets.
+	 **/
+	int getMaxRenderTargets() const;
+
 	/**
 	 * Get the GPU vendor of this OpenGL context.
 	 **/
 	Vendor getVendor() const;
 
+	// Get human-readable strings for debug info.
+	static const char *debugSeverityString(GLenum severity);
+	static const char *debugSourceString(GLenum source);
+	static const char *debugTypeString(GLenum type);
+
 private:
 
 	void initVendor();
@@ -210,6 +264,7 @@ private:
 
 	float maxAnisotropy;
 	int maxTextureSize;
+	int maxRenderTargets;
 
 	Vendor vendor;
 
@@ -230,6 +285,11 @@ private:
 		Viewport viewport;
 		Viewport scissor;
 
+		BlendState blend;
+
+		// The last ID value used for pseudo-instancing.
+		int lastPseudoInstanceID;
+
 	} state;
 
 }; // OpenGL

+ 78 - 44
src/modules/graphics/opengl/ParticleSystem.cpp

@@ -94,6 +94,7 @@ ParticleSystem::ParticleSystem(Texture *texture, uint32 size)
 	, spinVariation(0)
 	, offsetX(float(texture->getWidth())*0.5f)
 	, offsetY(float(texture->getHeight())*0.5f)
+	, relativeRotation(false)
 {
 	if (size == 0 || size > MAX_PARTICLES)
 		throw love::Exception("Invalid ParticleSystem size.");
@@ -118,6 +119,7 @@ ParticleSystem::ParticleSystem(const ParticleSystem &p)
 	, emissionRate(p.emissionRate)
 	, emitCounter(0.0f)
 	, position(p.position)
+	, prevPosition(p.prevPosition)
 	, areaSpreadDistribution(p.areaSpreadDistribution)
 	, areaSpread(p.areaSpread)
 	, lifetime(p.lifetime)
@@ -144,6 +146,7 @@ ParticleSystem::ParticleSystem(const ParticleSystem &p)
 	, offsetX(p.offsetX)
 	, offsetY(p.offsetY)
 	, colors(p.colors)
+	, relativeRotation(p.relativeRotation)
 {
 	setBufferSize(maxParticles);
 
@@ -168,7 +171,7 @@ void ParticleSystem::createBuffers(size_t size)
 {
 	try
 	{
-		pFree = pMem = new particle[size];
+		pFree = pMem = new Particle[size];
 		particleVerts = new love::Vertex[size * 4];
 		maxParticles = (uint32) size;
 	}
@@ -205,14 +208,14 @@ uint32 ParticleSystem::getBufferSize() const
 	return maxParticles;
 }
 
-void ParticleSystem::addParticle()
+void ParticleSystem::addParticle(float t)
 {
 	if (isFull())
 		return;
 
 	// Gets a free particle and updates the allocation pointer.
-	particle *p = pFree++;
-	initParticle(p);
+	Particle *p = pFree++;
+	initParticle(p, t);
 
 	switch (insertMode)
 	{
@@ -231,10 +234,13 @@ void ParticleSystem::addParticle()
 	activeParticles++;
 }
 
-void ParticleSystem::initParticle(particle *p)
+void ParticleSystem::initParticle(Particle *p, float t)
 {
 	float min,max;
 
+	// Linearly interpolate between the previous and current emitter position.
+	love::Vector pos = prevPosition + (position - prevPosition) * t;
+
 	min = particleLifeMin;
 	max = particleLifeMax;
 	if (min == max)
@@ -243,8 +249,8 @@ void ParticleSystem::initParticle(particle *p)
 		p->life = (float) rng.random(min, max);
 	p->lifetime = p->life;
 
-	p->position[0] = position.getX();
-	p->position[1] = position.getY();
+	p->position[0] = pos.x;
+	p->position[1] = pos.y;
 
 	switch (areaSpreadDistribution)
 	{
@@ -265,7 +271,7 @@ void ParticleSystem::initParticle(particle *p)
 	max = direction + spread/2.0f;
 	p->direction = (float) rng.random(min, max);
 
-	p->origin = position;
+	p->origin = pos;
 
 	min = speedMin;
 	max = speedMax;
@@ -294,10 +300,14 @@ void ParticleSystem::initParticle(particle *p)
 	p->spinEnd = calculate_variation(spinEnd, spinStart, spinVariation);
 	p->rotation = (float) rng.random(min, max);
 
+	p->angle = p->rotation;
+	if (relativeRotation)
+		p->angle += atan2f(p->speed.y, p->speed.x);
+
 	p->color = colors[0];
 }
 
-void ParticleSystem::insertTop(particle *p)
+void ParticleSystem::insertTop(Particle *p)
 {
 	if (pHead == nullptr)
 	{
@@ -313,7 +323,7 @@ void ParticleSystem::insertTop(particle *p)
 	pTail = p;
 }
 
-void ParticleSystem::insertBottom(particle *p)
+void ParticleSystem::insertBottom(Particle *p)
 {
 	if (pTail == nullptr)
 	{
@@ -329,7 +339,7 @@ void ParticleSystem::insertBottom(particle *p)
 	pHead = p;
 }
 
-void ParticleSystem::insertRandom(particle *p)
+void ParticleSystem::insertRandom(Particle *p)
 {
 	// Nonuniform, but 64-bit is so large nobody will notice. Hopefully.
 	uint64 pos = rng.rand() % ((int64) activeParticles + 1);
@@ -337,7 +347,7 @@ void ParticleSystem::insertRandom(particle *p)
 	// Special case where the particle gets inserted before the head.
 	if (pos == activeParticles)
 	{
-		particle *pA = pHead;
+		Particle *pA = pHead;
 		if (pA)
 			pA->prev = p;
 		p->prev = nullptr;
@@ -347,8 +357,8 @@ void ParticleSystem::insertRandom(particle *p)
 	}
 
 	// Inserts the particle after the randomly selected particle.
-	particle *pA = pMem + pos;
-	particle *pB = pA->next;
+	Particle *pA = pMem + pos;
+	Particle *pB = pA->next;
 	pA->next = p;
 	if (pB)
 		pB->prev = p;
@@ -358,12 +368,12 @@ void ParticleSystem::insertRandom(particle *p)
 	p->next = pB;
 }
 
-ParticleSystem::particle *ParticleSystem::removeParticle(particle *p)
+ParticleSystem::Particle *ParticleSystem::removeParticle(Particle *p)
 {
 	// The linked list is updated in this function and old pointers may be
 	// invalidated. The returned pointer will inform the caller of the new
 	// pointer to the next particle.
-	particle *pNext = nullptr;
+	Particle *pNext = nullptr;
 
 	// Removes the particle from the linked list.
 	if (p->prev)
@@ -428,14 +438,14 @@ ParticleSystem::InsertMode ParticleSystem::getInsertMode() const
 	return insertMode;
 }
 
-void ParticleSystem::setEmissionRate(int rate)
+void ParticleSystem::setEmissionRate(float rate)
 {
-	if (rate < 0)
+	if (rate < 0.0f)
 		throw love::Exception("Invalid emission rate");
 	emissionRate = rate;
 }
 
-int ParticleSystem::getEmissionRate() const
+float ParticleSystem::getEmissionRate() const
 {
 	return emissionRate;
 }
@@ -470,6 +480,7 @@ void ParticleSystem::getParticleLifetime(float *min, float *max) const
 void ParticleSystem::setPosition(float x, float y)
 {
 	position = love::Vector(x, y);
+	prevPosition = position;
 }
 
 const love::Vector &ParticleSystem::getPosition() const
@@ -477,6 +488,11 @@ const love::Vector &ParticleSystem::getPosition() const
 	return position;
 }
 
+void ParticleSystem::moveTo(float x, float y)
+{
+	position = love::Vector(x, y);
+}
+
 void ParticleSystem::setAreaSpread(AreaSpreadDistribution distribution, float x, float y)
 {
 	areaSpread = love::Vector(x, y);
@@ -705,6 +721,16 @@ std::vector<Color> ParticleSystem::getColor() const
 	return ncolors;
 }
 
+void ParticleSystem::setRelativeRotation(bool enable)
+{
+	relativeRotation = enable;
+}
+
+bool ParticleSystem::hasRelativeRotation() const
+{
+	return relativeRotation;
+}
+
 uint32 ParticleSystem::getCount() const
 {
 	return activeParticles;
@@ -748,7 +774,7 @@ void ParticleSystem::emit(uint32 num)
 	num = std::min(num, maxParticles - activeParticles);
 
 	while(num--)
-		addParticle();
+		addParticle(1.0f);
 }
 
 bool ParticleSystem::isActive() const
@@ -776,7 +802,7 @@ bool ParticleSystem::isFull() const
 	return activeParticles == maxParticles;
 }
 
-void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
+void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	uint32 pCount = getCount();
 	if (pCount == 0 || texture == nullptr || pMem == nullptr || particleVerts == nullptr)
@@ -792,13 +818,13 @@ void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, flo
 
 	const Vertex *textureVerts = texture->getVertices();
 	Vertex *pVerts = particleVerts;
-	particle *p = pHead;
+	Particle *p = pHead;
 
 	// set the vertex data for each particle (transformation, texcoords, color)
 	while (p)
 	{
 		// particle vertices are image vertices transformed by particle information
-		t.setTransformation(p->position[0], p->position[1], p->rotation, p->size, p->size, offsetX, offsetY, 0.0f, 0.0f);
+		t.setTransformation(p->position[0], p->position[1], p->angle, p->size, p->size, offsetX, offsetY, 0.0f, 0.0f);
 		t.transform(pVerts, textureVerts, 4);
 
 		// set the texture coordinate and color data for particle vertices
@@ -847,27 +873,8 @@ void ParticleSystem::update(float dt)
 	if (pMem == nullptr || dt == 0.0f)
 		return;
 
-	// Make some more particles.
-	if (active)
-	{
-		float rate = 1.0f / emissionRate; // the amount of time between each particle emit
-		emitCounter += dt;
-		while (emitCounter > rate)
-		{
-			addParticle();
-			emitCounter -= rate;
-		}
-		/*int particles = (int)(emissionRate * dt);
-		for (int i = 0; i != particles; i++)
-			add();*/
-
-		life -= dt;
-		if (lifetime != -1 && life < 0)
-			stop();
-	}
-
 	// Traverse all particles and update.
-	particle *p = pHead;
+	Particle *p = pHead;
 
 	while (p)
 	{
@@ -912,7 +919,12 @@ void ParticleSystem::update(float dt)
 			const float t = 1.0f - p->life / p->lifetime;
 
 			// Rotate.
-			p->rotation += (p->spinStart * (1.0f - t) + p->spinEnd * t)*dt;
+			p->rotation += (p->spinStart * (1.0f - t) + p->spinEnd * t) * dt;
+
+			p->angle = p->rotation;
+
+			if (relativeRotation)
+				p->angle += atan2f(p->speed.y, p->speed.x);
 
 			// Change size according to given intervals:
 			// i = 0       1       2      3          n-1
@@ -940,6 +952,28 @@ void ParticleSystem::update(float dt)
 			p = p->next;
 		}
 	}
+
+	// Make some more particles.
+	if (active)
+	{
+		float rate = 1.0f / emissionRate; // the amount of time between each particle emit
+		emitCounter += dt;
+		float total = emitCounter - rate;
+		while (emitCounter > rate)
+		{
+			addParticle(1.0f - (emitCounter - rate) / total);
+			emitCounter -= rate;
+		}
+		/*int particles = (int)(emissionRate * dt);
+		 for (int i = 0; i != particles; i++)
+		 add();*/
+
+		life -= dt;
+		if (lifetime != -1 && life < 0)
+			stop();
+	}
+
+	prevPosition = position;
 }
 
 bool ParticleSystem::getConstant(const char *in, AreaSpreadDistribution &out)

+ 39 - 19
src/modules/graphics/opengl/ParticleSystem.h

@@ -65,7 +65,7 @@ public:
 		INSERT_MODE_TOP,
 		INSERT_MODE_BOTTOM,
 		INSERT_MODE_RANDOM,
-		INSERT_MODE_MAX_ENUM,
+		INSERT_MODE_MAX_ENUM
 	};
 
 	/**
@@ -131,12 +131,12 @@ public:
 	 * Sets the emission rate.
 	 * @param rate The amount of particles per second.
 	 **/
-	void setEmissionRate(int rate);
+	void setEmissionRate(float rate);
 
 	/**
 	 * Returns the number of particles created per second.
 	 **/
-	int getEmissionRate() const;
+	float getEmissionRate() const;
 
 	/**
 	 * Sets the lifetime of the particle emitter (-1 means eternal)
@@ -176,6 +176,15 @@ public:
 	 **/
 	const love::Vector &getPosition() const;
 
+	/**
+	 * Moves the position of the center of the emitter.
+	 * When update is called, newly spawned particles will appear in a line
+	 * between the old emitter position and where the emitter was moved to,
+	 * resulting in a smoother-feeling particle system if moveTo is called
+	 * repeatedly.
+	 **/
+	void moveTo(float x, float y);
+
 	/**
 	 * Sets the emission area spread parameters and distribution type. The interpretation of
 	 * the parameters depends on the distribution type:
@@ -412,6 +421,12 @@ public:
 	 **/
 	std::vector<Color> getColor() const;
 
+	/**
+	 * sets whether particle angles & rotations are relative to their velocities.
+	 **/
+	void setRelativeRotation(bool enable);
+	bool hasRelativeRotation() const;
+
 	/**
 	 * Returns the amount of particles that are currently active in the system.
 	 **/
@@ -470,7 +485,7 @@ public:
 	 * @param x The x-coordinate.
 	 * @param y The y-coordinate.
 	 **/
-	virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
+	virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 	/**
 	 * Updates the particle system.
@@ -485,11 +500,12 @@ public:
 	static bool getConstant(InsertMode in, const char *&out);
 
 protected:
+
 	// Represents a single particle.
-	struct particle
+	struct Particle
 	{
-		particle *prev;
-		particle *next;
+		Particle *prev;
+		Particle *next;
 
 		float lifetime;
 		float life;
@@ -509,7 +525,8 @@ protected:
 		float sizeOffset;
 		float sizeIntervalSize;
 
-		float rotation;
+		float rotation; // Amount of rotation applied to the final angle.
+		float angle;
 		float spinStart;
 		float spinEnd;
 
@@ -517,16 +534,16 @@ protected:
 	};
 
 	// Pointer to the beginning of the allocated memory.
-	particle *pMem;
+	Particle *pMem;
 
 	// Pointer to a free particle.
-	particle *pFree;
+	Particle *pFree;
 
 	// Pointer to the start of the linked list.
-	particle *pHead;
+	Particle *pHead;
 
 	// Pointer to the end of the linked list.
-	particle *pTail;
+	Particle *pTail;
 
 	// array of transformed vertex data for all particles, for drawing
 	Vertex *particleVerts;
@@ -547,13 +564,14 @@ protected:
 	uint32 activeParticles;
 
 	// The emission rate (particles/sec).
-	int emissionRate;
+	float emissionRate;
 
 	// Used to determine when a particle should be emitted.
 	float emitCounter;
 
 	// The relative position of the particle emitter.
 	love::Vector position;
+	love::Vector prevPosition;
 
 	// Emission area spread.
 	AreaSpreadDistribution areaSpreadDistribution;
@@ -607,17 +625,19 @@ protected:
 	// Color.
 	std::vector<Colorf> colors;
 
+	bool relativeRotation;
+
 	void createBuffers(size_t size);
 	void deleteBuffers();
 
-	void addParticle();
-	particle *removeParticle(particle *p);
+	void addParticle(float t);
+	Particle *removeParticle(Particle *p);
 
 	// Called by addParticle.
-	void initParticle(particle *p);
-	void insertTop(particle *p);
-	void insertBottom(particle *p);
-	void insertRandom(particle *p);
+	void initParticle(Particle *p, float t);
+	void insertTop(Particle *p);
+	void insertBottom(Particle *p);
+	void insertRandom(Particle *p);
 
 	static StringMap<AreaSpreadDistribution, DISTRIBUTION_MAX_ENUM>::Entry distributionsEntries[];
 	static StringMap<AreaSpreadDistribution, DISTRIBUTION_MAX_ENUM> distributions;

+ 52 - 9
src/modules/graphics/opengl/Shader.cpp

@@ -70,7 +70,9 @@ Shader::Shader(const ShaderSources &sources)
 	: shaderSources(sources)
 	, program(0)
 	, builtinUniforms()
+	, vertexAttributes()
 	, lastCanvas((Canvas *) -1)
+	, lastViewport()
 {
 	if (shaderSources.empty())
 		throw love::Exception("Cannot create shader: no source code!");
@@ -181,6 +183,14 @@ void Shader::createProgram(const std::vector<GLuint> &shaderids)
 	for (it = shaderids.begin(); it != shaderids.end(); ++it)
 		glAttachShader(program, *it);
 
+	// Bind generic vertex attribute indices to names in the shader.
+	for (int i = 0; i < int(OpenGL::ATTRIB_MAX_ENUM); i++)
+	{
+		const char *name = nullptr;
+		if (attribNames.find((OpenGL::VertexAttrib) i, name))
+			glBindAttribLocation(program, i, (const GLchar *) name);
+	}
+
 	glLinkProgram(program);
 
 	// flag shaders for auto-deletion when the program object is deleted.
@@ -273,6 +283,15 @@ bool Shader::loadVolatile()
 	// Retreive all active uniform variables in this shader from OpenGL.
 	mapActiveUniforms();
 
+	for (int i = 0; i < int(OpenGL::ATTRIB_MAX_ENUM); i++)
+	{
+		const char *name = nullptr;
+		if (attribNames.find(OpenGL::VertexAttrib(i), name))
+			vertexAttributes[i] = glGetAttribLocation(program, name);
+		else
+			vertexAttributes[i] = -1;
+	}
+
 	if (current == this)
 	{
 		// make sure glUseProgram gets called.
@@ -633,6 +652,11 @@ int Shader::getTextureUnit(const std::string &name)
 	return texunit;
 }
 
+bool Shader::hasVertexAttrib(OpenGL::VertexAttrib attrib) const
+{
+	return vertexAttributes[int(attrib)] != -1;
+}
+
 bool Shader::hasBuiltinExtern(BuiltinExtern builtin) const
 {
 	return builtinUniforms[int(builtin)] != -1;
@@ -670,30 +694,42 @@ bool Shader::sendBuiltinFloat(BuiltinExtern builtin, int size, const GLfloat *ve
 
 void Shader::checkSetScreenParams()
 {
-	if (lastCanvas == Canvas::current)
+	OpenGL::Viewport view = gl.getViewport();
+
+	if (view == lastViewport && lastCanvas == Canvas::current)
 		return;
 
-	// In the shader, we do pixcoord.y = gl_FragCoord.y * params[0] + params[1].
+	// In the shader, we do pixcoord.y = gl_FragCoord.y * params.z + params.w.
 	// This lets us flip pixcoord.y when needed, to be consistent (Canvases
 	// have flipped y-values for pixel coordinates.)
-	GLfloat params[] = {0.0f, 0.0f};
+	GLfloat params[] = {
+		(GLfloat) view.w, (GLfloat) view.h,
+		0.0f, 0.0f,
+	};
 
 	if (Canvas::current != nullptr)
 	{
 		// gl_FragCoord.y is flipped in Canvases, so we un-flip:
 		// pixcoord.y = gl_FragCoord.y * -1.0 + height.
-		params[0] = -1.0f;
-		params[1] = (float) Canvas::current->getHeight();
+		params[2] = -1.0f;
+		params[3] = (GLfloat) view.h;
 	}
 	else
 	{
 		// No flipping: pixcoord.y = gl_FragCoord.y * 1.0 + 0.0.
-		params[0] = 1.0f;
-		params[1] = 0.0f;
+		params[2] = 1.0f;
+		params[3] = 0.0f;
 	}
 
-	sendBuiltinFloat(BUILTIN_SCREEN_PARAMS, 2, params, 1);
+	sendBuiltinFloat(BUILTIN_SCREEN_SIZE, 4, params, 1);
+
 	lastCanvas = Canvas::current;
+	lastViewport = view;
+}
+
+const std::map<std::string, Object *> &Shader::getBoundRetainables() const
+{
+	return boundRetainables;
 }
 
 std::string Shader::getGLSLVersion()
@@ -730,9 +766,16 @@ StringMap<Shader::ShaderType, Shader::TYPE_MAX_ENUM>::Entry Shader::typeNameEntr
 
 StringMap<Shader::ShaderType, Shader::TYPE_MAX_ENUM> Shader::typeNames(Shader::typeNameEntries, sizeof(Shader::typeNameEntries));
 
+StringMap<OpenGL::VertexAttrib, OpenGL::ATTRIB_MAX_ENUM>::Entry Shader::attribNameEntries[] =
+{
+	{"love_PseudoInstanceID", OpenGL::ATTRIB_PSEUDO_INSTANCE_ID},
+};
+
+StringMap<OpenGL::VertexAttrib, OpenGL::ATTRIB_MAX_ENUM> Shader::attribNames(Shader::attribNameEntries, sizeof(Shader::attribNameEntries));
+
 StringMap<Shader::BuiltinExtern, Shader::BUILTIN_MAX_ENUM>::Entry Shader::builtinNameEntries[] =
 {
-	{"love_ScreenParams", Shader::BUILTIN_SCREEN_PARAMS},
+	{"love_ScreenSize", Shader::BUILTIN_SCREEN_SIZE},
 };
 
 StringMap<Shader::BuiltinExtern, Shader::BUILTIN_MAX_ENUM> Shader::builtinNames(Shader::builtinNameEntries, sizeof(Shader::builtinNameEntries));

+ 12 - 1
src/modules/graphics/opengl/Shader.h

@@ -59,7 +59,7 @@ public:
 	// Built-in extern (uniform) variables.
 	enum BuiltinExtern
 	{
-		BUILTIN_SCREEN_PARAMS,
+		BUILTIN_SCREEN_SIZE,
 		BUILTIN_MAX_ENUM
 	};
 
@@ -138,10 +138,13 @@ public:
 	/**
 	 * Internal use only.
 	 **/
+	bool hasVertexAttrib(OpenGL::VertexAttrib attrib) const;
 	bool hasBuiltinExtern(BuiltinExtern builtin) const;
 	bool sendBuiltinFloat(BuiltinExtern builtin, int size, const GLfloat *m, int count);
 	void checkSetScreenParams();
 
+	const std::map<std::string, Object *> &getBoundRetainables() const;
+
 	static std::string getGLSLVersion();
 	static bool isSupported();
 
@@ -198,6 +201,9 @@ private:
 	// Location values for any built-in uniform variables.
 	GLint builtinUniforms[BUILTIN_MAX_ENUM];
 
+	// Location values for any generic vertex attribute variables.
+	GLint vertexAttributes[OpenGL::ATTRIB_MAX_ENUM];
+
 	// Uniform location buffer map
 	std::map<std::string, Uniform> uniforms;
 
@@ -210,6 +216,7 @@ private:
 
 	// Pointer to the active Canvas when the screen params were last checked.
 	Canvas *lastCanvas;
+	OpenGL::Viewport lastViewport;
 
 	// Max GPU texture units available for sent images
 	static GLint maxTexUnits;
@@ -220,6 +227,10 @@ private:
 	static StringMap<ShaderType, TYPE_MAX_ENUM>::Entry typeNameEntries[];
 	static StringMap<ShaderType, TYPE_MAX_ENUM> typeNames;
 
+	// Names for the generic vertex attributes used by love.
+	static StringMap<OpenGL::VertexAttrib, OpenGL::ATTRIB_MAX_ENUM>::Entry attribNameEntries[];
+	static StringMap<OpenGL::VertexAttrib, OpenGL::ATTRIB_MAX_ENUM> attribNames;
+
 	// Names for the built-in uniform variables.
 	static StringMap<BuiltinExtern, BUILTIN_MAX_ENUM>::Entry builtinNameEntries[];
 	static StringMap<BuiltinExtern, BUILTIN_MAX_ENUM> builtinNames;

+ 5 - 1
src/modules/graphics/opengl/SpriteBatch.cpp

@@ -105,6 +105,8 @@ int SpriteBatch::add(float x, float y, float a, float sx, float sy, float ox, fl
 	if ((index == -1 && next >= size) || index < -1 || index >= size)
 		return -1;
 
+	Vertex sprite[4];
+
 	// Needed for colors.
 	memcpy(sprite, texture->getVertices(), sizeof(Vertex) * 4);
 
@@ -131,6 +133,8 @@ int SpriteBatch::addq(Quad *quad, float x, float y, float a, float sx, float sy,
 	if ((index == -1 && next >= size) || index < -1 || index >= next)
 		return -1;
 
+	Vertex sprite[4];
+
 	// Needed for colors.
 	memcpy(sprite, quad->getVertices(), sizeof(Vertex) * 4);
 
@@ -263,7 +267,7 @@ int SpriteBatch::getBufferSize() const
 	return size;
 }
 
-void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
+void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	const size_t vertex_offset = offsetof(Vertex, x);
 	const size_t texel_offset = offsetof(Vertex, s);

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

@@ -109,7 +109,7 @@ public:
 	int getBufferSize() const;
 
 	// Implements Drawable.
-	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
+	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 	static bool getConstant(const char *in, UsageHint &out);
 	static bool getConstant(UsageHint in, const char *&out);
@@ -135,8 +135,6 @@ private:
 	// The next free element.
 	int next;
 
-	Vertex sprite[4];
-
 	// Current color. This color, if present, will be applied to the next
 	// added sprite.
 	Color *color;

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

@@ -48,12 +48,12 @@ public:
 	 * Any setup the texture might need to do before drawing, e.g. binding
 	 * the OpenGL texture for use.
 	 **/
-	virtual void predraw() const {}
+	virtual void predraw() {}
 
 	/**
 	 * Any cleanup the texture might need to do directly after drawing.
 	 **/
-	virtual void postdraw() const {}
+	virtual void postdraw() {}
 
 
 }; // Texture

+ 25 - 13
src/modules/graphics/opengl/wrap_Canvas.cpp

@@ -34,22 +34,21 @@ Canvas *luax_checkcanvas(lua_State *L, int idx)
 
 int w_Canvas_renderTo(lua_State *L)
 {
-	// As startGrab() clears the framebuffer, better not allow
-	// grabbing inside another grabbing
-	if (Canvas::current != NULL)
-	{
-		Canvas::bindDefaultCanvas();
-		return luaL_error(L, "Current render target not the default canvas!");
-	}
-
 	Canvas *canvas = luax_checkcanvas(L, 1);
 	luaL_checktype(L, 2, LUA_TFUNCTION);
 
+	// Save the current Canvas so we can restore it when we're done.
+	Canvas *oldcanvas = Canvas::current;
+
 	EXCEPT_GUARD(canvas->startGrab();)
 
 	lua_settop(L, 2); // make sure the function is on top of the stack
 	lua_call(L, 0, 0);
-	canvas->stopGrab();
+
+	if (oldcanvas != nullptr)
+		oldcanvas->startGrab(oldcanvas->getAttachedCanvases());
+	else
+		Canvas::bindDefaultCanvas();
 
 	return 0;
 }
@@ -111,16 +110,25 @@ int w_Canvas_clear(lua_State *L)
 	return 0;
 }
 
-int w_Canvas_getType(lua_State *L)
+int w_Canvas_getFormat(lua_State *L)
 {
 	Canvas *canvas = luax_checkcanvas(L, 1);
-	Canvas::TextureType type = canvas->getTextureType();
+	Texture::Format format = canvas->getTextureFormat();
 	const char *str;
-	Canvas::getConstant(type, str);
+	if (!Texture::getConstant(format, str))
+		return luaL_error(L, "Unknown texture format.");
+
 	lua_pushstring(L, str);
 	return 1;
 }
 
+int w_Canvas_getFSAA(lua_State *L)
+{
+	Canvas *canvas = luax_checkcanvas(L, 1);
+	lua_pushinteger(L, canvas->getFSAA());
+	return 1;
+}
+
 static const luaL_Reg functions[] =
 {
 	// From wrap_Texture.
@@ -136,7 +144,11 @@ static const luaL_Reg functions[] =
 	{ "getImageData", w_Canvas_getImageData },
 	{ "getPixel", w_Canvas_getPixel },
 	{ "clear", w_Canvas_clear },
-	{ "getType", w_Canvas_getType },
+	{ "getFormat", w_Canvas_getFormat },
+	{ "getFSAA", w_Canvas_getFSAA },
+
+	// Deprecated since 0.9.1.
+	{ "getType", w_Canvas_getFormat },
 	{ 0, 0 }
 };
 

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

@@ -39,7 +39,8 @@ int w_Canvas_renderTo(lua_State *L);
 int w_Canvas_getImageData(lua_State *L);
 int w_Canvas_getPixel(lua_State * L);
 int w_Canvas_clear(lua_State *L);
-int w_Canvas_getType(lua_State *L);
+int w_Canvas_getFormat(lua_State *L);
+int w_Canvas_getFSAA(lua_State *L);
 extern "C" int luaopen_canvas(lua_State *L);
 
 } // opengl

+ 106 - 49
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -146,14 +146,23 @@ int w_setInvertedStencil(lua_State *L)
 
 int w_getMaxTextureSize(lua_State *L)
 {
-	lua_pushinteger(L, instance->getMaxTextureSize());
+	lua_pushinteger(L, instance->getSystemLimit(Graphics::LIMIT_TEXTURE_SIZE));
 	return 1;
 }
 
 int w_newImage(lua_State *L)
 {
-	love::image::ImageData *data = 0;
-	love::image::CompressedData *cdata = 0;
+	love::image::ImageData *data = nullptr;
+	love::image::CompressedData *cdata = nullptr;
+
+	Texture::Format format = Texture::FORMAT_NORMAL;
+	const char *fstr = lua_isnoneornil(L, 2) ? nullptr : luaL_checkstring(L, 2);
+
+	if (fstr != nullptr && !Texture::getConstant(fstr, format))
+		return luaL_error(L, "Invalid texture format: %s", fstr);
+
+	if (format == Texture::FORMAT_HDR) // For now...
+		return luaL_error(L, "HDR images are not supported.");
 
 	// Convert to FileData, if necessary.
 	if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_T))
@@ -185,15 +194,15 @@ int w_newImage(lua_State *L)
 		return luaL_error(L, "Error creating image.");
 
 	// Create the image.
-	Image *image = 0;
+	Image *image = nullptr;
 	EXCEPT_GUARD(
 		if (cdata)
-			image = instance->newImage(cdata);
+			image = instance->newImage(cdata, format);
 		else if (data)
-			image = instance->newImage(data);
+			image = instance->newImage(data, format);
 	)
 
-	if (image == 0)
+	if (image == nullptr)
 		return luaL_error(L, "Could not load image.");
 
 	// Push the type.
@@ -323,15 +332,16 @@ int w_newCanvas(lua_State *L)
 	int width       = luaL_optint(L, 1, instance->getWidth());
 	int height      = luaL_optint(L, 2, instance->getHeight());
 	const char *str = luaL_optstring(L, 3, "normal");
+	int fsaa        = luaL_optint(L, 4, 0);
 
-	Canvas::TextureType texture_type;
-	if (!Canvas::getConstant(str, texture_type))
-		return luaL_error(L, "Invalid canvas type: %s", str);
+	Texture::Format format;
+	if (!Texture::getConstant(str, format))
+		return luaL_error(L, "Invalid texture format: %s", str);
 
-	Canvas *canvas = 0;
-	EXCEPT_GUARD(canvas = instance->newCanvas(width, height, texture_type);)
+	Canvas *canvas = nullptr;
+	EXCEPT_GUARD(canvas = instance->newCanvas(width, height, format, fsaa);)
 
-	if (canvas == 0)
+	if (canvas == nullptr)
 		return luaL_error(L, "Canvas not created, but no error thrown. I don't even...");
 
 	luax_pushtype(L, "Canvas", GRAPHICS_CANVAS_T, canvas);
@@ -440,8 +450,10 @@ int w_newShader(lua_State *L)
 
 int w_newMesh(lua_State *L)
 {
-	// Check first argument: mandatory table of vertices.
-	luaL_checktype(L, 1, LUA_TTABLE);
+	// Check first argument: table of vertices or number of vertices.
+	int ttype = lua_type(L, 1);
+	if (ttype != LUA_TTABLE && ttype != LUA_TNUMBER)
+		luaL_argerror(L, 1, "table or number expected");
 
 	// Second argument: optional texture.
 	Texture *tex = nullptr;
@@ -456,52 +468,60 @@ int w_newMesh(lua_State *L)
 	if (str && !Mesh::getConstant(str, mode))
 		return luaL_error(L, "Invalid mesh draw mode: %s", str);
 
-	size_t vertex_count = lua_objlen(L, 1);
-	std::vector<Vertex> vertices;
-	vertices.reserve(vertex_count);
-
-	bool use_colors = false;
+	Mesh *t = nullptr;
 
-	// Get the vertices from the table.
-	for (size_t i = 1; i <= vertex_count; i++)
+	if (ttype == LUA_TTABLE)
 	{
-		lua_rawgeti(L, 1, i);
+		size_t vertex_count = lua_objlen(L, 1);
+		std::vector<Vertex> vertices;
+		vertices.reserve(vertex_count);
 
-		if (lua_type(L, -1) != LUA_TTABLE)
-			return luax_typerror(L, 1, "table of tables");
+		bool use_colors = false;
 
-		for (int j = 1; j <= 8; j++)
-			lua_rawgeti(L, -j, j);
+		// Get the vertices from the table.
+		for (size_t i = 1; i <= vertex_count; i++)
+		{
+			lua_rawgeti(L, 1, i);
 
-		Vertex v;
+			if (lua_type(L, -1) != LUA_TTABLE)
+				return luax_typerror(L, 1, "table of tables");
 
-		v.x = (float) luaL_checknumber(L, -8);
-		v.y = (float) luaL_checknumber(L, -7);
+			for (int j = 1; j <= 8; j++)
+				lua_rawgeti(L, -j, j);
 
-		v.s = (float) luaL_checknumber(L, -6);
-		v.t = (float) luaL_checknumber(L, -5);
+			Vertex v;
 
-		v.r = (unsigned char) luaL_optinteger(L, -4, 255);
-		v.g = (unsigned char) luaL_optinteger(L, -3, 255);
-		v.b = (unsigned char) luaL_optinteger(L, -2, 255);
-		v.a = (unsigned char) luaL_optinteger(L, -1, 255);
+			v.x = (float) luaL_checknumber(L, -8);
+			v.y = (float) luaL_checknumber(L, -7);
 
-		// Enable per-vertex coloring if any color is not the default.
-		if (!use_colors && (v.r != 255 || v.g != 255 || v.b != 255 || v.a != 255))
-			use_colors = true;
+			v.s = (float) luaL_optnumber(L, -6, 0.0);
+			v.t = (float) luaL_optnumber(L, -5, 0.0);
 
-		lua_pop(L, 9);
-		vertices.push_back(v);
-	}
+			v.r = (unsigned char) luaL_optinteger(L, -4, 255);
+			v.g = (unsigned char) luaL_optinteger(L, -3, 255);
+			v.b = (unsigned char) luaL_optinteger(L, -2, 255);
+			v.a = (unsigned char) luaL_optinteger(L, -1, 255);
 
-	Mesh *t = nullptr;
-	EXCEPT_GUARD(t = instance->newMesh(vertices, mode);)
+			// Enable per-vertex coloring if any color is not the default.
+			if (!use_colors && (v.r != 255 || v.g != 255 || v.b != 255 || v.a != 255))
+				use_colors = true;
+
+			lua_pop(L, 9);
+			vertices.push_back(v);
+		}
+
+		EXCEPT_GUARD(t = instance->newMesh(vertices, mode);)
+		t->setVertexColors(use_colors);
+	}
+	else
+	{
+		int count = luaL_checkint(L, 1);
+		EXCEPT_GUARD(t = instance->newMesh(count, mode);)
+	}
 
 	if (tex)
 		t->setTexture(tex);
 
-	t->setVertexColors(use_colors);
-
 	luax_pushtype(L, "Mesh", GRAPHICS_MESH_T, t);
 	return 1;
 }
@@ -798,7 +818,19 @@ int w_getPointSize(lua_State *L)
 
 int w_getMaxPointSize(lua_State *L)
 {
-	lua_pushnumber(L, instance->getMaxPointSize());
+	lua_pushnumber(L, instance->getSystemLimit(Graphics::LIMIT_POINT_SIZE));
+	return 1;
+}
+
+int w_setWireframe(lua_State *L)
+{
+	instance->setWireframe(luax_toboolean(L, 1));
+	return 0;
+}
+
+int w_isWireframe(lua_State *L)
+{
+	luax_pushboolean(L, instance->isWireframe());
 	return 1;
 }
 
@@ -962,6 +994,14 @@ int w_isSupported(lua_State *L)
 			if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_BC5))
 				supported = false;
 			break;
+		case Graphics::SUPPORT_INSTANCING:
+			if (!GLEE_ARB_draw_instanced)
+				supported = false;
+			break;
+		case Graphics::SUPPORT_SRGB:
+			if (!Canvas::isSRGBSupported())
+				supported = false;
+			break;
 		default:
 			supported = false;
 		}
@@ -989,6 +1029,18 @@ int w_getRendererInfo(lua_State *L)
 	return 4;
 }
 
+int w_getSystemLimit(lua_State *L)
+{
+	const char *limitstr = luaL_checkstring(L, 1);
+	Graphics::SystemLimit limittype;
+
+	if (!Graphics::getConstant(limitstr, limittype))
+		return luaL_error(L, "Invalid system limit type: %s", limitstr);
+
+	lua_pushnumber(L, instance->getSystemLimit(limittype));
+	return 1;
+}
+
 int w_draw(lua_State *L)
 {
 	Drawable *drawable = nullptr;
@@ -1100,8 +1152,9 @@ int w_line(lua_State *L)
 		args = lua_objlen(L, 1);
 		is_table = true;
 	}
+
 	if (args % 2 != 0)
-		return luaL_error(L, "Number of vertices must be a multiple of two");
+		return luaL_error(L, "Number of vertex components must be a multiple of two");
 	else if (args < 4)
 		return luaL_error(L, "Need at least two vertices to draw a line");
 
@@ -1202,7 +1255,7 @@ int w_polygon(lua_State *L)
 	}
 
 	if (args % 2 != 0)
-		return luaL_error(L, "Number of vertices must be a multiple of two");
+		return luaL_error(L, "Number of vertex components must be a multiple of two");
 	else if (args < 6)
 		return luaL_error(L, "Need at least three vertices to draw a polygon");
 
@@ -1325,6 +1378,8 @@ static const luaL_Reg functions[] =
 	{ "getPointSize", w_getPointSize },
 	{ "getMaxPointSize", w_getMaxPointSize },
 	{ "getMaxTextureSize", w_getMaxTextureSize },
+	{ "setWireframe", w_setWireframe },
+	{ "isWireframe", w_isWireframe },
 	{ "newScreenshot", w_newScreenshot },
 	{ "setCanvas", w_setCanvas },
 	{ "getCanvas", w_getCanvas },
@@ -1334,6 +1389,7 @@ static const luaL_Reg functions[] =
 
 	{ "isSupported", w_isSupported },
 	{ "getRendererInfo", w_getRendererInfo },
+	{ "getSystemLimit", w_getSystemLimit },
 
 	{ "draw", w_draw },
 
@@ -1369,6 +1425,7 @@ static const luaL_Reg functions[] =
 
 	// Deprecated since 0.9.1.
 	{ "getMaxImageSize", w_getMaxTextureSize },
+	{ "getMaxPointSize", w_getMaxPointSize },
 
 	{ 0, 0 }
 };

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

@@ -83,6 +83,8 @@ int w_getLineJoin(lua_State *L);
 int w_setPointSize(lua_State *L);
 int w_getPointSize(lua_State *L);
 int w_getMaxPointSize(lua_State *L);
+int w_setWireframe(lua_State *L);
+int w_isWireframe(lua_State *L);
 int w_newScreenshot(lua_State *L);
 int w_setCanvas(lua_State *L);
 int w_getCanvas(lua_State *L);
@@ -90,6 +92,7 @@ int w_setShader(lua_State *L);
 int w_getShader(lua_State *L);
 int w_isSupported(lua_State *L);
 int w_getRendererInfo(lua_State *L);
+int w_getSystemLimit(lua_State *L);
 int w_draw(lua_State *L);
 int w_print(lua_State *L);
 int w_printf(lua_State *L);

+ 57 - 22
src/modules/graphics/opengl/wrap_Mesh.cpp

@@ -53,8 +53,8 @@ int w_Mesh_setVertex(lua_State *L)
 
 		v.x = luaL_checknumber(L, -8);
 		v.y = luaL_checknumber(L, -7);
-		v.s = luaL_checknumber(L, -6);
-		v.t = luaL_checknumber(L, -5);
+		v.s = luaL_optnumber(L, -6, 0.0);
+		v.t = luaL_optnumber(L, -5, 0.0);
 		v.r = luaL_optinteger(L, -4, 255);
 		v.g = luaL_optinteger(L, -3, 255);
 		v.b = luaL_optinteger(L, -2, 255);
@@ -66,8 +66,8 @@ int w_Mesh_setVertex(lua_State *L)
 	{
 		v.x = luaL_checknumber(L, 3);
 		v.y = luaL_checknumber(L, 4);
-		v.s = luaL_checknumber(L, 5);
-		v.t = luaL_checknumber(L, 6);
+		v.s = luaL_optnumber(L, 5, 0.0);
+		v.t = luaL_optnumber(L, 6, 0.0);
 		v.r = luaL_optinteger(L,  7, 255);
 		v.g = luaL_optinteger(L,  8, 255);
 		v.b = luaL_optinteger(L,  9, 255);
@@ -122,8 +122,8 @@ int w_Mesh_setVertices(lua_State *L)
 		v.x = (float) luaL_checknumber(L, -8);
 		v.y = (float) luaL_checknumber(L, -7);
 
-		v.s = (float) luaL_checknumber(L, -6);
-		v.t = (float) luaL_checknumber(L, -5);
+		v.s = (float) luaL_optnumber(L, -6, 0.0);
+		v.t = (float) luaL_optnumber(L, -5, 0.0);
 
 		v.r = (unsigned char) luaL_optinteger(L, -4, 255);
 		v.g = (unsigned char) luaL_optinteger(L, -3, 255);
@@ -220,14 +220,15 @@ int w_Mesh_setVertexMap(lua_State *L)
 int w_Mesh_getVertexMap(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
-	const uint32 *vertex_map = 0;
 
-	EXCEPT_GUARD(vertex_map = t->getVertexMap();)
-	size_t elements = t->getVertexMapCount();
+	std::vector<uint32> vertex_map;
+	EXCEPT_GUARD(t->getVertexMap(vertex_map);)
 
-	lua_createtable(L, elements, 0);
+	size_t element_count = vertex_map.size();
 
-	for (size_t i = 0; i < elements; i++)
+	lua_createtable(L, element_count, 0);
+
+	for (size_t i = 0; i < element_count; i++)
 	{
 		lua_pushinteger(L, lua_Integer(vertex_map[i]) + 1);
 		lua_rawseti(L, -2, i + 1);
@@ -236,6 +237,20 @@ int w_Mesh_getVertexMap(lua_State *L)
 	return 1;
 }
 
+int w_Mesh_setInstanceCount(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	t->setInstanceCount(luaL_checkint(L, 2));
+	return 0;
+}
+
+int w_Mesh_getInstanceCount(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	lua_pushinteger(L, t->getInstanceCount());
+	return 1;
+}
+
 int w_Mesh_setTexture(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
@@ -301,31 +316,49 @@ int w_Mesh_getDrawMode(lua_State *L)
 	return 1;
 }
 
-int w_Mesh_setVertexColors(lua_State *L)
+int w_Mesh_setDrawRange(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
-	t->setVertexColors(luax_toboolean(L, 2));
+
+	if (lua_isnoneornil(L, 2))
+		t->setDrawRange();
+	else
+	{
+		int rangemin = luaL_checkint(L, 2) - 1;
+		int rangemax = luaL_checkint(L, 3) - 1;
+		EXCEPT_GUARD(t->setDrawRange(rangemin, rangemax);)
+	}
+
 	return 0;
 }
 
-int w_Mesh_hasVertexColors(lua_State *L)
+int w_Mesh_getDrawRange(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
-	luax_pushboolean(L, t->hasVertexColors());
-	return 1;
+
+	int rangemin = -1;
+	int rangemax = -1;
+	t->getDrawRange(rangemin, rangemax);
+
+	if (rangemin < 0 || rangemax < 0)
+		return 0;
+
+	lua_pushinteger(L, rangemin + 1);
+	lua_pushinteger(L, rangemax + 1);
+	return 2;
 }
 
-int w_Mesh_setWireframe(lua_State *L)
+int w_Mesh_setVertexColors(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
-	t->setWireframe(luax_toboolean(L, 2));
+	t->setVertexColors(luax_toboolean(L, 2));
 	return 0;
 }
 
-int w_Mesh_isWireframe(lua_State *L)
+int w_Mesh_hasVertexColors(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
-	luax_pushboolean(L, t->isWireframe());
+	luax_pushboolean(L, t->hasVertexColors());
 	return 1;
 }
 
@@ -338,14 +371,16 @@ static const luaL_Reg functions[] =
 	{ "getVertexCount", w_Mesh_getVertexCount },
 	{ "setVertexMap", w_Mesh_setVertexMap },
 	{ "getVertexMap", w_Mesh_getVertexMap },
+	{ "setInstanceCount", w_Mesh_setInstanceCount },
+	{ "getInstanceCount", w_Mesh_getInstanceCount },
 	{ "setTexture", w_Mesh_setTexture },
 	{ "getTexture", w_Mesh_getTexture },
 	{ "setDrawMode", w_Mesh_setDrawMode },
 	{ "getDrawMode", w_Mesh_getDrawMode },
+	{ "setDrawRange", w_Mesh_setDrawRange },
+	{ "getDrawRange", w_Mesh_getDrawRange },
 	{ "setVertexColors", w_Mesh_setVertexColors },
 	{ "hasVertexColors", w_Mesh_hasVertexColors },
-	{ "setWireframe", w_Mesh_setWireframe },
-	{ "isWireframe", w_Mesh_isWireframe },
 
 	// Deprecated since 0.9.1.
 	{ "setImage", w_Mesh_setTexture },

+ 4 - 2
src/modules/graphics/opengl/wrap_Mesh.h

@@ -41,14 +41,16 @@ int w_Mesh_getVertices(lua_State *L);
 int w_Mesh_getVertexCount(lua_State *L);
 int w_Mesh_setVertexMap(lua_State *L);
 int w_Mesh_getVertexMap(lua_State *L);
+int w_Mesh_setInstanceCount(lua_State *L);
+int w_Mesh_getInstanceCount(lua_State *L);
 int w_Mesh_setTexture(lua_State *L);
 int w_Mesh_getTexture(lua_State *L);
 int w_Mesh_setDrawMode(lua_State *L);
 int w_Mesh_getDrawMode(lua_State *L);
+int w_Mesh_setDrawRange(lua_State *L);
+int w_Mesh_getDrawRange(lua_State *L);
 int w_Mesh_setVertexColors(lua_State *L);
 int w_Mesh_hasVertexColors(lua_State *L);
-int w_Mesh_setWireframe(lua_State *L);
-int w_Mesh_isWireframe(lua_State *L);
 
 extern "C" int luaopen_mesh(lua_State *L);
 

+ 28 - 2
src/modules/graphics/opengl/wrap_ParticleSystem.cpp

@@ -127,7 +127,7 @@ int w_ParticleSystem_getInsertMode(lua_State *L)
 int w_ParticleSystem_setEmissionRate(lua_State *L)
 {
 	ParticleSystem *t = luax_checkparticlesystem(L, 1);
-	int arg1 = luaL_checkint(L, 2);
+	float arg1 = (float) luaL_checknumber(L, 2);
 	EXCEPT_GUARD(t->setEmissionRate(arg1);)
 	return 0;
 }
@@ -135,7 +135,7 @@ int w_ParticleSystem_setEmissionRate(lua_State *L)
 int w_ParticleSystem_getEmissionRate(lua_State *L)
 {
 	ParticleSystem *t = luax_checkparticlesystem(L, 1);
-	lua_pushinteger(L, t->getEmissionRate());
+	lua_pushnumber(L, t->getEmissionRate());
 	return 1;
 }
 
@@ -191,6 +191,15 @@ int w_ParticleSystem_getPosition(lua_State *L)
 	return 2;
 }
 
+int w_ParticleSystem_moveTo(lua_State *L)
+{
+	ParticleSystem *t = luax_checkparticlesystem(L, 1);
+	float arg1 = (float)luaL_checknumber(L, 2);
+	float arg2 = (float)luaL_checknumber(L, 3);
+	t->moveTo(arg1, arg2);
+	return 0;
+}
+
 int w_ParticleSystem_setAreaSpread(lua_State *L)
 {
 	ParticleSystem *t = luax_checkparticlesystem(L, 1);
@@ -560,6 +569,20 @@ int w_ParticleSystem_getColors(lua_State *L)
 	return colors.size();
 }
 
+int w_ParticleSystem_setRelativeRotation(lua_State *L)
+{
+	ParticleSystem *t = luax_checkparticlesystem(L, 1);
+	t->setRelativeRotation(luax_toboolean(L, 2));
+	return 0;
+}
+
+int w_ParticleSystem_hasRelativeRotation(lua_State *L)
+{
+	ParticleSystem *t = luax_checkparticlesystem(L, 1);
+	luax_pushboolean(L, t->hasRelativeRotation());
+	return 1;
+}
+
 int w_ParticleSystem_getCount(lua_State *L)
 {
 	ParticleSystem *t = luax_checkparticlesystem(L, 1);
@@ -649,6 +672,7 @@ static const luaL_Reg functions[] =
 	{ "getParticleLifetime", w_ParticleSystem_getParticleLifetime },
 	{ "setPosition", w_ParticleSystem_setPosition },
 	{ "getPosition", w_ParticleSystem_getPosition },
+	{ "moveTo", w_ParticleSystem_moveTo },
 	{ "setAreaSpread", w_ParticleSystem_setAreaSpread },
 	{ "getAreaSpread", w_ParticleSystem_getAreaSpread },
 	{ "setDirection", w_ParticleSystem_setDirection },
@@ -677,6 +701,8 @@ static const luaL_Reg functions[] =
 	{ "getColors", w_ParticleSystem_getColors },
 	{ "setOffset", w_ParticleSystem_setOffset },
 	{ "getOffset", w_ParticleSystem_getOffset },
+	{ "setRelativeRotation", w_ParticleSystem_setRelativeRotation },
+	{ "hasRelativeRotation", w_ParticleSystem_hasRelativeRotation },
 	{ "getCount", w_ParticleSystem_getCount },
 	{ "start", w_ParticleSystem_start },
 	{ "stop", w_ParticleSystem_stop },

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

@@ -48,6 +48,7 @@ int w_ParticleSystem_setParticleLifetime(lua_State *L);
 int w_ParticleSystem_getParticleLifetime(lua_State *L);
 int w_ParticleSystem_setPosition(lua_State *L);
 int w_ParticleSystem_getPosition(lua_State *L);
+int w_ParticleSystem_moveTo(lua_State *L);
 int w_ParticleSystem_setAreaSpread(lua_State *L);
 int w_ParticleSystem_getAreaSpread(lua_State *L);
 int w_ParticleSystem_setDirection(lua_State *L);
@@ -76,6 +77,8 @@ int w_ParticleSystem_setColors(lua_State *L);
 int w_ParticleSystem_getColors(lua_State *L);
 int w_ParticleSystem_setOffset(lua_State *L);
 int w_ParticleSystem_getOffset(lua_State *L);
+int w_ParticleSystem_setRelativeRotation(lua_State *L);
+int w_ParticleSystem_hasRelativeRotation(lua_State *L);
 int w_ParticleSystem_getCount(lua_State *L);
 int w_ParticleSystem_start(lua_State *L);
 int w_ParticleSystem_stop(lua_State *L);

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

@@ -47,7 +47,7 @@ public:
 	/**
 	 * Destructor.
 	 **/
-	virtual ~Image() {};
+	virtual ~Image() {}
 
 	/**
 	 * Creates new ImageData from FileData.
@@ -93,4 +93,4 @@ public:
 } // image
 } // love
 
-#endif // LOVE_IMAGE_IMAGE_H
+#endif // LOVE_IMAGE_IMAGE_H

+ 14 - 18
src/modules/image/magpie/DevilHandler.cpp

@@ -23,15 +23,10 @@
 // LOVE
 #include "common/Exception.h"
 #include "common/math.h"
-#include "thread/threads.h"
 
 // DevIL
 #include <IL/il.h>
 
-using love::thread::Lock;
-
-static Mutex *devilMutex = 0;
-
 namespace love
 {
 namespace image
@@ -44,21 +39,22 @@ static inline void ilxClearErrors()
 	while (ilGetError() != IL_NO_ERROR);
 }
 
-void DevilHandler::init()
+DevilHandler::DevilHandler()
+	: mutex(nullptr)
 {
+	// There should only ever be one DevilHandler object (owned by the Image
+	// module), so we can use the global initialization function here.
 	ilInit();
 	ilEnable(IL_ORIGIN_SET);
 	ilOriginFunc(IL_ORIGIN_UPPER_LEFT);
 }
 
-void DevilHandler::quit()
+DevilHandler::~DevilHandler()
 {
 	ilShutDown();
-	if (devilMutex)
-	{
-		delete devilMutex;
-		devilMutex = 0;
-	}
+
+	if (mutex)
+		delete mutex;
 }
 
 bool DevilHandler::canDecode(love::filesystem::FileData * /*data*/)
@@ -85,10 +81,10 @@ bool DevilHandler::canEncode(ImageData::Format format)
 
 DevilHandler::DecodedImage DevilHandler::decode(love::filesystem::FileData *data)
 {
-	if (!devilMutex)
-		devilMutex = thread::newMutex();
+	if (!mutex)
+		mutex = love::thread::newMutex();
 
-	Lock lock(devilMutex);
+	love::thread::Lock lock(mutex);
 
 	ILuint image = ilGenImage();
 	ilBindImage(image);
@@ -140,10 +136,10 @@ DevilHandler::DecodedImage DevilHandler::decode(love::filesystem::FileData *data
 
 DevilHandler::EncodedImage DevilHandler::encode(const DecodedImage &img, ImageData::Format format)
 {
-	if (!devilMutex)
-		devilMutex = thread::newMutex();
+	if (!mutex)
+		mutex = love::thread::newMutex();
 
-	Lock lock(devilMutex);
+	love::thread::Lock lock(mutex);
 
 	ILuint tempimage = ilGenImage();
 	ilBindImage(tempimage);

+ 12 - 7
src/modules/image/magpie/DevilHandler.h

@@ -24,6 +24,7 @@
 // LOVE
 #include "filesystem/FileData.h"
 #include "FormatHandler.h"
+#include "thread/threads.h"
 
 namespace love
 {
@@ -39,16 +40,20 @@ class DevilHandler : public FormatHandler
 {
 public:
 
-	static void init();
-	static void quit();
-
 	// Implements FormatHandler.
 
-	static bool canDecode(love::filesystem::FileData *data);
-	static bool canEncode(ImageData::Format format);
+	DevilHandler();
+	virtual ~DevilHandler();
+
+	virtual bool canDecode(love::filesystem::FileData *data);
+	virtual bool canEncode(ImageData::Format format);
+
+	virtual DecodedImage decode(love::filesystem::FileData *data);
+	virtual EncodedImage encode(const DecodedImage &img, ImageData::Format format);
+
+private:
 
-	static DecodedImage decode(love::filesystem::FileData *data);
-	static EncodedImage encode(const DecodedImage &img, ImageData::Format format);
+	Mutex *mutex;
 
 }; // DevilHandler
 

+ 62 - 0
src/modules/image/magpie/FormatHandler.cpp

@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2006-2014 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+// LOVE
+#include "FormatHandler.h"
+#include "common/Exception.h"
+
+namespace love
+{
+namespace image
+{
+namespace magpie
+{
+
+FormatHandler::FormatHandler()
+{
+}
+
+FormatHandler::~FormatHandler()
+{
+}
+
+bool FormatHandler::canDecode(love::filesystem::FileData* /*data*/)
+{
+	return false;
+}
+
+bool FormatHandler::canEncode(ImageData::Format /*format*/)
+{
+	return false;
+}
+
+FormatHandler::DecodedImage FormatHandler::decode(love::filesystem::FileData* /*data*/)
+{
+	throw love::Exception("Image decoding is not implemented for this format backend.");
+}
+
+FormatHandler::EncodedImage FormatHandler::encode(const DecodedImage& /*img*/, ImageData::Format /*format*/)
+{
+	throw love::Exception("Image encoding is not implemented for this format backend.");
+}
+
+} // magpie
+} // image
+} // love

+ 18 - 10
src/modules/image/magpie/FormatHandler.h

@@ -24,6 +24,7 @@
 // LOVE
 #include "image/ImageData.h"
 #include "filesystem/FileData.h"
+#include "common/Object.h"
 
 namespace love
 {
@@ -34,8 +35,9 @@ namespace magpie
 
 /**
  * Base class for all ImageData encoder/decoder library interfaces.
+ * We inherit from love::Object to take advantage of reference counting...
  **/
-class FormatHandler
+class FormatHandler : public love::Object
 {
 public:
 
@@ -56,26 +58,32 @@ public:
 		EncodedImage() : size(0), data(0) {}
 	};
 
-	// Lets pretend we have virtual static methods...
+	/**
+	 * The default constructor is called when the Image module is initialized.
+	 **/
+	FormatHandler();
 
 	/**
-	 * Determines whether a particular FileData can be decoded by this handler.
-	 * @param data The data to decode.
+	 * The destructor is called when the Image module is uninitialized.
 	 **/
-	// virtual static bool canDecode(love::filesystem::FileData *data) = 0;
+	virtual ~FormatHandler();
 
 	/**
-	 * Determines whether this handler can encode to a particular format.
-	 * @param format The format to encode to.
+	 * Whether this format handler can decode a particular FileData.
+	 **/
+	virtual bool canDecode(love::filesystem::FileData *data);
+
+	/**
+	 * Whether this format handler can encode to a particular format.
 	 **/
-	// virtual static bool canEncode(ImageData::Format format) = 0;
+	virtual bool canEncode(ImageData::Format format);
 
 	/**
 	 * Decodes an image from its encoded form into raw pixel data.
 	 * @param data The encoded data to decode.
 	 * @return The decoded pixel data.
 	 **/
-	// virtual static DecodedImage decode(love::filesystem::FileData *data) = 0;
+	virtual DecodedImage decode(love::filesystem::FileData *data);
 
 	/**
 	 * Encodes an image from raw pixel data into a particular format.
@@ -83,7 +91,7 @@ public:
 	 * @param format The format to encode to.
 	 * @return The encoded image data.
 	 **/
-	// virtual static EncodedImage encode(const DecodedImage &img, ImageData::Format format) = 0;
+	virtual EncodedImage encode(const DecodedImage &img, ImageData::Format format);
 
 }; // FormatHandler
 

+ 8 - 5
src/modules/image/magpie/Image.cpp

@@ -34,12 +34,15 @@ namespace magpie
 
 Image::Image()
 {
-	DevilHandler::init();
+	formatHandlers.push_back(new DevilHandler);
 }
 
 Image::~Image()
 {
-	DevilHandler::quit();
+	// ImageData objects reference the FormatHandlers in our list, so we should
+	// release them instead of deleting them completely here.
+	for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it)
+		(*it)->release();
 }
 
 const char *Image::getName() const
@@ -49,17 +52,17 @@ const char *Image::getName() const
 
 love::image::ImageData *Image::newImageData(love::filesystem::FileData *data)
 {
-	return new ImageData(data);
+	return new ImageData(formatHandlers, data);
 }
 
 love::image::ImageData *Image::newImageData(int width, int height)
 {
-	return new ImageData(width, height);
+	return new ImageData(formatHandlers, width, height);
 }
 
 love::image::ImageData *Image::newImageData(int width, int height, void *data, bool own)
 {
-	return new ImageData(width, height, data, own);
+	return new ImageData(formatHandlers, width, height, data, own);
 }
 
 love::image::CompressedData *Image::newCompressedData(love::filesystem::FileData *data)

+ 9 - 0
src/modules/image/magpie/Image.h

@@ -23,6 +23,10 @@
 
 // LOVE
 #include "image/Image.h"
+#include "FormatHandler.h"
+
+// C++
+#include <list>
 
 namespace love
 {
@@ -54,6 +58,11 @@ public:
 
 	bool isCompressed(love::filesystem::FileData *data);
 
+private:
+
+	// Image format handlers we can use for decoding and encoding ImageData.
+	std::list<FormatHandler *> formatHandlers;
+
 }; // Image
 
 } // magpie

+ 41 - 14
src/modules/image/magpie/ImageData.cpp

@@ -18,11 +18,9 @@
  * 3. This notice may not be removed or altered from any source distribution.
  **/
 
+// LOVE
 #include "ImageData.h"
 
-#include "FormatHandler.h"
-#include "DevilHandler.h"
-
 namespace love
 {
 namespace image
@@ -30,23 +28,36 @@ namespace image
 namespace magpie
 {
 
-ImageData::ImageData(love::filesystem::FileData *data)
+ImageData::ImageData(std::list<FormatHandler *> formats, love::filesystem::FileData *data)
+	: formatHandlers(formats)
 {
+	for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it)
+		(*it)->retain();
+
 	decode(data);
 }
 
-ImageData::ImageData(int width, int height)
+ImageData::ImageData(std::list<FormatHandler *> formats, int width, int height)
+	: formatHandlers(formats)
 {
+	for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it)
+		(*it)->retain();
+
 	this->width = width;
 	this->height = height;
+
 	create(width, height);
 
 	// Set to black/transparency.
 	memset(data, 0, width*height*sizeof(pixel));
 }
 
-ImageData::ImageData(int width, int height, void *data, bool own)
+ImageData::ImageData(std::list<FormatHandler *> formats, int width, int height, void *data, bool own)
+	: formatHandlers(formats)
 {
+	for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it)
+		(*it)->retain();
+
 	this->width = width;
 	this->height = height;
 
@@ -59,6 +70,9 @@ ImageData::ImageData(int width, int height, void *data, bool own)
 ImageData::~ImageData()
 {
 	delete[] data;
+
+	for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it)
+		(*it)->release();
 }
 
 void ImageData::create(int width, int height, void *data)
@@ -80,16 +94,23 @@ void ImageData::decode(love::filesystem::FileData *data)
 {
 	FormatHandler::DecodedImage decodedimage;
 
-	if (DevilHandler::canDecode(data))
-		decodedimage = DevilHandler::decode(data);
-	else
+	for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it)
+	{
+		if ((*it)->canDecode(data))
+		{
+			decodedimage = (*it)->decode(data);
+			break;
+		}
+	}
+
+	if (decodedimage.data == nullptr)
 		throw love::Exception("Could not decode image: unrecognized format.");
 
 	// The decoder *must* output a 32 bits-per-pixel image.
 	if (decodedimage.size != decodedimage.width*decodedimage.height*sizeof(pixel))
 	{
 		delete[] decodedimage.data;
-		throw love::Exception("Coult not convert image!");
+		throw love::Exception("Could not convert image!");
 	}
 
 	if (this->data)
@@ -114,15 +135,21 @@ void ImageData::encode(love::filesystem::File *f, ImageData::Format format)
 		rawimage.size = width*height*sizeof(pixel);
 		rawimage.data = data;
 
-		if (DevilHandler::canEncode(format))
-			encodedimage = DevilHandler::encode(rawimage, format);
-		else
+		for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it)
+		{
+			if ((*it)->canEncode(format))
+			{
+				encodedimage = (*it)->encode(rawimage, format);
+				break;
+			}
+		}
+
+		if (encodedimage.data == nullptr)
 			throw love::Exception("Image format has no suitable encoder.");
 	}
 
 	try
 	{
-		
 		f->open(love::filesystem::File::WRITE);
 		f->write(encodedimage.data, encodedimage.size);
 		f->close();

+ 10 - 3
src/modules/image/magpie/ImageData.h

@@ -22,9 +22,13 @@
 #define LOVE_IMAGE_MAGPIE_IMAGE_DATA_H
 
 // LOVE
+#include "FormatHandler.h"
 #include "filesystem/File.h"
 #include "image/ImageData.h"
 
+// C++
+#include <list>
+
 namespace love
 {
 namespace image
@@ -36,9 +40,9 @@ class ImageData : public love::image::ImageData
 {
 public:
 
-	ImageData(love::filesystem::FileData *data);
-	ImageData(int width, int height);
-	ImageData(int width, int height, void *data, bool own);
+	ImageData(std::list<FormatHandler *> formats, love::filesystem::FileData *data);
+	ImageData(std::list<FormatHandler *> formats, int width, int height);
+	ImageData(std::list<FormatHandler *> formats, int width, int height, void *data, bool own);
 	virtual ~ImageData();
 
 	// Implements image::ImageData.
@@ -52,6 +56,9 @@ private:
 	// Decode and load an encoded format.
 	void decode(love::filesystem::FileData *data);
 
+	// Image format handlers we can use for decoding and encoding.
+	std::list<FormatHandler *> formatHandlers;
+
 }; // ImageData
 
 } // magpie

+ 11 - 0
src/modules/keyboard/Keyboard.cpp

@@ -220,6 +220,17 @@ StringMap<Keyboard::Key, Keyboard::KEY_MAX_ENUM>::Entry Keyboard::keyEntries[] =
 	{"audioplay", Keyboard::KEY_AUDIOPLAY},
 	{"audiomute", Keyboard::KEY_AUDIOMUTE},
 	{"mediaselect", Keyboard::KEY_MEDIASELECT},
+	{"www", Keyboard::KEY_WWW},
+	{"mail", Keyboard::KEY_MAIL},
+	{"calculator", Keyboard::KEY_CALCULATOR},
+	{"computer", Keyboard::KEY_COMPUTER},
+	{"appsearch", Keyboard::KEY_APP_SEARCH},
+	{"apphome", Keyboard::KEY_APP_HOME},
+	{"appback", Keyboard::KEY_APP_BACK},
+	{"appforward", Keyboard::KEY_APP_FORWARD},
+	{"appstop", Keyboard::KEY_APP_STOP},
+	{"apprefresh", Keyboard::KEY_APP_REFRESH},
+	{"appbookmarks", Keyboard::KEY_APP_BOOKMARKS},
 
 	{"brightnessdown", Keyboard::KEY_BRIGHTNESSDOWN},
 	{"brightnessup", Keyboard::KEY_BRIGHTNESSUP},

+ 11 - 0
src/modules/keyboard/Keyboard.h

@@ -220,6 +220,17 @@ public:
 		KEY_AUDIOPLAY,
 		KEY_AUDIOMUTE,
 		KEY_MEDIASELECT,
+		KEY_WWW,
+		KEY_MAIL,
+		KEY_CALCULATOR,
+		KEY_COMPUTER,
+		KEY_APP_SEARCH,
+		KEY_APP_HOME,
+		KEY_APP_BACK,
+		KEY_APP_FORWARD,
+		KEY_APP_STOP,
+		KEY_APP_REFRESH,
+		KEY_APP_BOOKMARKS,
 
 		KEY_BRIGHTNESSDOWN,
 		KEY_BRIGHTNESSUP,

+ 11 - 0
src/modules/keyboard/sdl/Keyboard.cpp

@@ -264,6 +264,17 @@ std::map<Keyboard::Key, SDL_Keycode> Keyboard::createKeyMap()
 	k[Keyboard::KEY_AUDIOPLAY] = SDLK_AUDIOPLAY;
 	k[Keyboard::KEY_AUDIOMUTE] = SDLK_AUDIOMUTE;
 	k[Keyboard::KEY_MEDIASELECT] = SDLK_MEDIASELECT;
+	k[Keyboard::KEY_WWW] = SDLK_WWW;
+	k[Keyboard::KEY_MAIL] = SDLK_MAIL;
+	k[Keyboard::KEY_CALCULATOR] = SDLK_CALCULATOR;
+	k[Keyboard::KEY_COMPUTER] = SDLK_COMPUTER;
+	k[Keyboard::KEY_APP_SEARCH] = SDLK_AC_SEARCH;
+	k[Keyboard::KEY_APP_HOME] = SDLK_AC_HOME;
+	k[Keyboard::KEY_APP_BACK] = SDLK_AC_BACK;
+	k[Keyboard::KEY_APP_FORWARD] = SDLK_AC_FORWARD;
+	k[Keyboard::KEY_APP_STOP] = SDLK_AC_STOP;
+	k[Keyboard::KEY_APP_REFRESH] = SDLK_AC_REFRESH;
+	k[Keyboard::KEY_APP_BOOKMARKS] = SDLK_AC_BOOKMARKS;
 
 	k[Keyboard::KEY_BRIGHTNESSDOWN] = SDLK_BRIGHTNESSDOWN;
 	k[Keyboard::KEY_BRIGHTNESSUP] = SDLK_BRIGHTNESSUP;

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

@@ -170,6 +170,15 @@ const char *love_codename()
 	return love::VERSION_CODENAME;
 }
 
+static int w_love_getVersion(lua_State *L)
+{
+	lua_pushinteger(L, love::VERSION_MAJOR);
+	lua_pushinteger(L, love::VERSION_MINOR);
+	lua_pushinteger(L, love::VERSION_REV);
+	lua_pushstring(L, love::VERSION_CODENAME);
+	return 4;
+}
+
 int luaopen_love(lua_State * L)
 {
 	love::luax_insistglobal(L, "love");
@@ -203,6 +212,9 @@ int luaopen_love(lua_State * L)
 
 	lua_setfield(L, -2, "_version_compat");
 
+	lua_pushcfunction(L, w_love_getVersion);
+	lua_setfield(L, -2, "getVersion");
+
 #ifdef LOVE_WINDOWS
 	lua_pushstring(L, "Windows");
 #elif defined(LOVE_MACOSX)

+ 30 - 0
src/modules/math/MathModule.cpp

@@ -193,5 +193,35 @@ bool Math::isConvex(const std::vector<Vertex> &polygon)
 	return true;
 }
 
+/**
+ * http://en.wikipedia.org/wiki/SRGB#The_reverse_transformation
+ **/
+float Math::gammaToLinear(float c) const
+{
+	if (c > 1.0)
+		return 1.0;
+	else if (c < 0.0)
+		return 0.0;
+	else if (c <= 0.04045)
+		return c / 12.92;
+	else
+		return powf((c + 0.055) / 1.055, 2.4);
+}
+
+/**
+ * http://en.wikipedia.org/wiki/SRGB#The_forward_transformation_.28CIE_xyY_or_CIE_XYZ_to_sRGB.29
+ **/
+float Math::linearToGamma(float c) const
+{
+	if (c > 1.0)
+		return 1.0;
+	else if (c < 0.0)
+		return 0.0;
+	else if (c < 0.0031308)
+		return c * 12.92;
+	else
+		return 1.055 * powf(c, 0.41666) - 0.055;
+}
+
 } // math
 } // love

+ 30 - 20
src/modules/math/MathModule.h

@@ -53,26 +53,6 @@ public:
 	virtual ~Math()
 	{}
 
-	inline void setRandomSeed(RandomGenerator::Seed seed)
-	{
-		rng.setSeed(seed);
-	}
-
-	inline void setRandomSeed(uint32 low, uint32 high)
-	{
-		rng.setSeed(low, high);
-	}
-
-	inline RandomGenerator::Seed getRandomSeed() const
-	{
-		return rng.getSeed();
-	}
-
-	inline void getRandomSeed(uint32 &low, uint32 &high) const
-	{
-		rng.getSeed(low, high);
-	}
-
 	/**
 	 * @copydoc RandomGenerator::random()
 	 **/
@@ -105,6 +85,26 @@ public:
 		return rng.randomNormal(stddev);
 	}
 
+	inline void setRandomSeed(RandomGenerator::Seed seed)
+	{
+		rng.setSeed(seed);
+	}
+
+	inline RandomGenerator::Seed getRandomSeed() const
+	{
+		return rng.getSeed();
+	}
+
+	inline void setRandomState(const std::string &statestr)
+	{
+		rng.setState(statestr);
+	}
+
+	inline std::string getRandomState() const
+	{
+		return rng.getState();
+	}
+
 	/**
 	 * Create a new random number generator.
 	 **/
@@ -136,6 +136,16 @@ public:
 	 **/
 	bool isConvex(const std::vector<Vertex> &polygon);
 
+	/**
+	 * Converts a value from the sRGB (gamma) colorspace to linear RGB.
+	 **/
+	float gammaToLinear(float c) const;
+
+	/**
+	 * Converts a value from linear RGB to the sRGB (gamma) colorspace.
+	 **/
+	float linearToGamma(float c) const;
+
 	/**
 	 * Calculate Simplex noise for the specified coordinate(s).
 	 *

+ 76 - 20
src/modules/math/RandomGenerator.cpp

@@ -20,8 +20,13 @@
 
 #include "RandomGenerator.h"
 
-// STL
+// C++
 #include <cmath>
+#include <sstream>
+#include <iomanip>
+
+// C
+#include <cstdlib>
 
 namespace love
 {
@@ -36,25 +41,10 @@ RandomGenerator::RandomGenerator()
 {
 	// because it is too big for some compilers to handle ... if you know what
 	// i mean
-#ifdef LOVE_BIG_ENDIAN
-	seed.b32.a = 0x0139408D;
-	seed.b32.b = 0xCBBF7A44;
-#else
-	seed.b32.b = 0x0139408D;
-	seed.b32.a = 0xCBBF7A44;
-#endif
-
-	rng_state = seed;
-}
-
-void RandomGenerator::setSeed(RandomGenerator::Seed newseed)
-{
-	// 0 xor 0 is still 0, so Xorshift can't generate new numbers.
-	if (newseed.b64 == 0)
-		throw love::Exception("Invalid random seed.");
-
-	seed = newseed;
-	rng_state = seed;
+	Seed newseed;
+	newseed.b32.low = 0xCBBF7A44;
+	newseed.b32.high = 0x0139408D;
+	setSeed(newseed);
 }
 
 uint64 RandomGenerator::rand()
@@ -83,5 +73,71 @@ double RandomGenerator::randomNormal(double stddev)
 	return r * sin(phi) * stddev;
 }
 
+void RandomGenerator::setSeed(RandomGenerator::Seed newseed)
+{
+	// 0 xor 0 is still 0, so Xorshift can't generate new numbers.
+	if (newseed.b64 == 0)
+		throw love::Exception("Invalid random seed.");
+
+	seed = newseed;
+	rng_state = seed;
+}
+
+RandomGenerator::Seed RandomGenerator::getSeed() const
+{
+	return seed;
+}
+
+void RandomGenerator::setState(const std::string &statestr)
+{
+	// For this implementation we'll accept a hex string representing the
+	// 64-bit state integer xorshift uses.
+
+	Seed state = {};
+
+	// Hex string must start with 0x.
+	if (statestr.find("0x") != 0 || statestr.size() < 3)
+		throw love::Exception("Invalid random state.");
+
+	// standardized strtoull (or 64 bit integer support for stringstream)
+	// requires C++11's standard library, which we can't use yet.
+	// I use strtol like this not because it's the best solution, but because
+	// it's "good enough".
+
+	// Convert the hex string to the state integer character-by-character.
+	for (size_t i = 2; i < statestr.size(); i++)
+	{
+		char hex[2] = {statestr[i], 0};
+		char *end = nullptr;
+
+		// Convert the current hex character to a number.
+		int nibble = strtol(hex, &end, 16);
+
+		// Check if strtol failed to convert it.
+		if (end != nullptr && *end != 0)
+			throw love::Exception("Invalid random state.");
+
+		state.b64 = (state.b64 << 4) + nibble;
+	}
+
+	rng_state = state;
+}
+
+std::string RandomGenerator::getState() const
+{
+	// For this implementation we'll return a hex string representing the 64-bit
+	// state integer xorshift uses.
+
+	std::stringstream ss;
+
+	ss << "0x";
+
+	// Again with the stringstream not dealing with 64 bit integers...
+	ss << std::setfill('0') << std::setw(8) << std::hex << rng_state.b32.high;
+	ss << std::setfill('0') << std::setw(8) << std::hex << rng_state.b32.low;
+
+	return ss.str();
+}
+
 } // math
 } // love

+ 31 - 43
src/modules/math/RandomGenerator.h

@@ -28,8 +28,9 @@
 #include "common/int.h"
 #include "common/Object.h"
 
-// STL
+// C++
 #include <limits>
+#include <string>
 
 namespace love
 {
@@ -45,54 +46,19 @@ public:
 		uint64 b64;
 		struct
 		{
-			uint32 a;
-			uint32 b;
+#ifdef LOVE_BIG_ENDIAN
+			uint32 high;
+			uint32 low;
+#else
+			uint32 low;
+			uint32 high;
+#endif
 		} b32;
 	};
 
 	RandomGenerator();
 	virtual ~RandomGenerator() {}
 
-	/**
-	 * Set pseudo-random seed.
-	 * It's up to the implementation how to use this.
-	 **/
-	void setSeed(Seed seed);
-
-	/**
-	 * Separately set the low and high bits of the pseudo-random seed.
-	 **/
-	inline void setSeed(uint32 low, uint32 high)
-	{
-		Seed newseed;
-
-#ifdef LOVE_BIG_ENDIAN
-		newseed.b32.a = high;
-		newseed.b32.b = low;
-#else
-		newseed.b32.b = high;
-		newseed.b32.a = low;
-#endif
-
-		setSeed(newseed);
-	}
-
-	inline Seed getSeed() const
-	{
-		return seed;
-	}
-
-	inline void getSeed(uint32 &low, uint32 &high) const
-	{
-#ifdef LOVE_BIG_ENDIAN
-		high = seed.b32.a;
-		low = seed.b32.b;
-#else
-		high = seed.b32.b;
-		low = seed.b32.a;
-#endif
-	}
-
 	/**
 	 * Return uniformly distributed pseudo random integer.
 	 *
@@ -138,6 +104,28 @@ public:
 	 **/
 	double randomNormal(double stddev);
 
+	/**
+	 * Set pseudo-random seed.
+	 * It's up to the implementation how to use this.
+	 **/
+	void setSeed(Seed seed);
+
+	/**
+	 * Get the previously set pseudo-random seed.
+	 **/
+	Seed getSeed() const;
+
+	/**
+	 * Set the internal implementation-dependent state value based on a string.
+	 **/
+	void setState(const std::string &statestr);
+
+	/**
+	 * Get a string representation of the implementation-dependent internal
+	 * state value.
+	 **/
+	std::string getState() const;
+
 private:
 
 	Seed seed;

+ 93 - 14
src/modules/math/wrap_Math.cpp

@@ -32,6 +32,21 @@ namespace love
 namespace math
 {
 
+int w_random(lua_State *L)
+{
+	return luax_getrandom(L, 1, Math::instance.random());
+}
+
+int w_randomNormal(lua_State *L)
+{
+	double stddev = luaL_optnumber(L, 1, 1.0);
+	double mean = luaL_optnumber(L, 2, 0.0);
+	double r = Math::instance.randomNormal(stddev);
+
+	lua_pushnumber(L, r + mean);
+	return 1;
+}
+
 int w_setRandomSeed(lua_State *L)
 {
 	EXCEPT_GUARD(Math::instance.setRandomSeed(luax_checkrandomseed(L, 1));)
@@ -40,25 +55,21 @@ int w_setRandomSeed(lua_State *L)
 
 int w_getRandomSeed(lua_State *L)
 {
-	uint32 low = 0, high = 0;
-	Math::instance.getRandomSeed(low, high);
-	lua_pushnumber(L, (lua_Number) low);
-	lua_pushnumber(L, (lua_Number) high);
+	RandomGenerator::Seed s = Math::instance.getRandomSeed();
+	lua_pushnumber(L, (lua_Number) s.b32.low);
+	lua_pushnumber(L, (lua_Number) s.b32.high);
 	return 2;
 }
 
-int w_random(lua_State *L)
+int w_setRandomState(lua_State *L)
 {
-	return luax_getrandom(L, 1, Math::instance.random());
+	EXCEPT_GUARD(Math::instance.setRandomState(luax_checkstring(L, 1));)
+	return 0;
 }
 
-int w_randomNormal(lua_State *L)
+int w_getRandomState(lua_State *L)
 {
-	double stddev = luaL_optnumber(L, 1, 1.0);
-	double mean = luaL_optnumber(L, 2, 0.0);
-	double r = Math::instance.randomNormal(stddev);
-
-	lua_pushnumber(L, r + mean);
+	luax_pushstring(L, Math::instance.getRandomState());
 	return 1;
 }
 
@@ -238,6 +249,70 @@ int w_isConvex(lua_State *L)
 	return 1;
 }
 
+static int getGammaArgs(lua_State *L, float color[4])
+{
+	int numcomponents = 0;
+
+	if (lua_istable(L, 1))
+	{
+		int n = lua_objlen(L, 1);
+		for (int i = 1; i <= n && i <= 4; i++)
+		{
+			lua_rawgeti(L, 1, i);
+			color[i - 1] = (float) luaL_checknumber(L, -1) / 255.0;
+			numcomponents++;
+		}
+
+		lua_pop(L, numcomponents);
+	}
+	else
+	{
+		int n = lua_gettop(L);
+		for (int i = 1; i <= n && i <= 4; i++)
+		{
+			color[i - 1] = (float) luaL_checknumber(L, i) / 255.0;
+			numcomponents++;
+		}
+	}
+
+	if (numcomponents == 0)
+		luaL_checknumber(L, 1);
+	
+	return numcomponents;
+}
+
+int w_gammaToLinear(lua_State *L)
+{
+	float color[4];
+	int numcomponents = getGammaArgs(L, color);
+
+	for (int i = 0; i < numcomponents; i++)
+	{
+		// Alpha should always be linear.
+		if (i < 3)
+			color[i] = Math::instance.gammaToLinear(color[i]);
+		lua_pushnumber(L, color[i] * 255);
+	}
+
+	return numcomponents;
+}
+
+int w_linearToGamma(lua_State *L)
+{
+	float color[4];
+	int numcomponents = getGammaArgs(L, color);
+
+	for (int i = 0; i < numcomponents; i++)
+	{
+		// Alpha should always be linear.
+		if (i < 3)
+			color[i] = Math::instance.linearToGamma(color[i]);
+		lua_pushnumber(L, color[i] * 255);
+	}
+
+	return numcomponents;
+}
+
 int w_noise(lua_State *L)
 {
 	float w, x, y, z;
@@ -277,14 +352,18 @@ int w_noise(lua_State *L)
 // List of functions to wrap.
 static const luaL_Reg functions[] =
 {
-	{ "setRandomSeed", w_setRandomSeed },
-	{ "getRandomSeed", w_getRandomSeed },
 	{ "random", w_random },
 	{ "randomNormal", w_randomNormal },
+	{ "setRandomSeed", w_setRandomSeed },
+	{ "getRandomSeed", w_getRandomSeed },
+	{ "setRandomState", w_setRandomState },
+	{ "getRandomState", w_getRandomState },
 	{ "newRandomGenerator", w_newRandomGenerator },
 	{ "newBezierCurve", w_newBezierCurve },
 	{ "triangulate", w_triangulate },
 	{ "isConvex", w_isConvex },
+	{ "gammaToLinear", w_gammaToLinear },
+	{ "linearToGamma", w_linearToGamma },
 	{ "noise", w_noise },
 	{ 0, 0 }
 };

+ 6 - 2
src/modules/math/wrap_Math.h

@@ -30,14 +30,18 @@ namespace love
 namespace math
 {
 
-int w_setRandomSeed(lua_State *L);
-int w_getRandomSeed(lua_State *L);
 int w_random(lua_State *L);
 int w_randomNormal(lua_State *L);
+int w_setRandomSeed(lua_State *L);
+int w_getRandomSeed(lua_State *L);
+int w_setRandomState(lua_State *L);
+int w_getRandomState(lua_State *L);
 int w_newRandomGenerator(lua_State *L);
 int w_newBezierCurve(lua_State *L);
 int w_triangulate(lua_State *L);
 int w_isConvex(lua_State *L);
+int w_gammaToLinear(lua_State *L);
+int w_linearToGamma(lua_State *L);
 int w_noise(lua_State *L);
 extern "C" LOVE_EXPORT int luaopen_love_math(lua_State *L);
 

+ 32 - 27
src/modules/math/wrap_RandomGenerator.cpp

@@ -47,16 +47,8 @@ RandomGenerator::Seed luax_checkrandomseed(lua_State *L, int idx)
 
 	if (!lua_isnoneornil(L, idx + 1))
 	{
-		uint32 low = checkrandomseed_part<uint32>(L, idx);
-		uint32 high = checkrandomseed_part<uint32>(L, idx + 1);
-
-#ifdef LOVE_BIG_ENDIAN
-		s.b32.a = high;
-		s.b32.b = low;
-#else
-		s.b32.b = high;
-		s.b32.a = low;
-#endif
+		s.b32.low = checkrandomseed_part<uint32>(L, idx);
+		s.b32.high = checkrandomseed_part<uint32>(L, idx + 1);
 	}
 	else
 		s.b64 = checkrandomseed_part<uint64>(L, idx);
@@ -95,6 +87,24 @@ RandomGenerator *luax_checkrandomgenerator(lua_State *L, int idx)
 	return luax_checktype<RandomGenerator>(L, idx, "RandomGenerator", MATH_RANDOM_GENERATOR_T);
 }
 
+int w_RandomGenerator_random(lua_State *L)
+{
+	RandomGenerator *rng = luax_checkrandomgenerator(L, 1);
+	return luax_getrandom(L, 2, rng->random());
+}
+
+int w_RandomGenerator_randomNormal(lua_State *L)
+{
+	RandomGenerator *rng = luax_checkrandomgenerator(L, 1);
+
+	double stddev = luaL_optnumber(L, 2, 1.0);
+	double mean = luaL_optnumber(L, 3, 0.0);
+	double r = rng->randomNormal(stddev);
+
+	lua_pushnumber(L, r + mean);
+	return 1;
+}
+
 int w_RandomGenerator_setSeed(lua_State *L)
 {
 	RandomGenerator *rng = luax_checkrandomgenerator(L, 1);
@@ -105,39 +115,34 @@ int w_RandomGenerator_setSeed(lua_State *L)
 int w_RandomGenerator_getSeed(lua_State *L)
 {
 	RandomGenerator *rng = luax_checkrandomgenerator(L, 1);
-
-	uint32 low = 0, high = 0;
-	rng->getSeed(low, high);
-
-	lua_pushnumber(L, (lua_Number) low);
-	lua_pushnumber(L, (lua_Number) high);
+	RandomGenerator::Seed s = rng->getSeed();
+	lua_pushnumber(L, (lua_Number) s.b32.low);
+	lua_pushnumber(L, (lua_Number) s.b32.high);
 	return 2;
 }
 
-int w_RandomGenerator_random(lua_State *L)
+int w_RandomGenerator_setState(lua_State *L)
 {
 	RandomGenerator *rng = luax_checkrandomgenerator(L, 1);
-	return luax_getrandom(L, 2, rng->random());
+	EXCEPT_GUARD(rng->setState(luax_checkstring(L, 2));)
+	return 0;
 }
 
-int w_RandomGenerator_randomNormal(lua_State *L)
+int w_RandomGenerator_getState(lua_State *L)
 {
 	RandomGenerator *rng = luax_checkrandomgenerator(L, 1);
-
-	double stddev = luaL_optnumber(L, 2, 1.0);
-	double mean = luaL_optnumber(L, 3, 0.0);
-	double r = rng->randomNormal(stddev);
-
-	lua_pushnumber(L, r + mean);
+	luax_pushstring(L, rng->getState());
 	return 1;
 }
 
 static const luaL_Reg functions[] =
 {
-	{ "setSeed", w_RandomGenerator_setSeed },
-	{ "getSeed", w_RandomGenerator_getSeed },
 	{ "random", w_RandomGenerator_random },
 	{ "randomNormal", w_RandomGenerator_randomNormal },
+	{ "setSeed", w_RandomGenerator_setSeed },
+	{ "getSeed", w_RandomGenerator_getSeed },
+	{ "setState", w_RandomGenerator_setState },
+	{ "getState", w_RandomGenerator_getState },
 	{ 0, 0 }
 };
 

+ 4 - 2
src/modules/math/wrap_RandomGenerator.h

@@ -36,10 +36,12 @@ RandomGenerator::Seed luax_checkrandomseed(lua_State *L, int idx);
 int luax_getrandom(lua_State *L, int startidx, double r);
 
 RandomGenerator *luax_checkrandomgenerator(lua_State *L, int idx);
-int w_RandomGenerator_setSeed(lua_State *L);
-int w_RandomGenerator_getSeed(lua_State *L);
 int w_RandomGenerator_random(lua_State *L);
 int w_RandomGenerator_randomNormal(lua_State *L);
+int w_RandomGenerator_setSeed(lua_State *L);
+int w_RandomGenerator_getSeed(lua_State *L);
+int w_RandomGenerator_setState(lua_State *L);
+int w_RandomGenerator_getState(lua_State *L);
 extern "C" int luaopen_randomgenerator(lua_State *L);
 
 } // math

+ 1 - 1
src/modules/mouse/Mouse.h

@@ -49,7 +49,7 @@ public:
 		BUTTON_MAX_ENUM
 	};
 
-	virtual ~Mouse() {};
+	virtual ~Mouse() {}
 
 	virtual Cursor *newCursor(love::image::ImageData *data, int hotx, int hoty) = 0;
 	virtual Cursor *getSystemCursor(Cursor::SystemCursor cursortype) = 0;

+ 47 - 4
src/modules/mouse/sdl/Mouse.cpp

@@ -32,6 +32,39 @@ namespace mouse
 namespace sdl
 {
 
+// SDL reports mouse coordinates in the window coordinate system in OS X, but
+// we want them in pixel coordinates (may be different with high-DPI enabled.)
+static void windowToPixelCoords(int *x, int *y)
+{
+	double scale = 1.0;
+
+	love::window::Window *window = love::window::sdl::Window::getSingleton();
+	if (window != nullptr)
+		scale = window->getPixelScale();
+
+	if (x != nullptr)
+		*x = int(double(*x) * scale);
+
+	if (y != nullptr)
+		*y = int(double(*y) * scale);
+}
+
+// And vice versa for setting mouse coordinates.
+static void pixelToWindowCoords(int *x, int *y)
+{
+	double scale = 1.0;
+
+	love::window::Window *window = love::window::sdl::Window::getSingleton();
+	if (window != nullptr)
+		scale = window->getPixelScale();
+
+	if (x != nullptr)
+		*x = int(double(*x) / scale);
+
+	if (y != nullptr)
+		*y = int(double(*y) / scale);
+}
+
 const char *Mouse::getName() const
 {
 	return "love.mouse.sdl";
@@ -98,30 +131,40 @@ love::mouse::Cursor *Mouse::getCursor() const
 int Mouse::getX() const
 {
 	int x;
-	SDL_GetMouseState(&x, 0);
+	SDL_GetMouseState(&x, nullptr);
+	windowToPixelCoords(&x, nullptr);
+
 	return x;
 }
 
 int Mouse::getY() const
 {
 	int y;
-	SDL_GetMouseState(0, &y);
+	SDL_GetMouseState(nullptr, &y);
+	windowToPixelCoords(nullptr, &y);
+
 	return y;
 }
 
 void Mouse::getPosition(int &x, int &y) const
 {
-	SDL_GetMouseState(&x, &y);
+	int mx, my;
+	SDL_GetMouseState(&mx, &my);
+	windowToPixelCoords(&mx, &my);
+
+	x = mx;
+	y = my;
 }
 
 void Mouse::setPosition(int x, int y)
 {
 	love::window::Window *window = love::window::sdl::Window::getSingleton();
 
-	SDL_Window *handle = NULL;
+	SDL_Window *handle = nullptr;
 	if (window)
 		handle = (SDL_Window *) window->getHandle();
 
+	pixelToWindowCoords(&x, &y);
 	SDL_WarpMouseInWindow(handle, x, y);
 }
 

+ 2 - 2
src/modules/mouse/wrap_Cursor.cpp

@@ -51,7 +51,7 @@ int w_Cursor_getType(lua_State *L)
 
 	lua_pushstring(L, typestr);
 	return 1;
-};
+}
 
 static const luaL_Reg functions[] =
 {
@@ -65,4 +65,4 @@ extern "C" int luaopen_cursor(lua_State *L)
 }
 
 } // mouse
-} // love
+} // love

+ 43 - 1
src/modules/physics/box2d/Body.cpp

@@ -37,10 +37,14 @@ namespace box2d
 
 Body::Body(World *world, b2Vec2 p, Body::Type type)
 	: world(world)
+	, udata(nullptr)
 {
+	udata = new bodyudata();
+	udata->ref = nullptr;
 	world->retain();
 	b2BodyDef def;
 	def.position = Physics::scaleDown(p);
+	def.userData = (void *) udata;
 	body = world->world->CreateBody(&def);
 	// Box2D body holds a reference to the love Body.
 	this->retain();
@@ -50,7 +54,9 @@ Body::Body(World *world, b2Vec2 p, Body::Type type)
 
 Body::Body(b2Body *b)
 	: body(b)
+	, udata(nullptr)
 {
+	udata = (bodyudata *) b->GetUserData();
 	world = (World *)Memoizer::find(b->GetWorld());
 	world->retain();
 	// Box2D body holds a reference to the love Body.
@@ -60,8 +66,10 @@ Body::Body(b2Body *b)
 
 Body::~Body()
 {
+	if (udata != nullptr)
+		delete udata->ref;
+	delete udata;
 	world->release();
-	body = 0;
 }
 
 float Body::getX()
@@ -469,6 +477,40 @@ void Body::destroy()
 	this->release();
 }
 
+int Body::setUserData(lua_State *L)
+{
+	love::luax_assert_argc(L, 1, 1);
+
+	if (udata == nullptr)
+	{
+		udata = new bodyudata();
+		body->SetUserData((void *) udata);
+	}
+
+	if (udata->ref != nullptr)
+	{
+		// We set the Reference's lua_State to this one before deleting it, so
+		// it unrefs using the current lua_State's stack. This is necessary
+		// if setUserData is called in a coroutine.
+		udata->ref->setL(L);
+		delete udata->ref;
+	}
+
+	udata->ref = new Reference(L);
+
+	return 0;
+}
+
+int Body::getUserData(lua_State *L)
+{
+	if (udata != nullptr && udata->ref != nullptr)
+		udata->ref->push(L);
+	else
+		lua_pushnil(L);
+
+	return 1;
+}
+
 } // box2d
 } // physics
 } // love

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