Browse Source

Merged default into minor

--HG--
branch : minor
Alex Szpakowski 11 years ago
parent
commit
53b20b63a5
35 changed files with 483 additions and 235 deletions
  1. 4 1
      readme.md
  2. 1 1
      src/common/runtime.h
  3. 1 1
      src/modules/audio/Audio.h
  4. 2 2
      src/modules/audio/Source.h
  5. 37 12
      src/modules/filesystem/physfs/Filesystem.cpp
  6. 2 2
      src/modules/filesystem/physfs/Filesystem.h
  7. 2 2
      src/modules/filesystem/physfs/wrap_Filesystem.cpp
  8. 7 7
      src/modules/graphics/Graphics.h
  9. 0 20
      src/modules/graphics/Texture.cpp
  10. 0 14
      src/modules/graphics/Texture.h
  11. 173 33
      src/modules/graphics/opengl/Canvas.cpp
  12. 33 4
      src/modules/graphics/opengl/Canvas.h
  13. 1 1
      src/modules/graphics/opengl/Font.h
  14. 11 10
      src/modules/graphics/opengl/Graphics.cpp
  15. 3 3
      src/modules/graphics/opengl/Graphics.h
  16. 21 3
      src/modules/graphics/opengl/Image.cpp
  17. 19 5
      src/modules/graphics/opengl/Image.h
  18. 2 3
      src/modules/graphics/opengl/Shader.cpp
  19. 1 1
      src/modules/graphics/opengl/SpriteBatch.h
  20. 12 4
      src/modules/graphics/opengl/VertexBuffer.cpp
  21. 3 3
      src/modules/graphics/opengl/wrap_Canvas.cpp
  22. 21 11
      src/modules/graphics/opengl/wrap_Graphics.cpp
  23. 1 0
      src/modules/graphics/opengl/wrap_Graphics.h
  24. 1 1
      src/modules/image/ImageData.h
  25. 2 2
      src/modules/joystick/Joystick.h
  26. 78 42
      src/modules/joystick/sdl/Joystick.cpp
  27. 7 3
      src/modules/joystick/sdl/Joystick.h
  28. 7 7
      src/modules/joystick/sdl/JoystickModule.cpp
  29. 2 1
      src/modules/joystick/sdl/wrap_Joystick.cpp
  30. 2 2
      src/modules/joystick/sdl/wrap_JoystickModule.cpp
  31. 2 2
      src/modules/sound/lullaby/Mpg123Decoder.cpp
  32. 24 30
      src/modules/system/System.cpp
  33. 1 0
      src/modules/window/sdl/Window.cpp
  34. 0 1
      src/scripts/boot.lua
  35. 0 1
      src/scripts/boot.lua.h

+ 4 - 1
readme.md

@@ -29,10 +29,12 @@ Repository information
 
 
 We use the 'default' branch for development, and therefore it should not be considered stable.
 We use the 'default' branch for development, and therefore it should not be considered stable.
 Also used is the 'minor' branch, which is used for features in the next minor version and it is
 Also used is the 'minor' branch, which is used for features in the next minor version and it is
-not our development target (which would be the next revision). (Version numbers formatted major.minor.revision.)
+not our development target (which would be the next revision - version numbers are formatted major.minor.revision.)
 
 
 We tag all our releases (since we started using mercurial), and have binary downloads available for them.
 We tag all our releases (since we started using mercurial), and have binary downloads available for them.
 
 
+Experimental changes are developed in the separate [love-experiments][love-experiments] repository.
+
 Builds
 Builds
 ------
 ------
 
 
@@ -69,3 +71,4 @@ Dependencies
 [stableppa]: https://launchpad.net/~bartbes/+archive/love-stable
 [stableppa]: https://launchpad.net/~bartbes/+archive/love-stable
 [unstableppa]: https://launchpad.net/~bartbes/+archive/love-unstable
 [unstableppa]: https://launchpad.net/~bartbes/+archive/love-unstable
 [aur]: http://aur.archlinux.org/packages.php?ID=35279
 [aur]: http://aur.archlinux.org/packages.php?ID=35279
+[love-experiments]: https://bitbucket.org/bartbes/love-experiments

+ 1 - 1
src/common/runtime.h

@@ -49,7 +49,7 @@ extern void *_gcmutex;
  **/
  **/
 enum Registry
 enum Registry
 {
 {
-	REGISTRY_GC = 1,
+	REGISTRY_GC,
 	REGISTRY_MODULES,
 	REGISTRY_MODULES,
 	REGISTRY_TYPES
 	REGISTRY_TYPES
 };
 };

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

@@ -51,7 +51,7 @@ public:
 	 */
 	 */
 	enum DistanceModel
 	enum DistanceModel
 	{
 	{
-		DISTANCE_NONE = 1,
+		DISTANCE_NONE,
 		DISTANCE_INVERSE,
 		DISTANCE_INVERSE,
 		DISTANCE_INVERSE_CLAMPED,
 		DISTANCE_INVERSE_CLAMPED,
 		DISTANCE_LINEAR,
 		DISTANCE_LINEAR,

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

@@ -36,14 +36,14 @@ public:
 
 
 	enum Type
 	enum Type
 	{
 	{
-		TYPE_STATIC = 1,
+		TYPE_STATIC,
 		TYPE_STREAM,
 		TYPE_STREAM,
 		TYPE_MAX_ENUM
 		TYPE_MAX_ENUM
 	}; // Type
 	}; // Type
 
 
 	enum Unit
 	enum Unit
 	{
 	{
-		UNIT_SECONDS = 1,
+		UNIT_SECONDS,
 		UNIT_SAMPLES,
 		UNIT_SAMPLES,
 		UNIT_MAX_ENUM
 		UNIT_MAX_ENUM
 	};
 	};

+ 37 - 12
src/modules/filesystem/physfs/Filesystem.cpp

@@ -130,6 +130,11 @@ bool Filesystem::setIdentity(const char *ident, bool appendToPath)
 	// (No error on fail, it means that the path doesn't exist).
 	// (No error on fail, it means that the path doesn't exist).
 	PHYSFS_addToSearchPath(save_path_full.c_str(), appendToPath);
 	PHYSFS_addToSearchPath(save_path_full.c_str(), appendToPath);
 
 
+	// HACK: This forces setupWriteDirectory to be called the next time a file
+	// is opened for writing - otherwise it won't be called at all if it was
+	// already called at least once before.
+	PHYSFS_setWriteDir(nullptr);
+
 	return true;
 	return true;
 }
 }
 
 
@@ -171,16 +176,36 @@ bool Filesystem::setupWriteDirectory()
 	if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty())
 	if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty())
 		return false;
 		return false;
 
 
-	// Set the appdata folder as writable directory.
+	// We need to make sure the write directory is created. To do that, we also
+	// need to make sure all its parent directories are also created.
+	std::string temp_writedir = getDriveRoot(save_path_full);
+	std::string temp_createdir = skipDriveRoot(save_path_full);
+
+	// On some sandboxed platforms, physfs will break when its write directory
+	// is the root of the drive and it tries to create a folder (even if the
+	// folder's path is in a writable location.) If the user's home folder is
+	// in the save path, we'll try starting from there instead.
+	if (save_path_full.find(getUserDirectory()) == 0)
+	{
+		temp_writedir = getUserDirectory();
+		temp_createdir = save_path_full.substr(getUserDirectory().length());
+
+		// Strip leading '/' characters from the path we want to create.
+		size_t startpos = temp_createdir.find_first_not_of('/');
+		if (startpos != std::string::npos)
+			temp_createdir = temp_createdir.substr(startpos);
+	}
+
+	// Set either '/' or the user's home as a writable directory.
 	// (We must create the save folder before mounting it).
 	// (We must create the save folder before mounting it).
-	if (!PHYSFS_setWriteDir(getDriveRoot(save_path_full).c_str()))
+	if (!PHYSFS_setWriteDir(temp_writedir.c_str()))
 		return false;
 		return false;
 
 
-	// Create the save folder. (We're now "at" %APPDATA%).
-	if (!createDirectory(skipDriveRoot(save_path_full).c_str()))
+	// Create the save folder. (We're now "at" either '/' or the user's home).
+	if (!createDirectory(temp_createdir.c_str()))
 	{
 	{
 		// Clear the write directory in case of error.
 		// Clear the write directory in case of error.
-		PHYSFS_setWriteDir(0);
+		PHYSFS_setWriteDir(nullptr);
 		return false;
 		return false;
 	}
 	}
 
 
@@ -191,7 +216,7 @@ bool Filesystem::setupWriteDirectory()
 	// Add the directory. (Will not be readded if already present).
 	// Add the directory. (Will not be readded if already present).
 	if (!PHYSFS_addToSearchPath(save_path_full.c_str(), 0))
 	if (!PHYSFS_addToSearchPath(save_path_full.c_str(), 0))
 	{
 	{
-		PHYSFS_setWriteDir(0); // Clear the write directory in case of error.
+		PHYSFS_setWriteDir(nullptr); // Clear the write directory in case of error.
 		return false;
 		return false;
 	}
 	}
 
 
@@ -327,12 +352,12 @@ const char *Filesystem::getWorkingDirectory()
 	return cwd.c_str();
 	return cwd.c_str();
 }
 }
 
 
-const char *Filesystem::getUserDirectory()
+std::string Filesystem::getUserDirectory()
 {
 {
-	return PHYSFS_getUserDir();
+	return std::string(PHYSFS_getUserDir());
 }
 }
 
 
