Browse Source

Reworked sRGB / gamma-correct APIs:

Gamma-correct blending and shader math can be enabled globally via the new 't.gammacorrect' boolean flag in love.conf.
The new function love.graphics.isGammaCorrect will return true if it was requested in love.conf and is supported on the system.

When gamma correct rendering is enabled, colors (including the colors of pixels from images) are automatically converted from sRGB to linear RGB before use. When drawing to the main screen or to a canvas with the 'normal' or 'srgb' format, the final output colors of pixel shaders are automatically converted from linear RGB to sRGB after blending and before the color is stored in the pixel.

This lets the rendering pipeline do math using linear RGB values for colors rather than sRGB values, so the math is correct, without making users of the APIs manually linearize their colors with the love.math.gammaToLinear function (which still exists). The final output of the screen is encoded as sRGB, which is what systems expect. Canvases (except when otherwise requested) store their contents with sRGB encoding for increased precision with darker colors.

the 'srgb' window setting flag has been removed, as well as the 'srgb' image flag. A new image flag 'linear' has been added, which when set to true will cause the colors of the image to always be treated as linear RGB rather than sRGB, when gamma-correct rendering is enabled.

A new function 'Shader:sendColor' has been added, which has the same argument structure as 'Shader:send' but expects colors in the range of [0, 255]. When gamma-correct rendering is enabled it automatically gamma-corrects the given colors (by applying gammaToLinear to them.)

New shader code functions have been added: gammaToLinear, linearToGamma, gammaCorrectColor, and unGammaCorrectColor. When gamma-correct rendering is enabled, the LOVE_GAMMA_CORRECT #define is set and gammaCorrectColor and unGammaCorrectColor are aliases for gammaToLinear and linearToGamma respectively, otherwise the functions do nothing.

The new shader functions have 'precise' and 'fast' variants. If the LOVE_PRECISE_GAMMA define is set, then the normal functions default to the precise variants, otherwise they default to the fast variants. Currently the define is set in vertex shaders and not set in pixel shaders.

The default per-vertex color is automatically gamma-corrected by LÖVE when gamma correct rendering is enabled, but any custom named per-vertex attribute specified with the new custom attribute Mesh functionality won't be, unless 'gammaCorrectColor' or similar functions are explicitly used (preferably inside the vertex shader code that has the custom attribute.)
Alex Szpakowski 10 years ago
parent
commit
d215d8a91e
32 changed files with 509 additions and 279 deletions
  1. 33 1
      src/modules/graphics/Graphics.cpp
  2. 27 2
      src/modules/graphics/Graphics.h
  3. 9 6
      src/modules/graphics/opengl/Canvas.cpp
  4. 1 1
      src/modules/graphics/opengl/Canvas.h
  5. 38 19
      src/modules/graphics/opengl/Graphics.cpp
  6. 11 9
      src/modules/graphics/opengl/Graphics.h
  7. 55 25
      src/modules/graphics/opengl/Image.cpp
  8. 5 3
      src/modules/graphics/opengl/Image.h
  9. 1 0
      src/modules/graphics/opengl/Mesh.cpp
  10. 21 20
      src/modules/graphics/opengl/ParticleSystem.cpp
  11. 3 3
      src/modules/graphics/opengl/ParticleSystem.h
  12. 60 70
      src/modules/graphics/opengl/wrap_Graphics.cpp
  13. 1 0
      src/modules/graphics/opengl/wrap_Graphics.h
  14. 66 3
      src/modules/graphics/opengl/wrap_Graphics.lua
  15. 2 2
      src/modules/graphics/opengl/wrap_Image.cpp
  16. 26 14
      src/modules/graphics/opengl/wrap_Mesh.cpp
  17. 3 0
      src/modules/graphics/opengl/wrap_Mesh.h
  18. 22 22
      src/modules/graphics/opengl/wrap_ParticleSystem.cpp
  19. 34 2
      src/modules/graphics/opengl/wrap_Shader.cpp
  20. 2 0
      src/modules/graphics/opengl/wrap_Shader.h
  21. 13 13
      src/modules/graphics/opengl/wrap_SpriteBatch.cpp
  22. 16 0
      src/modules/love/love.cpp
  23. 3 11
      src/modules/math/MathModule.cpp
  24. 0 1
      src/modules/window/Window.cpp
  25. 0 2
      src/modules/window/Window.h
  26. 31 15
      src/modules/window/sdl/Window.cpp
  27. 3 1
      src/modules/window/sdl/Window.h
  28. 0 4
      src/modules/window/wrap_Window.cpp
  29. 7 10
      src/scripts/boot.lua
  30. 13 20
      src/scripts/boot.lua.h
  31. 1 0
      src/scripts/nogame.lua
  32. 2 0
      src/scripts/nogame.lua.h

+ 33 - 1
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));

+ 27 - 2
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
 	};
 
@@ -168,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
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
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.

+ 38 - 19
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
 {
-	glVertexAttrib4f(ATTRIB_CONSTANTCOLOR, c.r/255.0f, c.g/255.0f, c.b/255.0f, c.a/255.0f);
+	return love::graphics::isGammaCorrect();
+}
+
+void Graphics::setColor(Colorf c)
+{
+	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;
 	}

