Browse Source

Updated to latest love-android code (b615972822cb)

fysx 10 years ago
parent
commit
9cbddc2f5e
50 changed files with 823 additions and 659 deletions
  1. 24 0
      jni/love/src/common/Matrix.cpp
  2. 21 0
      jni/love/src/common/Matrix.h
  3. 71 83
      jni/love/src/common/android.cpp
  4. 3 3
      jni/love/src/common/int.h
  5. 7 13
      jni/love/src/love.cpp
  6. 4 19
      jni/love/src/modules/filesystem/physfs/Filesystem.cpp
  7. 33 1
      jni/love/src/modules/graphics/Graphics.cpp
  8. 35 2
      jni/love/src/modules/graphics/Graphics.h
  9. 9 6
      jni/love/src/modules/graphics/opengl/Canvas.cpp
  10. 1 1
      jni/love/src/modules/graphics/opengl/Canvas.h
  11. 60 20
      jni/love/src/modules/graphics/opengl/GLBuffer.cpp
  12. 24 7
      jni/love/src/modules/graphics/opengl/GLBuffer.h
  13. 38 100
      jni/love/src/modules/graphics/opengl/Graphics.cpp
  14. 26 30
      jni/love/src/modules/graphics/opengl/Graphics.h
  15. 61 26
      jni/love/src/modules/graphics/opengl/Image.cpp
  16. 5 3
      jni/love/src/modules/graphics/opengl/Image.h
  17. 9 28
      jni/love/src/modules/graphics/opengl/Mesh.cpp
  18. 0 5
      jni/love/src/modules/graphics/opengl/Mesh.h
  19. 38 43
      jni/love/src/modules/graphics/opengl/ParticleSystem.cpp
  20. 3 10
      jni/love/src/modules/graphics/opengl/ParticleSystem.h
  21. 8 18
      jni/love/src/modules/graphics/opengl/SpriteBatch.cpp
  22. 1 5
      jni/love/src/modules/graphics/opengl/SpriteBatch.h
  23. 5 7
      jni/love/src/modules/graphics/opengl/Text.cpp
  24. 1 1
      jni/love/src/modules/graphics/opengl/Text.h
  25. 4 1
      jni/love/src/modules/graphics/opengl/wrap_Canvas.cpp
  26. 60 70
      jni/love/src/modules/graphics/opengl/wrap_Graphics.cpp
  27. 1 0
      jni/love/src/modules/graphics/opengl/wrap_Graphics.h
  28. 66 3
      jni/love/src/modules/graphics/opengl/wrap_Graphics.lua
  29. 2 2
      jni/love/src/modules/graphics/opengl/wrap_Image.cpp
  30. 26 14
      jni/love/src/modules/graphics/opengl/wrap_Mesh.cpp
  31. 3 0
      jni/love/src/modules/graphics/opengl/wrap_Mesh.h
  32. 19 31
      jni/love/src/modules/graphics/opengl/wrap_ParticleSystem.cpp
  33. 34 2
      jni/love/src/modules/graphics/opengl/wrap_Shader.cpp
  34. 2 0
      jni/love/src/modules/graphics/opengl/wrap_Shader.h
  35. 13 13
      jni/love/src/modules/graphics/opengl/wrap_SpriteBatch.cpp
  36. 3 3
      jni/love/src/modules/image/magpie/KTXHandler.cpp
  37. 1 1
      jni/love/src/modules/image/magpie/PKMHandler.cpp
  38. 11 11
      jni/love/src/modules/image/magpie/PVRHandler.cpp
  39. 16 0
      jni/love/src/modules/love/love.cpp
  40. 2 2
      jni/love/src/modules/math/Compressor.cpp
  41. 3 11
      jni/love/src/modules/math/MathModule.cpp
  42. 0 1
      jni/love/src/modules/window/Window.cpp
  43. 0 2
      jni/love/src/modules/window/Window.h
  44. 31 15
      jni/love/src/modules/window/sdl/Window.cpp
  45. 3 1
      jni/love/src/modules/window/sdl/Window.h
  46. 0 4
      jni/love/src/modules/window/wrap_Window.cpp
  47. 9 12
      jni/love/src/scripts/boot.lua
  48. 24 29
      jni/love/src/scripts/boot.lua.h
  49. 1 0
      jni/love/src/scripts/nogame.lua
  50. 2 0
      jni/love/src/scripts/nogame.lua.h

+ 24 - 0
jni/love/src/common/Matrix.cpp

@@ -221,6 +221,11 @@ Matrix3::Matrix3(const Matrix4 &mat4)
 	e[8] = mat4elems[10];
 }
 
+Matrix3::Matrix3(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
+{
+	setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
+}
+
 Matrix3::~Matrix3()
 {
 }
@@ -288,4 +293,23 @@ Matrix3 Matrix3::transposedInverse() const
 	return m;
 }
 
+void Matrix3::setTransformation(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
+{
+	float c = cosf(angle), s = sinf(angle);
+	// matrix multiplication carried out on paper:
+	// |1    x| |c -s  | |sx     | | 1 ky  | |1   -ox|
+	// |  1  y| |s  c  | |   sy  | |kx  1  | |  1 -oy|
+	// |     1| |     1| |      1| |      1| |     1 |
+	//   move    rotate    scale     skew      origin
+	e[0] = c * sx - ky * s * sy; // = a
+	e[1] = s * sx + ky * c * sy; // = b
+	e[3] = kx * c * sx - s * sy; // = c
+	e[4] = kx * s * sx + c * sy; // = d
+	e[6] = x - ox * e[0] - oy * e[3];
+	e[7] = y - ox * e[1] - oy * e[4];
+
+	e[2] = e[5] = 0.0f;
+	e[8] = 1.0f;
+}
+
 } // love

+ 21 - 0
jni/love/src/common/Matrix.h

@@ -185,6 +185,11 @@ public:
 	 **/
 	Matrix3(const Matrix4 &mat4);
 