-const char *Filesystem::getAppdataDirectory()
+std::string Filesystem::getAppdataDirectory()
 {
 {
 #ifdef LOVE_WINDOWS
 #ifdef LOVE_WINDOWS
 	if (appdata.empty())
 	if (appdata.empty())
@@ -341,7 +366,7 @@ const char *Filesystem::getAppdataDirectory()
 		appdata = to_utf8(w_appdata);
 		appdata = to_utf8(w_appdata);
 		replace_char(appdata, '\\', '/');
 		replace_char(appdata, '\\', '/');
 	}
 	}
-	return appdata.c_str();
+	return appdata;
 #elif defined(LOVE_MACOSX)
 #elif defined(LOVE_MACOSX)
 	if (appdata.empty())
 	if (appdata.empty())
 	{
 	{
@@ -349,7 +374,7 @@ const char *Filesystem::getAppdataDirectory()
 		udir.append("/Library/Application Support");
 		udir.append("/Library/Application Support");
 		appdata = udir;
 		appdata = udir;
 	}
 	}
-	return appdata.c_str();
+	return appdata;
 #elif defined(LOVE_LINUX)
 #elif defined(LOVE_LINUX)
 	if (appdata.empty())
 	if (appdata.empty())
 	{
 	{
@@ -359,7 +384,7 @@ const char *Filesystem::getAppdataDirectory()
 		else
 		else
 			appdata = xdgdatahome;
 			appdata = xdgdatahome;
 	}
 	}
-	return appdata.c_str();
+	return appdata;
 #else
 #else
 	return getUserDirectory();
 	return getUserDirectory();
 #endif
 #endif

+ 2 - 2
src/modules/filesystem/physfs/Filesystem.h

@@ -145,14 +145,14 @@ public:
 	/**
 	/**
 	 * Gets the user home directory.
 	 * Gets the user home directory.
 	 **/
 	 **/
-	const char *getUserDirectory();
+	std::string getUserDirectory();
 
 
 	/**
 	/**
 	 * Gets the APPDATA directory. On Windows, this is the folder
 	 * Gets the APPDATA directory. On Windows, this is the folder
 	 * in the %APPDATA% enviroment variable. On Linux, this is the
 	 * in the %APPDATA% enviroment variable. On Linux, this is the
 	 * user home folder.
 	 * user home folder.
 	 **/
 	 **/
-	const char *getAppdataDirectory();
+	std::string getAppdataDirectory();
 
 
 	/**
 	/**
 	 * Gets the full path of the save folder.
 	 * Gets the full path of the save folder.

+ 2 - 2
src/modules/filesystem/physfs/wrap_Filesystem.cpp

@@ -211,13 +211,13 @@ int w_getWorkingDirectory(lua_State *L)
 
 
 int w_getUserDirectory(lua_State *L)
 int w_getUserDirectory(lua_State *L)
 {
 {
-	lua_pushstring(L, instance->getUserDirectory());
+	luax_pushstring(L, instance->getUserDirectory());
 	return 1;
 	return 1;
 }
 }
 
 
 int w_getAppdataDirectory(lua_State *L)
 int w_getAppdataDirectory(lua_State *L)
 {
 {
-	lua_pushstring(L, instance->getAppdataDirectory());
+	luax_pushstring(L, instance->getAppdataDirectory());
 	return 1;
 	return 1;
 }
 }
 
 

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

@@ -36,14 +36,14 @@ public:
 
 
 	enum DrawMode
 	enum DrawMode
 	{
 	{
-		DRAW_LINE = 1,
+		DRAW_LINE,
 		DRAW_FILL,
 		DRAW_FILL,
 		DRAW_MAX_ENUM
 		DRAW_MAX_ENUM
 	};
 	};
 
 
 	enum AlignMode
 	enum AlignMode
 	{
 	{
-		ALIGN_LEFT = 1,
+		ALIGN_LEFT,
 		ALIGN_CENTER,
 		ALIGN_CENTER,
 		ALIGN_RIGHT,
 		ALIGN_RIGHT,
 		ALIGN_JUSTIFY,
 		ALIGN_JUSTIFY,
@@ -52,7 +52,7 @@ public:
 
 
 	enum BlendMode
 	enum BlendMode
 	{
 	{
-		BLEND_ALPHA = 1,
+		BLEND_ALPHA,
 		BLEND_ADD,
 		BLEND_ADD,
 		BLEND_SUBTRACT,
 		BLEND_SUBTRACT,
 		BLEND_MULTIPLY,
 		BLEND_MULTIPLY,
@@ -64,14 +64,14 @@ public:
 
 
 	enum LineStyle
 	enum LineStyle
 	{
 	{
-		LINE_ROUGH = 1,
+		LINE_ROUGH,
 		LINE_SMOOTH,
 		LINE_SMOOTH,
 		LINE_MAX_ENUM
 		LINE_MAX_ENUM
 	};
 	};
 
 
 	enum LineJoin
 	enum LineJoin
 	{
 	{
-		LINE_JOIN_NONE = 1,
+		LINE_JOIN_NONE,
 		LINE_JOIN_MITER,
 		LINE_JOIN_MITER,
 		LINE_JOIN_BEVEL,
 		LINE_JOIN_BEVEL,
 		LINE_JOIN_MAX_ENUM
 		LINE_JOIN_MAX_ENUM
@@ -79,7 +79,7 @@ public:
 
 
 	enum Support
 	enum Support
 	{
 	{
-		SUPPORT_HDR_CANVAS = 1,
+		SUPPORT_HDR_CANVAS,
 		SUPPORT_MULTI_CANVAS,
 		SUPPORT_MULTI_CANVAS,
 		SUPPORT_DXT,
 		SUPPORT_DXT,
 		SUPPORT_BC5,
 		SUPPORT_BC5,
@@ -90,7 +90,7 @@ public:
 
 
 	enum RendererInfo
 	enum RendererInfo
 	{
 	{
-		RENDERER_INFO_NAME = 1,
+		RENDERER_INFO_NAME,
 		RENDERER_INFO_VERSION,
 		RENDERER_INFO_VERSION,
 		RENDERER_INFO_VENDOR,
 		RENDERER_INFO_VENDOR,
 		RENDERER_INFO_DEVICE,
 		RENDERER_INFO_DEVICE,

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

@@ -109,16 +109,6 @@ bool Texture::getConstant(WrapMode in, const char  *&out)
 	return wrapModes.find(in, 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[] =
 StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM>::Entry Texture::filterModeEntries[] =
 {
 {
 	{ "linear", Texture::FILTER_LINEAR },
 	{ "linear", Texture::FILTER_LINEAR },
@@ -135,15 +125,5 @@ 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::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
 } // graphics
 } // love
 } // love

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

@@ -55,14 +55,6 @@ public:
 		FILTER_MAX_ENUM
 		FILTER_MAX_ENUM
 	};
 	};
 
 
-	enum Format
-	{
-		FORMAT_NORMAL,
-		FORMAT_HDR,
-		FORMAT_SRGB,
-		FORMAT_MAX_ENUM
-	};
-
 	struct Filter
 	struct Filter
 	{
 	{
 		Filter();
 		Filter();
@@ -119,9 +111,6 @@ public:
 	static bool getConstant(const char *in, WrapMode &out);
 	static bool getConstant(const char *in, WrapMode &out);
 	static bool getConstant(WrapMode in, const char  *&out);
 	static bool getConstant(WrapMode in, const char  *&out);
 
 
-	static bool getConstant(const char *in, Format &out);
-	static bool getConstant(Format in, const char *&out);
-
 protected:
 protected:
 
 
 	int width;
 	int width;
@@ -143,9 +132,6 @@ private:
 	static StringMap<WrapMode, WRAP_MAX_ENUM>::Entry wrapModeEntries[];
 	static StringMap<WrapMode, WRAP_MAX_ENUM>::Entry wrapModeEntries[];
 	static StringMap<WrapMode, WRAP_MAX_ENUM> wrapModes;
 	static StringMap<WrapMode, WRAP_MAX_ENUM> wrapModes;
 
 
-	static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
-	static StringMap<Format, FORMAT_MAX_ENUM> formats;
-
 }; // Texture
 }; // Texture
 
 
 } // graphics
 } // graphics

+ 173 - 33
src/modules/graphics/opengl/Canvas.cpp

@@ -417,7 +417,7 @@ static void getStrategy()
 	}
 	}
 }
 }
 
 
-Canvas::Canvas(int width, int height, Texture::Format format, int fsaa)
+Canvas::Canvas(int width, int height, Format format, int fsaa)
 	: fbo(0)
 	: fbo(0)
     , resolve_fbo(0)
     , resolve_fbo(0)
 	, texture(0)
 	, texture(0)
@@ -525,33 +525,21 @@ bool Canvas::loadVolatile()
 	setFilter(filter);
 	setFilter(filter);
 	setWrap(wrap);
 	setWrap(wrap);
 
 
-	GLint internalformat;
-	GLenum textype;
-	switch (format)
-	{
-	case Texture::FORMAT_HDR:
-		internalformat = GL_RGBA16F;
-		textype = GL_FLOAT;
-		break;
-	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;
-	}
+	GLenum internalformat = GL_RGBA;
+	GLenum externalformat = GL_RGBA;
+	GLenum textype = GL_UNSIGNED_BYTE;
+
+	convertFormat(format, internalformat, externalformat, textype);
 
 
 	while (glGetError() != GL_NO_ERROR)
 	while (glGetError() != GL_NO_ERROR)
 		/* Clear the error buffer. */;
 		/* Clear the error buffer. */;
 
 
 	glTexImage2D(GL_TEXTURE_2D,
 	glTexImage2D(GL_TEXTURE_2D,
 	             0,
 	             0,
-	             internalformat,
+	             (GLint) internalformat,
 	             width, height,
 	             width, height,
 	             0,
 	             0,
-	             GL_RGBA,
+	             externalformat,
 	             textype,
 	             textype,
 	             nullptr);
 	             nullptr);
 
 