+ 11 - 9
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,8 +465,8 @@ private:
 
 	struct DisplayState
 	{
-		Color color = Color(255, 255, 255, 255);
-		Color backgroundColor = Color(0, 0, 0, 255);
+		Colorf color = Colorf(255.0, 255.0, 255.0, 255.0);
+		Colorf backgroundColor = Colorf(0.0, 0.0, 0.0, 255.0);
 
 		BlendMode blendMode = BLEND_ALPHA;
 		bool blendMultiplyAlpha = true;

+ 55 - 25
src/modules/graphics/opengl/Image.cpp

@@ -20,6 +20,7 @@
 
 #include "Image.h"
 
+#include "graphics/Graphics.h"
 #include "common/int.h"
 
 // STD
@@ -81,6 +82,7 @@ Image::Image(const std::vector<love::image::ImageData *> &imagedata, const Flags
 	, mipmapSharpness(defaultMipmapSharpness)
 	, compressed(false)
 	, flags(flags)
+	, sRGB(false)
 	, usingDefaultTexture(false)
 	, textureMemorySize(0)
 {
@@ -107,11 +109,10 @@ 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);
 
@@ -169,6 +170,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()
@@ -204,7 +213,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;
 
@@ -228,13 +240,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;
 	}
 
@@ -257,24 +269,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;
@@ -392,7 +404,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;
@@ -560,93 +572,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;
@@ -719,7 +749,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
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;

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

@@ -298,6 +298,7 @@ Mesh::DataType Mesh::getAttributeInfo(int attribindex, int &components) const
 
 	DataType type = vertexFormat[attribindex].type;
 	components = vertexFormat[attribindex].components;
+
 	return type;
 }
 

+ 21 - 20
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;
@@ -706,30 +701,36 @@ love::Vector ParticleSystem::getOffset() const
 	return offset;
 }
 
-void ParticleSystem::setColor(const Color &color)
+void ParticleSystem::setColor(const Colorf &color)
 {
-	colors.resize(1);
-	colors[0] = colorToFloat(color);
+	setColor({color});
 }
 
-void ParticleSystem::setColor(const std::vector<Color> &newColors)
+void ParticleSystem::setColor(const std::vector<Colorf> &newColors)
 {
-	colors.resize(newColors.size());
-	for (size_t i = 0; i < newColors.size(); ++i)
-		colors[i] = colorToFloat(newColors[i]);
+	colors = newColors;
+
+	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;

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

@@ -421,18 +421,18 @@ public:
 	 * Sets the color of the particles.
 	 * @param color The color.
 	 **/
-	void setColor(const Color &color);
+	void setColor(const Colorf &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.

+ 60 - 70
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
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
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
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
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
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);

+ 22 - 22
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,15 @@ 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);
+			float r = (float) luaL_checknumber(L, -4);
+			float g = (float) luaL_checknumber(L, -3);
+			float b = (float) luaL_checknumber(L, -2);
+			float 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);
+			colors[i] = Colorf(r, g, b, a);
 		}
 
 		t->setColor(colors);
@@ -538,22 +538,22 @@ int w_ParticleSystem_setColors(lua_State *L)
 
 		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));
+			float r = (float) luaL_checknumber(L, 2);
+			float g = (float) luaL_checknumber(L, 3);
+			float b = (float) luaL_checknumber(L, 4);
+			float a = (float) luaL_optnumber(L, 5, 255);
+			t->setColor(Colorf(r,g,b,a));
 		}
 		else
 		{
-			std::vector<Color> colors(nColors);
+			std::vector<Colorf> 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);
+				float r = (float) luaL_checknumber(L, 1 + i*4 + 1);
+				float g = (float) luaL_checknumber(L, 1 + i*4 + 2);
+				float b = (float) luaL_checknumber(L, 1 + i*4 + 3);
+				float a = (float) luaL_checknumber(L, 1 + i*4 + 4);
+				colors[i] = Colorf(r,g,b,a);
 			}
 			t->setColor(colors);
 		}
@@ -566,19 +566,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
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
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
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;
 }

+ 16 - 0
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++)

+ 3 - 11
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
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
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
src/modules/window/sdl/Window.cpp

@@ -58,9 +58,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()
@@ -84,12 +89,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
@@ -154,7 +164,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;
 
@@ -171,6 +181,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;
 		}
 	}
@@ -192,11 +210,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.
@@ -225,7 +240,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);
@@ -308,7 +323,10 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 		}
 
 		if (context)
+		{
+			love::graphics::setGammaCorrect(curSRGB);
 			break;
+		}
 	}
 
 	if (!context || !window)
@@ -428,7 +446,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.
@@ -451,7 +469,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);
 
 	return true;
 }
@@ -521,8 +539,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
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
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));
 

+ 7 - 10
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",
@@ -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()

+ 13 - 20
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, 
@@ -866,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, 
@@ -880,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, 
@@ -1075,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
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
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,