+	/**
+	 * Creates a new matrix set to a transformation.
+	 **/
+	Matrix3(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
+
 	~Matrix3();
 
 	/**
@@ -205,6 +210,22 @@ public:
 	 **/
 	Matrix3 transposedInverse() const;
 
+	/**
+	 * Creates a transformation with a certain position, orientation, scale
+	 * and offset.
+	 *
+	 * @param x The translation along the x-axis.
+	 * @param y The translation along the y-axis.
+	 * @param angle The rotation (rad) around the center with offset (ox,oy).
+	 * @param sx Scale along x-axis.
+	 * @param sy Scale along y-axis.
+	 * @param ox The offset for rotation along the x-axis.
+	 * @param oy The offset for rotation along the y-axis.
+	 * @param kx Shear along x-axis
+	 * @param ky Shear along y-axis
+	 **/
+	void setTransformation(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
+
 	/**
 	 * Transforms an array of vertices by this matrix.
 	 **/

+ 71 - 83
jni/love/src/common/android.cpp

@@ -35,65 +35,67 @@ namespace love
 namespace android
 {
 
-void setImmersive (bool immersive_active) {
+void setImmersive(bool immersive_active)
+{
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
 
 	jobject activity = (jobject) SDL_AndroidGetActivity();
 
-	jclass clazz (env->GetObjectClass(activity));
-	jmethodID method_id = env->GetMethodID (clazz, "setImmersiveMode", "(Z)V");
+	jclass clazz(env->GetObjectClass(activity));
+	jmethodID method_id = env->GetMethodID(clazz, "setImmersiveMode", "(Z)V");
 
-	env->CallVoidMethod (activity, method_id, immersive_active);
+	env->CallVoidMethod(activity, method_id, immersive_active);
 
-	env->DeleteLocalRef (activity);
-	env->DeleteLocalRef (clazz);
+	env->DeleteLocalRef(activity);
+	env->DeleteLocalRef(clazz);
 }
 
-bool getImmersive () {
+bool getImmersive()
+{
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
 
 	jobject activity = (jobject) SDL_AndroidGetActivity();
 
-	jclass clazz (env->GetObjectClass(activity));
-	jmethodID method_id = env->GetMethodID (clazz, "getImmersiveMode", "()Z");
+	jclass clazz(env->GetObjectClass(activity));
+	jmethodID method_id = env->GetMethodID(clazz, "getImmersiveMode", "()Z");
 
-	jboolean immersive_active = env->CallBooleanMethod (activity, method_id);
+	jboolean immersive_active = env->CallBooleanMethod(activity, method_id);
 
-	env->DeleteLocalRef (activity);
-	env->DeleteLocalRef (clazz);
+	env->DeleteLocalRef(activity);
+	env->DeleteLocalRef(clazz);
 
-	if (immersive_active)
-		return true;
-	return false;
+	return immersive_active;
 }
 
 double getScreenScale()
 {
-  static double result = -1.;
+	static double result = -1.;
 
-  if (result == -1.) {
-    JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
-    jclass activity = env->FindClass("org/love2d/android/GameActivity");
+	if (result == -1.)
+	{
+		JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
+		jclass activity = env->FindClass("org/love2d/android/GameActivity");
 
-    jmethodID getMetrics = env->GetStaticMethodID(activity, "getMetrics", "()Landroid/util/DisplayMetrics;");
-    jobject metrics = env->CallStaticObjectMethod(activity, getMetrics);
-    jclass metricsClass = env->GetObjectClass(metrics);
+		jmethodID getMetrics = env->GetStaticMethodID(activity, "getMetrics", "()Landroid/util/DisplayMetrics;");
+		jobject metrics = env->CallStaticObjectMethod(activity, getMetrics);
+		jclass metricsClass = env->GetObjectClass(metrics);
 
-    result = env->GetFloatField(metrics, env->GetFieldID(metricsClass, "density", "F"));
+		result = env->GetFloatField(metrics, env->GetFieldID(metricsClass, "density", "F"));
 
-    env->DeleteLocalRef (metricsClass);
-    env->DeleteLocalRef (metrics);
-    env->DeleteLocalRef (activity);
-  }
+		env->DeleteLocalRef(metricsClass);
+		env->DeleteLocalRef(metrics);
+		env->DeleteLocalRef(activity);
+	}
 
-  return result;
+	return result;
 }
 
 const char* getSelectedGameFile()
 {
 	static const char *path = NULL;
 
-	if (path) {
+	if (path)
+	{
 		delete path;
 		path = NULL;
 	}
@@ -110,8 +112,8 @@ const char* getSelectedGameFile()
 		env->ReleaseStringUTFChars(gamePath, utf);
 	}
 
-	env->DeleteLocalRef (gamePath);
-	env->DeleteLocalRef (activity);
+	env->DeleteLocalRef(gamePath);
+	env->DeleteLocalRef(activity);
 
 	return path;
 }
@@ -122,16 +124,16 @@ bool openURL(const std::string &url)
 	jclass activity = env->FindClass("org/love2d/android/GameActivity");
 
 	jmethodID openURL= env->GetStaticMethodID(activity, "openURL", "(Ljava/lang/String;)V");
-	jstring url_jstring = (jstring) env->NewStringUTF (url.c_str());
+	jstring url_jstring = (jstring) env->NewStringUTF(url.c_str());
 
-	env->CallStaticVoidMethod (activity, openURL, url_jstring); 	
+	env->CallStaticVoidMethod(activity, openURL, url_jstring);
 
-	env->DeleteLocalRef (url_jstring);
-	env->DeleteLocalRef (activity);
+	env->DeleteLocalRef(url_jstring);
+	env->DeleteLocalRef(activity);
 	return true;
 }
 
-void vibrate (double seconds)
+void vibrate(double seconds)
 {
 	JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
 	jclass activity = env->FindClass("org/love2d/android/GameActivity");
@@ -139,100 +141,86 @@ void vibrate (double seconds)
 	jmethodID vibrate_method = env->GetStaticMethodID(activity, "vibrate", "(D)V");
 	env->CallStaticVoidMethod(activity, vibrate_method, seconds);
 
-	env->DeleteLocalRef (activity);
+	env->DeleteLocalRef(activity);
 }
 
 /*
  * Helper functions for the filesystem module
  */
-void freeGameArchiveMemory (void *ptr) {
-	SDL_Log ("Freeing memory for in-memory LÖVE archive 0x%x", (int) ptr);
+void freeGameArchiveMemory(void *ptr)
+{
 	char *game_love_data = static_cast<char*>(ptr);
 	delete[] game_love_data;
 }
 
-bool loadGameArchiveToMemory (const char* filename, char **ptr, size_t *size) {
-	SDL_Log ("Trying to mount %s", filename);
+bool loadGameArchiveToMemory(const char* filename, char **ptr, size_t *size)
+{
 	SDL_RWops *asset_game_file = SDL_RWFromFile(filename, "rb");
 	if (!asset_game_file) {
-		SDL_Log ("Could not find %s", filename);
+		SDL_Log("Could not find %s", filename);
 		return false;
 	}
 
 	Sint64 file_size = asset_game_file->size(asset_game_file);
-
 	if (file_size <= 0) {
-		SDL_Log ("Could not load game from %s. File has invalid file size: %d.", filename, (int) file_size);
+		SDL_Log("Could not load game from %s. File has invalid file size: %d.", filename, (int) file_size);
 		return false;
 	}
 
-	(*ptr) = new char[file_size];
-	SDL_Log ("Allocated memory for in-memory LÖVE archive at: 0x%x", (int) (*ptr));
-
-	if (!(*ptr)) {
-		SDL_Log ("Could not allocate memory for in-memory game archive");
+	*ptr = new char[file_size];
+	if (!*ptr) {
+		SDL_Log("Could not allocate memory for in-memory game archive");
 		return false;
 	}
 
-	size_t bytes_copied = asset_game_file->read(asset_game_file, (void*) (*ptr), sizeof(char), (size_t) file_size);
-
+	size_t bytes_copied = asset_game_file->read(asset_game_file, (void*) *ptr, sizeof(char), (size_t) file_size);
 	if (bytes_copied != file_size) {
-		SDL_Log ("Only copied %d of %d bytes into in-memory game archive!", (unsigned int) bytes_copied, (unsigned int) file_size);
-		delete[] (*ptr);
+		SDL_Log("Incomplete copy of in-memory game archive!");
+		delete[] *ptr;
 		return false;
 	}
 
-	SDL_Log ("Copied %d of %d bytes into in-memory game archive", (unsigned int) bytes_copied, (unsigned int) file_size);
-
 	*size = (size_t) file_size;
-
 	return true;
 }
 
-bool directoryExists(const char* path) {
-	SDL_Log ("Checking directory exists for %s", path);
+bool directoryExists(const char *path)
+{
 	struct stat s;
 	int err = stat(path, &s);
-	if (err == -1) {
-		if (errno == ENOENT)
-			return false;
-		else {
-			SDL_Log ("Error checking for directory %s errno = %d: %s", path, errno, strerror (errno));
-			return false;
-		}
-	} else if(S_ISDIR(s.st_mode)) {
-		SDL_Log ("Directory %s exists!", path);
-		return true;
+	if (err == -1)
+	{
+		if (errno != ENOENT)
+			SDL_Log ("Error checking for directory %s errno = %d: %s", path, errno, strerror(errno));
+		return false;
 	}
-	return false;
+
+	return S_ISDIR(s.st_mode);
 }
 
-bool mkdir (const char* path) {
+bool mkdir(const char* path)
+{
 	int err = ::mkdir (path, 0770);
-	if (err == -1) {
+	if (err == -1)
+	{
 		SDL_Log ("Error: Could not create directory %s", path);
-			return false;
+		return false;
 	}
 
 	return true;
 }
 
-bool createStorageDirectories () {
+bool createStorageDirectories()
+{
 	std::string internal_storage_path = SDL_AndroidGetInternalStoragePath();
 
 	std::string save_directory = internal_storage_path + "/save";
-	if (!directoryExists (save_directory.c_str())) {
-		if (!mkdir(save_directory.c_str()))
-			return false;
-	}
+	if (!directoryExists(save_directory.c_str()) && !mkdir(save_directory.c_str()))
+		return false;
 
 	std::string game_directory = internal_storage_path + "/game";
-	if (!directoryExists (game_directory.c_str())) {
-		if (!mkdir(game_directory.c_str()))
-			return false;
-	}
-
-	SDL_Log ("Creating storage directories successful!");
+	if (!directoryExists (game_directory.c_str()) && !mkdir(game_directory.c_str()))
+		return false;
 
 	return true;
 }

+ 3 - 3
jni/love/src/common/int.h

@@ -54,12 +54,12 @@ typedef uint32_t uint32;
 typedef int64_t int64;
 typedef uint64_t uint64;
 
-static inline uint16 swap16(uint16 x)
+static inline uint16 swapuint16(uint16 x)
 {
 	return (x >> 8) | (x << 8);
 }
 
-static inline uint32 swap32(uint32 x)
+static inline uint32 swapuint32(uint32 x)
 {
 	return ((x & 0x000000FF) << 24) |
 	       ((x & 0x0000FF00) <<  8) |
@@ -67,7 +67,7 @@ static inline uint32 swap32(uint32 x)
 	       ((x & 0xFF000000) >> 24);
 }
 
-static inline uint64 swap64(uint64 x)
+static inline uint64 swapuint64(uint64 x)
 {
 	return ((x << 56) & 0xFF00000000000000ULL) | ((x << 40) & 0x00FF000000000000ULL) |
 	       ((x << 24) & 0x0000FF0000000000ULL) | ((x <<  8) & 0x000000FF00000000ULL) |

+ 7 - 13
jni/love/src/love.cpp

@@ -18,13 +18,8 @@
  * 3. This notice may not be removed or altered from any source distribution.
  **/
 
-#include "modules/love/love.h"
-
-#ifndef LOVE_ANDROID
 #include "common/version.h"
-#endif
-#include <string>
-
+#include "modules/love/love.h"
 #include <SDL.h>
 
 #ifdef LOVE_BUILD_EXE
@@ -48,6 +43,10 @@ extern "C" {
 #include "common/iOS.h"
 #endif
 
+#ifdef LOVE_ANDROID
+#include "common/android.h"
+#endif
+
 #ifdef LOVE_WINDOWS
 extern "C"
 {
@@ -192,7 +191,7 @@ static int l_print_sdl_log(lua_State *L)
 			break;
 		default:
 			out_string += lua_typename(L, lua_type(L, i));
-			sprintf(pointer_buf, ": 0x%lx", (size_t) lua_topointer(L, i));
+			sprintf(pointer_buf, ": 0x%lx", (long unsigned int) lua_topointer(L, i));
 			out_string += pointer_buf;
 			break;
 		}
@@ -238,17 +237,12 @@ int main(int argc, char **argv)
 	argv = hack_argv;
 #endif // LOVE_LEGENDARY_APP_ARGV_HACK
 
-#ifndef LOVE_ANDROID
-	// TODO: LOVE for Android creates a single library instead of executable
-	// and library. Therefore this check is not required, but causes a
-	// duplicate definition of the love::VERSION symbol.
-	if (strcmp(love::VERSION, love_version()) != 0)
+	if (strcmp(LOVE_VERSION_STRING, love_version()) != 0)
 	{
 		printf("Version mismatch detected!\nLOVE binary is version %s\n"
 				"LOVE library is version %s\n", LOVE_VERSION_STRING, love_version());
 		return 1;
 	}
-#endif
 
 	// Oh, you just want the version? Okay!
 	if (argc > 1 && strcmp(argv[1], "--version") == 0)

+ 4 - 19
jni/love/src/modules/filesystem/physfs/Filesystem.cpp

@@ -175,25 +175,13 @@ bool Filesystem::setIdentity(const char *ident, bool appendToPath)
 		save_identity = "unnamed";
 	
 	std::string internal_storage_path = SDL_AndroidGetInternalStoragePath();
-
 	std::string save_directory = internal_storage_path + "/save";
 
 	save_path_full = std::string(SDL_AndroidGetInternalStoragePath()) + std::string("/save/") + save_identity;
 
-	if (love::android::directoryExists(save_path_full.c_str()))
-		SDL_Log("dir exists");
-	else
-		SDL_Log("does not exist");
-
-	if (!love::android::directoryExists(save_path_full.c_str()))
-	{
-		if (!love::android::mkdir(save_path_full.c_str()))
-			SDL_Log("Error: Could not create save directory %s!", save_path_full.c_str());
-		else
-			SDL_Log("Save directory %s successfuly created!", save_path_full.c_str());
-	}
-	else
-		SDL_Log("Save directory %s exists!", save_path_full.c_str());
+	if (!love::android::directoryExists(save_path_full.c_str()) &&
+			!love::android::mkdir(save_path_full.c_str()))
+		SDL_Log("Error: Could not create save directory %s!", save_path_full.c_str());
 #endif
 
 	// We now have something like:
@@ -253,9 +241,7 @@ bool Filesystem::setSource(const char *source)
 
 	if (archive_loaded)
 	{
-		if (PHYSFS_mountMemory(game_archive_ptr, game_archive_size, love::android::freeGameArchiveMemory, "archive.zip", "/", 0))
-			SDL_Log("Mounting of in-memory game archive successful!");
-		else
+		if (!PHYSFS_mountMemory(game_archive_ptr, game_archive_size, love::android::freeGameArchiveMemory, "archive.zip", "/", 0))
 		{
 			SDL_Log("Mounting of in-memory game archive failed!");
 			love::android::freeGameArchiveMemory(game_archive_ptr);
@@ -278,7 +264,6 @@ bool Filesystem::setSource(const char *source)
 
 		if (sdcard_main)
 		{
-			SDL_Log("using game from %s", game_path.c_str());
 			new_search_path = game_path;
 			sdcard_main->close(sdcard_main);
 

+ 33 - 1
jni/love/src/modules/graphics/Graphics.cpp

@@ -19,12 +19,45 @@
  **/
 
 #include "Graphics.h"
+#include "math/MathModule.h"
 
 namespace love
 {
 namespace graphics
 {
 
+static bool gammaCorrect = false;
+
+void setGammaCorrect(bool gammacorrect)
+{
+	gammaCorrect = gammacorrect;
+}
+
+bool isGammaCorrect()
+{
+	return gammaCorrect;
+}
+
+void gammaCorrectColor(Colorf &c)
+{
+	if (isGammaCorrect())
+	{
+		c.r = math::Math::instance.gammaToLinear(c.r);
+		c.g = math::Math::instance.gammaToLinear(c.g);
+		c.b = math::Math::instance.gammaToLinear(c.b);
+	}
+}
+
+void unGammaCorrectColor(Colorf &c)
+{
+	if (isGammaCorrect())
+	{
+		c.r = math::Math::instance.linearToGamma(c.r);
+		c.g = math::Math::instance.linearToGamma(c.g);
+		c.b = math::Math::instance.linearToGamma(c.b);
+	}
+}
+
 Graphics::~Graphics()
 {
 }
@@ -150,7 +183,6 @@ StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM>::Entry Graphics::suppor
 {
 	{ "multicanvas", SUPPORT_MULTI_CANVAS },
 	{ "multicanvasformats", SUPPORT_MULTI_CANVAS_FORMATS },
-	{ "srgb", SUPPORT_SRGB },
 };
 
 StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM> Graphics::support(Graphics::supportEntries, sizeof(Graphics::supportEntries));

+ 35 - 2
jni/love/src/modules/graphics/Graphics.h

@@ -24,6 +24,7 @@
 // LOVE
 #include "common/Module.h"
 #include "common/StringMap.h"
+#include "Color.h"
 
 // C++
 #include <string>
@@ -33,6 +34,31 @@ namespace love
 namespace graphics
 {
 
+/**
+ * Globally sets whether gamma correction is enabled. Ideally this should be set
+ * prior to using any Graphics module function.
+ **/
+void setGammaCorrect(bool gammacorrect);
+
+/**
+ * Gets whether global gamma correction is enabled.
+ **/
+bool isGammaCorrect();
+
+/**
+ * Gamma-corrects a color (converts it from sRGB to linear RGB, if
+ * gamma correction is enabled.)
+ * The color's components are expected to be in the range of [0, 1].
+ **/
+void gammaCorrectColor(Colorf &c);
+
+/**
+ * Un-gamma-corrects a color (converts it from linear RGB to sRGB, if
+ * gamma correction is enabled.)
+ * The color's components are expected to be in the range of [0, 1].
+ **/
+void unGammaCorrectColor(Colorf &c);
+
 class Graphics : public Module
 {
 public:
@@ -74,7 +100,6 @@ public:
 	{
 		SUPPORT_MULTI_CANVAS,
 		SUPPORT_MULTI_CANVAS_FORMATS,
-		SUPPORT_SRGB,
 		SUPPORT_MAX_ENUM
 	};
 
@@ -134,6 +159,14 @@ public:
 	{
 		bool r, g, b, a;
 
+		ColorMask()
+			: r(true), g(true), b(true), a(true)
+		{}
+
+		ColorMask(bool _r, bool _g, bool _b, bool _a)
+			: r(_r), g(_g), b(_b), a(_a)
+		{}
+
 		bool operator == (const ColorMask &m) const
 		{
 			return r == m.r && g == m.g && b == m.b && a == m.a;
@@ -160,7 +193,7 @@ public:
 	 * @param width The viewport width.
 	 * @param height The viewport height.
 	 **/
-	virtual bool setMode(int width, int height, bool &sRGB) = 0;
+	virtual bool setMode(int width, int height) = 0;
 
 	/**
 	 * Un-sets the current graphics display mode (uninitializing objects if

+ 9 - 6
jni/love/src/modules/graphics/opengl/Canvas.cpp

@@ -398,7 +398,7 @@ void Canvas::startGrab(const std::vector<Canvas *> &canvases)
 	// Whether the new canvas list is different from the old one.
 	// A more thorough check is done below.
 	bool canvaseschanged = canvases.size() != attachedCanvases.size();
-	bool hasSRGBcanvas = (format == FORMAT_SRGB);
+	bool hasSRGBcanvas = getSizedFormat(format) == FORMAT_SRGB;
 
 	if (canvases.size() > 0)
 	{
@@ -430,7 +430,7 @@ void Canvas::startGrab(const std::vector<Canvas *> &canvases)
 		if (!canvaseschanged && canvases[i] != attachedCanvases[i])
 			canvaseschanged = true;
 
-		if (otherformat == FORMAT_SRGB)
+		if (getSizedFormat(otherformat) == FORMAT_SRGB)
 			hasSRGBcanvas = true;
 	}
 
@@ -484,9 +484,10 @@ void Canvas::startGrab()
 	// Make sure the correct sRGB setting is used when drawing to the canvas.
 	if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
 	{
-		if (format == FORMAT_SRGB && !gl.hasFramebufferSRGB())
+		bool isSRGB = getSizedFormat(format) == FORMAT_SRGB;
+		if (isSRGB && !gl.hasFramebufferSRGB())
 			gl.setFramebufferSRGB(true);
-		else if (format != FORMAT_SRGB && gl.hasFramebufferSRGB())
+		else if (!isSRGB && gl.hasFramebufferSRGB())
 			gl.setFramebufferSRGB(false);
 	}
 
@@ -655,8 +656,10 @@ Canvas::Format Canvas::getSizedFormat(Canvas::Format format)
 	switch (format)
 	{
 	case FORMAT_NORMAL:
-		// 32-bit render targets don't have guaranteed support on OpenGL ES 2.
-		if (GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_rgb8_rgba8 || GLAD_ARM_rgba8))
+		if (isGammaCorrect())
+			return FORMAT_SRGB;
+		else if (GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_rgb8_rgba8 || GLAD_ARM_rgba8))
+			// 32-bit render targets don't have guaranteed support on GLES2.
 			return FORMAT_RGBA4;
 		else
 			return FORMAT_RGBA8;

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

@@ -46,7 +46,7 @@ public:
 	// Different Canvas render target formats.
 	enum Format
 	{
-		FORMAT_NORMAL,   // Usually RGBA8 or a similar fallback. Always supported.
+		FORMAT_NORMAL,   // Usually SRGB, RGBA8 or a similar fallback. Always supported.
 		FORMAT_HDR,      // Usually RGBA16F. Not always supported.
 		FORMAT_RGBA4,    // RGBA with 4 bits per channel.
 		FORMAT_RGB5A1,   // RGB with 5 bits per channel, and A with 1 bit.

+ 60 - 20
jni/love/src/modules/graphics/opengl/GLBuffer.cpp

@@ -34,7 +34,7 @@ namespace graphics
 namespace opengl
 {
 
-GLBuffer::GLBuffer(size_t size, const void *data, GLenum target, GLenum usage)
+GLBuffer::GLBuffer(size_t size, const void *data, GLenum target, GLenum usage, uint32 mapflags)
 	: is_bound(false)
 	, is_mapped(false)
 	, size(size)
@@ -42,6 +42,9 @@ GLBuffer::GLBuffer(size_t size, const void *data, GLenum target, GLenum usage)
 	, usage(usage)
 	, vbo(0)
 	, memory_map(nullptr)
+	, modified_offset(0)
+	, modified_size(0)
+	, map_flags(mapflags)
 {
 	try
 	{
@@ -79,6 +82,9 @@ void *GLBuffer::map()
 
 	is_mapped = true;
 
+	modified_offset = 0;
+	modified_size = 0;
+
 	return memory_map;
 }
 
@@ -96,13 +102,21 @@ void GLBuffer::unmapStream()
 	glBufferData(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage());
 }
 
-void GLBuffer::unmap(size_t usedOffset, size_t usedSize)
+void GLBuffer::unmap()
 {
 	if (!is_mapped)
 		return;
 
-	usedOffset = std::min(usedOffset, getSize());
-	usedSize = std::min(usedSize, getSize() - usedOffset);
+	if ((map_flags & MAP_EXPLICIT_RANGE_MODIFY) != 0)
+	{
+		modified_offset = std::min(modified_offset, getSize() - 1);
+		modified_size = std::min(modified_size, getSize() - modified_offset);
+	}
+	else
+	{
+		modified_offset = 0;
+		modified_size = getSize();
+	}
 
 	// VBO::bind is a no-op when the VBO is mapped, so we have to make sure it's
 	// bound here.
@@ -112,28 +126,52 @@ void GLBuffer::unmap(size_t usedOffset, size_t usedSize)
 		is_bound = true;
 	}
 
-	switch (getUsage())
+	if (modified_size > 0)
 	{
-	case GL_STATIC_DRAW:
-		unmapStatic(usedOffset, usedSize);
-		break;
-	case GL_STREAM_DRAW:
-		unmapStream();
-		break;
-	case GL_DYNAMIC_DRAW:
-	default:
-		// It's probably more efficient to treat it like a streaming buffer if
-		// more than a third of its contents have been modified during the map().
-		if (usedSize >= getSize() / 3)
+		switch (getUsage())
+		{
+		case GL_STATIC_DRAW:
+			unmapStatic(modified_offset, modified_size);
+			break;
+		case GL_STREAM_DRAW:
 			unmapStream();
-		else
-			unmapStatic(usedOffset, usedSize);
-		break;
+			break;
+		case GL_DYNAMIC_DRAW:
+		default:
+			// It's probably more efficient to treat it like a streaming buffer if
+			// at least a third of its contents have been modified during the map().
+			if (modified_size >= getSize() / 3)
+				unmapStream();
+			else
+				unmapStatic(modified_offset, modified_size);
+			break;
+		}
 	}
 
+	modified_offset = 0;
+	modified_size = 0;
+
 	is_mapped = false;
 }
 
+void GLBuffer::setMappedRangeModified(size_t offset, size_t modifiedsize)
+{
+	if (!is_mapped || !(map_flags & MAP_EXPLICIT_RANGE_MODIFY))
+		return;
+
+	// We're being conservative right now by internally marking the whole range
+	// from the start of section a to the end of section b as modified if both
+	// a and b are marked as modified.
+
+	size_t old_range_end = modified_offset + modified_offset;
+
+	modified_offset = std::min(modified_offset, offset);
+
+	size_t new_range_end = std::max(offset + modifiedsize, old_range_end);
+
+	modified_size = new_range_end - modified_offset;
+}
+
 void GLBuffer::bind()
 {
 	if (!is_mapped)
@@ -155,7 +193,9 @@ void GLBuffer::fill(size_t offset, size_t size, const void *data)
 {
 	memcpy(memory_map + offset, data, size);
 
-	if (!is_mapped)
+	if (is_mapped)
+		setMappedRangeModified(offset, size);
+	else
 		glBufferSubData(getTarget(), (GLintptr) offset, (GLsizeiptr) size, data);
 }
 

+ 24 - 7
jni/love/src/modules/graphics/opengl/GLBuffer.h

@@ -47,6 +47,11 @@ class GLBuffer : public Volatile
 {
 public:
 
+	enum MapFlags
+	{
+		MAP_EXPLICIT_RANGE_MODIFY = 0x01, // see setMappedRangeModified.
+	};
+
 	/**
 	 * Constructor.
 	 *
@@ -54,7 +59,7 @@ public:
 	 * @param target The target GLBuffer object, e.g. GL_ARRAY_BUFFER.
 	 * @param usage Usage hint, e.g. GL_DYNAMIC_DRAW.
 	 */
-	GLBuffer(size_t size, const void *data, GLenum target, GLenum usage);
+	GLBuffer(size_t size, const void *data, GLenum target, GLenum usage, uint32 mapflags = 0);
 
 	/**
 	 * Destructor.
@@ -118,12 +123,14 @@ public:
 	 * when used to draw elements.
 	 *
 	 * The GLBuffer must be bound to use this function.
-	 *
-	 * @param usedOffset The offset into the mapped buffer indicating the
-	 *                   sub-range of data modified. Optional.
-	 * @param usedSize   The size of the sub-range of modified data. Optional.
 	 */
-	void unmap(size_t usedOffset = 0, size_t usedSize = -1);
+	void unmap();
+
+	/**
+	 * Marks a range of mapped data as modified.
+	 * NOTE: GLBuffer::fill calls this internally for you.
+	 **/
+	void setMappedRangeModified(size_t offset, size_t size);
 
 	/**
 	 * Bind the GLBuffer to its specified target.
@@ -137,7 +144,7 @@ public:
 	void unbind();
 
 	/**
-	 * Fill a portion of the buffer with data.
+	 * Fill a portion of the buffer with data and marks the range as modified.
 	 *
 	 * The GLBuffer must be bound to use this function.
 	 *
@@ -155,6 +162,11 @@ public:
 	 */
 	const void *getPointer(size_t offset) const;
 
+	uint32 getMapFlags() const
+	{
+		return map_flags;
+	}
+
 	// Implements Volatile.
 	bool loadVolatile() override;
 	void unloadVolatile() override;
@@ -262,6 +274,11 @@ private:
 	// A pointer to mapped memory.
 	char *memory_map;
 
+	size_t modified_offset;
+	size_t modified_size;
+
+	uint32 map_flags;
+
 }; // GLBuffer
 
 

+ 38 - 100
jni/love/src/modules/graphics/opengl/Graphics.cpp

@@ -26,6 +26,7 @@
 #include "Graphics.h"
 #include "font/Font.h"
 #include "Polyline.h"
+#include "math/MathModule.h"
 
 // C++
 #include <vector>
@@ -70,7 +71,7 @@ Graphics::Graphics()
 		currentWindow->getWindow(w, h, wsettings);
 
 		if (currentWindow->isOpen())
-			setMode(w, h, wsettings.sRGB);
+			setMode(w, h);
 	}
 }
 
@@ -235,7 +236,7 @@ void Graphics::setViewportSize(int width, int height)
 	setCanvas(canvases);
 }
 
-bool Graphics::setMode(int width, int height, bool &sRGB)
+bool Graphics::setMode(int width, int height)
 {
 	currentWindow.set(Module::getInstance<love::window::Window>(Module::M_WINDOW));
 
@@ -275,12 +276,12 @@ bool Graphics::setMode(int width, int height, bool &sRGB)
 		|| GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB)
 	{
 		if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
-			gl.setFramebufferSRGB(sRGB);
+			gl.setFramebufferSRGB(isGammaCorrect());
 	}
 	else
-		sRGB = false;
+		setGammaCorrect(false);
 
-	Canvas::screenHasSRGB = sRGB;
+	Canvas::screenHasSRGB = isGammaCorrect();
 
 	bool enabledebug = false;
 
@@ -345,7 +346,7 @@ void Graphics::unSetMode()
 void Graphics::setActive(bool enable)
 {
 	// Make sure all pending OpenGL commands have fully executed before
-	// returning, if we're going from active to inactive.
+	// returning, when going from active to inactive. This is required on iOS.
 	if (isCreated() && this->active && !enable)
 		glFinish();
 
@@ -426,13 +427,17 @@ void Graphics::reset()
 	origin();
 }
 
-void Graphics::clear(Color c)
+void Graphics::clear(Colorf c)
 {
-	glClearColor(c.r / 255.0f, c.g / 255.0f, c.b / 255.0f, c.a / 255.0f);
+	Colorf nc = Colorf(c.r/255.0f, c.g/255.0f, c.b/255.0f, c.a/255.0f);
+
+	gammaCorrectColor(nc);
+
+	glClearColor(nc.r, nc.g, nc.b, nc.a);
 	glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 }
 
-void Graphics::clear(const std::vector<Color> &colors)
+void Graphics::clear(const std::vector<Colorf> &colors)
 {
 	if (colors.size() == 0)
 		return;
@@ -447,7 +452,16 @@ void Graphics::clear(const std::vector<Color> &colors)
 
 	for (int i = 0; i < (int) colors.size(); i++)
 	{
-		const GLfloat c[] = {colors[i].r/255.f, colors[i].g/255.f, colors[i].b/255.f, colors[i].a/255.f};
+		GLfloat c[] = {colors[i].r/255.f, colors[i].g/255.f, colors[i].b/255.f, colors[i].a/255.f};
+
+		// TODO: Investigate a potential bug on AMD drivers in Windows/Linux
+		// which apparently causes the clear color to be incorrect when mixed
+		// sRGB and linear render targets are active.
+		if (isGammaCorrect())
+		{
+			for (int i = 0; i < 3; i++)
+				c[i] = math::Math::instance.gammaToLinear(c[i]);
+		}
 
 		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0)
 			glClearBufferfv(GL_COLOR, i, c);
@@ -785,23 +799,32 @@ Text *Graphics::newText(Font *font, const std::string &text)
 	return new Text(font, text);
 }
 
-void Graphics::setColor(Color c)
+bool Graphics::isGammaCorrect() const
+{
+	return love::graphics::isGammaCorrect();
+}
+
+void Graphics::setColor(Colorf c)
 {
-	glVertexAttrib4f(ATTRIB_CONSTANTCOLOR, c.r/255.0f, c.g/255.0f, c.b/255.0f, c.a/255.0f);
+	Colorf nc = Colorf(c.r/255.0f, c.g/255.0f, c.b/255.0f, c.a/255.0f);
+
+	gammaCorrectColor(nc);
+
+	glVertexAttrib4f(ATTRIB_CONSTANTCOLOR, nc.r, nc.g, nc.b, nc.a);
 	states.back().color = c;
 }
 
-Color Graphics::getColor() const
+Colorf Graphics::getColor() const
 {
 	return states.back().color;
 }
 
-void Graphics::setBackgroundColor(Color c)
+void Graphics::setBackgroundColor(Colorf c)
 {
 	states.back().backgroundColor = c;
 }
 
-Color Graphics::getBackgroundColor() const
+Colorf Graphics::getBackgroundColor() const
 {
 	return states.back().backgroundColor;
 }
@@ -1456,10 +1479,6 @@ bool Graphics::isSupported(Support feature) const
 		return Canvas::isMultiCanvasSupported();
 	case SUPPORT_MULTI_CANVAS_FORMATS:
 		return Canvas::isMultiFormatMultiCanvasSupported();
-	case SUPPORT_SRGB:
-		// sRGB support for the screen is guaranteed if it's supported as a
-		// Canvas format.
-		return Canvas::isFormatSupported(Canvas::FORMAT_SRGB);
 	default:
 		return false;
 	}
@@ -1528,87 +1547,6 @@ void Graphics::origin()
 	pixelSizeStack.back() = 1;
 }
 
-Graphics::DisplayState::DisplayState()
-	: color(255, 255, 255, 255)
-	, backgroundColor(0, 0, 0, 255)
-	, blendMode(BLEND_ALPHA)
-	, blendMultiplyAlpha(true)
-	, lineWidth(1.0f)
-	, lineStyle(LINE_SMOOTH)
-	, lineJoin(LINE_JOIN_MITER)
-	, pointSize(1.0f)
-	, scissor(false)
-	, scissorBox()
-	, stencilTest(false)
-	, stencilInvert(false)
-	, font(nullptr)
-	, shader(nullptr)
-	, colorMask({true, true, true, true})
-	, wireframe(false)
-	, defaultFilter()
-	, defaultMipmapFilter(Texture::FILTER_NEAREST)
-	, defaultMipmapSharpness(0.0f)
-{
-}
-
-Graphics::DisplayState::DisplayState(const DisplayState &other)
-	: color(other.color)
-	, backgroundColor(other.backgroundColor)
-	, blendMode(other.blendMode)
-	, blendMultiplyAlpha(other.blendMultiplyAlpha)
-	, lineWidth(other.lineWidth)
-	, lineStyle(other.lineStyle)
-	, lineJoin(other.lineJoin)
-	, pointSize(other.pointSize)
-	, scissor(other.scissor)
-	, scissorBox(other.scissorBox)
-	, stencilTest(other.stencilTest)
-	, stencilInvert(other.stencilInvert)
-	, font(other.font)
-	, shader(other.shader)
-	, canvases(other.canvases)
-	, colorMask(other.colorMask)
-	, wireframe(other.wireframe)
-	, defaultFilter(other.defaultFilter)
-	, defaultMipmapFilter(other.defaultMipmapFilter)
-	, defaultMipmapSharpness(other.defaultMipmapSharpness)
-{
-}
-
-Graphics::DisplayState::~DisplayState()
-{
-}
-
-Graphics::DisplayState &Graphics::DisplayState::operator = (const DisplayState &other)
-{
-	color = other.color;
-	backgroundColor = other.backgroundColor;
-	blendMode = other.blendMode;
-	blendMultiplyAlpha = other.blendMultiplyAlpha;
-	lineWidth = other.lineWidth;
-	lineStyle = other.lineStyle;
-	lineJoin = other.lineJoin;
-	pointSize = other.pointSize;
-	scissor = other.scissor;
-	scissorBox = other.scissorBox;
-	stencilTest = other.stencilTest;
-	stencilInvert = other.stencilInvert;
-
-	font = other.font;
-	shader = other.shader;
-	canvases = other.canvases;
-
-	colorMask = other.colorMask;
-
-	wireframe = other.wireframe;
-
-	defaultFilter = other.defaultFilter;
-	defaultMipmapFilter = other.defaultMipmapFilter;
-	defaultMipmapSharpness = other.defaultMipmapSharpness;
-
-	return *this;
-}
-
 } // opengl
 } // graphics
 } // love

+ 26 - 30
jni/love/src/modules/graphics/opengl/Graphics.h

@@ -66,7 +66,7 @@ public:
 	const char *getName() const;
 
 	virtual void setViewportSize(int width, int height);
-	virtual bool setMode(int width, int height, bool &sRGB);
+	virtual bool setMode(int width, int height);
 	virtual void unSetMode();
 
 	virtual void setActive(bool active);
@@ -84,12 +84,12 @@ public:
 	/**
 	 * Clears the screen to a specific color.
 	 **/
-	void clear(Color c);
+	void clear(Colorf c);
 
 	/**
 	 * Clears each active canvas to a different color.
 	 **/
-	void clear(const std::vector<Color> &colors);
+	void clear(const std::vector<Colorf> &colors);
 
 	/**
 	 * Discards the contents of the screen.
@@ -183,26 +183,28 @@ public:
 
 	Text *newText(Font *font, const std::string &text = "");
 
+	bool isGammaCorrect() const;
+
 	/**
 	 * Sets the foreground color.
 	 * @param c The new foreground color.
 	 **/
-	void setColor(Color c);
+	void setColor(Colorf c);
 
 	/**
 	 * Gets current color.
 	 **/
-	Color getColor() const;
+	Colorf getColor() const;
 
 	/**
 	 * Sets the background Color.
 	 **/
-	void setBackgroundColor(Color c);
+	void setBackgroundColor(Colorf c);
 
 	/**
 	 * Gets the current background color.
 	 **/
-	Color getBackgroundColor() const;
+	Colorf getBackgroundColor() const;
 
 	void setFont(Font *font);
 	Font *getFont();
@@ -463,44 +465,38 @@ private:
 
 	struct DisplayState
 	{
-		Color color;
-		Color backgroundColor;
+		Colorf color = Colorf(255.0, 255.0, 255.0, 255.0);
+		Colorf backgroundColor = Colorf(0.0, 0.0, 0.0, 255.0);
 
-		BlendMode blendMode;
-		bool blendMultiplyAlpha;
+		BlendMode blendMode = BLEND_ALPHA;
+		bool blendMultiplyAlpha = true;
 
-		float lineWidth;
-		LineStyle lineStyle;
-		LineJoin lineJoin;
+		float lineWidth = 1.0f;
+		LineStyle lineStyle = LINE_SMOOTH;
+		LineJoin lineJoin = LINE_JOIN_MITER;
 
-		float pointSize;
+		float pointSize = 1.0f;
 
-		bool scissor;
-		OpenGL::Viewport scissorBox;
+		bool scissor = false;
+		OpenGL::Viewport scissorBox = OpenGL::Viewport();
 
 		// Stencil.
-		bool stencilTest;
-		bool stencilInvert;
+		bool stencilTest = false;
+		bool stencilInvert = false;
 
 		StrongRef<Font> font;
 		StrongRef<Shader> shader;
 
 		std::vector<StrongRef<Canvas>> canvases;
 
-		ColorMask colorMask;
-
-		bool wireframe;
-
-		Texture::Filter defaultFilter;
+		ColorMask colorMask = ColorMask(true, true, true, true);
 
-		Texture::FilterMode defaultMipmapFilter;
-		float defaultMipmapSharpness;
+		bool wireframe = false;
 
-		DisplayState();
-		DisplayState(const DisplayState &other);
-		~DisplayState();
+		Texture::Filter defaultFilter = Texture::Filter();
 
-		DisplayState &operator = (const DisplayState &other);
+		Texture::FilterMode defaultMipmapFilter = Texture::FILTER_NEAREST;
+		float defaultMipmapSharpness = 0.0f;
 	};
 
 	void restoreState(const DisplayState &s);

+ 61 - 26
jni/love/src/modules/graphics/opengl/Image.cpp

@@ -20,6 +20,7 @@
 
 #include "Image.h"
 
+#include "graphics/Graphics.h"
 #include "common/int.h"
 
 // STD
@@ -91,6 +92,7 @@ Image::Image(const std::vector<love::image::ImageData *> &imagedata, const Flags
 	, mipmapSharpness(defaultMipmapSharpness)
 	, compressed(false)
 	, flags(flags)
+	, sRGB(false)
 	, usingDefaultTexture(false)
 	, textureMemorySize(0)
 {
@@ -117,18 +119,22 @@ Image::Image(const std::vector<love::image::CompressedImageData *> &compressedda
 	, mipmapSharpness(defaultMipmapSharpness)
 	, compressed(true)
 	, flags(flags)
+	, sRGB(false)
 	, usingDefaultTexture(false)
 	, textureMemorySize(0)
 {
-	this->flags.sRGB = (flags.sRGB || compresseddata[0]->isSRGB());
-
 	width = compresseddata[0]->getWidth(0);
 	height = compresseddata[0]->getHeight(0);
 
 	if (verifyMipmapLevels(compresseddata))
 		this->flags.mipmaps = true;
 	else if (flags.mipmaps && getMipmapCount(width, height) != compresseddata[0]->getMipmapCount())
-		throw love::Exception("Image cannot have mipmaps: compressed image data does not have all required mipmap levels.");
+	{
+		if (compresseddata[0]->getMipmapCount() == 1)
+			this->flags.mipmaps = false;
+		else
+			throw love::Exception("Image cannot have mipmaps: compressed image data does not have all required mipmap levels.");
+	}
 
 	for (const auto &cd : compresseddata)
 	{
@@ -179,6 +185,14 @@ void Image::preload()
 
 	if (flags.mipmaps)
 		filter.mipmap = defaultMipmapFilter;
+
+	if (!isGammaCorrect())
+		flags.linear = false;
+
+	if (isGammaCorrect() && !flags.linear)
+		sRGB = true;
+	else
+		sRGB = false;
 }
 
 void Image::generateMipmaps()
@@ -214,7 +228,10 @@ void Image::loadDefaultTexture()
 
 void Image::loadFromCompressedData()
 {
-	GLenum iformat = getCompressedFormat(cdata[0]->getFormat());
+	GLenum iformat = getCompressedFormat(cdata[0]->getFormat(), sRGB);
+
+	if (isGammaCorrect() && !sRGB)
+		flags.linear = true;
 
 	int count = 1;
 
@@ -238,13 +255,13 @@ void Image::loadFromCompressedData()
 
 void Image::loadFromImageData()
 {
-	GLenum iformat = flags.sRGB ? GL_SRGB8_ALPHA8 : GL_RGBA8;
+	GLenum iformat = sRGB ? GL_SRGB8_ALPHA8 : GL_RGBA8;
 	GLenum format  = GL_RGBA;
 
 	// in GLES2, the internalformat and format params of TexImage have to match.
 	if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0)
 	{
-		format  = flags.sRGB ? GL_SRGB_ALPHA : GL_RGBA;
+		format  = sRGB ? GL_SRGB_ALPHA : GL_RGBA;
 		iformat = format;
 	}
 
@@ -267,24 +284,24 @@ bool Image::loadVolatile()
 {
 	OpenGL::TempDebugGroup debuggroup("Image load");
 
-	if (isCompressed() && !hasCompressedTextureSupport(cdata[0]->getFormat(), flags.sRGB))
+	if (isCompressed() && !hasCompressedTextureSupport(cdata[0]->getFormat(), sRGB))
 	{
 		const char *str;
 		if (image::CompressedImageData::getConstant(cdata[0]->getFormat(), str))
 		{
 			throw love::Exception("Cannot create image: "
-			                      "%s%s compressed images are not supported on this system.", flags.sRGB ? "sRGB " : "", str);
+			                      "%s%s compressed images are not supported on this system.", sRGB ? "sRGB " : "", str);
 		}
 		else
 			throw love::Exception("cannot create image: format is not supported on this system.");
 	}
 	else if (!isCompressed())
 	{
-		if (flags.sRGB && !hasSRGBSupport())
+		if (sRGB && !hasSRGBSupport())
 			throw love::Exception("sRGB images are not supported on this system.");
 
 		// GL_EXT_sRGB doesn't support glGenerateMipmap for sRGB textures.
-		if (flags.sRGB && (GLAD_ES_VERSION_2_0 && GLAD_EXT_sRGB && !GLAD_ES_VERSION_3_0)
+		if (sRGB && (GLAD_ES_VERSION_2_0 && GLAD_EXT_sRGB && !GLAD_ES_VERSION_3_0)
 			&& data.size() <= 1)
 		{
 			flags.mipmaps = false;
@@ -402,7 +419,7 @@ bool Image::refresh(int xoffset, int yoffset, int w, int h)
 
 	// In ES2, the format parameter of TexSubImage must match the internal
 	// format of the texture.
-	if (flags.sRGB && (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0))
+	if (sRGB && (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0))
 		format = GL_SRGB_ALPHA;
 
 	int mipcount = flags.mipmaps ? (int) data.size() : 1;
@@ -570,93 +587,111 @@ bool Image::isCompressed() const
 	return compressed;
 }
 
-GLenum Image::getCompressedFormat(image::CompressedImageData::Format cformat) const
+GLenum Image::getCompressedFormat(image::CompressedImageData::Format cformat, bool &isSRGB) const
 {
 	switch (cformat)
 	{
 	case image::CompressedImageData::FORMAT_DXT1:
-		if (flags.sRGB)
+		if (isSRGB)
 			return GL_COMPRESSED_SRGB_S3TC_DXT1_EXT;
 		else
 			return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
 	case image::CompressedImageData::FORMAT_DXT3:
-		if (flags.sRGB)
+		if (isSRGB)
 			return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
 		else
 			return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
 	case image::CompressedImageData::FORMAT_DXT5:
-		if (flags.sRGB)
+		if (isSRGB)
 			return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
 		else
 			return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
 	case image::CompressedImageData::FORMAT_BC4:
+		isSRGB = false;
 		return GL_COMPRESSED_RED_RGTC1;
 	case image::CompressedImageData::FORMAT_BC4s:
+		isSRGB = false;
 		return GL_COMPRESSED_SIGNED_RED_RGTC1;
 	case image::CompressedImageData::FORMAT_BC5:
+		isSRGB = false;
 		return GL_COMPRESSED_RG_RGTC2;
 	case image::CompressedImageData::FORMAT_BC5s:
+		isSRGB = false;
 		return GL_COMPRESSED_SIGNED_RG_RGTC2;
 	case image::CompressedImageData::FORMAT_BC6H:
+		isSRGB = false;
 		return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT;
 	case image::CompressedImageData::FORMAT_BC6Hs:
+		isSRGB = false;
 		return GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT;
 	case image::CompressedImageData::FORMAT_BC7:
-		if (flags.sRGB)
+		if (isSRGB)
 			return GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
 		else
 			return GL_COMPRESSED_RGBA_BPTC_UNORM;
 	case image::CompressedImageData::FORMAT_ETC1:
 		// The ETC2 format can load ETC1 textures.
 		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility)
-			return GL_COMPRESSED_RGB8_ETC2;
+		{
+			if (isSRGB)
+				return GL_COMPRESSED_SRGB8_ETC2;
+			else
+				return GL_COMPRESSED_RGB8_ETC2;
+		}
 		else
+		{
+			isSRGB = false;
 			return GL_ETC1_RGB8_OES;
+		}
 	case image::CompressedImageData::FORMAT_ETC2_RGB:
-		if (flags.sRGB)
+		if (isSRGB)
 			return GL_COMPRESSED_SRGB8_ETC2;
 		else
 			return GL_COMPRESSED_RGB8_ETC2;
 	case image::CompressedImageData::FORMAT_ETC2_RGBA:
-		if (flags.sRGB)
+		if (isSRGB)
 			return GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
 		else
 			return GL_COMPRESSED_RGBA8_ETC2_EAC;
 	case image::CompressedImageData::FORMAT_ETC2_RGBA1:
-		if (flags.sRGB)
+		if (isSRGB)
 			return GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2;
 		else
 			return GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
 	case image::CompressedImageData::FORMAT_EAC_R:
+		isSRGB = false;
 		return GL_COMPRESSED_R11_EAC;
 	case image::CompressedImageData::FORMAT_EAC_Rs:
+		isSRGB = false;
 		return GL_COMPRESSED_SIGNED_R11_EAC;
 	case image::CompressedImageData::FORMAT_EAC_RG:
+		isSRGB = false;
 		return GL_COMPRESSED_RG11_EAC;
 	case image::CompressedImageData::FORMAT_EAC_RGs:
+		isSRGB = false;
 		return GL_COMPRESSED_SIGNED_RG11_EAC;
 	case image::CompressedImageData::FORMAT_PVR1_RGB2:
-		if (flags.sRGB)
+		if (isSRGB)
 			return GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT;
 		else
 			return GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
 	case image::CompressedImageData::FORMAT_PVR1_RGB4:
-		if (flags.sRGB)
+		if (isSRGB)
 			return GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT;
 		else
 			return GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
 	case image::CompressedImageData::FORMAT_PVR1_RGBA2:
-		if (flags.sRGB)
+		if (isSRGB)
 			return GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT;
 		else
 			return GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
 	case image::CompressedImageData::FORMAT_PVR1_RGBA4:
-		if (flags.sRGB)
+		if (isSRGB)
 			return GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT;
 		else
 			return GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
 	default:
-		if (flags.sRGB)
+		if (isSRGB)
 			return GL_SRGB8_ALPHA8;
 		else
 			return GL_RGBA8;
@@ -729,7 +764,7 @@ bool Image::getConstant(FlagType in, const char *&out)
 StringMap<Image::FlagType, Image::FLAG_TYPE_MAX_ENUM>::Entry Image::flagNameEntries[] =
 {
 	{"mipmaps", Image::FLAG_TYPE_MIPMAPS},
-	{"srgb", Image::FLAG_TYPE_SRGB},
+	{"linear", Image::FLAG_TYPE_LINEAR},
 };
 
 StringMap<Image::FlagType, Image::FLAG_TYPE_MAX_ENUM> Image::flagNames(Image::flagNameEntries, sizeof(Image::flagNameEntries));

+ 5 - 3
jni/love/src/modules/graphics/opengl/Image.h

@@ -55,14 +55,14 @@ public:
 	enum FlagType
 	{
 		FLAG_TYPE_MIPMAPS,
-		FLAG_TYPE_SRGB,
+		FLAG_TYPE_LINEAR,
 		FLAG_TYPE_MAX_ENUM
 	};
 
 	struct Flags
 	{
 		bool mipmaps = false;
-		bool sRGB = false;;
+		bool linear = false;
 	};
 
 	/**
@@ -147,7 +147,7 @@ private:
 	void loadFromCompressedData();
 	void loadFromImageData();
 
-	GLenum getCompressedFormat(image::CompressedImageData::Format cformat) const;
+	GLenum getCompressedFormat(image::CompressedImageData::Format cformat, bool &isSRGB) const;
 
 	// The ImageData from which the texture is created. May be empty if
 	// Compressed image data was used to create the texture.
@@ -170,6 +170,8 @@ private:
 	// The flags used to initialize this Image.
 	Flags flags;
 
+	bool sRGB;
+
 	// True if the image wasn't able to be properly created and it had to fall
 	// back to a default texture.
 	bool usingDefaultTexture;

+ 9 - 28
jni/love/src/modules/graphics/opengl/Mesh.cpp

@@ -63,8 +63,6 @@ Mesh::Mesh(const std::vector<AttribFormat> &vertexformat, const void *data, size
 	, vbo(nullptr)
 	, vertexCount(0)
 	, vertexStride(0)
-	, vboUsedOffset(0)
-	, vboUsedSize(0)
 	, ibo(nullptr)
 	, elementCount(0)
 	, elementDataType(0)
@@ -81,7 +79,7 @@ Mesh::Mesh(const std::vector<AttribFormat> &vertexformat, const void *data, size
 	if (vertexCount == 0)
 		throw love::Exception("Data size is too small for specified vertex attribute formats.");
 
-	vbo = new GLBuffer(datasize, data, GL_ARRAY_BUFFER, getGLBufferUsage(usage));
+	vbo = new GLBuffer(datasize, data, GL_ARRAY_BUFFER, getGLBufferUsage(usage), GLBuffer::MAP_EXPLICIT_RANGE_MODIFY);
 
 	vertexScratchBuffer = new char[vertexStride];
 }
@@ -91,8 +89,6 @@ Mesh::Mesh(const std::vector<AttribFormat> &vertexformat, int vertexcount, DrawM
 	, vbo(nullptr)
 	, vertexCount((size_t) vertexcount)
 	, vertexStride(0)
-	, vboUsedOffset(0)
-	, vboUsedSize(0)
 	, ibo(nullptr)
 	, elementCount(0)
 	, elementDataType(getGLDataTypeFromMax(vertexcount))
@@ -108,11 +104,12 @@ Mesh::Mesh(const std::vector<AttribFormat> &vertexformat, int vertexcount, DrawM
 
 	size_t buffersize = vertexCount * vertexStride;
 
-	vbo = new GLBuffer(buffersize, nullptr, GL_ARRAY_BUFFER, getGLBufferUsage(usage));
+	vbo = new GLBuffer(buffersize, nullptr, GL_ARRAY_BUFFER, getGLBufferUsage(usage), GLBuffer::MAP_EXPLICIT_RANGE_MODIFY);
 
 	// Initialize the buffer's contents to 0.
 	GLBuffer::Bind bind(*vbo);
 	memset(vbo->map(), 0, buffersize);
+	vbo->setMappedRangeModified(0, vbo->getSize());
 	vbo->unmap();
 
 	vertexScratchBuffer = new char[vertexStride];
@@ -198,8 +195,7 @@ void Mesh::setVertex(size_t vertindex, const void *data, size_t datasize)
 
 	memcpy(bufferdata + offset, data, size);
 
-	vboUsedOffset = std::min(vboUsedOffset, offset);
-	vboUsedSize = std::max(vboUsedSize, (offset + size) - vboUsedOffset);
+	vbo->setMappedRangeModified(offset, size);
 }
 
 size_t Mesh::getVertex(size_t vertindex, void *data, size_t datasize)
@@ -216,12 +212,6 @@ size_t Mesh::getVertex(size_t vertindex, void *data, size_t datasize)
 
 	memcpy(data, bufferdata + offset, size);
 
-	if (vboUsedSize == 0)
-	{
-		vboUsedOffset = std::min(vboUsedOffset, offset);
-		vboUsedSize = std::max(vboUsedSize, (offset + size) - vboUsedOffset);
-	}
-
 	return size;
 }
 
@@ -246,8 +236,7 @@ void Mesh::setVertexAttribute(size_t vertindex, int attribindex, const void *dat
 
 	memcpy(bufferdata + offset, data, size);
 
-	vboUsedOffset = std::min(vboUsedOffset, offset);
-	vboUsedSize = std::max(vboUsedSize, (offset + size) - vboUsedOffset);
+	vbo->setMappedRangeModified(offset, size);
 }
 
 size_t Mesh::getVertexAttribute(size_t vertindex, int attribindex, void *data, size_t datasize)
@@ -267,12 +256,6 @@ size_t Mesh::getVertexAttribute(size_t vertindex, int attribindex, void *data, s
 
 	memcpy(data, bufferdata + offset, size);
 
-	if (vboUsedSize == 0)
-	{
-		vboUsedOffset = std::min(vboUsedOffset, offset);
-		vboUsedSize = std::max(vboUsedSize, (offset + size) - vboUsedOffset);
-	}
-
 	return size;
 }
 
@@ -298,6 +281,7 @@ Mesh::DataType Mesh::getAttributeInfo(int attribindex, int &components) const
 
 	DataType type = vertexFormat[attribindex].type;
 	components = vertexFormat[attribindex].components;
+
 	return type;
 }
 
@@ -377,17 +361,15 @@ void Mesh::unmapVertexData()
 {
 	// Assume the whole buffer was modified.
 	GLBuffer::Bind bind(*vbo);
+	vbo->setMappedRangeModified(0, vbo->getSize());
 	vbo->unmap();
-
-	vboUsedOffset = vboUsedSize = 0;
 }
 
 void Mesh::flush()
 {
 	{
 		GLBuffer::Bind vbobind(*vbo);
-		vbo->unmap(vboUsedOffset, vboUsedSize);
-		vboUsedOffset = vboUsedSize = 0;
+		vbo->unmap();
 	}
 
 	if (ibo != nullptr)
@@ -574,8 +556,7 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 		GLBuffer::Bind vbobind(*mesh->vbo);
 
 		// Make sure the buffer isn't mapped (sends data to GPU if needed.)
-		mesh->vbo->unmap(mesh->vboUsedOffset, mesh->vboUsedSize);
-		mesh->vboUsedOffset = mesh->vboUsedSize = 0;
+		mesh->vbo->unmap();
 
 		size_t offset = mesh->getAttributeOffset(attrib.second.index);
 		const void *gloffset = mesh->vbo->getPointer(offset);

+ 0 - 5
jni/love/src/modules/graphics/opengl/Mesh.h

@@ -240,11 +240,6 @@ private:
 	// avoid memory allocations when using Mesh::setVertex etc.
 	char *vertexScratchBuffer;
 
-	// Tracks the range of the vertex buffer that has been used, to make unmap()
-	// calls as efficient as possible.
-	size_t vboUsedOffset;
-	size_t vboUsedSize;
-
 	// Element (vertex index) buffer, for the vertex map.
 	GLBuffer *ibo;
 	size_t elementCount;

+ 38 - 43
jni/love/src/modules/graphics/opengl/ParticleSystem.cpp

@@ -43,11 +43,6 @@ namespace
 
 love::math::RandomGenerator rng;
 
-Colorf colorToFloat(const Color &c)
-{
-	return Colorf((float)c.r/255.0f, (float)c.g/255.0f, (float)c.b/255.0f, (float)c.a/255.0f);
-}
-
 float calculate_variation(float inner, float outer, float var)
 {
 	float low = inner - (outer/2.0f)*var;
@@ -261,35 +256,34 @@ void ParticleSystem::initParticle(Particle *p, float t)
 		p->life = (float) rng.random(min, max);
 	p->lifetime = p->life;
 
-	p->position[0] = pos.x;
-	p->position[1] = pos.y;
+	p->position = pos;
 
 	switch (areaSpreadDistribution)
 	{
 	case DISTRIBUTION_UNIFORM:
-		p->position[0] += (float) rng.random(-areaSpread.getX(), areaSpread.getX());
-		p->position[1] += (float) rng.random(-areaSpread.getY(), areaSpread.getY());
+		p->position.x += (float) rng.random(-areaSpread.getX(), areaSpread.getX());
+		p->position.y += (float) rng.random(-areaSpread.getY(), areaSpread.getY());
 		break;
 	case DISTRIBUTION_NORMAL:
-		p->position[0] += (float) rng.randomNormal(areaSpread.getX());
-		p->position[1] += (float) rng.randomNormal(areaSpread.getY());
+		p->position.x += (float) rng.randomNormal(areaSpread.getX());
+		p->position.y += (float) rng.randomNormal(areaSpread.getY());
 		break;
 	case DISTRIBUTION_NONE:
 	default:
 		break;
 	}
 
-	min = direction - spread/2.0f;
-	max = direction + spread/2.0f;
-	p->direction = (float) rng.random(min, max);
-
 	p->origin = pos;
 
 	min = speedMin;
 	max = speedMax;
 	float speed = (float) rng.random(min, max);
-	p->velocity = love::Vector(cosf(p->direction), sinf(p->direction));
-	p->velocity *= speed;
+
+	min = direction - spread/2.0f;
+	max = direction + spread/2.0f;
+	float dir = (float) rng.random(min, max);
+
+	p->velocity = love::Vector(cosf(dir), sinf(dir)) * speed;
 
 	p->linearAcceleration.x = (float) rng.random(linearAccelerationMin.x, linearAccelerationMax.x);
 	p->linearAcceleration.y = (float) rng.random(linearAccelerationMin.y, linearAccelerationMax.y);
@@ -706,30 +700,31 @@ love::Vector ParticleSystem::getOffset() const
 	return offset;
 }
 
-void ParticleSystem::setColor(const Color &color)
+void ParticleSystem::setColor(const std::vector<Colorf> &newColors)
 {
-	colors.resize(1);
-	colors[0] = colorToFloat(color);
-}
+	colors = newColors;
 
-void ParticleSystem::setColor(const std::vector<Color> &newColors)
-{
-	colors.resize(newColors.size());
-	for (size_t i = 0; i < newColors.size(); ++i)
-		colors[i] = colorToFloat(newColors[i]);
+	for (Colorf &c : colors)
+	{
+		// We want to store the colors as [0, 1], rather than [0, 255].
+		c.r /= 255.0f;
+		c.g /= 255.0f;
+		c.b /= 255.0f;
+		c.a /= 255.0f;
+	}
 }
 
-std::vector<Color> ParticleSystem::getColor() const
+std::vector<Colorf> ParticleSystem::getColor() const
 {
-	// The particle system stores colors as floats...
-	std::vector<Color> ncolors(colors.size());
+	// The particle system stores colors in the range of [0, 1]...
+	std::vector<Colorf> ncolors(colors);
 
-	for (size_t i = 0; i < colors.size(); ++i)
+	for (Colorf &c : ncolors)
 	{
-		ncolors[i].r = (unsigned char) (colors[i].r * 255);
-		ncolors[i].g = (unsigned char) (colors[i].g * 255);
-		ncolors[i].b = (unsigned char) (colors[i].b * 255);
-		ncolors[i].a = (unsigned char) (colors[i].a * 255);
+		c.r *= 255.0f;
+		c.g *= 255.0f;
+		c.b *= 255.0f;
+		c.a *= 255.0f;
 	}
 
 	return ncolors;
@@ -854,10 +849,8 @@ void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, flo
 
 	OpenGL::TempDebugGroup debuggroup("ParticleSystem draw");
 
-	Matrix4 t(x, y, angle, sx, sy, ox, oy, kx, ky);
-
 	OpenGL::TempTransform transform(gl);
-	transform.get() *= t;
+	transform.get() *= Matrix4(x, y, angle, sx, sy, ox, oy, kx, ky);
 
 	const Vertex *textureVerts = texture->getVertices();
 	Vertex *pVerts = particleVerts;
@@ -865,14 +858,16 @@ void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, flo
 
 	bool useQuads = !quads.empty();
 
+	Matrix3 t;
+
 	// set the vertex data for each particle (transformation, texcoords, color)
 	while (p)
 	{
 		if (useQuads)
 			textureVerts = quads[p->quadIndex]->getVertices();
 
-		// particle vertices are image vertices transformed by particle information
-		t.setTransformation(p->position[0], p->position[1], p->angle, p->size, p->size, offset.x, offset.y, 0.0f, 0.0f);
+		// particle vertices are image vertices transformed by particle info
+		t.setTransformation(p->position.x, p->position.y, p->angle, p->size, p->size, offset.x, offset.y, 0.0f, 0.0f);
 		t.transform(pVerts, textureVerts, 4);
 
 		// set the texture coordinate and color data for particle vertices
@@ -881,7 +876,8 @@ void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, flo
 			pVerts[v].s = textureVerts[v].s;
 			pVerts[v].t = textureVerts[v].t;
 
-			// particle colors are stored as floats (0-1) but vertex colors are stored as unsigned bytes (0-255)
+			// Particle colors are stored as floats (0-1) but vertex colors are
+			// unsigned bytes (0-255).
 			pVerts[v].r = (unsigned char) (p->color.r*255);
 			pVerts[v].g = (unsigned char) (p->color.g*255);
 			pVerts[v].b = (unsigned char) (p->color.b*255);
@@ -927,7 +923,7 @@ void ParticleSystem::update(float dt)
 		{
 			// Temp variables.
 			love::Vector radial, tangential;
-			love::Vector ppos(p->position[0], p->position[1]);
+			love::Vector ppos = p->position;
 
 			// Get vector from particle center to particle.
 			radial = ppos - p->origin;
@@ -956,8 +952,7 @@ void ParticleSystem::update(float dt)
 			// Modify position.
 			ppos += p->velocity * dt;
 
-			p->position[0] = ppos.getX();
-			p->position[1] = ppos.getY();
+			p->position = ppos;
 
 			const float t = 1.0f - p->life / p->lifetime;
 

+ 3 - 10
jni/love/src/modules/graphics/opengl/ParticleSystem.h

@@ -417,22 +417,16 @@ public:
 	 **/
 	love::Vector getOffset() const;
 
-	/**
-	 * Sets the color of the particles.
-	 * @param color The color.
-	 **/
-	void setColor(const Color &color);
-
 	/**
 	 * Sets the color of the particles.
 	 * @param newColors Array of colors
 	 **/
-	void setColor(const std::vector<Color> &newColors);
+	void setColor(const std::vector<Colorf> &newColors);
 
 	/**
 	 * Returns the color of the particles.
 	 **/
-	std::vector<Color> getColor() const;
+	std::vector<Colorf> getColor() const;
 
 	/**
 	 * Sets a list of Quads to use for particles over their lifetime.
@@ -534,8 +528,7 @@ protected:
 		float lifetime;
 		float life;
 
-		float position[2];
-		float direction;
+		love::Vector position;
 
 		// Particles gravitate towards this point.
 		love::Vector origin;

+ 8 - 18
jni/love/src/modules/graphics/opengl/SpriteBatch.cpp

@@ -48,8 +48,6 @@ SpriteBatch::SpriteBatch(Texture *texture, int size, Mesh::Usage usage)
 	, color(0)
 	, array_buf(nullptr)
 	, quad_indices(size)
-	, buffer_used_offset(0)
-	, buffer_used_size(0)
 {
 	if (size <= 0)
 		throw love::Exception("Invalid SpriteBatch size.");
@@ -60,7 +58,7 @@ SpriteBatch::SpriteBatch(Texture *texture, int size, Mesh::Usage usage)
 
 	try
 	{
-		array_buf = new GLBuffer(vertex_size, nullptr, GL_ARRAY_BUFFER, gl_usage);
+		array_buf = new GLBuffer(vertex_size, nullptr, GL_ARRAY_BUFFER, gl_usage, GLBuffer::MAP_EXPLICIT_RANGE_MODIFY);
 	}
 	catch (love::Exception &)
 	{
@@ -86,7 +84,7 @@ 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;
 
-	Matrix4 t(x, y, a, sx, sy, ox, oy, kx, ky);
+	Matrix3 t(x, y, a, sx, sy, ox, oy, kx, ky);
 
 	addv(texture->getVertices(), t, (index == -1) ? next : index);
 
@@ -103,7 +101,7 @@ 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;
 
-	Matrix4 t(x, y, a, sx, sy, ox, oy, kx, ky);
+	Matrix3 t(x, y, a, sx, sy, ox, oy, kx, ky);
 
 	addv(quad->getVertices(), t, (index == -1) ? next : index);
 
@@ -123,9 +121,7 @@ void SpriteBatch::clear()
 void SpriteBatch::flush()
 {
 	GLBuffer::Bind bind(*array_buf);
-	array_buf->unmap(buffer_used_offset, buffer_used_size);
-
-	buffer_used_offset = buffer_used_size = 0;
+	array_buf->unmap();
 }
 
 void SpriteBatch::setTexture(Texture *newtexture)
@@ -182,7 +178,7 @@ void SpriteBatch::setBufferSize(int newsize)
 
 	try
 	{
-		new_array_buf = new GLBuffer(vertex_size, nullptr, array_buf->getTarget(), array_buf->getUsage());
+		new_array_buf = new GLBuffer(vertex_size, nullptr, array_buf->getTarget(), array_buf->getUsage(), array_buf->getMapFlags());
 
 		// Copy as much of the old data into the new GLBuffer as can fit.
 		GLBuffer::Bind bind(*new_array_buf);
@@ -222,10 +218,8 @@ void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float
 
 	OpenGL::TempDebugGroup debuggroup("SpriteBatch draw");
 
-	Matrix4 t(x, y, angle, sx, sy, ox, oy, kx, ky);
-
 	OpenGL::TempTransform transform(gl);
-	transform.get() *= t;
+	transform.get() *= Matrix4(x, y, angle, sx, sy, ox, oy, kx, ky);
 
 	gl.bindTexture(*(GLuint *) texture->getHandle());
 
@@ -233,8 +227,7 @@ void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float
 	GLBuffer::Bind element_bind(*quad_indices.getBuffer());
 
 	// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
-	array_buf->unmap(buffer_used_offset, buffer_used_size);
-	buffer_used_offset = buffer_used_size = 0;
+	array_buf->unmap();
 
 	uint32 enabledattribs = ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
 
@@ -254,7 +247,7 @@ void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float
 	gl.drawElements(GL_TRIANGLES, (GLsizei) quad_indices.getIndexCount(next), quad_indices.getType(), quad_indices.getPointer(0));
 }
 
-void SpriteBatch::addv(const Vertex *v, const Matrix4 &m, int index)
+void SpriteBatch::addv(const Vertex *v, const Matrix3 &m, int index)
 {
 	// Needed for colors.
 	Vertex sprite[4] = {v[0], v[1], v[2], v[3]};
@@ -272,9 +265,6 @@ void SpriteBatch::addv(const Vertex *v, const Matrix4 &m, int index)
 	array_buf->map();
 
 	array_buf->fill(index * sprite_size, sprite_size, sprite);
-
-	buffer_used_offset = std::min(buffer_used_offset, index * sprite_size);
-	buffer_used_size = std::max(buffer_used_size, (index + 1) * sprite_size - buffer_used_offset);
 }
 
 void SpriteBatch::setColorv(Vertex *v, const Color &color)

+ 1 - 5
jni/love/src/modules/graphics/opengl/SpriteBatch.h

@@ -103,7 +103,7 @@ public:
 
 private:
 
-	void addv(const Vertex *v, const Matrix4 &m, int index);
+	void addv(const Vertex *v, const Matrix3 &m, int index);
 
 	/**
 	 * Set the color for vertices.
@@ -129,10 +129,6 @@ private:
 	GLBuffer *array_buf;
 	QuadIndices quad_indices;
 
-	// The portion of the vertex buffer that's been modified while mapped.
-	size_t buffer_used_offset;
-	size_t buffer_used_size;
-
 }; // SpriteBatch
 
 } // opengl

+ 5 - 7
jni/love/src/modules/graphics/opengl/Text.cpp

@@ -170,7 +170,7 @@ void Text::set(const std::string &text)
 	if (text.empty())
 		return set();
 
-	addTextData({text, -1.0f, Font::ALIGN_MAX_ENUM, false, false, Matrix4()});
+	addTextData({text, -1.0f, Font::ALIGN_MAX_ENUM, false, false, Matrix3()});
 }
 
 void Text::set(const std::string &text, float wrap, Font::AlignMode align)
@@ -178,7 +178,7 @@ void Text::set(const std::string &text, float wrap, Font::AlignMode align)
 	if (text.empty())
 		return set();
 
-	addTextData({text, wrap, align, false, false, Matrix4()});
+	addTextData({text, wrap, align, false, false, Matrix3()});
 }
 
 void Text::set()
@@ -191,7 +191,7 @@ void Text::add(const std::string &text, float x, float y, float angle, float sx,
 	if (text.empty())
 		return;
 
-	Matrix4 m(x, y, angle, sx, sy, ox, oy, kx, ky);
+	Matrix3 m(x, y, angle, sx, sy, ox, oy, kx, ky);
 
 	addTextData({text, -1.0f, Font::ALIGN_MAX_ENUM, true, true, m});
 }
@@ -201,7 +201,7 @@ void Text::addf(const std::string &text, float wrap, Font::AlignMode align, floa
 	if (text.empty())
 		return;
 
-	Matrix4 m(x, y, angle, sx, sy, ox, oy, kx, ky);
+	Matrix3 m(x, y, angle, sx, sy, ox, oy, kx, ky);
 
 	addTextData({text, wrap, align, true, true, m});
 }
@@ -230,10 +230,8 @@ void Text::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 	const size_t tex_offset = offsetof(Font::GlyphVertex, s);
 	const size_t stride = sizeof(Font::GlyphVertex);
 
-	Matrix4 t(ceilf(x), ceilf(y), angle, sx, sy, ox, oy, kx, ky);
-
 	OpenGL::TempTransform transform(gl);
-	transform.get() *= t;
+	transform.get() *= Matrix4(ceilf(x), ceilf(y), angle, sx, sy, ox, oy, kx, ky);
 
 	{
 		GLBuffer::Bind bind(*vbo);

+ 1 - 1
jni/love/src/modules/graphics/opengl/Text.h

@@ -74,7 +74,7 @@ private:
 		Font::AlignMode align;
 		bool use_matrix;
 		bool append_vertices;
-		Matrix4 matrix;
+		Matrix3 matrix;
 	};
 
 	void uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t vertoffset);

+ 4 - 1
jni/love/src/modules/graphics/opengl/wrap_Canvas.cpp

@@ -51,12 +51,15 @@ int w_Canvas_renderTo(lua_State *L)
 		luax_catchexcept(L, [&](){ graphics->setCanvas(canvas); });
 
 		lua_settop(L, 2); // make sure the function is on top of the stack
-		lua_call(L, 0, 0);
+		int status = lua_pcall(L, 0, 0, 0);
 
 		graphics->setCanvas(oldcanvases);
 
 		for (Canvas *c : oldcanvases)
 			c->release();
+
+		if (status != 0)
+			return lua_error(L);
 	}
 
 	return 0;

+ 60 - 70
jni/love/src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -54,7 +54,7 @@ int w_reset(lua_State *)
 
 int w_clear(lua_State *L)
 {
-	std::vector<Color> colors(1);
+	std::vector<Colorf> colors(1);
 
 	if (lua_isnoneornil(L, 1))
 		colors[0].set(0, 0, 0, 0);
@@ -67,20 +67,20 @@ int w_clear(lua_State *L)
 			for (int j = 1; j <= 4; j++)
 				lua_rawgeti(L, i + 1, j);
 
-			colors[i].r = (unsigned char) luaL_checkinteger(L, -4);
-			colors[i].g = (unsigned char) luaL_checkinteger(L, -3);
-			colors[i].b = (unsigned char) luaL_checkinteger(L, -2);
-			colors[i].a = (unsigned char) luaL_optinteger(L, -1, 255);
+			colors[i].r = (float) luaL_checknumber(L, -4);
+			colors[i].g = (float) luaL_checknumber(L, -3);
+			colors[i].b = (float) luaL_checknumber(L, -2);
+			colors[i].a = (float) luaL_optnumber(L, -1, 255);
 
 			lua_pop(L, 4);
 		}
 	}
 	else
 	{
-		colors[0].r = (unsigned char) luaL_checkinteger(L, 1);
-		colors[0].g = (unsigned char) luaL_checkinteger(L, 2);
-		colors[0].b = (unsigned char) luaL_checkinteger(L, 3);
-		colors[0].a = (unsigned char) luaL_optinteger(L, 4, 255);
+		colors[0].r = (float) luaL_checknumber(L, 1);
+		colors[0].g = (float) luaL_checknumber(L, 2);
+		colors[0].b = (float) luaL_checknumber(L, 3);
+		colors[0].a = (float) luaL_optnumber(L, 4, 255);
 	}
 
 	luax_catchexcept(L, [&]() {
@@ -136,6 +136,12 @@ int w_isActive(lua_State *L)
 	return 1;
 }
 
+int w_isGammaCorrect(lua_State *L)
+{
+	luax_pushboolean(L, instance()->isGammaCorrect());
+	return 1;
+}
+
 int w_getWidth(lua_State *L)
 {
 	lua_pushinteger(L, instance()->getWidth());
@@ -245,7 +251,7 @@ int w_newImage(lua_State *L)
 	{
 		luaL_checktype(L, 2, LUA_TTABLE);
 		flags.mipmaps = luax_boolflag(L, 2, imageFlagName(Image::FLAG_TYPE_MIPMAPS), flags.mipmaps);
-		flags.sRGB = luax_boolflag(L, 2, imageFlagName(Image::FLAG_TYPE_SRGB), flags.sRGB);
+		flags.linear = luax_boolflag(L, 2, imageFlagName(Image::FLAG_TYPE_LINEAR), flags.linear);
 	}
 
 	bool releasedata = false;
@@ -617,24 +623,6 @@ static Mesh::DrawMode luax_optmeshdrawmode(lua_State *L, int idx, Mesh::DrawMode
 	return def;
 }
 
-static inline size_t writeVertexByteData(lua_State *L, int startidx, int components, char *data)
-{
-	uint8 *componentdata = (uint8 *) data;
-	for (int i = 0; i < components; i++)
-		componentdata[i] = (uint8) luaL_optnumber(L, startidx + i, 255);
-
-	return sizeof(uint8) * components;
-}
-
-static inline size_t writeVertexFloatData(lua_State *L, int startidx, int components, char *data)
-{
-	float *componentdata = (float *) data;
-	for (int i = 0; i < components; i++)
-		componentdata[i] = (float) luaL_optnumber(L, startidx + i, 0);
-
-	return sizeof(float) * components;
-}
-
 static Mesh *newStandardMesh(lua_State *L)
 {
 	Mesh *t = nullptr;
@@ -670,10 +658,20 @@ static Mesh *newStandardMesh(lua_State *L)
 			v.y = (float) luaL_checknumber(L, -7);
 			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);
-			v.b = (unsigned char) luaL_optinteger(L, -2, 255);
-			v.a = (unsigned char) luaL_optinteger(L, -1, 255);
+
+			Colorf c = {
+				(float) luaL_optnumber(L, -4, 255) / 255.0f,
+				(float) luaL_optnumber(L, -3, 255) / 255.0f,
+				(float) luaL_optnumber(L, -2, 255) / 255.0f,
+				(float) luaL_optnumber(L, -1, 255) / 255.0f
+			};
+
+			gammaCorrectColor(c);
+
+			v.r = (unsigned char) (c.r * 255.0f);
+			v.g = (unsigned char) (c.g * 255.0f);
+			v.b = (unsigned char) (c.b * 255.0f);
+			v.a = (unsigned char) (c.a * 255.0f);
 
 			lua_pop(L, 9);
 			vertices.push_back(v);
@@ -799,16 +797,7 @@ static Mesh *newCustomMesh(lua_State *L)
 				}
 
 				// Fetch the values from Lua and store them in data buffer.
-				switch (vertexformat[i].type)
-				{
-				case Mesh::DATA_BYTE:
-					writeVertexByteData(L, -components, components, data);
-					break;
-				case Mesh::DATA_FLOAT:
-				default:
-					writeVertexFloatData(L, -components, components, data);
-					break;
-				}
+				luax_writeAttributeData(L, -components, vertexformat[i].type, components, data);
 
 				lua_pop(L, components);
 
@@ -867,25 +856,25 @@ int w_newText(lua_State *L)
 
 int w_setColor(lua_State *L)
 {
-	Color c;
+	Colorf c;
 	if (lua_istable(L, 1))
 	{
 		for (int i = 1; i <= 4; i++)
 			lua_rawgeti(L, 1, i);
 
-		c.r = (unsigned char) luaL_checknumber(L, -4);
-		c.g = (unsigned char) luaL_checknumber(L, -3);
-		c.b = (unsigned char) luaL_checknumber(L, -2);
-		c.a = (unsigned char) luaL_optnumber(L, -1, 255);
+		c.r = (float) luaL_checknumber(L, -4);
+		c.g = (float) luaL_checknumber(L, -3);
+		c.b = (float) luaL_checknumber(L, -2);
+		c.a = (float) luaL_optnumber(L, -1, 255);
 
 		lua_pop(L, 4);
 	}
 	else
 	{
-		c.r = (unsigned char) luaL_checknumber(L, 1);
-		c.g = (unsigned char) luaL_checknumber(L, 2);
-		c.b = (unsigned char) luaL_checknumber(L, 3);
-		c.a = (unsigned char) luaL_optnumber(L, 4, 255);
+		c.r = (float) luaL_checknumber(L, 1);
+		c.g = (float) luaL_checknumber(L, 2);
+		c.b = (float) luaL_checknumber(L, 3);
+		c.a = (float) luaL_optnumber(L, 4, 255);
 	}
 	instance()->setColor(c);
 	return 0;
@@ -893,35 +882,35 @@ int w_setColor(lua_State *L)
 
 int w_getColor(lua_State *L)
 {
-	Color c = instance()->getColor();
-	lua_pushinteger(L, c.r);
-	lua_pushinteger(L, c.g);
-	lua_pushinteger(L, c.b);
-	lua_pushinteger(L, c.a);
+	Colorf c = instance()->getColor();
+	lua_pushnumber(L, c.r);
+	lua_pushnumber(L, c.g);
+	lua_pushnumber(L, c.b);
+	lua_pushnumber(L, c.a);
 	return 4;
 }
 
 int w_setBackgroundColor(lua_State *L)
 {
-	Color c;
+	Colorf c;
 	if (lua_istable(L, 1))
 	{
 		for (int i = 1; i <= 4; i++)
 			lua_rawgeti(L, 1, i);
 
-		c.r = (unsigned char) luaL_checknumber(L, -4);
-		c.g = (unsigned char) luaL_checknumber(L, -3);
-		c.b = (unsigned char) luaL_checknumber(L, -2);
-		c.a = (unsigned char) luaL_optnumber(L, -1, 255);
+		c.r = (float) luaL_checknumber(L, -4);
+		c.g = (float) luaL_checknumber(L, -3);
+		c.b = (float) luaL_checknumber(L, -2);
+		c.a = (float) luaL_optnumber(L, -1, 255);
 
 		lua_pop(L, 4);
 	}
 	else
 	{
-		c.r = (unsigned char) luaL_checknumber(L, 1);
-		c.g = (unsigned char) luaL_checknumber(L, 2);
-		c.b = (unsigned char) luaL_checknumber(L, 3);
-		c.a = (unsigned char) luaL_optnumber(L, 4, 255);
+		c.r = (float) luaL_checknumber(L, 1);
+		c.g = (float) luaL_checknumber(L, 2);
+		c.b = (float) luaL_checknumber(L, 3);
+		c.a = (float) luaL_optnumber(L, 4, 255);
 	}
 	instance()->setBackgroundColor(c);
 	return 0;
@@ -929,11 +918,11 @@ int w_setBackgroundColor(lua_State *L)
 
 int w_getBackgroundColor(lua_State *L)
 {
-	Color c = instance()->getBackgroundColor();
-	lua_pushinteger(L, c.r);
-	lua_pushinteger(L, c.g);
-	lua_pushinteger(L, c.b);
-	lua_pushinteger(L, c.a);
+	Colorf c = instance()->getBackgroundColor();
+	lua_pushnumber(L, c.r);
+	lua_pushnumber(L, c.g);
+	lua_pushnumber(L, c.b);
+	lua_pushnumber(L, c.a);
 	return 4;
 }
 
@@ -1837,6 +1826,7 @@ static const luaL_Reg functions[] =
 
 	{ "isCreated", w_isCreated },
 	{ "isActive", w_isActive },
+	{ "isGammaCorrect", w_isGammaCorrect },
 	{ "getWidth", w_getWidth },
 	{ "getHeight", w_getHeight },
 	{ "getDimensions", w_getDimensions },

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

@@ -47,6 +47,7 @@ int w_discard(lua_State *L);
 int w_present(lua_State *L);
 int w_isCreated(lua_State *L);
 int w_isActive(lua_State *L);
+int w_isGammaCorrect(lua_State *L);
 int w_getWidth(lua_State *L);
 int w_getHeight(lua_State *L);
 int w_getDimensions(lua_State *L);

+ 66 - 3
jni/love/src/modules/graphics/opengl/wrap_Graphics.lua

@@ -66,9 +66,66 @@ uniform LOVE_UNIFORM_PRECISION mat3 NormalMatrix;
 #endif
 uniform mediump vec4 love_ScreenSize;]]
 
+GLSL.FUNCTIONS = [[
+float gammaToLinearPrecise(float c) {
+	return c <= 0.04045 ? c * 0.077399380804954 : pow((c + 0.055) * 0.9478672985782, 2.4);
+}
+vec3 gammaToLinearPrecise(vec3 c) {
+	bvec3 leq = lessThanEqual(c, vec3(0.04045));
+	c.r = leq.r ? c.r * 0.077399380804954 : pow((c.r + 0.055) * 0.9478672985782, 2.4);
+	c.g = leq.g ? c.g * 0.077399380804954 : pow((c.g + 0.055) * 0.9478672985782, 2.4);
+	c.b = leq.b ? c.b * 0.077399380804954 : pow((c.b + 0.055) * 0.9478672985782, 2.4);
+	return c;
+}
+vec4 gammaToLinearPrecise(vec4 c) { return vec4(gammaToLinearPrecise(c.rgb), c.a); }
+float linearToGammaPrecise(float c) {
+	return c < 0.0031308 ? c * 12.92 : 1.055 * pow(c, 1.0 / 2.4) - 0.055;
+}
+vec3 linearToGammaPrecise(vec3 c) {
+	bvec3 lt = lessThan(c, vec3(0.0031308));
+	c.r = lt.r ? c.r * 12.92 : 1.055 * pow(c.r, 1.0 / 2.4) - 0.055;
+	c.g = lt.g ? c.g * 12.92 : 1.055 * pow(c.g, 1.0 / 2.4) - 0.055;
+	c.b = lt.b ? c.b * 12.92 : 1.055 * pow(c.b, 1.0 / 2.4) - 0.055;
+	return c;
+}
+vec4 linearToGammaPrecise(vec4 c) { return vec4(linearToGammaPrecise(c.rgb), c.a); }
+
+// pow(x, 2.2) isn't an amazing approximation, but at least it's efficient...
+mediump float gammaToLinearFast(mediump float c) { return pow(max(c, 0.0), 2.2); }
+mediump vec3 gammaToLinearFast(mediump vec3 c) { return pow(max(c, vec3(0.0)), vec3(2.2)); }
+mediump vec4 gammaToLinearFast(mediump vec4 c) { return vec4(gammaToLinearFast(c.rgb), c.a); }
+mediump float linearToGammaFast(mediump float c) { return pow(max(c, 0.0), 1.0 / 2.2); }
+mediump vec3 linearToGammaFast(mediump vec3 c) { return pow(max(c, vec3(0.0)), vec3(1.0 / 2.2)); }
+mediump vec4 linearToGammaFast(mediump vec4 c) { return vec4(linearToGammaFast(c.rgb), c.a); }
+
+#ifdef LOVE_PRECISE_GAMMA
+#define gammaToLinear gammaToLinearPrecise
+#define linearToGamma linearToGammaPrecise
+#else
+#define gammaToLinear gammaToLinearFast
+#define linearToGamma linearToGammaFast
+#endif
+
+#ifdef LOVE_GAMMA_CORRECT
+#define gammaCorrectColor gammaToLinear
+#define unGammaCorrectColor linearToGamma
+#define gammaCorrectColorPrecise gammaToLinearPrecise
+#define unGammaCorrectColorPrecise linearToGammaPrecise
+#define gammaCorrectColorFast gammaToLinearFast
+#define unGammaCorrectColorFast linearToGammaFast
+#else
+#define gammaCorrectColor
+#define unGammaCorrectColor
+#define gammaCorrectColorPrecise
+#define unGammaCorrectColorPrecise
+#define gammaCorrectColorFast
+#define unGammaCorrectColorFast
+#endif]]
+
 GLSL.VERTEX = {
 	HEADER = [[
 #define VERTEX
+#define LOVE_PRECISE_GAMMA
 
 attribute vec4 VertexPosition;
 attribute vec4 VertexTexCoord;
@@ -85,7 +142,7 @@ uniform mediump float love_PointSize;
 	FOOTER = [[
 void main() {
 	VaryingTexCoord = VertexTexCoord;
-	VaryingColor = VertexColor * ConstantColor;
+	VaryingColor = gammaCorrectColor(VertexColor) * ConstantColor;
 #ifdef GL_ES
 	gl_PointSize = love_PointSize;
 #endif
@@ -134,7 +191,10 @@ void main() {
 local function createVertexCode(vertexcode, lang)
 	local vertexcodes = {
 		lang == "glsles" and GLSL.VERSION_ES or GLSL.VERSION,
-		GLSL.SYNTAX, GLSL.VERTEX.HEADER, GLSL.UNIFORMS,
+		GLSL.SYNTAX,
+		love.graphics.isGammaCorrect() and "#define LOVE_GAMMA_CORRECT 1" or "",
+		GLSL.VERTEX.HEADER, GLSL.UNIFORMS,
+		GLSL.FUNCTIONS,
 		lang == "glsles" and "#line 1" or "#line 0",
 		vertexcode,
 		GLSL.VERTEX.FOOTER,
@@ -145,7 +205,10 @@ end
 local function createPixelCode(pixelcode, is_multicanvas, lang)
 	local pixelcodes = {
 		lang == "glsles" and GLSL.VERSION_ES or GLSL.VERSION,
-		GLSL.SYNTAX, GLSL.PIXEL.HEADER, GLSL.UNIFORMS,
+		GLSL.SYNTAX,
+		love.graphics.isGammaCorrect() and "#define LOVE_GAMMA_CORRECT 1" or "",
+		GLSL.PIXEL.HEADER, GLSL.UNIFORMS,
+		GLSL.FUNCTIONS,
 		lang == "glsles" and "#line 1" or "#line 0",
 		pixelcode,
 		is_multicanvas and GLSL.PIXEL.FOOTER_MULTI_CANVAS or GLSL.PIXEL.FOOTER,

+ 2 - 2
jni/love/src/modules/graphics/opengl/wrap_Image.cpp

@@ -131,8 +131,8 @@ int w_Image_getFlags(lua_State *L)
 	lua_pushboolean(L, flags.mipmaps);
 	lua_setfield(L, -2, imageFlagName(Image::FLAG_TYPE_MIPMAPS));
 
-	lua_pushboolean(L, flags.sRGB);
-	lua_setfield(L, -2, imageFlagName(Image::FLAG_TYPE_SRGB));
+	lua_pushboolean(L, flags.linear);
+	lua_setfield(L, -2, imageFlagName(Image::FLAG_TYPE_LINEAR));
 
 	return 1;
 }

+ 26 - 14
jni/love/src/modules/graphics/opengl/wrap_Mesh.cpp

@@ -42,6 +42,7 @@ Mesh *luax_checkmesh(lua_State *L, int idx)
 static inline size_t writeByteData(lua_State *L, int startidx, int components, char *data)
 {
 	uint8 *componentdata = (uint8 *) data;
+
 	for (int i = 0; i < components; i++)
 		componentdata[i] = (uint8) luaL_optnumber(L, startidx + i, 255);
 
@@ -51,13 +52,14 @@ static inline size_t writeByteData(lua_State *L, int startidx, int components, c
 static inline size_t writeFloatData(lua_State *L, int startidx, int components, char *data)
 {
 	float *componentdata = (float *) data;
+
 	for (int i = 0; i < components; i++)
 		componentdata[i] = (float) luaL_optnumber(L, startidx + i, 0);
 
 	return sizeof(float) * components;
 }
 
-static inline char *writeAttributeData(lua_State *L, int startidx, Mesh::DataType type, int components, char *data)
+char *luax_writeAttributeData(lua_State *L, int startidx, Mesh::DataType type, int components, char *data)
 {
 	switch (type)
 	{
@@ -70,24 +72,34 @@ static inline char *writeAttributeData(lua_State *L, int startidx, Mesh::DataTyp
 	}
 }
 
-template <typename T>
-static inline size_t readData(lua_State *L, int components, const char *data)
+static inline size_t readByteData(lua_State *L, int components, const char *data)
 {
-	const T *componentdata = (const T *) data;
+	const uint8 *componentdata = (const uint8 *) data;
+
 	for (int i = 0; i < components; i++)
 		lua_pushnumber(L, (lua_Number) componentdata[i]);
 
-	return sizeof(T) * components;
+	return sizeof(uint8) * components;
+}
+
+static inline size_t readFloatData(lua_State *L, int components, const char *data)
+{
+	const float *componentdata = (const float *) data;
+
+	for (int i = 0; i < components; i++)
+		lua_pushnumber(L, componentdata[i]);
+
+	return sizeof(float) * components;
 }
 
-static inline const char *readAttributeData(lua_State *L, Mesh::DataType type, int components, const char *data)
+const char *luax_readAttributeData(lua_State *L, Mesh::DataType type, int components, const char *data)
 {
 	switch (type)
 	{
 	case Mesh::DATA_BYTE:
-		return data + readData<uint8>(L, components, data);
+		return data + readByteData(L, components, data);
 	case Mesh::DATA_FLOAT:
-		return data + readData<float>(L, components, data);
+		return data + readFloatData(L, components, data);
 	default:
 		return data;
 	}
@@ -125,7 +137,7 @@ int w_Mesh_setVertices(lua_State *L)
 		for (const Mesh::AttribFormat &format : vertexformat)
 		{
 			// Fetch the values from Lua and store them in data buffer.
-			data = writeAttributeData(L, idx, format.type, format.components, data);
+			data = luax_writeAttributeData(L, idx, format.type, format.components, data);
 
 			idx += format.components;
 		}
@@ -159,7 +171,7 @@ int w_Mesh_setVertex(lua_State *L)
 				lua_rawgeti(L, 3, i);
 
 			// Fetch the values from Lua and store them in data buffer.
-			writtendata = writeAttributeData(L, -format.components, format.type, format.components, writtendata);
+			writtendata = luax_writeAttributeData(L, -format.components, format.type, format.components, writtendata);
 
 			idx += format.components;
 			lua_pop(L, format.components);
@@ -170,7 +182,7 @@ int w_Mesh_setVertex(lua_State *L)
 		for (const Mesh::AttribFormat &format : vertexformat)
 		{
 			// Fetch the values from Lua and store them in data buffer.
-			writtendata = writeAttributeData(L, idx, format.type, format.components, writtendata);
+			writtendata = luax_writeAttributeData(L, idx, format.type, format.components, writtendata);
 			idx += format.components;
 		}
 	}
@@ -195,7 +207,7 @@ int w_Mesh_getVertex(lua_State *L)
 
 	for (const Mesh::AttribFormat &format : vertexformat)
 	{
-		readdata = readAttributeData(L, format.type, format.components, readdata);
+		readdata = luax_readAttributeData(L, format.type, format.components, readdata);
 		n += format.components;
 	}
 
@@ -216,7 +228,7 @@ int w_Mesh_setVertexAttribute(lua_State *L)
 	char data[sizeof(float) * 4];
 
 	// Fetch the values from Lua and store them in the data buffer.
-	writeAttributeData(L, 4, type, components, data);
+	luax_writeAttributeData(L, 4, type, components, data);
 
 	luax_catchexcept(L, [&](){ t->setVertexAttribute(vertindex, attribindex, data, sizeof(float) * 4); });
 	return 0;
@@ -237,7 +249,7 @@ int w_Mesh_getVertexAttribute(lua_State *L)
 
 	luax_catchexcept(L, [&](){ t->getVertexAttribute(vertindex, attribindex, data, sizeof(float) * 4); });
 
-	readAttributeData(L, type, components, data);
+	luax_readAttributeData(L, type, components, data);
 	return components;
 }
 

+ 3 - 0
jni/love/src/modules/graphics/opengl/wrap_Mesh.h

@@ -32,6 +32,9 @@ namespace graphics
 namespace opengl
 {
 
+char *luax_writeAttributeData(lua_State *L, int startidx, Mesh::DataType type, int components, char *data);
+const char *luax_readAttributeData(lua_State *L, Mesh::DataType type, int components, const char *data);
+
 Mesh *luax_checkmesh(lua_State *L, int idx);
 int w_Mesh_setVertices(lua_State *L);
 int w_Mesh_setVertex(lua_State *L);

+ 19 - 31
jni/love/src/modules/graphics/opengl/wrap_ParticleSystem.cpp

@@ -499,7 +499,7 @@ int w_ParticleSystem_setColors(lua_State *L)
 		if (nColors > 8)
 			return luaL_error(L, "At most eight (8) colors may be used.");
 
-		std::vector<Color> colors(nColors);
+		std::vector<Colorf> colors(nColors);
 
 		for (int i = 0; i < nColors; i++)
 		{
@@ -512,15 +512,13 @@ int w_ParticleSystem_setColors(lua_State *L)
 				// push args[i+2][j+1] onto the stack
 				lua_rawgeti(L, i + 2, j + 1);
 
-			unsigned char r = (unsigned char) luaL_checkinteger(L, -4);
-			unsigned char g = (unsigned char) luaL_checkinteger(L, -3);
-			unsigned char b = (unsigned char) luaL_checkinteger(L, -2);
-			unsigned char a = (unsigned char) luaL_optinteger(L, -1, 255);
+			colors[i].r = (float) luaL_checknumber(L, -4);
+			colors[i].g = (float) luaL_checknumber(L, -3);
+			colors[i].b = (float) luaL_checknumber(L, -2);
+			colors[i].a = (float) luaL_optnumber(L, -1, 255);
 
 			// pop the color components from the stack
 			lua_pop(L, 4);
-
-			colors[i] = Color(r, g, b, a);
 		}
 
 		t->setColor(colors);
@@ -536,27 +534,17 @@ int w_ParticleSystem_setColors(lua_State *L)
 		if (nColors > 8)
 			return luaL_error(L, "At most eight (8) colors may be used.");
 
-		if (nColors == 1)
-		{
-			unsigned char r = (unsigned char) luaL_checkinteger(L, 2);
-			unsigned char g = (unsigned char) luaL_checkinteger(L, 3);
-			unsigned char b = (unsigned char) luaL_checkinteger(L, 4);
-			unsigned char a = (unsigned char) luaL_optinteger(L, 5, 255);
-			t->setColor(Color(r,g,b,a));
-		}
-		else
+		std::vector<Colorf> colors(nColors);
+
+		for (int i = 0; i < nColors; ++i)
 		{
-			std::vector<Color> colors(nColors);
-			for (int i = 0; i < nColors; ++i)
-			{
-				unsigned char r = (unsigned char) luaL_checkinteger(L, 1 + i*4 + 1);
-				unsigned char g = (unsigned char) luaL_checkinteger(L, 1 + i*4 + 2);
-				unsigned char b = (unsigned char) luaL_checkinteger(L, 1 + i*4 + 3);
-				unsigned char a = (unsigned char) luaL_checkinteger(L, 1 + i*4 + 4);
-				colors[i] = Color(r,g,b,a);
-			}
-			t->setColor(colors);
+			colors[i].r = (float) luaL_checknumber(L, 1 + i*4 + 1);
+			colors[i].g = (float) luaL_checknumber(L, 1 + i*4 + 2);
+			colors[i].b = (float) luaL_checknumber(L, 1 + i*4 + 3);
+			colors[i].a = (float) luaL_checknumber(L, 1 + i*4 + 4);
 		}
+
+		t->setColor(colors);
 	}
 
 	return 0;
@@ -566,19 +554,19 @@ int w_ParticleSystem_getColors(lua_State *L)
 {
 	ParticleSystem *t = luax_checkparticlesystem(L, 1);
 
-	const std::vector<Color> &colors =t->getColor();
+	const std::vector<Colorf> &colors =t->getColor();
 
 	for (size_t i = 0; i < colors.size(); i++)
 	{
 		lua_createtable(L, 4, 0);
 
-		lua_pushinteger(L, colors[i].r);
+		lua_pushnumber(L, colors[i].r);
 		lua_rawseti(L, -2, 1);
-		lua_pushinteger(L, colors[i].g);
+		lua_pushnumber(L, colors[i].g);
 		lua_rawseti(L, -2, 2);
-		lua_pushinteger(L, colors[i].b);
+		lua_pushnumber(L, colors[i].b);
 		lua_rawseti(L, -2, 3);
-		lua_pushinteger(L, colors[i].a);
+		lua_pushnumber(L, colors[i].a);
 		lua_rawseti(L, -2, 4);
 	}
 

+ 34 - 2
jni/love/src/modules/graphics/opengl/wrap_Shader.cpp

@@ -20,8 +20,11 @@
 
 #include "wrap_Shader.h"
 #include "graphics/wrap_Texture.h"
+#include "math/MathModule.h"
+
 #include <string>
 #include <iostream>
+#include <algorithm>
 
 namespace love
 {
@@ -148,7 +151,7 @@ int w_Shader_sendInt(lua_State *L)
 	return 0;
 }
 
-int w_Shader_sendFloat(lua_State *L)
+static int w__Shader_sendFloat(lua_State *L, bool colors)
 {
 	Shader *shader = luax_checkshader(L, 1);
 	const char *name = luaL_checkstring(L, 2);
@@ -157,7 +160,7 @@ int w_Shader_sendFloat(lua_State *L)
 	if (count < 1)
 		return luaL_error(L, "No variable to send.");
 
-	float *values = 0;
+	float *values = nullptr;
 	size_t dimension = 1;
 
 	if (lua_isnumber(L, 3) || lua_isboolean(L, 3))
@@ -170,6 +173,24 @@ int w_Shader_sendFloat(lua_State *L)
 	if (!values)
 		return luaL_error(L, "Error in arguments.");
 
+	if (colors)
+	{
+		bool gammacorrect = love::graphics::isGammaCorrect();
+		const auto &m = love::math::Math::instance;
+
+		for (int i = 0; i < count; i++)
+		{
+			for (int j = 0; j < (int) dimension; j++)
+			{
+				// the fourth component (alpha) is always already linear, if it exists.
+				if (gammacorrect && i < 4)
+					values[i * dimension + j] = m.gammaToLinear(values[i * dimension + j] / 255.0f);
+				else
+					values[i * dimension + j] /= 255.0f;
+			}
+		}
+	}
+
 	bool should_error = false;
 	try
 	{
@@ -189,6 +210,16 @@ int w_Shader_sendFloat(lua_State *L)
 	return 0;
 }
 
+int w_Shader_sendFloat(lua_State *L)
+{
+	return w__Shader_sendFloat(L, false);
+}
+
+int w_Shader_sendColor(lua_State *L)
+{
+	return w__Shader_sendFloat(L, true);
+}
+
 int w_Shader_sendMatrix(lua_State *L)
 {
 	int count = lua_gettop(L) - 2;
@@ -372,6 +403,7 @@ static const luaL_Reg functions[] =
 	{ "sendInt",     w_Shader_sendInt },
 	{ "sendBoolean", w_Shader_sendInt },
 	{ "sendFloat",   w_Shader_sendFloat },
+	{ "sendColor",   w_Shader_sendColor },
 	{ "sendMatrix",  w_Shader_sendMatrix },
 	{ "sendTexture", w_Shader_sendTexture },
 	{ "send",        w_Shader_send },

+ 2 - 0
jni/love/src/modules/graphics/opengl/wrap_Shader.h

@@ -22,6 +22,7 @@
 #define LOVE_GRAPHICS_OPENGL_WRAP_PROGRAM_H
 
 #include "common/runtime.h"
+#include "common/config.h"
 #include "Shader.h"
 
 namespace love
@@ -35,6 +36,7 @@ Shader *luax_checkshader(lua_State *L, int idx);
 int w_Shader_getWarnings(lua_State *L);
 int w_Shader_sendInt(lua_State *L);
 int w_Shader_sendFloat(lua_State *L);
+int w_Shader_sendColor(lua_State *L);
 int w_Shader_sendMatrix(lua_State *L);
 int w_Shader_sendTexture(lua_State *L);
 int w_Shader_send(lua_State *L);

+ 13 - 13
jni/love/src/modules/graphics/opengl/wrap_SpriteBatch.cpp

@@ -144,19 +144,19 @@ int w_SpriteBatch_setColor(lua_State *L)
 		for (int i = 1; i <= 4; i++)
 			lua_rawgeti(L, 2, i);
 
-		c.r = (unsigned char) luaL_checkinteger(L, -4);
-		c.g = (unsigned char) luaL_checkinteger(L, -3);
-		c.b = (unsigned char) luaL_checkinteger(L, -2);
-		c.a = (unsigned char) luaL_optinteger(L, -1, 255);
+		c.r = (unsigned char) luaL_checknumber(L, -4);
+		c.g = (unsigned char) luaL_checknumber(L, -3);
+		c.b = (unsigned char) luaL_checknumber(L, -2);
+		c.a = (unsigned char) luaL_optnumber(L, -1, 255);
 
 		lua_pop(L, 4);
 	}
 	else
 	{
-		c.r = (unsigned char)luaL_checkinteger(L, 2);
-		c.g = (unsigned char)luaL_checkinteger(L, 3);
-		c.b = (unsigned char)luaL_checkinteger(L, 4);
-		c.a = (unsigned char)luaL_optinteger(L, 5, 255);
+		c.r = (unsigned char) luaL_checknumber(L, 2);
+		c.g = (unsigned char) luaL_checknumber(L, 3);
+		c.b = (unsigned char) luaL_checknumber(L, 4);
+		c.a = (unsigned char) luaL_optnumber(L, 5, 255);
 	}
 
 	t->setColor(c);
@@ -169,14 +169,14 @@ int w_SpriteBatch_getColor(lua_State *L)
 	SpriteBatch *t = luax_checkspritebatch(L, 1);
 	const Color *color = t->getColor();
 
-	// getColor returns NULL if no color is set.
+	// getColor returns null if no color is set.
 	if (!color)
 		return 0;
 
-	lua_pushinteger(L, (lua_Integer) color->r);
-	lua_pushinteger(L, (lua_Integer) color->g);
-	lua_pushinteger(L, (lua_Integer) color->b);
-	lua_pushinteger(L, (lua_Integer) color->a);
+	lua_pushnumber(L, color->r);
+	lua_pushnumber(L, color->g);
+	lua_pushnumber(L, color->b);
+	lua_pushnumber(L, color->a);
 
 	return 4;
 }

+ 3 - 3
jni/love/src/modules/image/magpie/KTXHandler.cpp

@@ -170,7 +170,7 @@ uint8 *KTXHandler::parse(filesystem::FileData *filedata, std::vector<CompressedI
 	{
 		uint32 *headerArray = (uint32 *) &header.glType;
 		for (int i = 0; i < 12; i++)
-			headerArray[i] = swap32(headerArray[i]);
+			headerArray[i] = swapuint32(headerArray[i]);
 	}
 
 	header.numberOfMipmapLevels = std::max(header.numberOfMipmapLevels, 1u);
@@ -203,7 +203,7 @@ uint8 *KTXHandler::parse(filesystem::FileData *filedata, std::vector<CompressedI
 		uint32 mipsize = *(uint32 *) (filebytes + fileoffset);
 
 		if (header.endianness == KTX_ENDIAN_REF_REV)
-			mipsize = swap32(mipsize);
+			mipsize = swapuint32(mipsize);
 
 		fileoffset += sizeof(uint32);
 
@@ -235,7 +235,7 @@ uint8 *KTXHandler::parse(filesystem::FileData *filedata, std::vector<CompressedI
 		uint32 mipsize = *(uint32 *) (filebytes + fileoffset);
 
 		if (header.endianness == KTX_ENDIAN_REF_REV)
-			mipsize = swap32(mipsize);
+			mipsize = swapuint32(mipsize);
 
 		fileoffset += sizeof(uint32);
 

+ 1 - 1
jni/love/src/modules/image/magpie/PKMHandler.cpp

@@ -38,7 +38,7 @@ inline uint16 swap16big(uint16 x)
 #ifdef LOVE_BIG_ENDIAN
 	return x;
 #else
-	return swap16(x);
+	return swapuint16(x);
 #endif
 }
 

+ 11 - 11
jni/love/src/modules/image/magpie/PVRHandler.cpp

@@ -117,7 +117,7 @@ void ConvertPVRHeader(PVRTexHeaderV2 header2, PVRTexHeaderV3 *header3)
 		// All of the struct's members are uint32 values, so we can do this.
 		uint32 *headerArray = (uint32 *) &header2;
 		for (size_t i = 0; i < sizeof(PVRTexHeaderV2) / sizeof(uint32); i++)
-			headerArray[i] = swap32(headerArray[i]);
+			headerArray[i] = swapuint32(headerArray[i]);
 	}
 
 	memset(header3, 0, sizeof(PVRTexHeaderV3));
@@ -309,16 +309,16 @@ uint8 *PVRHandler::parse(filesystem::FileData *filedata, std::vector<CompressedI
 	if (header3.version == PVRTEX3_IDENT_REV)
 	{
 		header3.version = PVRTEX3_IDENT;
-		header3.flags = swap32(header3.flags);
-		header3.pixelFormat = swap64(header3.pixelFormat);
-		header3.colorSpace = swap32(header3.colorSpace);
-		header3.channelType = swap32(header3.channelType);
-		header3.height = swap32(header3.height);
-		header3.width = swap32(header3.width);
-		header3.depth = swap32(header3.depth);
-		header3.numFaces = swap32(header3.numFaces);
-		header3.numMipmaps = swap32(header3.numMipmaps);
-		header3.metaDataSize = swap32(header3.metaDataSize);
+		header3.flags = swapuint32(header3.flags);
+		header3.pixelFormat = swapuint64(header3.pixelFormat);
+		header3.colorSpace = swapuint32(header3.colorSpace);
+		header3.channelType = swapuint32(header3.channelType);
+		header3.height = swapuint32(header3.height);
+		header3.width = swapuint32(header3.width);
+		header3.depth = swapuint32(header3.depth);
+		header3.numFaces = swapuint32(header3.numFaces);
+		header3.numMipmaps = swapuint32(header3.numMipmaps);
+		header3.metaDataSize = swapuint32(header3.metaDataSize);
 	}
 
 	if (header3.depth > 1)

+ 16 - 0
jni/love/src/modules/love/love.cpp

@@ -55,6 +55,11 @@
 #	include "libraries/luautf8/lutf8lib.h"
 #endif
 
+// For love::graphics::setGammaCorrect.
+#ifdef LOVE_ENABLE_GRAPHICS
+#	include "graphics/Graphics.h"
+#endif
+
 // Scripts
 #include "scripts/nogame.lua.h"
 #include "scripts/boot.lua.h"
@@ -238,6 +243,14 @@ static int w_love_isVersionCompatible(lua_State *L)
 	return 1;
 }
 
+static int w__setGammaCorrect(lua_State *L)
+{
+#ifdef LOVE_ENABLE_GRAPHICS
+	love::graphics::setGammaCorrect((bool) lua_toboolean(L, 1));
+#endif
+	return 0;
+}
+
 int luaopen_love(lua_State *L)
 {
 	love::luax_insistpinnedthread(L);
@@ -268,6 +281,9 @@ int luaopen_love(lua_State *L)
 	lua_setfield(L, -2, "_setAccelerometerAsJoystick");
 #endif
 
+	lua_pushcfunction(L, w__setGammaCorrect);
+	lua_setfield(L, -2, "_setGammaCorrect");
+
 	lua_newtable(L);
 
 	for (int i = 0; love::VERSION_COMPATIBILITY[i] != nullptr; i++)

+ 2 - 2
jni/love/src/modules/math/Compressor.cpp

@@ -63,7 +63,7 @@ public:
 		// Store the size of the uncompressed data as a header.
 #ifdef LOVE_BIG_ENDIAN
 		// Make sure it's little-endian for storage.
-		*(uint32 *) compressedbytes = swap32((uint32) dataSize);
+		*(uint32 *) compressedbytes = swapuint32((uint32) dataSize);
 #else
 		*(uint32 *) compressedbytes = (uint32) dataSize;
 #endif
@@ -110,7 +110,7 @@ public:
 		// Extract the original uncompressed size (stored in our custom header.)
 #ifdef LOVE_BIG_ENDIAN
 		// Convert from stored little-endian to big-endian.
-		uint32 rawsize = swap32(*(uint32 *) data);
+		uint32 rawsize = swapuint32(*(uint32 *) data);
 #else
 		uint32 rawsize = *(uint32 *) data;
 #endif

+ 3 - 11
jni/love/src/modules/math/MathModule.cpp

@@ -208,11 +208,7 @@ bool Math::isConvex(const std::vector<Vertex> &polygon)
  **/
 float Math::gammaToLinear(float c) const
 {
-	if (c > 1.0f)
-		return 1.0f;
-	else if (c < 0.0f)
-		return 0.0f;
-	else if (c <= 0.04045)
+	if (c <= 0.04045f)
 		return c / 12.92f;
 	else
 		return powf((c + 0.055f) / 1.055f, 2.4f);
@@ -223,14 +219,10 @@ float Math::gammaToLinear(float c) const
  **/
 float Math::linearToGamma(float c) const
 {
-	if (c > 1.0f)
-		return 1.0f;
-	else if (c < 0.0f)
-		return 0.0f;
-	else if (c < 0.0031308f)
+	if (c < 0.0031308f)
 		return c * 12.92f;
 	else
-		return 1.055f * powf(c, 0.41666f) - 0.055f;
+		return 1.055f * powf(c, 1.0f / 2.4f) - 0.055f;
 }
 
 CompressedData *Math::compress(Compressor::Format format, love::Data *rawdata, int level)

+ 0 - 1
jni/love/src/modules/window/Window.cpp

@@ -77,7 +77,6 @@ StringMap<Window::Setting, Window::SETTING_MAX_ENUM>::Entry Window::settingEntri
 	{"centered", SETTING_CENTERED},
 	{"display", SETTING_DISPLAY},
 	{"highdpi", SETTING_HIGHDPI},
-	{"srgb", SETTING_SRGB},
 	{"refreshrate", SETTING_REFRESHRATE},
 	{"x", SETTING_X},
 	{"y", SETTING_Y},

+ 0 - 2
jni/love/src/modules/window/Window.h

@@ -57,7 +57,6 @@ public:
 		SETTING_CENTERED,
 		SETTING_DISPLAY,
 		SETTING_HIGHDPI,
-		SETTING_SRGB,
 		SETTING_REFRESHRATE,
 		SETTING_X,
 		SETTING_Y,
@@ -210,7 +209,6 @@ struct WindowSettings
 	bool centered = true;
 	int display = 0;
 	bool highdpi = false;
-	bool sRGB = false;
 	double refreshrate = 0.0;
 	bool useposition = false;
 	int x = 0;

+ 31 - 15
jni/love/src/modules/window/sdl/Window.cpp

@@ -62,9 +62,14 @@ Window::Window()
 	, context(nullptr)
 	, displayedWindowError(false)
 	, displayedContextError(false)
+	, hasSDL203orEarlier(false)
 {
 	if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
 		throw love::Exception("Could not initialize SDL video subsystem (%s)", SDL_GetError());
+
+	SDL_version version = {};
+	SDL_GetVersion(&version);
+	hasSDL203orEarlier = (version.major == 2 && version.minor == 0 && version.patch <= 3);
 }
 
 Window::~Window()
@@ -88,12 +93,17 @@ void Window::setGLFramebufferAttributes(int msaa, bool sRGB)
 	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (msaa > 0) ? 1 : 0);
 	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, (msaa > 0) ? msaa : 0);
 
-	// SDL or GLX may have bugs with this: https://bugzilla.libsdl.org/show_bug.cgi?id=2897
-	// It's fine to leave the attribute disabled on desktops though, because in
-	// practice the framebuffer will be sRGB-capable even if it's not requested.
-#if !defined(LOVE_LINUX)
 	SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, sRGB ? 1 : 0);
-#endif
+
+	const char *driver = SDL_GetCurrentVideoDriver();
+	if (driver && strstr(driver, "x11") == driver)
+	{
+		// Always disable the sRGB flag when GLX is used with older SDL versions,
+		// because of this bug: https://bugzilla.libsdl.org/show_bug.cgi?id=2897
+		// In practice GLX will always give an sRGB-capable framebuffer anyway.
+		if (hasSDL203orEarlier)
+			SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 0);
+	}
 
 #if defined(LOVE_WINDOWS)
 	// Avoid the Microsoft OpenGL 1.1 software renderer on Windows. Apparently
@@ -158,7 +168,7 @@ bool Window::checkGLVersion(const ContextAttribs &attribs)
 	return true;
 }
 
-bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, int msaa, bool sRGB)
+bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, int msaa)
 {
 	bool preferGLES = false;
 
@@ -175,6 +185,14 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 		if (curdriver && strstr(curdriver, glesdriver) == curdriver)
 		{
 			preferGLES = true;
+
+			// Prior to SDL 2.0.4, backends that use OpenGL ES didn't properly
+			// ask for a sRGB framebuffer when requested by SDL_GL_SetAttribute.
+			// FIXME: This doesn't account for windowing backends that sometimes
+			// use EGL, e.g. the X11 and windows SDL backends.
+			if (hasSDL203orEarlier)
+				graphics::setGammaCorrect(false);
+
 			break;
 		}
 	}
@@ -196,11 +214,8 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 		{2, 0, true,  debug}, // OpenGL ES 2.
 	};
 
-	SDL_version sdlversion = {};
-	SDL_GetVersion(&sdlversion);
-
 	// OpenGL ES 3+ contexts are only properly supported in SDL 2.0.4+.
-	if (sdlversion.major == 2 && sdlversion.minor == 0 && sdlversion.patch <= 3)
+	if (hasSDL203orEarlier)
 		attribslist.erase(attribslist.begin() + 1);
 
 	// Move OpenGL ES to the front of the list if we should prefer GLES.
@@ -229,7 +244,7 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 		}
 
 		int curMSAA  = msaa;
-		bool curSRGB = sRGB;
+		bool curSRGB = love::graphics::isGammaCorrect();
 
 		setGLFramebufferAttributes(curMSAA, curSRGB);
 		setGLContextAttributes(attribs);
@@ -312,7 +327,10 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 		}
 
 		if (context)
+		{
+			love::graphics::setGammaCorrect(curSRGB);
 			break;
+		}
 	}
 
 	if (!context || !window)
@@ -437,7 +455,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 
 	close();
 
-	if (!createWindowAndContext(x, y, width, height, sdlflags, f.msaa, f.sRGB))
+	if (!createWindowAndContext(x, y, width, height, sdlflags, f.msaa))
 		return false;
 
 	// Make sure the window keeps any previously set icon.
@@ -460,7 +478,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 
 	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 	if (gfx != nullptr)
-		gfx->setMode(curMode.pixelwidth, curMode.pixelheight, curMode.settings.sRGB);
+		gfx->setMode(curMode.pixelwidth, curMode.pixelheight);
 
 #ifdef LOVE_ANDROID
 		love::android::setImmersive(f.fullscreen);
@@ -538,8 +556,6 @@ void Window::updateSettings(const WindowSettings &newsettings)
 	else
 		SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
 
-	curMode.settings.sRGB = newsettings.sRGB;
-
 	// Verify MSAA setting.
 	int buffers = 0;
 	int samples = 0;

+ 3 - 1
jni/love/src/modules/window/sdl/Window.h

@@ -119,7 +119,7 @@ private:
 	void setGLFramebufferAttributes(int msaa, bool sRGB);
 	void setGLContextAttributes(const ContextAttribs &attribs);
 	bool checkGLVersion(const ContextAttribs &attribs);
-	bool createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, int msaa, bool sRGB);
+	bool createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, int msaa);
 
 	// Update the saved window settings based on the window's actual state.
 	void updateSettings(const WindowSettings &newsettings);
@@ -149,6 +149,8 @@ private:
 	bool displayedWindowError;
 	bool displayedContextError;
 
+	bool hasSDL203orEarlier;
+
 }; // Window
 
 } // sdl

+ 0 - 4
jni/love/src/modules/window/wrap_Window.cpp

@@ -107,7 +107,6 @@ int w_setMode(lua_State *L)
 	settings.centered = luax_boolflag(L, 3, settingName(Window::SETTING_CENTERED), true);
 	settings.display = luax_intflag(L, 3, settingName(Window::SETTING_DISPLAY), 1) - 1;
 	settings.highdpi = luax_boolflag(L, 3, settingName(Window::SETTING_HIGHDPI), false);
-	settings.sRGB = luax_boolflag(L, 3, settingName(Window::SETTING_SRGB), false);
 
 	lua_getfield(L, 3, settingName(Window::SETTING_X));
 	lua_getfield(L, 3, settingName(Window::SETTING_Y));
@@ -175,9 +174,6 @@ int w_getMode(lua_State *L)
 	luax_pushboolean(L, settings.highdpi);
 	lua_setfield(L, -2, settingName(Window::SETTING_HIGHDPI));
 
-	luax_pushboolean(L, settings.sRGB);
-	lua_setfield(L, -2, settingName(Window::SETTING_SRGB));
-
 	lua_pushnumber(L, settings.refreshrate);
 	lua_setfield(L, -2, settingName(Window::SETTING_REFRESHRATE));
 

+ 9 - 12
jni/love/src/scripts/boot.lua

@@ -351,7 +351,6 @@ function love.init()
 			resizable = false,
 			centered = true,
 			highdpi = false,
-			srgb = false,
 		},
 		modules = {
 			event = true,
@@ -375,6 +374,7 @@ function love.init()
 		identity = false,
 		appendidentity = false,
 		accelerometerjoystick = true, -- Only relevant for Android / iOS.
+		gammacorrect = false,
 	}
 
 	-- Console hack, part 1.
@@ -408,6 +408,10 @@ function love.init()
 		love._setAccelerometerAsJoystick(c.accelerometerjoystick)
 	end
 
+	if love._setGammaCorrect then
+		love._setGammaCorrect(c.gammacorrect)
+	end
+
 	-- Gets desired modules.
 	for k,v in ipairs{
 		"thread",
@@ -441,8 +445,8 @@ function love.init()
 	if not love.isVersionCompatible(c.version) then
 		local major, minor, revision = c.version:match("^(%d+)%.(%d+)%.(%d+)$")
 		if (not major or not minor or not revision) or (major ~= love._version_major and minor ~= love._version_minor) then
-			local msg = "This game was made for a different version of LOVE.\n"..
-			"It may not be not be compatible with the running version ("..love._version..")."
+			local msg = ("This game indicates it was made for version '%s' of LOVE.\n"..
+				"It may not be compatible with the running version (%s)."):format(c.version, love._version)
 
 			print(msg)
 
@@ -471,13 +475,12 @@ function love.init()
 			centered = c.window.centered,
 			display = c.window.display,
 			highdpi = c.window.highdpi,
-			srgb = c.window.srgb,
 			x = c.window.x,
 			y = c.window.y,
 		}), "Could not set window mode")
 		love.window.setTitle(c.window.title or c.title)
 		if c.window.icon then
-			assert(love.image, "If an icon is set in love.conf, love.image has to be loaded!")
+			assert(love.image, "If an icon is set in love.conf, love.image must be loaded!")
 			love.window.setIcon(love.image.newImageData(c.window.icon))
 		end
 	end
@@ -589,13 +592,7 @@ function love.errhand(msg)
 	love.graphics.reset()
 	local font = love.graphics.setNewFont(math.floor(love.window.toPixels(14)))
 
-	local sRGB = select(3, love.window.getMode()).srgb
-	if sRGB and love.math then
-		love.graphics.setBackgroundColor(love.math.gammaToLinear(89, 157, 220))
-	else
-		love.graphics.setBackgroundColor(89, 157, 220)
-	end
-
+	love.graphics.setBackgroundColor(89, 157, 220)
 	love.graphics.setColor(255, 255, 255, 255)
 
 	local trace = debug.traceback()

+ 24 - 29
jni/love/src/scripts/boot.lua.h

@@ -654,7 +654,6 @@ const unsigned char boot_lua[] =
 	0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x68, 0x69, 0x67, 0x68, 0x64, 0x70, 0x69, 0x20, 0x3d, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 
 	0x2c, 0x0a,
-	0x09, 0x09, 0x09, 0x73, 0x72, 0x67, 0x62, 0x20, 0x3d, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c, 0x0a,
 	0x09, 0x09, 0x7d, 0x2c, 0x0a,
 	0x09, 0x09, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x0a,
 	0x09, 0x09, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x0a,
@@ -688,6 +687,8 @@ const unsigned char boot_lua[] =
 	0x73, 0x74, 0x69, 0x63, 0x6b, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x2d, 0x2d, 0x20, 0x4f, 
 	0x6e, 0x6c, 0x79, 0x20, 0x72, 0x65, 0x6c, 0x65, 0x76, 0x61, 0x6e, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x41, 
 	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x20, 0x2f, 0x20, 0x69, 0x4f, 0x53, 0x2e, 0x0a,
+	0x09, 0x09, 0x67, 0x61, 0x6d, 0x6d, 0x61, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x20, 0x3d, 0x20, 0x66, 
+	0x61, 0x6c, 0x73, 0x65, 0x2c, 0x0a,
 	0x09, 0x7d, 0x0a,
 	0x09, 0x2d, 0x2d, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x20, 0x68, 0x61, 0x63, 0x6b, 0x2c, 0x20, 
 	0x70, 0x61, 0x72, 0x74, 0x20, 0x31, 0x2e, 0x0a,
@@ -757,6 +758,12 @@ const unsigned char boot_lua[] =
 	0x2e, 0x61, 0x63, 0x63, 0x65, 0x6c, 0x65, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x6a, 0x6f, 0x79, 0x73, 
 	0x74, 0x69, 0x63, 0x6b, 0x29, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
+	0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x5f, 0x73, 0x65, 0x74, 0x47, 0x61, 0x6d, 0x6d, 0x61, 
+	0x43, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x5f, 0x73, 0x65, 0x74, 0x47, 0x61, 0x6d, 0x6d, 0x61, 0x43, 0x6f, 
+	0x72, 0x72, 0x65, 0x63, 0x74, 0x28, 0x63, 0x2e, 0x67, 0x61, 0x6d, 0x6d, 0x61, 0x63, 0x6f, 0x72, 0x72, 0x65, 
+	0x63, 0x74, 0x29, 0x0a,
+	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x2d, 0x2d, 0x20, 0x47, 0x65, 0x74, 0x73, 0x20, 0x64, 0x65, 0x73, 0x69, 0x72, 0x65, 0x64, 0x20, 0x6d, 
 	0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x2e, 0x0a,
 	0x09, 0x66, 0x6f, 0x72, 0x20, 0x6b, 0x2c, 0x76, 0x20, 0x69, 0x6e, 0x20, 0x69, 0x70, 0x61, 0x69, 0x72, 0x73, 
@@ -808,15 +815,17 @@ const unsigned char boot_lua[] =
 	0x5f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x20, 0x7e, 
 	0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x69, 
 	0x6e, 0x6f, 0x72, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
-	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6d, 0x73, 0x67, 0x20, 0x3d, 0x20, 0x22, 0x54, 0x68, 
-	0x69, 0x73, 0x20, 0x67, 0x61, 0x6d, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, 0x6d, 0x61, 0x64, 0x65, 0x20, 0x66, 
-	0x6f, 0x72, 0x20, 0x61, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x76, 0x65, 0x72, 
-	0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x4c, 0x4f, 0x56, 0x45, 0x2e, 0x5c, 0x6e, 0x22, 0x2e, 0x2e, 0x0a,
-	0x09, 0x09, 0x09, 0x22, 0x49, 0x74, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 
-	0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x20, 
-	0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x76, 
-	0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x22, 0x2e, 0x2e, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x5f, 0x76, 
-	0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x2e, 0x22, 0x29, 0x2e, 0x22, 0x0a,
+	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6d, 0x73, 0x67, 0x20, 0x3d, 0x20, 0x28, 0x22, 0x54, 
+	0x68, 0x69, 0x73, 0x20, 0x67, 0x61, 0x6d, 0x65, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 
+	0x20, 0x69, 0x74, 0x20, 0x77, 0x61, 0x73, 0x20, 0x6d, 0x61, 0x64, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x76, 
+	0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x27, 0x25, 0x73, 0x27, 0x20, 0x6f, 0x66, 0x20, 0x4c, 0x4f, 0x56, 
+	0x45, 0x2e, 0x5c, 0x6e, 0x22, 0x2e, 0x2e, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x22, 0x49, 0x74, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 
+	0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 
+	0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x20, 
+	0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x25, 0x73, 0x29, 0x2e, 0x22, 0x29, 0x3a, 0x66, 0x6f, 
+	0x72, 0x6d, 0x61, 0x74, 0x28, 0x63, 0x2e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x6c, 0x6f, 
+	0x76, 0x65, 0x2e, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x29, 0x0a,
 	0x09, 0x09, 0x09, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x28, 0x6d, 0x73, 0x67, 0x29, 0x0a,
 	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 
 	0x74, 0x68, 0x65, 0x6e, 0x0a,
@@ -864,8 +873,6 @@ const unsigned char boot_lua[] =
 	0x64, 0x6f, 0x77, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x68, 0x69, 0x67, 0x68, 0x64, 0x70, 0x69, 0x20, 0x3d, 0x20, 0x63, 0x2e, 0x77, 0x69, 0x6e, 
 	0x64, 0x6f, 0x77, 0x2e, 0x68, 0x69, 0x67, 0x68, 0x64, 0x70, 0x69, 0x2c, 0x0a,
-	0x09, 0x09, 0x09, 0x73, 0x72, 0x67, 0x62, 0x20, 0x3d, 0x20, 0x63, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 
-	0x2e, 0x73, 0x72, 0x67, 0x62, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x78, 0x20, 0x3d, 0x20, 0x63, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x78, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x79, 0x20, 0x3d, 0x20, 0x63, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x79, 0x2c, 0x0a,
 	0x09, 0x09, 0x7d, 0x29, 0x2c, 0x20, 0x22, 0x43, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 
@@ -878,8 +885,8 @@ const unsigned char boot_lua[] =
 	0x09, 0x09, 0x09, 0x61, 0x73, 0x73, 0x65, 0x72, 0x74, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x69, 0x6d, 0x61, 
 	0x67, 0x65, 0x2c, 0x20, 0x22, 0x49, 0x66, 0x20, 0x61, 0x6e, 0x20, 0x69, 0x63, 0x6f, 0x6e, 0x20, 0x69, 0x73, 
 	0x20, 0x73, 0x65, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x2c, 
-	0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x20, 0x68, 0x61, 0x73, 0x20, 0x74, 0x6f, 
-	0x20, 0x62, 0x65, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x21, 0x22, 0x29, 0x0a,
+	0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 
+	0x65, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x21, 0x22, 0x29, 0x0a,
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x73, 0x65, 0x74, 
 	0x49, 0x63, 0x6f, 0x6e, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2e, 0x6e, 0x65, 
 	0x77, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x44, 0x61, 0x74, 0x61, 0x28, 0x63, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 
@@ -1073,21 +1080,9 @@ const unsigned char boot_lua[] =
 	0x6e, 0x74, 0x28, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x6c, 0x6f, 0x76, 0x65, 
 	0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x74, 0x6f, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x28, 0x31, 
 	0x34, 0x29, 0x29, 0x29, 0x0a,
-	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x52, 0x47, 0x42, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x6c, 0x65, 
-	0x63, 0x74, 0x28, 0x33, 0x2c, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 
-	0x67, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x28, 0x29, 0x29, 0x2e, 0x73, 0x72, 0x67, 0x62, 0x0a,
-	0x09, 0x69, 0x66, 0x20, 0x73, 0x52, 0x47, 0x42, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 
-	0x6d, 0x61, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 
-	0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x6c, 
-	0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x67, 0x61, 0x6d, 0x6d, 0x61, 0x54, 0x6f, 0x4c, 0x69, 
-	0x6e, 0x65, 0x61, 0x72, 0x28, 0x38, 0x39, 0x2c, 0x20, 0x31, 0x35, 0x37, 0x2c, 0x20, 0x32, 0x32, 0x30, 0x29, 
-	0x29, 0x0a,
-	0x09, 0x65, 0x6c, 0x73, 0x65, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 
-	0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x38, 
-	0x39, 0x2c, 0x20, 0x31, 0x35, 0x37, 0x2c, 0x20, 0x32, 0x32, 0x30, 0x29, 0x0a,
-	0x09, 0x65, 0x6e, 0x64, 0x0a,
+	0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 
+	0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x38, 0x39, 
+	0x2c, 0x20, 0x31, 0x35, 0x37, 0x2c, 0x20, 0x32, 0x32, 0x30, 0x29, 0x0a,
 	0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 
 	0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 
 	0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, 0x0a,

+ 1 - 0
jni/love/src/scripts/nogame.lua

@@ -994,6 +994,7 @@ function love.nogame()
 
 	function love.conf(t)
 		t.title = "L\195\150VE " .. love._version .. " (" .. love._version_codename .. ")"
+		t.gammacorrect = true
 		t.modules.audio = false
 		t.modules.sound = false
 		t.modules.physics = false

+ 2 - 0
jni/love/src/scripts/nogame.lua.h

@@ -4313,6 +4313,8 @@ const unsigned char nogame_lua[] =
 	0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x20, 0x28, 0x22, 0x20, 0x2e, 0x2e, 
 	0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 
 	0x65, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x29, 0x22, 0x0a,
+	0x09, 0x09, 0x74, 0x2e, 0x67, 0x61, 0x6d, 0x6d, 0x61, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x20, 0x3d, 
+	0x20, 0x74, 0x72, 0x75, 0x65, 0x0a,
 	0x09, 0x09, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x20, 
 	0x3d, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x0a,
 	0x09, 0x09, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x2e, 0x73, 0x6f, 0x75, 0x6e, 0x64, 0x20,