@@ -975,33 +963,158 @@ bool Canvas::resolveMSAA()
 	return true;
 	return true;
 }
 }
 
 
+Canvas::Format Canvas::getSizedFormat(Canvas::Format format)
+{
+	switch (format)
+	{
+	case FORMAT_NORMAL:
+		return FORMAT_RGBA8;
+	case FORMAT_HDR:
+		return FORMAT_RGBA16F;
+	default:
+		return format;
+	}
+}
+
+void Canvas::convertFormat(Canvas::Format format, GLenum &internalformat, GLenum &externalformat, GLenum &type)
+{
+	format = getSizedFormat(format);
+	externalformat = GL_RGBA;
+
+	switch (format)
+	{
+	case FORMAT_RGBA8:
+	default:
+		internalformat = GL_RGBA8;
+		type = GL_UNSIGNED_BYTE;
+		break;
+	case FORMAT_RGBA4:
+		internalformat = GL_RGBA4;
+		type = GL_UNSIGNED_SHORT_4_4_4_4;
+		break;
+	case FORMAT_RGB5A1:
+		internalformat = GL_RGB5_A1;
+		type = GL_UNSIGNED_SHORT_5_5_5_1;
+		break;
+	case FORMAT_RGB565:
+		internalformat = GL_RGB565;
+		externalformat = GL_RGB;
+		type = GL_UNSIGNED_SHORT_5_6_5;
+		break;
+	case FORMAT_RGB10A2:
+		internalformat = GL_RGB10_A2;
+		type = GL_UNSIGNED_INT_10_10_10_2;
+		break;
+	case FORMAT_RG11B10F:
+		internalformat = GL_R11F_G11F_B10F;
+		externalformat = GL_RGB;
+		type = GL_UNSIGNED_INT_10F_11F_11F_REV;
+		break;
+	case FORMAT_RGBA16F:
+		internalformat = GL_RGBA16F;
+		type = GL_FLOAT;
+		break;
+	case FORMAT_RGBA32F:
+		internalformat = GL_RGBA32F;
+		type = GL_FLOAT;
+		break;
+	case FORMAT_SRGB:
+		internalformat = GL_SRGB8_ALPHA8;
+		type = GL_UNSIGNED_BYTE;
+		break;
+	}
+}
+
 bool Canvas::isSupported()
 bool Canvas::isSupported()
 {
 {
 	getStrategy();
 	getStrategy();
 	return (strategy != &strategyNone);
 	return (strategy != &strategyNone);
 }
 }
 
 
-bool Canvas::isHDRSupported()
+bool Canvas::isMultiCanvasSupported()
 {
 {
-	return GLEE_VERSION_3_0 || (isSupported() && GLEE_ARB_texture_float);
+	// system must support at least 4 simultanious active canvases.
+	return gl.getMaxRenderTargets() >= 4;
 }
 }
 
 
-bool Canvas::isSRGBSupported()
-{
-	if (GLEE_VERSION_3_0)
-		return true;
+bool Canvas::supportedFormats[] = {false};
+bool Canvas::checkedFormats[] = {false};
 
 
+bool Canvas::isFormatSupported(Canvas::Format format)
+{
 	if (!isSupported())
 	if (!isSupported())
 		return false;
 		return false;
 
 
-	return (GLEE_ARB_framebuffer_sRGB || GLEE_EXT_framebuffer_sRGB)
-		&& GLEE_EXT_texture_sRGB;
-}
+	bool supported = true;
+	format = getSizedFormat(format);
 
 
-bool Canvas::isMultiCanvasSupported()
-{
-	// system must support at least 4 simultanious active canvases.
-	return gl.getMaxRenderTargets() >= 4;
+	switch (format)
+	{
+	case FORMAT_RGBA8:
+	case FORMAT_RGBA4:
+	case FORMAT_RGB5A1:
+	case FORMAT_RGB10A2:
+		supported = true;
+		break;
+	case FORMAT_RGB565:
+		supported = GLEE_VERSION_4_2 || GLEE_ARB_ES2_compatibility;
+		break;
+	case FORMAT_RG11B10F:
+		supported = GLEE_VERSION_3_0 || GLEE_EXT_packed_float;
+		break;
+	case FORMAT_RGBA16F:
+	case FORMAT_RGBA32F:
+		supported = GLEE_VERSION_3_0 || GLEE_ARB_texture_float;
+		break;
+	case FORMAT_SRGB:
+		supported = GLEE_VERSION_3_0 || ((GLEE_ARB_framebuffer_sRGB || GLEE_EXT_framebuffer_sRGB)
+			&& (GLEE_VERSION_2_1 || GLEE_EXT_texture_sRGB));
+		break;
+	default:
+		supported = false;
+		break;
+	}
+
+	if (!supported)
+		return false;
+
+	if (checkedFormats[format])
+		return supportedFormats[format];
+
+	// Even though we might have the necessary OpenGL version or extension,
+	// drivers are still allowed to throw FRAMEBUFFER_UNSUPPORTED when attaching
+	// a texture to a FBO whose format the driver doesn't like. So we should
+	// test with an actual FBO.
+
+	GLenum internalformat = GL_RGBA;
+	GLenum externalformat = GL_RGBA;
+	GLenum textype = GL_UNSIGNED_BYTE;
+	convertFormat(format, internalformat, externalformat, textype);
+
+	GLuint texture = 0;
+	glGenTextures(1, &texture);
+	gl.bindTexture(texture);
+
+	Texture::Filter f;
+	f.min = f.mag = Texture::FILTER_NEAREST;
+	gl.setTextureFilter(f);
+
+	Texture::Wrap w;
+	gl.setTextureWrap(w);
+
+	glTexImage2D(GL_TEXTURE_2D, 0, internalformat, 2, 2, 0, externalformat, textype, nullptr);
+
+	GLuint fbo = 0;
+	supported = (strategy->createFBO(fbo, texture) == GL_FRAMEBUFFER_COMPLETE);
+	strategy->deleteFBO(fbo, 0, 0);
+
+	gl.deleteTexture(texture);
+
+	// Cache the result so we don't do this for every isFormatSupported call.
+	checkedFormats[format] = true;
+	supportedFormats[format] = supported;
+
+	return supported;
 }
 }
 
 
 void Canvas::bindDefaultCanvas()
 void Canvas::bindDefaultCanvas()
@@ -1010,6 +1123,33 @@ void Canvas::bindDefaultCanvas()
 		current->stopGrab();
 		current->stopGrab();
 }
 }
 
 
+bool Canvas::getConstant(const char *in, Format &out)
+{
+	return formats.find(in, out);
+}
+
+bool Canvas::getConstant(Format in, const char *&out)
+{
+	return formats.find(in, out);
+}
+
+StringMap<Canvas::Format, Canvas::FORMAT_MAX_ENUM>::Entry Canvas::formatEntries[] =
+{
+	{"normal", Canvas::FORMAT_NORMAL},
+	{"hdr", Canvas::FORMAT_HDR},
+	{"rgba8", Canvas::FORMAT_RGBA8},
+	{"rgba4", Canvas::FORMAT_RGBA4},
+	{"rgb5a1", Canvas::FORMAT_RGB5A1},
+	{"rgb565", Canvas::FORMAT_RGB565},
+	{"rgb10a2", Canvas::FORMAT_RGB10A2},
+	{"rg11b10f", Canvas::FORMAT_RG11B10F},
+	{"rgba16f", Canvas::FORMAT_RGBA16F},
+	{"rgba32f", Canvas::FORMAT_RGBA32F},
+	{"srgb", Canvas::FORMAT_SRGB},
+};
+
+StringMap<Canvas::Format, Canvas::FORMAT_MAX_ENUM> Canvas::formats(Canvas::formatEntries, sizeof(Canvas::formatEntries));
+
 } // opengl
 } // opengl
 } // graphics
 } // graphics
 } // love
 } // love

+ 33 - 4
src/modules/graphics/opengl/Canvas.h

@@ -25,6 +25,7 @@
 #include "image/Image.h"
 #include "image/Image.h"
 #include "image/ImageData.h"
 #include "image/ImageData.h"
 #include "common/Matrix.h"
 #include "common/Matrix.h"
+#include "common/StringMap.h"
 #include "Texture.h"
 #include "Texture.h"
 #include "OpenGL.h"
 #include "OpenGL.h"
 
 
@@ -39,7 +40,24 @@ class Canvas : public Texture
 {
 {
 public:
 public:
 
 
-	Canvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0);
+	// Different Canvas render target formats.
+	enum Format
+	{
+		FORMAT_NORMAL,   // Usually RGBA8 or a similar fallback. Always supported.
+		FORMAT_HDR,      // Usually RGBA16F. Not always supported.
+		FORMAT_RGBA8,    // RGBA with 8 bits per component.
+		FORMAT_RGBA4,    // RGBA with 4 bits per component.
+		FORMAT_RGB5A1,   // RGB with 5 bits per component, and A with 1 bit.
+		FORMAT_RGB565,   // RGB with 5, 6, and 5 bits each, respectively.
+		FORMAT_RGB10A2,  // RGB with 10 bits each, and A with 2 bits.
+		FORMAT_RG11B10F, // Floating point [0, +inf]. RG with 11 FP bits each, and B with 10 FP bits.
+		FORMAT_RGBA16F,  // Floating point [-inf, +inf]. RGBA with 16 FP bits per component.
+		FORMAT_RGBA32F,  // Floating point [-inf, +inf]. RGBA with 32 FP bits per component.
+		FORMAT_SRGB,     // sRGB with 8 bits per component, plus 8 bit linear A.
+		FORMAT_MAX_ENUM
+	};
+
+	Canvas(int width, int height, Format format = FORMAT_NORMAL, int fsaa = 0);
 	virtual ~Canvas();
 	virtual ~Canvas();
 
 
 	// Implements Volatile.
 	// Implements Volatile.
@@ -85,7 +103,7 @@ public:
 		return status;
 		return status;
 	}
 	}
 
 
-	inline Texture::Format getTextureFormat() const
+	inline Format getTextureFormat() const
 	{
 	{
 		return format;
 		return format;
 	}
 	}
@@ -98,9 +116,8 @@ public:
 	bool resolveMSAA();
 	bool resolveMSAA();
 
 
 	static bool isSupported();
 	static bool isSupported();
-	static bool isHDRSupported();
-	static bool isSRGBSupported();
 	static bool isMultiCanvasSupported();
 	static bool isMultiCanvasSupported();
+	static bool isFormatSupported(Format format);
 
 
 	static Canvas *current;
 	static Canvas *current;
 	static void bindDefaultCanvas();
 	static void bindDefaultCanvas();
@@ -111,10 +128,16 @@ public:
 	// Whether the main screen should have linear -> sRGB conversions enabled.
 	// Whether the main screen should have linear -> sRGB conversions enabled.
 	static bool screenHasSRGB;
 	static bool screenHasSRGB;
 
 
+	static bool getConstant(const char *in, Format &out);
+	static bool getConstant(Format in, const char *&out);
+
 private:
 private:
 
 
 	bool createFSAAFBO(GLenum internalformat);
 	bool createFSAAFBO(GLenum internalformat);
 
 
+	static Format getSizedFormat(Format format);
+	static void convertFormat(Format format, GLenum &internalformat, GLenum &externalformat, GLenum &type);
+
 	GLuint fbo;
 	GLuint fbo;
 	GLuint resolve_fbo;
 	GLuint resolve_fbo;
 
 
@@ -134,6 +157,12 @@ private:
 	void setupGrab();
 	void setupGrab();
 	void drawv(const Matrix &t, const Vertex *v);
 	void drawv(const Matrix &t, const Vertex *v);
 
 
+	static bool supportedFormats[FORMAT_MAX_ENUM];
+	static bool checkedFormats[FORMAT_MAX_ENUM];
+
+	static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
+	static StringMap<Format, FORMAT_MAX_ENUM> formats;
+
 }; // Canvas
 }; // Canvas
 
 
 } // opengl
 } // opengl

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

@@ -142,7 +142,7 @@ private:
 
 
 	enum FontType
 	enum FontType
 	{
 	{
-		FONT_TRUETYPE = 1,
+		FONT_TRUETYPE,
 		FONT_IMAGE,
 		FONT_IMAGE,
 		FONT_UNKNOWN
 		FONT_UNKNOWN
 	};
 	};

+ 11 - 10
src/modules/graphics/opengl/Graphics.cpp

@@ -428,7 +428,7 @@ void Graphics::discardStencil()
 	activeStencil = false;
 	activeStencil = false;
 }
 }
 
 
-Image *Graphics::newImage(love::image::ImageData *data, Texture::Format format)
+Image *Graphics::newImage(love::image::ImageData *data, Image::Format format)
 {
 {
 	// Create the image.
 	// Create the image.
 	Image *image = new Image(data, format);
 	Image *image = new Image(data, format);
@@ -455,7 +455,7 @@ Image *Graphics::newImage(love::image::ImageData *data, Texture::Format format)
 	return image;
 	return image;
 }
 }
 
 
-Image *Graphics::newImage(love::image::CompressedData *cdata, Texture::Format format)
+Image *Graphics::newImage(love::image::CompressedData *cdata, Image::Format format)
 {
 {
 	// Create the image.
 	// Create the image.
 	Image *image = new Image(cdata, format);
 	Image *image = new Image(cdata, format);
@@ -502,13 +502,14 @@ ParticleSystem *Graphics::newParticleSystem(Texture *texture, int size)
 	return new ParticleSystem(texture, size);
 	return new ParticleSystem(texture, size);
 }
 }
 
 
-Canvas *Graphics::newCanvas(int width, int height, Texture::Format format, int fsaa)
+Canvas *Graphics::newCanvas(int width, int height, Canvas::Format format, int fsaa)
 {
 {
-	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 (!Canvas::isFormatSupported(format))
+	{
+		const char *fstr = "rgba8";
+		Canvas::getConstant(format, fstr);
+		throw love::Exception("The %s canvas format is not supported by your OpenGL implementation.", fstr);
+	}
 
 
 	if (width > gl.getMaxTextureSize())
 	if (width > gl.getMaxTextureSize())
 		throw Exception("Cannot create canvas: width of %d pixels is too large for this system.", width);
 		throw Exception("Cannot create canvas: width of %d pixels is too large for this system.", width);
@@ -559,8 +560,8 @@ Canvas *Graphics::newCanvas(int width, int height, Texture::Format format, int f
 	}
 	}
 
 
 	canvas->release();
 	canvas->release();
-	throw Exception(error_string.str().c_str());
-	return NULL; // never reached
+	throw Exception("%s", error_string.str().c_str());
+	return nullptr; // never reached
 }
 }
 
 
 Shader *Graphics::newShader(const Shader::ShaderSources &sources)
 Shader *Graphics::newShader(const Shader::ShaderSources &sources)

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

@@ -192,8 +192,8 @@ public:
 	/**
 	/**
 	 * Creates an Image object with padding and/or optimization.
 	 * Creates an Image object with padding and/or optimization.
 	 **/
 	 **/
-	Image *newImage(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL);
-	Image *newImage(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL);
+	Image *newImage(love::image::ImageData *data, Image::Format format = Image::FORMAT_NORMAL);
+	Image *newImage(love::image::CompressedData *cdata, Image::Format format = Image::FORMAT_NORMAL);
 
 
 	Quad *newQuad(Quad::Viewport v, float sw, float sh);
 	Quad *newQuad(Quad::Viewport v, float sw, float sh);
 
 
@@ -206,7 +206,7 @@ public:
 
 
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 
 
-	Canvas *newCanvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0);
+	Canvas *newCanvas(int width, int height, Canvas::Format format = Canvas::FORMAT_NORMAL, int fsaa = 0);
 
 
 	Shader *newShader(const Shader::ShaderSources &sources);
 	Shader *newShader(const Shader::ShaderSources &sources);
 
 

+ 21 - 3
src/modules/graphics/opengl/Image.cpp

@@ -36,7 +36,7 @@ float Image::maxMipmapSharpness = 0.0f;
 Texture::FilterMode Image::defaultMipmapFilter = Texture::FILTER_NONE;
 Texture::FilterMode Image::defaultMipmapFilter = Texture::FILTER_NONE;
 float Image::defaultMipmapSharpness = 0.0f;
 float Image::defaultMipmapSharpness = 0.0f;
 
 
-Image::Image(love::image::ImageData *data, Texture::Format format)
+Image::Image(love::image::ImageData *data, Format format)
 	: data(data)
 	: data(data)
 	, cdata(nullptr)
 	, cdata(nullptr)
 	, texture(0)
 	, texture(0)
@@ -53,7 +53,7 @@ Image::Image(love::image::ImageData *data, Texture::Format format)
 	preload();
 	preload();
 }
 }
 
 
-Image::Image(love::image::CompressedData *cdata, Texture::Format format)
+Image::Image(love::image::CompressedData *cdata, Format format)
 	: data(nullptr)
 	: data(nullptr)
 	, cdata(cdata)
 	, cdata(cdata)
 	, texture(0)
 	, texture(0)
@@ -420,7 +420,7 @@ bool Image::refresh()
 	return true;
 	return true;
 }
 }
 
 
-Texture::Format Image::getFormat() const
+Image::Format Image::getFormat() const
 {
 {
 	return format;
 	return format;
 }
 }
@@ -556,6 +556,24 @@ bool Image::hasSRGBSupport()
 	return GLEE_VERSION_2_1 || GLEE_EXT_texture_sRGB;
 	return GLEE_VERSION_2_1 || GLEE_EXT_texture_sRGB;
 }
 }
 
 
+bool Image::getConstant(const char *in, Format &out)
+{
+	return formats.find(in, out);
+}
+
+bool Image::getConstant(Format in, const char *&out)
+{
+	return formats.find(in, out);
+}
+
+StringMap<Image::Format, Image::FORMAT_MAX_ENUM>::Entry Image::formatEntries[] =
+{
+	{"normal", Image::FORMAT_NORMAL},
+	{"srgb", Image::FORMAT_SRGB},
+};
+
+StringMap<Image::Format, Image::FORMAT_MAX_ENUM> Image::formats(Image::formatEntries, sizeof(Image::formatEntries));
+
 } // opengl
 } // opengl
 } // graphics
 } // graphics
 } // love
 } // love

+ 19 - 5
src/modules/graphics/opengl/Image.h

@@ -25,6 +25,7 @@
 #include "common/config.h"
 #include "common/config.h"
 #include "common/Matrix.h"
 #include "common/Matrix.h"
 #include "common/Vector.h"
 #include "common/Vector.h"
+#include "common/StringMap.h"
 #include "common/math.h"
 #include "common/math.h"
 #include "image/ImageData.h"
 #include "image/ImageData.h"
 #include "image/CompressedData.h"
 #include "image/CompressedData.h"
@@ -50,20 +51,27 @@ class Image : public Texture
 {
 {
 public:
 public:
 
 
+	enum Format
+	{
+		FORMAT_NORMAL,
+		FORMAT_SRGB,
+		FORMAT_MAX_ENUM
+	};
+
 	/**
 	/**
 	 * Creates a new Image. Not that anything is ready to use
 	 * Creates a new Image. Not that anything is ready to use
 	 * before load is called.
 	 * before load is called.
 	 *
 	 *
 	 * @param data The data from which to load the image.
 	 * @param data The data from which to load the image.
 	 **/
 	 **/
-	Image(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL);
+	Image(love::image::ImageData *data, Format format = FORMAT_NORMAL);
 
 
 	/**
 	/**
 	 * Creates a new Image with compressed image data.
 	 * Creates a new Image with compressed image data.
 	 *
 	 *
 	 * @param cdata The compressed data from which to load the image.
 	 * @param cdata The compressed data from which to load the image.
 	 **/
 	 **/
-	Image(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL);
+	Image(love::image::CompressedData *cdata, Format format = FORMAT_NORMAL);
 
 
 	/**
 	/**
 	 * Destructor. Deletes the hardware texture and other resources.
 	 * Destructor. Deletes the hardware texture and other resources.
@@ -120,7 +128,7 @@ public:
 	 **/
 	 **/
 	bool refresh();
 	bool refresh();
 
 
-	Texture::Format getFormat() const;
+	Format getFormat() const;
 
 
 	static void setDefaultMipmapSharpness(float sharpness);
 	static void setDefaultMipmapSharpness(float sharpness);
 	static float getDefaultMipmapSharpness();
 	static float getDefaultMipmapSharpness();
@@ -131,6 +139,9 @@ public:
 	static bool hasCompressedTextureSupport(image::CompressedData::Format format);
 	static bool hasCompressedTextureSupport(image::CompressedData::Format format);
 	static bool hasSRGBSupport();
 	static bool hasSRGBSupport();
 
 
+	static bool getConstant(const char *in, Format &out);
+	static bool getConstant(Format in, const char *&out);
+
 private:
 private:
 
 
 	void uploadDefaultTexture();
 	void uploadDefaultTexture();
@@ -157,8 +168,8 @@ private:
 	// Whether this Image is using a compressed texture.
 	// Whether this Image is using a compressed texture.
 	bool compressed;
 	bool compressed;
 
 
-	// The format to interpret the texture's data as.
-	Texture::Format format;
+	// The format to interpret the image's data as.
+	Format format;
 
 
 	// True if the image wasn't able to be properly created and it had to fall
 	// True if the image wasn't able to be properly created and it had to fall
 	// back to a default texture.
 	// back to a default texture.
@@ -179,6 +190,9 @@ private:
 
 
 	GLenum getCompressedFormat(image::CompressedData::Format cformat) const;
 	GLenum getCompressedFormat(image::CompressedData::Format cformat) const;
 
 
+	static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
+	static StringMap<Format, FORMAT_MAX_ENUM> formats;
+
 }; // Image
 }; // Image
 
 
 } // opengl
 } // opengl

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

@@ -98,10 +98,9 @@ Shader::~Shader()
 		detach();
 		detach();
 
 
 	for (auto it = boundRetainables.begin(); it != boundRetainables.end(); ++it)
 	for (auto it = boundRetainables.begin(); it != boundRetainables.end(); ++it)
-	{
 		it->second->release();
 		it->second->release();
-		boundRetainables.erase(it);
-	}
+
+	boundRetainables.clear();
 
 
 	unloadVolatile();
 	unloadVolatile();
 }
 }

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

@@ -52,7 +52,7 @@ public:
 
 
 	enum UsageHint
 	enum UsageHint
 	{
 	{
-		USAGE_DYNAMIC = 1,
+		USAGE_DYNAMIC,
 		USAGE_STATIC,
 		USAGE_STATIC,
 		USAGE_STREAM,
 		USAGE_STREAM,
 		USAGE_MAX_ENUM
 		USAGE_MAX_ENUM

+ 12 - 4
src/modules/graphics/opengl/VertexBuffer.cpp

@@ -115,10 +115,18 @@ void VertexBuffer::unmap()
 		is_bound = true;
 		is_bound = true;
 	}
 	}
 
 
-	// "orphan" current buffer to avoid implicit synchronisation on the GPU:
-	// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
-	glBufferData(getTarget(), (GLsizeiptr) getSize(), NULL,       getUsage());
-	glBufferData(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage());
+	if (getUsage() == GL_STATIC_DRAW)
+	{
+		// Upload the mapped data to the buffer.
+		glBufferSubData(getTarget(), 0, (GLsizeiptr) getSize(), memory_map);
+	}
+	else
+	{
+		// "orphan" current buffer to avoid implicit synchronisation on the GPU:
+		// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
+		glBufferData(getTarget(), (GLsizeiptr) getSize(), NULL,       getUsage());
+		glBufferData(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage());
+	}
 
 
 	is_mapped = false;
 	is_mapped = false;
 }
 }

+ 3 - 3
src/modules/graphics/opengl/wrap_Canvas.cpp

@@ -113,10 +113,10 @@ int w_Canvas_clear(lua_State *L)
 int w_Canvas_getFormat(lua_State *L)
 int w_Canvas_getFormat(lua_State *L)
 {
 {
 	Canvas *canvas = luax_checkcanvas(L, 1);
 	Canvas *canvas = luax_checkcanvas(L, 1);
-	Texture::Format format = canvas->getTextureFormat();
+	Canvas::Format format = canvas->getTextureFormat();
 	const char *str;
 	const char *str;
-	if (!Texture::getConstant(format, str))
-		return luaL_error(L, "Unknown texture format.");
+	if (!Canvas::getConstant(format, str))
+		return luaL_error(L, "Unknown Canvas format.");
 
 
 	lua_pushstring(L, str);
 	lua_pushstring(L, str);
 	return 1;
 	return 1;

+ 21 - 11
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -149,14 +149,11 @@ int w_newImage(lua_State *L)
 	love::image::ImageData *data = nullptr;
 	love::image::ImageData *data = nullptr;
 	love::image::CompressedData *cdata = nullptr;
 	love::image::CompressedData *cdata = nullptr;
 
 
-	Texture::Format format = Texture::FORMAT_NORMAL;
+	Image::Format format = Image::FORMAT_NORMAL;
 	const char *fstr = lua_isnoneornil(L, 2) ? nullptr : luaL_checkstring(L, 2);
 	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.");
+	if (fstr != nullptr && !Image::getConstant(fstr, format))
+		return luaL_error(L, "Invalid Image format: %s", fstr);
 
 
 	// Convert to FileData, if necessary.
 	// Convert to FileData, if necessary.
 	if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_T))
 	if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_T))
@@ -328,9 +325,9 @@ int w_newCanvas(lua_State *L)
 	const char *str = luaL_optstring(L, 3, "normal");
 	const char *str = luaL_optstring(L, 3, "normal");
 	int fsaa        = luaL_optint(L, 4, 0);
 	int fsaa        = luaL_optint(L, 4, 0);
 
 
-	Texture::Format format;
-	if (!Texture::getConstant(str, format))
-		return luaL_error(L, "Invalid texture format: %s", str);
+	Canvas::Format format;
+	if (!Canvas::getConstant(str, format))
+		return luaL_error(L, "Invalid Canvas format: %s", str);
 
 
 	Canvas *canvas = nullptr;
 	Canvas *canvas = nullptr;
 	EXCEPT_GUARD(canvas = instance->newCanvas(width, height, format, fsaa);)
 	EXCEPT_GUARD(canvas = instance->newCanvas(width, height, format, fsaa);)
@@ -944,7 +941,7 @@ int w_isSupported(lua_State *L)
 		switch (support)
 		switch (support)
 		{
 		{
 		case Graphics::SUPPORT_HDR_CANVAS:
 		case Graphics::SUPPORT_HDR_CANVAS:
-			if (!Canvas::isHDRSupported())
+			if (!Canvas::isFormatSupported(Canvas::FORMAT_HDR))
 				supported = false;
 				supported = false;
 			break;
 			break;
 		case Graphics::SUPPORT_MULTI_CANVAS:
 		case Graphics::SUPPORT_MULTI_CANVAS:
@@ -964,7 +961,7 @@ int w_isSupported(lua_State *L)
 				supported = false;
 				supported = false;
 			break;
 			break;
 		case Graphics::SUPPORT_SRGB:
 		case Graphics::SUPPORT_SRGB:
-			if (!Canvas::isSRGBSupported())
+			if (!Canvas::isFormatSupported(Canvas::FORMAT_SRGB))
 				supported = false;
 				supported = false;
 			break;
 			break;
 		default:
 		default:
@@ -977,6 +974,18 @@ int w_isSupported(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_hasCanvasFormat(lua_State *L)
+{
+	const char *str = luaL_checkstring(L, 1);
+	Canvas::Format format;
+
+	if (!Canvas::getConstant(str, format))
+		return luaL_error(L, "Invalid canvas format: %s", str);
+
+	luax_pushboolean(L, Canvas::isFormatSupported(format));
+	return 1;
+}
+
 int w_getRendererInfo(lua_State *L)
 int w_getRendererInfo(lua_State *L)
 {
 {
 	std::string name, version, vendor, device;
 	std::string name, version, vendor, device;
@@ -1351,6 +1360,7 @@ static const luaL_Reg functions[] =
 	{ "getShader", w_getShader },
 	{ "getShader", w_getShader },
 
 
 	{ "isSupported", w_isSupported },
 	{ "isSupported", w_isSupported },
+	{ "hasCanvasFormat", w_hasCanvasFormat },
 	{ "getRendererInfo", w_getRendererInfo },
 	{ "getRendererInfo", w_getRendererInfo },
 	{ "getSystemLimit", w_getSystemLimit },
 	{ "getSystemLimit", w_getSystemLimit },
 
 

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

@@ -89,6 +89,7 @@ int w_getCanvas(lua_State *L);
 int w_setShader(lua_State *L);
 int w_setShader(lua_State *L);
 int w_getShader(lua_State *L);
 int w_getShader(lua_State *L);
 int w_isSupported(lua_State *L);
 int w_isSupported(lua_State *L);
+int w_hasCanvasFormat(lua_State *L);
 int w_getRendererInfo(lua_State *L);
 int w_getRendererInfo(lua_State *L);
 int w_getSystemLimit(lua_State *L);
 int w_getSystemLimit(lua_State *L);
 int w_draw(lua_State *L);
 int w_draw(lua_State *L);

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

@@ -49,7 +49,7 @@ public:
 
 
 	enum Format
 	enum Format
 	{
 	{
-		FORMAT_TGA = 1,
+		FORMAT_TGA,
 		FORMAT_BMP,
 		FORMAT_BMP,
 		FORMAT_JPG,
 		FORMAT_JPG,
 		FORMAT_PNG,
 		FORMAT_PNG,

+ 2 - 2
src/modules/joystick/Joystick.h

@@ -157,9 +157,9 @@ public:
 	virtual int getID() const = 0;
 	virtual int getID() const = 0;
 
 
 	virtual bool isVibrationSupported() = 0;
 	virtual bool isVibrationSupported() = 0;
-	virtual bool setVibration(float left, float right) = 0;
+	virtual bool setVibration(float left, float right, float duration = -1.0f) = 0;
 	virtual bool setVibration() = 0;
 	virtual bool setVibration() = 0;
-	virtual void getVibration(float &left, float &right) const = 0;
+	virtual void getVibration(float &left, float &right) = 0;
 
 
 	static bool getConstant(const char *in, Hat &out);
 	static bool getConstant(const char *in, Hat &out);
 	static bool getConstant(Hat in, const char *&out);
 	static bool getConstant(Hat in, const char *&out);

+ 78 - 42
src/modules/joystick/sdl/Joystick.cpp

@@ -25,6 +25,11 @@
 
 
 // C++
 // C++
 #include <algorithm>
 #include <algorithm>
+#include <limits>
+
+#ifndef SDL_TICKS_PASSED
+#define SDL_TICKS_PASSED(A, B)  ((Sint32)((B) - (A)) <= 0)
+#endif
 
 
 namespace love
 namespace love
 {
 {
@@ -34,9 +39,9 @@ namespace sdl
 {
 {
 
 
 Joystick::Joystick(int id)
 Joystick::Joystick(int id)
-	: joyhandle(0)
-	, controller(0)
-	, haptic(0)
+	: joyhandle(nullptr)
+	, controller(nullptr)
+	, haptic(nullptr)
 	, instanceid(-1)
 	, instanceid(-1)
 	, id(id)
 	, id(id)
 	, vibration()
 	, vibration()
@@ -44,9 +49,9 @@ Joystick::Joystick(int id)
 }
 }
 
 
 Joystick::Joystick(int id, int joyindex)
 Joystick::Joystick(int id, int joyindex)
-	: joyhandle(0)
-	, controller(0)
-	, haptic(0)
+	: joyhandle(nullptr)
+	, controller(nullptr)
+	, haptic(nullptr)
 	, instanceid(-1)
 	, instanceid(-1)
 	, id(id)
 	, id(id)
 	, vibration()
 	, vibration()
@@ -95,10 +100,7 @@ bool Joystick::open(int deviceindex)
 void Joystick::close()
 void Joystick::close()
 {
 {
 	if (haptic)
 	if (haptic)
-	{
-		SDL_HapticRumbleStop(haptic);
 		SDL_HapticClose(haptic);
 		SDL_HapticClose(haptic);
-	}
 
 
 	if (controller)
 	if (controller)
 		SDL_GameControllerClose(controller);
 		SDL_GameControllerClose(controller);
@@ -106,16 +108,16 @@ void Joystick::close()
 	if (joyhandle)
 	if (joyhandle)
 		SDL_JoystickClose(joyhandle);
 		SDL_JoystickClose(joyhandle);
 
 
-	joyhandle = 0;
-	controller = 0;
-	haptic = 0;
+	joyhandle = nullptr;
+	controller = nullptr;
+	haptic = nullptr;
 	instanceid = -1;
 	instanceid = -1;
 	vibration = Vibration();
 	vibration = Vibration();
 }
 }
 
 
 bool Joystick::isConnected() const
 bool Joystick::isConnected() const
 {
 {
-	return joyhandle != 0 && SDL_JoystickGetAttached(joyhandle);
+	return joyhandle != nullptr && SDL_JoystickGetAttached(joyhandle);
 }
 }
 
 
 const char *Joystick::getName() const
 const char *Joystick::getName() const
@@ -209,7 +211,7 @@ bool Joystick::openGamepad(int deviceindex)
 	if (isGamepad())
 	if (isGamepad())
 	{
 	{
 		SDL_GameControllerClose(controller);
 		SDL_GameControllerClose(controller);
-		controller = 0;
+		controller = nullptr;
 	}
 	}
 
 
 	controller = SDL_GameControllerOpen(deviceindex);
 	controller = SDL_GameControllerOpen(deviceindex);
@@ -218,7 +220,7 @@ bool Joystick::openGamepad(int deviceindex)
 
 
 bool Joystick::isGamepad() const
 bool Joystick::isGamepad() const
 {
 {
-	return controller != 0;
+	return controller != nullptr;
 }
 }
 
 
 float Joystick::getGamepadAxis(love::joystick::Joystick::GamepadAxis axis) const
 float Joystick::getGamepadAxis(love::joystick::Joystick::GamepadAxis axis) const
@@ -289,13 +291,13 @@ bool Joystick::checkCreateHaptic()
 	if (haptic)
 	if (haptic)
 	{
 	{
 		SDL_HapticClose(haptic);
 		SDL_HapticClose(haptic);
-		haptic = 0;
+		haptic = nullptr;
 	}
 	}
 
 
 	haptic = SDL_HapticOpenFromJoystick(joyhandle);
 	haptic = SDL_HapticOpenFromJoystick(joyhandle);
 	vibration = Vibration();
 	vibration = Vibration();
 
 
-	return haptic != 0;
+	return haptic != nullptr;
 }
 }
 
 
 bool Joystick::isVibrationSupported()
 bool Joystick::isVibrationSupported()
@@ -312,8 +314,8 @@ bool Joystick::isVibrationSupported()
 	if (isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0)
 	if (isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0)
 		return true;
 		return true;
 
 
-	// Check SDL's simple rumble as a last resort.
-	if (SDL_HapticRumbleSupported(haptic) == 1)
+	// Test for simple sine wave support as a last resort.
+	if ((features & SDL_HAPTIC_SINE) != 0)
 		return true;
 		return true;
 
 
 	return false;
 	return false;
@@ -325,7 +327,7 @@ bool Joystick::runVibrationEffect()
 	{
 	{
 		if (SDL_HapticUpdateEffect(haptic, vibration.id, &vibration.effect) == 0)
 		if (SDL_HapticUpdateEffect(haptic, vibration.id, &vibration.effect) == 0)
 		{
 		{
-			if (SDL_HapticRunEffect(haptic, vibration.id, 1) != -1)
+			if (SDL_HapticRunEffect(haptic, vibration.id, 1) == 0)
 				return true;
 				return true;
 		}
 		}
 
 
@@ -336,17 +338,14 @@ bool Joystick::runVibrationEffect()
 
 
 	vibration.id = SDL_HapticNewEffect(haptic, &vibration.effect);
 	vibration.id = SDL_HapticNewEffect(haptic, &vibration.effect);
 
 
-	if (vibration.id != -1 && SDL_HapticRunEffect(haptic, vibration.id, 1) != -1)
+	if (vibration.id != -1 && SDL_HapticRunEffect(haptic, vibration.id, 1) == 0)
 		return true;
 		return true;
 	
 	
 	return false;
 	return false;
 }
 }
 
 
-bool Joystick::setVibration(float left, float right)
+bool Joystick::setVibration(float left, float right, float duration)
 {
 {
-	// TODO: support non-infinite durations? The working Tattiebogle Xbox
-	// controller driver in OS X seems to ignore durations under 1 second.
-
 	left = std::min(std::max(left, 0.0f), 1.0f);
 	left = std::min(std::max(left, 0.0f), 1.0f);
 	right = std::min(std::max(right, 0.0f), 1.0f);
 	right = std::min(std::max(right, 0.0f), 1.0f);
 
 
@@ -356,24 +355,32 @@ bool Joystick::setVibration(float left, float right)
 	if (!checkCreateHaptic())
 	if (!checkCreateHaptic())
 		return false;
 		return false;
 
 
+	Uint32 length = SDL_HAPTIC_INFINITY;
+	if (duration >= 0.0f)
+	{
+		float maxduration = std::numeric_limits<Uint32>::max() / 1000.0f;
+		length = Uint32(std::min(duration, maxduration) * 1000);
+	}
+
 	bool success = false;
 	bool success = false;
 	unsigned int features = SDL_HapticQuery(haptic);
 	unsigned int features = SDL_HapticQuery(haptic);
+	int axes = SDL_HapticNumAxes(haptic);
 
 
 	if ((features & SDL_HAPTIC_LEFTRIGHT) != 0)
 	if ((features & SDL_HAPTIC_LEFTRIGHT) != 0)
 	{
 	{
 		memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
 		memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
 		vibration.effect.type = SDL_HAPTIC_LEFTRIGHT;
 		vibration.effect.type = SDL_HAPTIC_LEFTRIGHT;
 
 
-		vibration.effect.leftright.length = SDL_HAPTIC_INFINITY;
+		vibration.effect.leftright.length = length;
 		vibration.effect.leftright.large_magnitude = Uint16(left * LOVE_UINT16_MAX);
 		vibration.effect.leftright.large_magnitude = Uint16(left * LOVE_UINT16_MAX);
 		vibration.effect.leftright.small_magnitude = Uint16(right * LOVE_UINT16_MAX);
 		vibration.effect.leftright.small_magnitude = Uint16(right * LOVE_UINT16_MAX);
 
 
 		success = runVibrationEffect();
 		success = runVibrationEffect();
 	}
 	}
 
 
-	// Some gamepad drivers only give support for controlling the motors via
-	// a custom FF effect.
-	if (!success && isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0)
+	// Some gamepad drivers only give support for controlling individual motors
+	// through a custom FF effect.
+	if (!success && isGamepad() && (features & SDL_HAPTIC_CUSTOM) && axes == 2)
 	{
 	{
 		// NOTE: this may cause issues with drivers which support custom effects
 		// NOTE: this may cause issues with drivers which support custom effects
 		// but aren't similar to https://github.com/d235j/360Controller .
 		// but aren't similar to https://github.com/d235j/360Controller .
@@ -385,7 +392,7 @@ bool Joystick::setVibration(float left, float right)
 		memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
 		memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
 		vibration.effect.type = SDL_HAPTIC_CUSTOM;
 		vibration.effect.type = SDL_HAPTIC_CUSTOM;
 
 
-		vibration.effect.custom.length = SDL_HAPTIC_INFINITY;
+		vibration.effect.custom.length = length;
 		vibration.effect.custom.channels = 2;
 		vibration.effect.custom.channels = 2;
 		vibration.effect.custom.period = 10;
 		vibration.effect.custom.period = 10;
 		vibration.effect.custom.samples = 2;
 		vibration.effect.custom.samples = 2;
@@ -394,19 +401,36 @@ bool Joystick::setVibration(float left, float right)
 		success = runVibrationEffect();
 		success = runVibrationEffect();
 	}
 	}
 
 
-	// Fall back to a simple rumble if all else fails. SDL's simple rumble API
-	// only supports a single strength value.
-	if (!success && SDL_HapticRumbleInit(haptic) == 0)
+	// Fall back to a simple sine wave if all else fails. This only supports a
+	// single strength value.
+	if (!success && (features & SDL_HAPTIC_SINE) != 0)
 	{
 	{
+		memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
+		vibration.effect.type = SDL_HAPTIC_SINE;
+
+		vibration.effect.periodic.length = length;
+		vibration.effect.periodic.period = 10;
+
 		float strength = std::max(left, right);
 		float strength = std::max(left, right);
-		int played = SDL_HapticRumblePlay(haptic, strength, SDL_HAPTIC_INFINITY);
-		success = (played == 0);
+		vibration.effect.periodic.magnitude = Sint16(strength * 0x7FFF);
+
+		success = runVibrationEffect();
 	}
 	}
 
 
 	if (success)
 	if (success)
 	{
 	{
 		vibration.left = left;
 		vibration.left = left;
 		vibration.right = right;
 		vibration.right = right;
+
+		if (length == SDL_HAPTIC_INFINITY)
+			vibration.endtime = SDL_HAPTIC_INFINITY;
+		else
+			vibration.endtime = SDL_GetTicks() + length;
+	}
+	else
+	{
+		vibration.left = vibration.right = 0.0f;
+		vibration.endtime = SDL_HAPTIC_INFINITY;
 	}
 	}
 
 
 	return success;
 	return success;
@@ -417,12 +441,7 @@ bool Joystick::setVibration()
 	bool success = true;
 	bool success = true;
 
 
 	if (SDL_WasInit(SDL_INIT_HAPTIC) && haptic && SDL_HapticIndex(haptic) != -1)
 	if (SDL_WasInit(SDL_INIT_HAPTIC) && haptic && SDL_HapticIndex(haptic) != -1)
-	{
-		// Stop all playing effects on the haptic device.
-		// FIXME: We should only stop the vibration effect, in case we use the
-		// Haptic API for other things in the future.
-		success = (SDL_HapticStopAll(haptic) == 0);
-	}
+		success = (SDL_HapticStopEffect(haptic, vibration.id) == 0);
 
 
 	if (success)
 	if (success)
 		vibration.left = vibration.right = 0.0f;
 		vibration.left = vibration.right = 0.0f;
@@ -430,8 +449,25 @@ bool Joystick::setVibration()
 	return success;
 	return success;
 }
 }
 
 
-void Joystick::getVibration(float &left, float &right) const
+void Joystick::getVibration(float &left, float &right)
 {
 {
+	if (vibration.endtime != SDL_HAPTIC_INFINITY)
+	{
+		// With some drivers, the effect physically stops at the right time, but
+		// SDL_HapticGetEffectStatus still thinks it's playing. So we explicitly
+		// stop it once it's done, just to be sure.
+		if (SDL_TICKS_PASSED(SDL_GetTicks(), vibration.endtime))
+		{
+			setVibration();
+			vibration.endtime = SDL_HAPTIC_INFINITY;
+		}
+	}
+
+	// Check if the haptic effect has stopped playing.
+	int id = vibration.id;
+	if (!haptic || id == -1 || SDL_HapticGetEffectStatus(haptic, id) != 1)
+		vibration.left = vibration.right = 0.0f;
+
 	left = vibration.left;
 	left = vibration.left;
 	right = vibration.right;
 	right = vibration.right;
 }
 }

+ 7 - 3
src/modules/joystick/sdl/Joystick.h

@@ -74,9 +74,9 @@ public:
 	int getID() const;
 	int getID() const;
 
 
 	bool isVibrationSupported();
 	bool isVibrationSupported();
-	bool setVibration(float left, float right);
+	bool setVibration(float left, float right, float duration = -1.0f);
 	bool setVibration();
 	bool setVibration();
-	void getVibration(float &left, float &right) const;
+	void getVibration(float &left, float &right);
 
 
 	static bool getConstant(Hat in, Uint8 &out);
 	static bool getConstant(Hat in, Uint8 &out);
 	static bool getConstant(Uint8 in, Hat &out);
 	static bool getConstant(Uint8 in, Hat &out);
@@ -111,8 +111,12 @@ private:
 		Uint16 data[4];
 		Uint16 data[4];
 		int id;
 		int id;
 
 
+		Uint32 endtime;
+
 		Vibration()
 		Vibration()
-			: left(0.0f), right(0.0f), effect(), data(), id(-1)
+			: left(0.0f), right(0.0f)
+			, effect(), data(), id(-1)
+			, endtime(SDL_HAPTIC_INFINITY)
 		{}
 		{}
 
 
 	} vibration;
 	} vibration;

+ 7 - 7
src/modules/joystick/sdl/JoystickModule.cpp

@@ -77,7 +77,7 @@ const char *JoystickModule::getName() const
 love::joystick::Joystick *JoystickModule::getJoystick(int joyindex)
 love::joystick::Joystick *JoystickModule::getJoystick(int joyindex)
 {
 {
 	if (joyindex < 0 || (size_t) joyindex >= activeSticks.size())
 	if (joyindex < 0 || (size_t) joyindex >= activeSticks.size())
-		return 0;
+		return nullptr;
 
 
 	return activeSticks[joyindex];
 	return activeSticks[joyindex];
 }
 }
@@ -107,13 +107,13 @@ love::joystick::Joystick *JoystickModule::getJoystickFromID(int instanceid)
 			return activeSticks[i];
 			return activeSticks[i];
 	}
 	}
 
 
-	return 0;
+	return nullptr;
 }
 }
 
 
 love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex)
 love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex)
 {
 {
 	if (deviceindex < 0 || deviceindex >= SDL_NumJoysticks())
 	if (deviceindex < 0 || deviceindex >= SDL_NumJoysticks())
-		return 0;
+		return nullptr;
 
 
 	std::string guidstr = getDeviceGUID(deviceindex);
 	std::string guidstr = getDeviceGUID(deviceindex);
 	joystick::Joystick *joystick = 0;
 	joystick::Joystick *joystick = 0;
@@ -140,7 +140,7 @@ love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex)
 	removeJoystick(joystick);
 	removeJoystick(joystick);
 
 
 	if (!joystick->open(deviceindex))
 	if (!joystick->open(deviceindex))
-		return 0;
+		return nullptr;
 
 
 	// Make sure multiple instances of the same physical joystick aren't added
 	// Make sure multiple instances of the same physical joystick aren't added
 	// to the active list.
 	// to the active list.
@@ -262,7 +262,7 @@ bool JoystickModule::setGamepadMapping(const std::string &guid, Joystick::Gamepa
 	if (status == 1)
 	if (status == 1)
 		checkGamepads(guid);
 		checkGamepads(guid);
 
 
-	return  status >= 0;
+	return status >= 0;
 }
 }
 
 
 Joystick::JoystickInput JoystickModule::getGamepadMapping(const std::string &guid, Joystick::GamepadInput gpinput)
 Joystick::JoystickInput JoystickModule::getGamepadMapping(const std::string &guid, Joystick::GamepadInput gpinput)
@@ -318,7 +318,7 @@ std::string JoystickModule::stringFromGamepadInput(Joystick::GamepadInput gpinpu
 	SDL_GameControllerAxis sdlaxis;
 	SDL_GameControllerAxis sdlaxis;
 	SDL_GameControllerButton sdlbutton;
 	SDL_GameControllerButton sdlbutton;
 
 
-	const char *gpinputname = 0;
+	const char *gpinputname = nullptr;
 
 
 	switch (gpinput.type)
 	switch (gpinput.type)
 	{
 	{
@@ -435,7 +435,7 @@ void JoystickModule::checkGamepads(const std::string &guid) const
 			// Big hack time: open the index as a game controller and compare
 			// Big hack time: open the index as a game controller and compare
 			// the underlying joystick handle to the active stick's.
 			// the underlying joystick handle to the active stick's.
 			SDL_GameController *ctrl = SDL_GameControllerOpen(d_index);
 			SDL_GameController *ctrl = SDL_GameControllerOpen(d_index);
-			if (ctrl == NULL)
+			if (ctrl == nullptr)
 				continue;
 				continue;
 
 
 			SDL_Joystick *stick = SDL_GameControllerGetJoystick(ctrl);
 			SDL_Joystick *stick = SDL_GameControllerGetJoystick(ctrl);

+ 2 - 1
src/modules/joystick/sdl/wrap_Joystick.cpp

@@ -207,7 +207,8 @@ int w_Joystick_setVibration(lua_State *L)
 	{
 	{
 		float left = (float) luaL_checknumber(L, 2);
 		float left = (float) luaL_checknumber(L, 2);
 		float right = (float) luaL_optnumber(L, 3, left);
 		float right = (float) luaL_optnumber(L, 3, left);
-		success = j->setVibration(left, right);
+		float duration = (float) luaL_optnumber(L, 4, -1.0); // -1 is infinite.
+		success = j->setVibration(left, right, duration);
 	}
 	}
 
 
 	luax_pushboolean(L, success);
 	luax_pushboolean(L, success);

+ 2 - 2
src/modules/joystick/sdl/wrap_JoystickModule.cpp

@@ -28,7 +28,7 @@ namespace joystick
 namespace sdl
 namespace sdl
 {
 {
 
 
-static JoystickModule *instance = 0;
+static JoystickModule *instance = nullptr;
 
 
 int w_getJoysticks(lua_State *L)
 int w_getJoysticks(lua_State *L)
 {
 {
@@ -194,7 +194,7 @@ static const lua_CFunction types[] =
 
 
 extern "C" int luaopen_love_joystick(lua_State *L)
 extern "C" int luaopen_love_joystick(lua_State *L)
 {
 {
-	if (instance == 0)
+	if (instance == nullptr)
 	{
 	{
 		EXCEPT_GUARD(instance = new JoystickModule();)
 		EXCEPT_GUARD(instance = new JoystickModule();)
 	}
 	}

+ 2 - 2
src/modules/sound/lullaby/Mpg123Decoder.cpp

@@ -117,7 +117,7 @@ int Mpg123Decoder::decode()
 			size += numbytes;
 			size += numbytes;
 			long rate = 0;
 			long rate = 0;
 			int encoding = 0;
 			int encoding = 0;
-			int ret = mpg123_getformat(handle, &rate, &channels, &encoding);
+			mpg123_getformat(handle, &rate, &channels, &encoding);
 			if (rate == 0)
 			if (rate == 0)
 				rate = sampleRate;
 				rate = sampleRate;
 			else
 			else
@@ -126,7 +126,7 @@ int Mpg123Decoder::decode()
 				channels = MPG123_STEREO;
 				channels = MPG123_STEREO;
 			if (encoding == 0)
 			if (encoding == 0)
 				encoding = MPG123_ENC_SIGNED_16;
 				encoding = MPG123_ENC_SIGNED_16;
-			ret = mpg123_format(handle, rate, channels, encoding);
+			int ret = mpg123_format(handle, rate, channels, encoding);
 			if (ret != MPG123_OK)
 			if (ret != MPG123_OK)
 				throw love::Exception("Could not set output format.");
 				throw love::Exception("Could not set output format.");
 		}
 		}

+ 24 - 30
src/modules/system/System.cpp

@@ -25,8 +25,9 @@
 #if defined(LOVE_MACOSX)
 #if defined(LOVE_MACOSX)
 #include <CoreServices/CoreServices.h>
 #include <CoreServices/CoreServices.h>
 #elif defined(LOVE_LINUX)
 #elif defined(LOVE_LINUX)
-#include <stdlib.h>
-#include <unistd.h>
+#include <spawn.h>
+//#include <stdlib.h>
+//#include <unistd.h>
 #include <sys/wait.h>
 #include <sys/wait.h>
 #elif defined(LOVE_WINDOWS)
 #elif defined(LOVE_WINDOWS)
 #include "common/utf8.h"
 #include "common/utf8.h"
@@ -53,12 +54,17 @@ std::string System::getOS() const
 #endif
 #endif
 }
 }
 
 
+extern "C"
+{
+	extern char **environ; // The environment, always available
+}
+
 bool System::openURL(const std::string &url) const
 bool System::openURL(const std::string &url) const
 {
 {
-	bool success = false;
 
 
 #if defined(LOVE_MACOSX)
 #if defined(LOVE_MACOSX)
 
 
+	bool success = false;
 	// We could be lazy and use system("open " + url), but this is safer.
 	// We could be lazy and use system("open " + url), but this is safer.
 	CFURLRef cfurl = CFURLCreateWithBytes(nullptr,
 	CFURLRef cfurl = CFURLCreateWithBytes(nullptr,
 	                                      (const UInt8 *) url.c_str(),
 	                                      (const UInt8 *) url.c_str(),
@@ -68,34 +74,24 @@ bool System::openURL(const std::string &url) const
 
 
 	success = LSOpenCFURLRef(cfurl, nullptr) == noErr;
 	success = LSOpenCFURLRef(cfurl, nullptr) == noErr;
 	CFRelease(cfurl);
 	CFRelease(cfurl);
+	return success;
 
 
 #elif defined(LOVE_LINUX)
 #elif defined(LOVE_LINUX)
 
 
-	// Spawn a child process, which we'll replace with xdg-open.
-	pid_t pid = vfork();
-
-	if (pid == 0) // Child process.
-	{
-		// Replace the child process with xdg-open and pass in the URL.
-		execlp("xdg-open", "xdg-open", url.c_str(), nullptr);
-
-		// exec will only return if it errored, so we should exit with non-zero.
-		_exit(1);
-	}
-	else if (pid > 0) // Parent process.
-	{
-		// Wait for xdg-open to complete (or fail.)
-		int status = 0;
-		if (waitpid(pid, &status, 0) == pid)
-			success = (status == 0);
-		else
-			success = false;
-	}
+	pid_t pid;
+	const char *argv[] = {"xdg-open", url.c_str(), nullptr};
+
+	// Note: at the moment this process inherits our file descriptors.
+	// Note: the below const_cast is really ugly as well.
+	if (posix_spawnp(&pid, "xdg-open", nullptr, nullptr, const_cast<char **>(argv), environ) != 0)
+		return false;
+
+	// Wait for xdg-open to complete (or fail.)
+	int status = 0;
+	if (waitpid(pid, &status, 0) == pid)
+		return (status == 0);
 	else
 	else
-	{
-		// vfork() failed.
-		success = false;
-	}
+		return false;
 
 
 #elif defined(LOVE_WINDOWS)
 #elif defined(LOVE_WINDOWS)
 
 
@@ -109,11 +105,9 @@ bool System::openURL(const std::string &url) const
 	                                 nullptr,
 	                                 nullptr,
 	                                 SW_SHOW);
 	                                 SW_SHOW);
 
 
-	success = (int) result > 32;
+	return (int) result > 32;
 
 
 #endif
 #endif
-
-	return success;
 }
 }
 
 
 bool System::getConstant(const char *in, System::PowerState &out)
 bool System::getConstant(const char *in, System::PowerState &out)

+ 1 - 0
src/modules/window/sdl/Window.cpp

@@ -304,6 +304,7 @@ void Window::setWindowGLAttributes(int fsaa, bool /* sRGB */) const
 	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
 	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
 	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
 	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
 	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
 	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
+	SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0);
 
 
 	// FSAA.
 	// FSAA.
 	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (fsaa > 0) ? 1 : 0);
 	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (fsaa > 0) ? 1 : 0);

+ 0 - 1
src/scripts/boot.lua

@@ -238,7 +238,6 @@ local no_game_code = false
 function love.boot()
 function love.boot()
 
 
 	-- This is absolutely needed.
 	-- This is absolutely needed.
-	require("love")
 	require("love.filesystem")
 	require("love.filesystem")
 
 
 	love.arg.parse_options()
 	love.arg.parse_options()

+ 0 - 1
src/scripts/boot.lua.h

@@ -429,7 +429,6 @@ const unsigned char boot_lua[] =
 	0x28, 0x29, 0x0a,
 	0x28, 0x29, 0x0a,
 	0x09, 0x2d, 0x2d, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x62, 0x73, 0x6f, 0x6c, 0x75, 
 	0x09, 0x2d, 0x2d, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x62, 0x73, 0x6f, 0x6c, 0x75, 
 	0x74, 0x65, 0x6c, 0x79, 0x20, 0x6e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x2e, 0x0a,
 	0x74, 0x65, 0x6c, 0x79, 0x20, 0x6e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x2e, 0x0a,
-	0x09, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x28, 0x22, 0x6c, 0x6f, 0x76, 0x65, 0x22, 0x29, 0x0a,
 	0x09, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x28, 0x22, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 
 	0x09, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x28, 0x22, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 
 	0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x22, 0x29, 0x0a,
 	0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x22, 0x29, 0x0a,
 	0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x61, 0x72, 0x67, 0x2e, 0x70, 0x61, 0x72, 0x73, 0x65, 0x5f, 0x6f, 0x70, 
 	0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x61, 0x72, 0x67, 0x2e, 0x70, 0x61, 0x72, 0x73, 0x65, 0x5f, 0x6f, 0x70,