Browse Source

updated to changeset 80f0adedfaa5 of love-android (including newest mobile-common changes)

fysx 12 years ago
parent
commit
0bde453b2e
43 changed files with 789 additions and 397 deletions
  1. 5 0
      jni/love/src/libraries/luasocket/libluasocket/lua.h
  2. 2 0
      jni/love/src/modules/audio/openal/Pool.cpp
  3. 1 0
      jni/love/src/modules/graphics/Graphics.cpp
  4. 2 1
      jni/love/src/modules/graphics/Graphics.h
  5. 19 0
      jni/love/src/modules/graphics/Texture.cpp
  6. 14 0
      jni/love/src/modules/graphics/Texture.h
  7. 62 43
      jni/love/src/modules/graphics/opengl/Canvas.cpp
  8. 10 18
      jni/love/src/modules/graphics/opengl/Canvas.h
  9. 80 89
      jni/love/src/modules/graphics/opengl/Graphics.cpp
  10. 21 4
      jni/love/src/modules/graphics/opengl/Graphics.h
  11. 41 10
      jni/love/src/modules/graphics/opengl/Image.cpp
  12. 10 3
      jni/love/src/modules/graphics/opengl/Image.h
  13. 0 18
      jni/love/src/modules/graphics/opengl/Mesh.cpp
  14. 0 11
      jni/love/src/modules/graphics/opengl/Mesh.h
  15. 37 0
      jni/love/src/modules/graphics/opengl/OpenGL.cpp
  16. 20 0
      jni/love/src/modules/graphics/opengl/OpenGL.h
  17. 36 15
      jni/love/src/modules/graphics/opengl/ParticleSystem.cpp
  18. 23 13
      jni/love/src/modules/graphics/opengl/ParticleSystem.h
  19. 4 0
      jni/love/src/modules/graphics/opengl/SpriteBatch.cpp
  20. 0 2
      jni/love/src/modules/graphics/opengl/SpriteBatch.h
  21. 9 4
      jni/love/src/modules/graphics/opengl/wrap_Canvas.cpp
  22. 1 1
      jni/love/src/modules/graphics/opengl/wrap_Canvas.h
  23. 37 10
      jni/love/src/modules/graphics/opengl/wrap_Graphics.cpp
  24. 2 0
      jni/love/src/modules/graphics/opengl/wrap_Graphics.h
  25. 0 16
      jni/love/src/modules/graphics/opengl/wrap_Mesh.cpp
  26. 0 2
      jni/love/src/modules/graphics/opengl/wrap_Mesh.h
  27. 16 0
      jni/love/src/modules/graphics/opengl/wrap_ParticleSystem.cpp
  28. 2 0
      jni/love/src/modules/graphics/opengl/wrap_ParticleSystem.h
  29. 30 0
      jni/love/src/modules/math/MathModule.cpp
  30. 30 20
      jni/love/src/modules/math/MathModule.h
  31. 76 20
      jni/love/src/modules/math/RandomGenerator.cpp
  32. 31 43
      jni/love/src/modules/math/RandomGenerator.h
  33. 93 14
      jni/love/src/modules/math/wrap_Math.cpp
  34. 6 2
      jni/love/src/modules/math/wrap_Math.h
  35. 32 27
      jni/love/src/modules/math/wrap_RandomGenerator.cpp
  36. 4 2
      jni/love/src/modules/math/wrap_RandomGenerator.h
  37. 2 0
      jni/love/src/modules/window/Window.cpp
  38. 2 0
      jni/love/src/modules/window/Window.h
  39. 18 7
      jni/love/src/modules/window/sdl/Window.cpp
  40. 2 2
      jni/love/src/modules/window/sdl/Window.h
  41. 4 0
      jni/love/src/modules/window/wrap_Window.cpp
  42. 2 0
      jni/love/src/scripts/boot.lua
  43. 3 0
      jni/love/src/scripts/boot.lua.h

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

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

+ 2 - 0
jni/love/src/modules/audio/openal/Pool.cpp

@@ -61,11 +61,13 @@ Pool::Pool()
 	// Make all sources available initially.
 	for (int i = 0; i < totalSources; i++)
 	{
+#ifdef AL_SOFT_direct_channels
 		if (hasext)
 		{
 			// Bypass virtualization of speakers for multi-channel sources in OpenAL Soft.
 			alSourcei(sources[i], AL_DIRECT_CHANNELS_SOFT, AL_TRUE);
 		}
+#endif
 
 		available.push(sources[i]);
 	}

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

@@ -176,6 +176,7 @@ StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM>::Entry Graphics::suppor
 	{ "dxt", Graphics::SUPPORT_DXT },
 	{ "bc5", Graphics::SUPPORT_BC5 },
 	{ "instancing", Graphics::SUPPORT_INSTANCING },
+	{ "srgb", Graphics::SUPPORT_SRGB },
 };
 
 StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM> Graphics::support(Graphics::supportEntries, sizeof(Graphics::supportEntries));

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

@@ -95,6 +95,7 @@ public:
 		SUPPORT_DXT,
 		SUPPORT_BC5,
 		SUPPORT_INSTANCING,
+		SUPPORT_SRGB,
 		SUPPORT_MAX_ENUM
 	};
 
@@ -135,7 +136,7 @@ public:
 	 * @param width The viewport width.
 	 * @param height The viewport height.
 	 **/
-	virtual bool setMode(int width, int height) = 0;
+	virtual bool setMode(int width, int height, bool &sRGB) = 0;
 
 	/**
 	 * Un-sets the current graphics display mode (uninitializing objects if

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

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

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

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

+ 62 - 43
jni/love/src/modules/graphics/opengl/Canvas.cpp

@@ -389,7 +389,7 @@ struct FramebufferStrategyEXT : public FramebufferStrategyPackedEXT
 	}
 };
 
-FramebufferStrategy *strategy = NULL;
+FramebufferStrategy *strategy = nullptr;
 
 FramebufferStrategy strategyNone;
 
@@ -401,8 +401,9 @@ FramebufferStrategyPackedEXT strategyPackedEXT;
 
 FramebufferStrategyEXT strategyEXT;
 
-Canvas *Canvas::current = NULL;
+Canvas *Canvas::current = nullptr;
 OpenGL::Viewport Canvas::systemViewport = OpenGL::Viewport();
+bool Canvas::screenHasSRGB = false;
 
 static void getStrategy()
 {
@@ -421,16 +422,13 @@ static void getStrategy()
 	}
 }
 
-static int maxFBOColorAttachments = 0;
-static int maxDrawBuffers = 0;
-
-Canvas::Canvas(int width, int height, TextureType texture_type, int fsaa)
+Canvas::Canvas(int width, int height, Texture::Format format, int fsaa)
 	: fbo(0)
     , resolve_fbo(0)
 	, texture(0)
     , fsaa_buffer(0)
 	, depth_stencil(0)
-	, texture_type(texture_type)
+	, format(format)
     , fsaa_samples(fsaa)
 	, fsaa_dirty(false)
 {
@@ -534,15 +532,19 @@ bool Canvas::loadVolatile()
 
 	GLint internalformat;
 	GLenum textype;
-	switch (texture_type)
+	switch (format)
 	{
-	case TYPE_HDR:
+	case Texture::FORMAT_HDR:
 		internalformat = GL_RGBA16F;
 		textype = GL_FLOAT;
 		break;
-	case TYPE_NORMAL:
+	case Texture::FORMAT_SRGB:
+		internalformat = GL_SRGB_ALPHA;
+		textype = GL_UNSIGNED_BYTE;
+		break;
+	case Texture::FORMAT_NORMAL:
 	default:
-		internalformat = GL_RGBA8;
+		internalformat = GL_RGBA;
 		textype = GL_UNSIGNED_BYTE;
 	}
 
@@ -682,20 +684,30 @@ void Canvas::setupGrab()
 	if (current == this)
 		return;
 
-	// cleanup after previous fbo
-	if (current != NULL)
-		current->stopGrab();
+	// cleanup after previous Canvas
+	if (current != nullptr)
+	{
+		systemViewport = current->systemViewport;
+		current->stopGrab(true);
+	}
+	else
+		systemViewport = gl.getViewport();
+
+	// indicate we are using this Canvas.
+	current = this;
 
 	// bind the framebuffer object.
-	systemViewport = gl.getViewport();
 	strategy->bindFBO(fbo);
 	gl.setViewport(OpenGL::Viewport(0, 0, width, height));
 
 	// Set up orthographic view (no depth)
 	gl.matrices.projection.push(Matrix::ortho(0.0, width, 0.0, height));
 
-	// indicate we are using this fbo
-	current = this;
+	// Make sure the correct sRGB setting is used when drawing to the canvas.
+	if (format == FORMAT_SRGB)
+		glEnable(GL_FRAMEBUFFER_SRGB);
+	else if (screenHasSRGB)
+		glDisable(GL_FRAMEBUFFER_SRGB);
 
 	if (fsaa_buffer != 0)
 		fsaa_dirty = true;
@@ -712,7 +724,7 @@ void Canvas::startGrab(const std::vector<Canvas *> &canvases)
 		if (!isMultiCanvasSupported())
 			throw love::Exception("Multi-canvas rendering is not supported on this system.");
 
-		if (canvases.size()+1 > size_t(maxDrawBuffers) || canvases.size()+1 > size_t(maxFBOColorAttachments))
+		if ((int) canvases.size() + 1 > gl.getMaxRenderTargets())
 			throw love::Exception("This system can't simultaniously render to %d canvases.", canvases.size()+1);
 
 		if (fsaa_samples != 0)
@@ -724,8 +736,8 @@ void Canvas::startGrab(const std::vector<Canvas *> &canvases)
 		if (canvases[i]->getWidth() != width || canvases[i]->getHeight() != height)
 			throw love::Exception("All canvas arguments must have the same dimensions.");
 
-		if (canvases[i]->getTextureType() != texture_type)
-			throw love::Exception("All canvas arguments must have the same texture type.");
+		if (canvases[i]->getTextureFormat() != format)
+			throw love::Exception("All canvas arguments must have the same texture format.");
 
 		if (canvases[i]->getFSAA() != 0)
 			throw love::Exception("Multi-canvas rendering is not supported with FSAA.");
@@ -769,7 +781,7 @@ void Canvas::startGrab()
 	attachedCanvases.clear();
 }
 
-void Canvas::stopGrab()
+void Canvas::stopGrab(bool switchingToOtherCanvas)
 {
 	// i am not grabbing. leave me alone
 	if (current != this)
@@ -782,13 +794,25 @@ void Canvas::stopGrab()
 		glDiscardFramebufferEXT(GL_FRAMEBUFFER, 2, attachments);
 	}
 
-	// bind default
-	strategy->bindFBO(gl.getDefaultFBO());
-	gl.matrices.projection.pop();
+	if (switchingToOtherCanvas)
+	{
+		if (format == FORMAT_SRGB)
+			glDisable(GL_FRAMEBUFFER_SRGB);
+	}
+	else
+	{
+		// bind the default framebuffer.
+		strategy->bindFBO(gl.getDefaultFBO());
+		current = nullptr;
+		gl.setViewport(systemViewport);
 
-	current = nullptr;
+		if (format == FORMAT_SRGB && !screenHasSRGB)
+			glDisable(GL_FRAMEBUFFER_SRGB);
+		else if (format != FORMAT_SRGB && screenHasSRGB)
+			glEnable(GL_FRAMEBUFFER_SRGB);
+	}
 
-	gl.setViewport(systemViewport);
+	gl.matrices.projection.pop();
 }
 
 void Canvas::clear(Color c)
@@ -959,6 +983,18 @@ bool Canvas::isHDRSupported()
 	return GLAD_VERSION_3_0 || (isSupported() && GLAD_ARB_texture_float);
 }
 
+bool Canvas::isSRGBSupported()
+{
+	if (GLAD_VERSION_3_0)
+		return true;
+
+	if (!isSupported())
+		return false;
+
+	return (GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB)
+		&& GLAD_EXT_texture_sRGB;
+}
+
 bool Canvas::isMultiCanvasSupported()
 {
 	// system must support at least 4 simultanious active canvases.
@@ -971,23 +1007,6 @@ void Canvas::bindDefaultCanvas()
 		current->stopGrab();
 }
 
-bool Canvas::getConstant(const char *in, Canvas::TextureType &out)
-{
-	return textureTypes.find(in, out);
-}
-
-bool Canvas::getConstant(Canvas::TextureType in, const char *&out)
-{
-	return textureTypes.find(in, out);
-}
-
-StringMap<Canvas::TextureType, Canvas::TYPE_MAX_ENUM>::Entry Canvas::textureTypeEntries[] =
-{
-	{"normal", Canvas::TYPE_NORMAL},
-	{"hdr",    Canvas::TYPE_HDR},
-};
-StringMap<Canvas::TextureType, Canvas::TYPE_MAX_ENUM> Canvas::textureTypes(Canvas::textureTypeEntries, sizeof(Canvas::textureTypeEntries));
-
 } // opengl
 } // graphics
 } // love

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

@@ -39,14 +39,7 @@ class Canvas : public Texture
 {
 public:
 
-	enum TextureType
-	{
-		TYPE_NORMAL,
-		TYPE_HDR,
-		TYPE_MAX_ENUM
-	};
-
-	Canvas(int width, int height, TextureType texture_type = TYPE_NORMAL, int fsaa = 0);
+	Canvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0);
 	virtual ~Canvas();
 
 	// Implements Volatile.
@@ -69,7 +62,7 @@ public:
 	 **/
 	void startGrab(const std::vector<Canvas *> &canvases);
 	void startGrab();
-	void stopGrab();
+	void stopGrab(bool switchingToOtherCanvas = false);
 
 	void clear(Color c);
 
@@ -92,9 +85,9 @@ public:
 		return status;
 	}
 
-	inline TextureType getTextureType() const
+	inline Texture::Format getTextureFormat() const
 	{
-		return texture_type;
+		return format;
 	}
 
 	inline int getFSAA() const
@@ -106,17 +99,18 @@ public:
 
 	static bool isSupported();
 	static bool isHDRSupported();
+	static bool isSRGBSupported();
 	static bool isMultiCanvasSupported();
 
-	static bool getConstant(const char *in, TextureType &out);
-	static bool getConstant(TextureType in, const char *&out);
-
 	static Canvas *current;
 	static void bindDefaultCanvas();
 
 	// The viewport dimensions of the system (default) framebuffer.
 	static OpenGL::Viewport systemViewport;
 
+	// Whether the main screen should have linear -> sRGB conversions enabled.
+	static bool screenHasSRGB;
+
 private:
 
 	bool createFSAAFBO(GLenum internalformat);
@@ -128,7 +122,7 @@ private:
 	GLuint fsaa_buffer;
 	GLuint depth_stencil;
 
-	TextureType texture_type;
+	Format format;
 
 	GLenum status;
 
@@ -140,9 +134,7 @@ private:
 	void setupGrab();
 	void drawv(const Matrix &t, const Vertex *v);
 
-	static StringMap<TextureType, TYPE_MAX_ENUM>::Entry textureTypeEntries[];
-	static StringMap<TextureType, TYPE_MAX_ENUM> textureTypes;
-};
+}; // Canvas
 
 } // opengl
 } // graphics

+ 80 - 89
jni/love/src/modules/graphics/opengl/Graphics.cpp

@@ -56,8 +56,13 @@ Graphics::Graphics()
 {
 	currentWindow = love::window::sdl::Window::createSingleton();
 
+	int w, h;
+	love::window::WindowSettings wsettings;
+
+	currentWindow->getWindow(w, h, wsettings);
+
 	if (currentWindow->isCreated())
-		setMode(currentWindow->getWidth(), currentWindow->getHeight());
+		setMode(w, h, wsettings.sRGB);
 }
 
 Graphics::~Graphics()
@@ -104,6 +109,8 @@ DisplayState Graphics::saveState()
 	for (int i = 0; i < 4; i++)
 		s.colorMask[i] = colorMask[i];
 
+	wireframe = isWireframe();
+
 	return s;
 }
 
@@ -121,6 +128,7 @@ void Graphics::restoreState(const DisplayState &s)
 	else
 		setScissor();
 	setColorMask(s.colorMask[0], s.colorMask[1], s.colorMask[2], s.colorMask[3]);
+	setWireframe(s.wireframe);
 }
 
 void Graphics::setViewportSize(int width, int height)
@@ -151,7 +159,7 @@ void Graphics::setViewportSize(int width, int height)
 		c->startGrab(c->getAttachedCanvases());
 }
 
-bool Graphics::setMode(int width, int height)
+bool Graphics::setMode(int width, int height, bool &sRGB)
 {
 	this->width = width;
 	this->height = height;
@@ -181,9 +189,6 @@ bool Graphics::setMode(int width, int height)
 	// Enable blending
 	glEnable(GL_BLEND);
 
-	// "Normal" blending
-	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
 	// Enable all color component writes.
 	setColorMask(true, true, true, true);
 
@@ -228,6 +233,19 @@ bool Graphics::setMode(int width, int height)
 			Shader::defaultShader->attach();
 	}
 
+	// Set whether drawing converts input from linear -> sRGB colorspace.
+	if (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB)
+	{
+		if (sRGB)
+			glEnable(GL_FRAMEBUFFER_SRGB);
+		else
+			glDisable(GL_FRAMEBUFFER_SRGB);
+	}
+	else
+		sRGB = false;
+
+	Canvas::screenHasSRGB = sRGB;
+
 	bool enabledebug = false;
 
 	if (GLAD_VERSION_3_0)
@@ -238,6 +256,8 @@ bool Graphics::setMode(int width, int height)
 		enabledebug = (flags & GL_CONTEXT_FLAG_DEBUG_BIT) != 0;
 	}
 
+	// FIXME: workaround for KHR_debug being supported but function pointers
+	// resolving to null on Ouya.
 	if (!GLAD_ES_VERSION_2_0)
 		setDebug(enabledebug);
 
@@ -313,6 +333,9 @@ void Graphics::setDebug(bool enable)
 	glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR, GL_DONT_CARE, 0, 0, GL_FALSE);
 	glDebugMessageControl(GL_DEBUG_SOURCE_SHADER_COMPILER, GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR, GL_DONT_CARE, 0, 0, GL_FALSE);
 
+	if (GLAD_VERSION_4_3 || GLAD_KHR_debug)
+		glEnable(GL_DEBUG_OUTPUT);
+
 	::printf("OpenGL debug output enabled (LOVE_GRAPHICS_DEBUG=1)\n");
 }
 
@@ -431,10 +454,10 @@ void Graphics::discardStencil()
 	glDisable(GL_STENCIL_TEST);
 }
 
-Image *Graphics::newImage(love::image::ImageData *data)
+Image *Graphics::newImage(love::image::ImageData *data, Texture::Format format)
 {
 	// Create the image.
-	Image *image = new Image(data);
+	Image *image = new Image(data, format);
 
 	if (!isCreated())
 		return image;
@@ -458,10 +481,10 @@ Image *Graphics::newImage(love::image::ImageData *data)
 	return image;
 }
 
-Image *Graphics::newImage(love::image::CompressedData *cdata)
+Image *Graphics::newImage(love::image::CompressedData *cdata, Texture::Format format)
 {
 	// Create the image.
-	Image *image = new Image(cdata);
+	Image *image = new Image(cdata, format);
 
 	if (!isCreated())
 		return image;
@@ -505,11 +528,14 @@ ParticleSystem *Graphics::newParticleSystem(Texture *texture, int size)
 	return new ParticleSystem(texture, size);
 }
 
-Canvas *Graphics::newCanvas(int width, int height, Canvas::TextureType texture_type, int fsaa)
+Canvas *Graphics::newCanvas(int width, int height, Texture::Format format, int fsaa)
 {
-	if (texture_type == Canvas::TYPE_HDR && !Canvas::isHDRSupported())
+	if (format == Texture::FORMAT_HDR && !Canvas::isHDRSupported())
 		throw Exception("HDR Canvases are not supported by your OpenGL implementation");
 
+	if (format == Texture::FORMAT_SRGB && !Canvas::isSRGBSupported())
+		throw Exception("sRGB Canvases are not supported by your OpenGL implementation");
+
 	if (width > gl.getMaxTextureSize())
 		throw Exception("Cannot create canvas: width of %d pixels is too large for this system.", width);
 	else if (height > gl.getMaxTextureSize())
@@ -518,7 +544,7 @@ Canvas *Graphics::newCanvas(int width, int height, Canvas::TextureType texture_t
 	while (GL_NO_ERROR != glGetError())
 		/* clear opengl error flag */;
 
-	Canvas *canvas = new Canvas(width, height, texture_type, fsaa);
+	Canvas *canvas = new Canvas(width, height, format, fsaa);
 	GLenum err = canvas->getStatus();
 
 	// everything ok, return canvas (early out)
@@ -630,22 +656,16 @@ const bool *Graphics::getColorMask() const
 
 void Graphics::setBlendMode(Graphics::BlendMode mode)
 {
-	const int gl_1_4 = GLAD_VERSION_1_4;
-
-	GLenum func = GL_FUNC_ADD;
-	GLenum src_rgb = GL_ONE;
-	GLenum src_a = GL_ONE;
-	GLenum dst_rgb = GL_ZERO;
-	GLenum dst_a = GL_ZERO;
+	OpenGL::BlendState state = {GL_ONE, GL_ONE, GL_ZERO, GL_ZERO, GL_FUNC_ADD};
 
 	switch (mode)
 	{
 	case BLEND_ALPHA:
-		if (gl_1_4 || GLAD_ES_VERSION_2_0 || GLAD_EXT_blend_func_separate)
+		if (GLAD_ES_VERSION_2_0 || GLAD_VERSION_1_4 || GLAD_EXT_blend_func_separate)
 		{
-			src_rgb = GL_SRC_ALPHA;
-			src_a = GL_ONE;
-			dst_rgb = dst_a = GL_ONE_MINUS_SRC_ALPHA;
+			state.srcRGB = GL_SRC_ALPHA;
+			state.srcA = GL_ONE;
+			state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA;
 		}
 		else
 		{
@@ -653,100 +673,56 @@ void Graphics::setBlendMode(Graphics::BlendMode mode)
 			// This will most likely only be used for the Microsoft software renderer and
 			// since it's still stuck with OpenGL 1.1, the only expected difference is a
 			// different alpha value when reading back the default framebuffer (newScreenshot).
-			src_rgb = src_a = GL_SRC_ALPHA;
-			dst_rgb = dst_a = GL_ONE_MINUS_SRC_ALPHA;
+			state.srcRGB = state.srcA = GL_SRC_ALPHA;
+			state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA;
 		}
 		break;
 	case BLEND_MULTIPLICATIVE:
-		src_rgb = src_a = GL_DST_COLOR;
-		dst_rgb = dst_a = GL_ZERO;
+		state.srcRGB = state.srcA = GL_DST_COLOR;
+		state.dstRGB = state.dstA = GL_ZERO;
 		break;
 	case BLEND_PREMULTIPLIED:
-		src_rgb = src_a = GL_ONE;
-		dst_rgb = dst_a = GL_ONE_MINUS_SRC_ALPHA;
+		state.srcRGB = state.srcA = GL_ONE;
+		state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA;
 		break;
 	case BLEND_SUBTRACTIVE:
-		func = GL_FUNC_REVERSE_SUBTRACT;
+		state.func = GL_FUNC_REVERSE_SUBTRACT;
 	case BLEND_ADDITIVE:
-		src_rgb = src_a = GL_SRC_ALPHA;
-		dst_rgb = dst_a = GL_ONE;
+		state.srcRGB = state.srcA = GL_SRC_ALPHA;
+		state.dstRGB = state.dstA = GL_ONE;
 		break;
 	case BLEND_REPLACE:
 	default:
-		src_rgb = src_a = GL_ONE;
-		dst_rgb = dst_a = GL_ZERO;
+		state.srcRGB = state.srcA = GL_ONE;
+		state.dstRGB = state.dstA = GL_ZERO;
 		break;
 	}
 
-	if (gl_1_4 || GLAD_ES_VERSION_2_0)
-		glBlendEquation(func);
-	else if (GLAD_EXT_blend_minmax && GLAD_EXT_blend_subtract)
-		glBlendEquationEXT(func);
-	else
-	{
-		if (func == GL_FUNC_REVERSE_SUBTRACT)
-			throw Exception("This graphics card does not support the subtractive blend mode!");
-		// GL_FUNC_ADD is the default even without access to glBlendEquation, so that'll still work.
-	}
-
-	if (src_rgb == src_a && dst_rgb == dst_a)
-		glBlendFunc(src_rgb, dst_rgb);
-	else
-	{
-		if (gl_1_4 || GLAD_ES_VERSION_2_0)
-			glBlendFuncSeparate(src_rgb, dst_rgb, src_a, dst_a);
-		else if (GLAD_EXT_blend_func_separate)
-			glBlendFuncSeparateEXT(src_rgb, dst_rgb, src_a, dst_a);
-		else
-			throw Exception("This graphics card does not support separated rgb and alpha blend functions!");
-	}
+	gl.setBlendState(state);
 }
 
 Graphics::BlendMode Graphics::getBlendMode() const
 {
-	const int gl_1_4 = GLAD_VERSION_1_4;
-
-	GLint src_rgb, src_a, dst_rgb, dst_a;
-	GLint equation = GL_FUNC_ADD;
-
-	if (gl_1_4 || GLAD_ES_VERSION_2_0 || GLAD_EXT_blend_func_separate)
-	{
-		glGetIntegerv(GL_BLEND_SRC_RGB, &src_rgb);
-		glGetIntegerv(GL_BLEND_SRC_ALPHA, &src_a);
-		glGetIntegerv(GL_BLEND_DST_RGB, &dst_rgb);
-		glGetIntegerv(GL_BLEND_DST_ALPHA, &dst_a);
-	}
-	else
-	{
-		glGetIntegerv(GL_BLEND_SRC, &src_rgb);
-		glGetIntegerv(GL_BLEND_DST, &dst_rgb);
-		src_a = src_rgb;
-		dst_a = dst_rgb;
-	}
-
-	if (GLAD_ES_VERSION_2_0)
-		glGetIntegerv(GL_BLEND_EQUATION_RGB, &equation);
-	else if (gl_1_4 || (GLAD_EXT_blend_minmax && GLAD_EXT_blend_subtract))
-		glGetIntegerv(GL_BLEND_EQUATION, &equation);
+	OpenGL::BlendState state = gl.getBlendState();
 
-	if (equation == GL_FUNC_REVERSE_SUBTRACT)  // && src == GL_SRC_ALPHA && dst == GL_ONE
+	if (state.func == GL_FUNC_REVERSE_SUBTRACT)  // && src == GL_SRC_ALPHA && dst == GL_ONE
 		return BLEND_SUBTRACTIVE;
 	// Everything else has equation == GL_FUNC_ADD.
-	else if (src_rgb == src_a && dst_rgb == dst_a)
+	else if (state.srcRGB == state.srcA && state.dstRGB == state.dstA)
 	{
-		if (src_rgb == GL_SRC_ALPHA && dst_rgb == GL_ONE)
+		if (state.srcRGB == GL_SRC_ALPHA && state.dstRGB == GL_ONE)
 			return BLEND_ADDITIVE;
-		else if (src_rgb == GL_SRC_ALPHA && dst_rgb == GL_ONE_MINUS_SRC_ALPHA)
+		else if (state.srcRGB == GL_SRC_ALPHA && state.dstRGB == GL_ONE_MINUS_SRC_ALPHA)
 			return BLEND_ALPHA; // alpha blend mode fallback for very old OpenGL versions.
-		else if (src_rgb == GL_DST_COLOR && dst_rgb == GL_ZERO)
+		else if (state.srcRGB == GL_DST_COLOR && state.dstRGB == GL_ZERO)
 			return BLEND_MULTIPLICATIVE;
-		else if (src_rgb == GL_ONE && dst_rgb == GL_ONE_MINUS_SRC_ALPHA)
+		else if (state.srcRGB == GL_ONE && state.dstRGB == GL_ONE_MINUS_SRC_ALPHA)
 			return BLEND_PREMULTIPLIED;
-		else if (src_rgb == GL_ONE && dst_rgb == GL_ZERO)
+		else if (state.srcRGB == GL_ONE && state.dstRGB == GL_ZERO)
 			return BLEND_REPLACE;
 	}
-	else if (src_rgb == GL_SRC_ALPHA && src_a == GL_ONE &&
-		dst_rgb == GL_ONE_MINUS_SRC_ALPHA && dst_a == GL_ONE_MINUS_SRC_ALPHA)
+	else if (state.srcRGB == GL_SRC_ALPHA && state.srcA == GL_ONE &&
+		state.dstRGB == GL_ONE_MINUS_SRC_ALPHA && state.dstA == GL_ONE_MINUS_SRC_ALPHA)
 		return BLEND_ALPHA;
 
 	throw Exception("Unknown blend mode");
@@ -836,6 +812,21 @@ Graphics::PointStyle Graphics::getPointStyle() const
 		return POINT_ROUGH;
 }
 
+void Graphics::setWireframe(bool enable)
+{
+	// Not supported in OpenGL ES.
+	if (GLAD_ES_VERSION_2_0)
+		return;
+
+	wireframe = enable;
+	glPolygonMode(GL_FRONT_AND_BACK, enable ? GL_LINE : GL_FILL);
+}
+
+bool Graphics::isWireframe() const
+{
+	return wireframe;
+}
+
 void Graphics::print(const std::string &str, float x, float y , float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	if (currentFont != nullptr)

+ 21 - 4
jni/love/src/modules/graphics/opengl/Graphics.h

@@ -81,6 +81,8 @@ struct DisplayState
 	// Color mask.
 	bool colorMask[4];
 
+	bool wireframe;
+
 	// Default values.
 	DisplayState()
 	{
@@ -93,6 +95,7 @@ struct DisplayState
 		pointStyle = Graphics::POINT_SMOOTH;
 		scissor = false;
 		colorMask[0] = colorMask[1] = colorMask[2] = colorMask[3] = true;
+		wireframe = false;
 	}
 
 };
@@ -112,7 +115,7 @@ public:
 	void restoreState(const DisplayState &s);
 
 	virtual void setViewportSize(int width, int height);
-	virtual bool setMode(int width, int height);
+	virtual bool setMode(int width, int height, bool &sRGB);
 	virtual void unSetMode();
 
 	void setDebug(bool enable);
@@ -191,8 +194,8 @@ public:
 	/**
 	 * Creates an Image object with padding and/or optimization.
 	 **/
-	Image *newImage(love::image::ImageData *data);
-	Image *newImage(love::image::CompressedData *cdata);
+	Image *newImage(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL);
+	Image *newImage(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL);
 
 	Quad *newQuad(Quad::Viewport v, float sw, float sh);
 
@@ -205,7 +208,7 @@ public:
 
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 
-	Canvas *newCanvas(int width, int height, Canvas::TextureType texture_type = Canvas::TYPE_NORMAL, int fsaa = 0);
+	Canvas *newCanvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0);
 
 	Shader *newShader(const Shader::ShaderSources &sources);
 
@@ -334,6 +337,19 @@ public:
 	 **/
 	PointStyle getPointStyle() const;
 
+	/**
+	 * Sets whether graphics will be drawn as wireframe lines instead of filled
+	 * triangles (has no effect for drawn points.)
+	 * This should only be used as a debugging tool. The wireframe lines do not
+	 * behave the same as regular love.graphics lines.
+	 **/
+	void setWireframe(bool enable);
+
+	/**
+	 * Gets whether wireframe drawing mode is enabled.
+	 **/
+	bool isWireframe() const;
+
 	/**
 	 * Draws text at the specified coordinates, with rotation and
 	 * scaling along both axes.
@@ -459,6 +475,7 @@ private:
 	float lineWidth;
 	size_t matrixLimit;
 	bool colorMask[4];
+	bool wireframe;
 
 	int width;
 	int height;

+ 41 - 10
jni/love/src/modules/graphics/opengl/Image.cpp

@@ -36,7 +36,7 @@ float Image::maxMipmapSharpness = 0.0f;
 Texture::FilterMode Image::defaultMipmapFilter = Texture::FILTER_NONE;
 float Image::defaultMipmapSharpness = 0.0f;
 
-Image::Image(love::image::ImageData *data)
+Image::Image(love::image::ImageData *data, Texture::Format format)
 	: data(data)
 	, cdata(nullptr)
 	, paddedWidth(width)
@@ -45,6 +45,7 @@ Image::Image(love::image::ImageData *data)
 	, mipmapSharpness(defaultMipmapSharpness)
 	, mipmapsCreated(false)
 	, compressed(false)
+	, format(format)
 	, usingDefaultTexture(false)
 {
 	width = data->getWidth();
@@ -54,7 +55,7 @@ Image::Image(love::image::ImageData *data)
 	preload();
 }
 
-Image::Image(love::image::CompressedData *cdata)
+Image::Image(love::image::CompressedData *cdata, Texture::Format format)
 	: data(nullptr)
 	, cdata(cdata)
 	, paddedWidth(width)
@@ -63,6 +64,7 @@ Image::Image(love::image::CompressedData *cdata)
 	, mipmapSharpness(defaultMipmapSharpness)
 	, mipmapsCreated(false)
 	, compressed(true)
+	, format(format)
 	, usingDefaultTexture(false)
 {
 	width = cdata->getWidth(0);
@@ -328,6 +330,9 @@ void Image::unload()
 
 bool Image::loadVolatile()
 {
+	if (format == FORMAT_SRGB && !hasSRGBSupport())
+		throw love::Exception("sRGB images are not supported on this system.");
+
 	if (isCompressed() && cdata && !hasCompressedTextureSupport(cdata->getFormat()))
 	{
 		const char *str;
@@ -400,9 +405,10 @@ void Image::uploadTexturePadded()
 	}
 	else if (data)
 	{
+		GLenum iformat = (format == FORMAT_SRGB) ? GL_SRGB_ALPHA : GL_RGBA;
 		glTexImage2D(GL_TEXTURE_2D,
 		             0,
-		             GL_RGBA,
+		             iformat,
 		             (GLsizei)paddedWidth,
 		             (GLsizei)paddedHeight,
 		             0,
@@ -437,9 +443,10 @@ void Image::uploadTexture()
 	}
 	else if (data)
 	{
+		GLenum iformat = (format == FORMAT_SRGB) ? GL_SRGB_ALPHA : GL_RGBA;
 		glTexImage2D(GL_TEXTURE_2D,
 		             0,
-		             GL_RGBA,
+		             iformat,
 		             (GLsizei)width,
 		             (GLsizei)height,
 		             0,
@@ -497,6 +504,11 @@ bool Image::refresh()
 	return true;
 }
 
+Texture::Format Image::getFormat() const
+{
+	return format;
+}
+
 void Image::uploadDefaultTexture()
 {
 	usingDefaultTexture = true;
@@ -561,16 +573,27 @@ bool Image::isCompressed() const
 	return compressed;
 }
 
-GLenum Image::getCompressedFormat(image::CompressedData::Format format) const
+GLenum Image::getCompressedFormat(image::CompressedData::Format cformat) const
 {
-	switch (format)
+	bool srgb = format == FORMAT_SRGB;
+
+	switch (cformat)
 	{
 	case image::CompressedData::FORMAT_DXT1:
-		return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
+		if (srgb)
+			return GL_COMPRESSED_SRGB_S3TC_DXT1_EXT;
+		else
+			return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
 	case image::CompressedData::FORMAT_DXT3:
-		return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+		if (srgb)
+			return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
+		else
+			return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
 	case image::CompressedData::FORMAT_DXT5:
-		return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+		if (srgb)
+			return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
+		else
+			return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
 	case image::CompressedData::FORMAT_BC4:
 		return GL_COMPRESSED_RED_RGTC1;
 	case image::CompressedData::FORMAT_BC4s:
@@ -580,7 +603,10 @@ GLenum Image::getCompressedFormat(image::CompressedData::Format format) const
 	case image::CompressedData::FORMAT_BC5s:
 		return GL_COMPRESSED_SIGNED_RG_RGTC2;
 	default:
-		return GL_RGBA;
+		if (srgb)
+			return GL_SRGB_ALPHA;
+		else
+			return GL_RGBA;
 	}
 }
 
@@ -624,6 +650,11 @@ bool Image::hasCompressedTextureSupport(image::CompressedData::Format format)
 	return false;
 }
 
+bool Image::hasSRGBSupport()
+{
+	return GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB || GLAD_EXT_sRGB;
+}
+
 } // opengl
 } // graphics
 } // love

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

@@ -56,14 +56,14 @@ public:
 	 *
 	 * @param data The data from which to load the image.
 	 **/
-	Image(love::image::ImageData *data);
+	Image(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL);
 
 	/**
 	 * Creates a new Image with compressed image data.
 	 *
 	 * @param cdata The compressed data from which to load the image.
 	 **/
-	Image(love::image::CompressedData *cdata);
+	Image(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL);
 
 	/**
 	 * Destructor. Deletes the hardware texture and other resources.
@@ -120,6 +120,8 @@ public:
 	 **/
 	bool refresh();
 
+	Texture::Format getFormat() const;
+
 	static void setDefaultMipmapSharpness(float sharpness);
 	static float getDefaultMipmapSharpness();
 	static void setDefaultMipmapFilter(FilterMode f);
@@ -132,6 +134,8 @@ public:
 
 	static bool hasCompressedTextureSupport(image::CompressedData::Format format);
 
+	static bool hasSRGBSupport();
+
 private:
 
 	void uploadDefaultTexture();
@@ -161,6 +165,9 @@ private:
 	// Whether this Image is using a compressed texture.
 	bool compressed;
 
+	// The format to interpret the texture's data as.
+	Texture::Format format;
+
 	// True if the image wasn't able to be properly created and it had to fall
 	// back to a default texture.
 	bool usingDefaultTexture;
@@ -179,7 +186,7 @@ private:
 	static FilterMode defaultMipmapFilter;
 	static float defaultMipmapSharpness;
 
-	GLenum getCompressedFormat(image::CompressedData::Format format) const;
+	GLenum getCompressedFormat(image::CompressedData::Format cformat) const;
 
 }; // Image
 

+ 0 - 18
jni/love/src/modules/graphics/opengl/Mesh.cpp

@@ -44,7 +44,6 @@ Mesh::Mesh(const std::vector<Vertex> &verts, Mesh::DrawMode mode)
 	, range_max(-1)
 	, texture(nullptr)
 	, colors_enabled(false)
-	, wireframe(false)
 {
 	setVertices(verts);
 }
@@ -59,7 +58,6 @@ Mesh::Mesh(int vertexcount, Mesh::DrawMode mode)
 	, range_max(-1)
 	, texture(nullptr)
 	, colors_enabled(false)
-	, wireframe(false)
 {
 	if (vertexcount < 1)
 		throw love::Exception("Invalid number of vertices.");
@@ -278,16 +276,6 @@ bool Mesh::hasVertexColors() const
 	return colors_enabled;
 }
 
-void Mesh::setWireframe(bool enable)
-{
-	wireframe = enable;
-}
-
-bool Mesh::isWireframe() const
-{
-	return wireframe;
-}
-
 void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	const size_t pos_offset   = offsetof(Vertex, x);
@@ -326,9 +314,6 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 		gl.setVertexAttribArray(OpenGL::ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, sizeof(Vertex), vbo->getPointer(color_offset));
 	}
 
-	if (wireframe && GLAD_VERSION_1_0)
-		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
-
 	GLenum mode = getGLDrawMode(draw_mode);
 
 	gl.prepareDraw();
@@ -377,9 +362,6 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 	gl.disableVertexAttribArray(OpenGL::ATTRIB_POS);
 	gl.disableVertexAttribArray(OpenGL::ATTRIB_TEXCOORD);
 
-	if (wireframe && GLAD_VERSION_1_0)
-		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
-
 	if (hasVertexColors())
 	{
 		gl.disableVertexAttribArray(OpenGL::ATTRIB_COLOR);

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

@@ -162,15 +162,6 @@ public:
 	void setVertexColors(bool enable);
 	bool hasVertexColors() const;
 
-	/**
-	 * Sets whether the Mesh will be drawn as wireframe lines instead of filled
-	 * triangles (has no effect for DRAW_MODE_POINTS.)
-	 * This should only be used as a debugging tool. The wireframe lines do not
-	 * behave the same as regular love.graphics lines.
-	 **/
-	void setWireframe(bool enable);
-	bool isWireframe() const;
-
 	// Implements Drawable.
 	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
@@ -201,8 +192,6 @@ private:
 	// Whether the per-vertex colors are used when drawing.
 	bool colors_enabled;
 
-	bool wireframe;
-
 	static StringMap<DrawMode, DRAW_MODE_MAX_ENUM>::Entry drawModeEntries[];
 	static StringMap<DrawMode, DRAW_MODE_MAX_ENUM> drawModes;
 

+ 37 - 0
jni/love/src/modules/graphics/opengl/OpenGL.cpp

@@ -127,6 +127,9 @@ bool OpenGL::initContext()
 	if (Canvas::isSupported())
 		glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (GLint *) &state.defaultFBO);
 
+	BlendState blend = {GL_ONE, GL_ONE, GL_ZERO, GL_ZERO, GL_FUNC_ADD};
+	setBlendState(blend);
+
 	initMaxValues();
 	createDefaultTexture();
 
@@ -492,6 +495,40 @@ OpenGL::Viewport OpenGL::getScissor() const
 	return state.scissor;
 }
 
+void OpenGL::setBlendState(const BlendState &blend)
+{
+	if (GLAD_ES_VERSION_2_0 || GLAD_VERSION_1_4)
+		glBlendEquation(blend.func);
+	else if (GLAD_EXT_blend_minmax && GLAD_EXT_blend_subtract)
+		glBlendEquationEXT(blend.func);
+	else
+	{
+		if (blend.func == GL_FUNC_REVERSE_SUBTRACT)
+			throw love::Exception("This graphics card does not support the subtractive blend mode!");
+		// GL_FUNC_ADD is the default even without access to glBlendEquation, so that'll still work.
+	}
+
+	if (blend.srcRGB == blend.srcA && blend.dstRGB == blend.dstA)
+		glBlendFunc(blend.srcRGB, blend.dstRGB);
+	else
+	{
+		if (GLAD_ES_VERSION_2_0 || GLAD_VERSION_1_4)
+			glBlendFuncSeparate(blend.srcRGB, blend.dstRGB, blend.srcA, blend.dstA);
+		else if (GLAD_EXT_blend_func_separate)
+			glBlendFuncSeparateEXT(blend.srcRGB, blend.dstRGB, blend.srcA, blend.dstA);
+		else
+			throw love::Exception("This graphics card does not support separated rgb and alpha blend functions!");
+	}
+
+	state.blend = blend;
+}
+
+OpenGL::BlendState OpenGL::getBlendState() const
+{
+	return state.blend;
+}
+
+
 void OpenGL::setPointSize(float size)
 {
 	if (GLAD_VERSION_1_0)

+ 20 - 0
jni/love/src/modules/graphics/opengl/OpenGL.h

@@ -103,6 +103,13 @@ public:
 		}
 	};
 
+	struct BlendState
+	{
+		GLenum srcRGB, srcA;
+		GLenum dstRGB, dstA;
+		GLenum func;
+	};
+
 	// Transformation matrix stacks.
 	struct
 	{
@@ -202,6 +209,17 @@ public:
 	 **/
 	Viewport getScissor() const;
 
+	/**
+	 * Sets blending functionality.
+	 * Note: This does not globally enable or disable blending.
+	 **/
+	void setBlendState(const BlendState &blend);
+
+	/**
+	 * Gets the currently set blending functionality.
+	 **/
+	BlendState getBlendState() const;
+
 	/**
 	 * Sets the global point size.
 	 **/
@@ -332,6 +350,8 @@ private:
 
 		GLuint defaultFBO;
 
+		BlendState blend;
+
 		// The last ID value used for pseudo-instancing.
 		int lastPseudoInstanceID;
 

+ 36 - 15
jni/love/src/modules/graphics/opengl/ParticleSystem.cpp

@@ -95,6 +95,7 @@ ParticleSystem::ParticleSystem(Texture *texture, uint32 size)
 	, spinVariation(0)
 	, offsetX(float(texture->getWidth())*0.5f)
 	, offsetY(float(texture->getHeight())*0.5f)
+	, relativeRotation(false)
 {
 	if (size == 0 || size > MAX_PARTICLES)
 		throw love::Exception("Invalid ParticleSystem size.");
@@ -146,6 +147,7 @@ ParticleSystem::ParticleSystem(const ParticleSystem &p)
 	, offsetX(p.offsetX)
 	, offsetY(p.offsetY)
 	, colors(p.colors)
+	, relativeRotation(p.relativeRotation)
 {
 	setBufferSize(maxParticles);
 
@@ -170,7 +172,7 @@ void ParticleSystem::createBuffers(size_t size)
 {
 	try
 	{
-		pFree = pMem = new particle[size];
+		pFree = pMem = new Particle[size];
 		particleVerts = new love::Vertex[size * 4];
 		ibo = new VertexIndex(size);
 		maxParticles = (uint32) size;
@@ -222,7 +224,7 @@ void ParticleSystem::addParticle(float t)
 		return;
 
 	// Gets a free particle and updates the allocation pointer.
-	particle *p = pFree++;
+	Particle *p = pFree++;
 	initParticle(p, t);
 
 	switch (insertMode)
@@ -242,7 +244,7 @@ void ParticleSystem::addParticle(float t)
 	activeParticles++;
 }
 
-void ParticleSystem::initParticle(particle *p, float t)
+void ParticleSystem::initParticle(Particle *p, float t)
 {
 	float min,max;
 
@@ -308,10 +310,14 @@ void ParticleSystem::initParticle(particle *p, float t)
 	p->spinEnd = calculate_variation(spinEnd, spinStart, spinVariation);
 	p->rotation = (float) rng.random(min, max);
 
+	p->angle = p->rotation;
+	if (relativeRotation)
+		p->angle += atan2f(p->speed.y, p->speed.x);
+
 	p->color = colors[0];
 }
 
-void ParticleSystem::insertTop(particle *p)
+void ParticleSystem::insertTop(Particle *p)
 {
 	if (pHead == nullptr)
 	{
@@ -327,7 +333,7 @@ void ParticleSystem::insertTop(particle *p)
 	pTail = p;
 }
 
-void ParticleSystem::insertBottom(particle *p)
+void ParticleSystem::insertBottom(Particle *p)
 {
 	if (pTail == nullptr)
 	{
@@ -343,7 +349,7 @@ void ParticleSystem::insertBottom(particle *p)
 	pHead = p;
 }
 
-void ParticleSystem::insertRandom(particle *p)
+void ParticleSystem::insertRandom(Particle *p)
 {
 	// Nonuniform, but 64-bit is so large nobody will notice. Hopefully.
 	uint64 pos = rng.rand() % ((int64) activeParticles + 1);
@@ -351,7 +357,7 @@ void ParticleSystem::insertRandom(particle *p)
 	// Special case where the particle gets inserted before the head.
 	if (pos == activeParticles)
 	{
-		particle *pA = pHead;
+		Particle *pA = pHead;
 		if (pA)
 			pA->prev = p;
 		p->prev = nullptr;
@@ -361,8 +367,8 @@ void ParticleSystem::insertRandom(particle *p)
 	}
 
 	// Inserts the particle after the randomly selected particle.
-	particle *pA = pMem + pos;
-	particle *pB = pA->next;
+	Particle *pA = pMem + pos;
+	Particle *pB = pA->next;
 	pA->next = p;
 	if (pB)
 		pB->prev = p;
@@ -372,12 +378,12 @@ void ParticleSystem::insertRandom(particle *p)
 	p->next = pB;
 }
 
-ParticleSystem::particle *ParticleSystem::removeParticle(particle *p)
+ParticleSystem::Particle *ParticleSystem::removeParticle(Particle *p)
 {
 	// The linked list is updated in this function and old pointers may be
 	// invalidated. The returned pointer will inform the caller of the new
 	// pointer to the next particle.
-	particle *pNext = nullptr;
+	Particle *pNext = nullptr;
 
 	// Removes the particle from the linked list.
 	if (p->prev)
@@ -725,6 +731,16 @@ std::vector<Color> ParticleSystem::getColor() const
 	return ncolors;
 }
 
+void ParticleSystem::setRelativeRotation(bool enable)
+{
+	relativeRotation = enable;
+}
+
+bool ParticleSystem::hasRelativeRotation() const
+{
+	return relativeRotation;
+}
+
 uint32 ParticleSystem::getCount() const
 {
 	return activeParticles;
@@ -812,13 +828,13 @@ void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, flo
 
 	const Vertex *textureVerts = texture->getVertices();
 	Vertex *pVerts = particleVerts;
-	particle *p = pHead;
+	Particle *p = pHead;
 
 	// set the vertex data for each particle (transformation, texcoords, color)
 	while (p)
 	{
 		// particle vertices are image vertices transformed by particle information
-		t.setTransformation(p->position[0], p->position[1], p->rotation, p->size, p->size, offsetX, offsetY, 0.0f, 0.0f);
+		t.setTransformation(p->position[0], p->position[1], p->angle, p->size, p->size, offsetX, offsetY, 0.0f, 0.0f);
 		t.transform(pVerts, textureVerts, 4);
 
 		// set the texture coordinate and color data for particle vertices
@@ -871,7 +887,7 @@ void ParticleSystem::update(float dt)
 		return;
 
 	// Traverse all particles and update.
-	particle *p = pHead;
+	Particle *p = pHead;
 
 	while (p)
 	{
@@ -916,7 +932,12 @@ void ParticleSystem::update(float dt)
 			const float t = 1.0f - p->life / p->lifetime;
 
 			// Rotate.
-			p->rotation += (p->spinStart * (1.0f - t) + p->spinEnd * t)*dt;
+			p->rotation += (p->spinStart * (1.0f - t) + p->spinEnd * t) * dt;
+
+			p->angle = p->rotation;
+
+			if (relativeRotation)
+				p->angle += atan2f(p->speed.y, p->speed.x);
 
 			// Change size according to given intervals:
 			// i = 0       1       2      3          n-1

+ 23 - 13
jni/love/src/modules/graphics/opengl/ParticleSystem.h

@@ -422,6 +422,12 @@ public:
 	 **/
 	std::vector<Color> getColor() const;
 
+	/**
+	 * sets whether particle angles & rotations are relative to their velocities.
+	 **/
+	void setRelativeRotation(bool enable);
+	bool hasRelativeRotation() const;
+
 	/**
 	 * Returns the amount of particles that are currently active in the system.
 	 **/
@@ -495,11 +501,12 @@ public:
 	static bool getConstant(InsertMode in, const char *&out);
 
 protected:
+
 	// Represents a single particle.
-	struct particle
+	struct Particle
 	{
-		particle *prev;
-		particle *next;
+		Particle *prev;
+		Particle *next;
 
 		float lifetime;
 		float life;
@@ -519,7 +526,8 @@ protected:
 		float sizeOffset;
 		float sizeIntervalSize;
 
-		float rotation;
+		float rotation; // Amount of rotation applied to the final angle.
+		float angle;
 		float spinStart;
 		float spinEnd;
 
@@ -527,16 +535,16 @@ protected:
 	};
 
 	// Pointer to the beginning of the allocated memory.
-	particle *pMem;
+	Particle *pMem;
 
 	// Pointer to a free particle.
-	particle *pFree;
+	Particle *pFree;
 
 	// Pointer to the start of the linked list.
-	particle *pHead;
+	Particle *pHead;
 
 	// Pointer to the end of the linked list.
-	particle *pTail;
+	Particle *pTail;
 
 	// array of transformed vertex data for all particles, for drawing
 	Vertex *particleVerts;
@@ -621,17 +629,19 @@ protected:
 	// Color.
 	std::vector<Colorf> colors;
 
+	bool relativeRotation;
+
 	void createBuffers(size_t size);
 	void deleteBuffers();
 
 	void addParticle(float t);
-	particle *removeParticle(particle *p);
+	Particle *removeParticle(Particle *p);
 
 	// Called by addParticle.
-	void initParticle(particle *p, float t);
-	void insertTop(particle *p);
-	void insertBottom(particle *p);
-	void insertRandom(particle *p);
+	void initParticle(Particle *p, float t);
+	void insertTop(Particle *p);
+	void insertBottom(Particle *p);
+	void insertRandom(Particle *p);
 
 	static StringMap<AreaSpreadDistribution, DISTRIBUTION_MAX_ENUM>::Entry distributionsEntries[];
 	static StringMap<AreaSpreadDistribution, DISTRIBUTION_MAX_ENUM> distributions;

+ 4 - 0
jni/love/src/modules/graphics/opengl/SpriteBatch.cpp

@@ -105,6 +105,8 @@ int SpriteBatch::add(float x, float y, float a, float sx, float sy, float ox, fl
 	if ((index == -1 && next >= size) || index < -1 || index >= size)
 		return -1;
 
+	Vertex sprite[4];
+
 	// Needed for colors.
 	memcpy(sprite, texture->getVertices(), sizeof(Vertex) * 4);
 
@@ -131,6 +133,8 @@ int SpriteBatch::addq(Quad *quad, float x, float y, float a, float sx, float sy,
 	if ((index == -1 && next >= size) || index < -1 || index >= next)
 		return -1;
 
+	Vertex sprite[4];
+
 	// Needed for colors.
 	memcpy(sprite, quad->getVertices(), sizeof(Vertex) * 4);
 

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

@@ -135,8 +135,6 @@ private:
 	// The next free element.
 	int next;
 
-	Vertex sprite[4];
-
 	// Current color. This color, if present, will be applied to the next
 	// added sprite.
 	Color *color;

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

@@ -110,12 +110,14 @@ int w_Canvas_clear(lua_State *L)
 	return 0;
 }
 
-int w_Canvas_getType(lua_State *L)
+int w_Canvas_getFormat(lua_State *L)
 {
 	Canvas *canvas = luax_checkcanvas(L, 1);
-	Canvas::TextureType type = canvas->getTextureType();
+	Texture::Format format = canvas->getTextureFormat();
 	const char *str;
-	Canvas::getConstant(type, str);
+	if (!Texture::getConstant(format, str))
+		return luaL_error(L, "Unknown texture format.");
+
 	lua_pushstring(L, str);
 	return 1;
 }
@@ -142,8 +144,11 @@ static const luaL_Reg functions[] =
 	{ "getImageData", w_Canvas_getImageData },
 	{ "getPixel", w_Canvas_getPixel },
 	{ "clear", w_Canvas_clear },
-	{ "getType", w_Canvas_getType },
+	{ "getFormat", w_Canvas_getFormat },
 	{ "getFSAA", w_Canvas_getFSAA },
+
+	// Deprecated since 0.9.1.
+	{ "getType", w_Canvas_getFormat },
 	{ 0, 0 }
 };
 

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

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

+ 37 - 10
jni/love/src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -152,8 +152,17 @@ int w_getMaxTextureSize(lua_State *L)
 
 int w_newImage(lua_State *L)
 {
-	love::image::ImageData *data = 0;
-	love::image::CompressedData *cdata = 0;
+	love::image::ImageData *data = nullptr;
+	love::image::CompressedData *cdata = nullptr;
+
+	Texture::Format format = Texture::FORMAT_NORMAL;
+	const char *fstr = lua_isnoneornil(L, 2) ? nullptr : luaL_checkstring(L, 2);
+
+	if (fstr != nullptr && !Texture::getConstant(fstr, format))
+		return luaL_error(L, "Invalid texture format: %s", fstr);
+
+	if (format == Texture::FORMAT_HDR) // For now...
+		return luaL_error(L, "HDR images are not supported.");
 
 	// Convert to FileData, if necessary.
 	if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_T))
@@ -185,15 +194,15 @@ int w_newImage(lua_State *L)
 		return luaL_error(L, "Error creating image.");
 
 	// Create the image.
-	Image *image = 0;
+	Image *image = nullptr;
 	EXCEPT_GUARD(
 		if (cdata)
-			image = instance->newImage(cdata);
+			image = instance->newImage(cdata, format);
 		else if (data)
-			image = instance->newImage(data);
+			image = instance->newImage(data, format);
 	)
 
-	if (image == 0)
+	if (image == nullptr)
 		return luaL_error(L, "Could not load image.");
 
 	// Push the type.
@@ -325,12 +334,12 @@ int w_newCanvas(lua_State *L)
 	const char *str = luaL_optstring(L, 3, "normal");
 	int fsaa        = luaL_optint(L, 4, 0);
 
-	Canvas::TextureType texture_type;
-	if (!Canvas::getConstant(str, texture_type))
-		return luaL_error(L, "Invalid canvas type: %s", str);
+	Texture::Format format;
+	if (!Texture::getConstant(str, format))
+		return luaL_error(L, "Invalid texture format: %s", str);
 
 	Canvas *canvas = nullptr;
-	EXCEPT_GUARD(canvas = instance->newCanvas(width, height, texture_type, fsaa);)
+	EXCEPT_GUARD(canvas = instance->newCanvas(width, height, format, fsaa);)
 
 	if (canvas == nullptr)
 		return luaL_error(L, "Canvas not created, but no error thrown. I don't even...");
@@ -835,6 +844,18 @@ int w_getMaxPointSize(lua_State *L)
 	return 1;
 }
 
+int w_setWireframe(lua_State *L)
+{
+	instance->setWireframe(luax_toboolean(L, 1));
+	return 0;
+}
+
+int w_isWireframe(lua_State *L)
+{
+	luax_pushboolean(L, instance->isWireframe());
+	return 1;
+}
+
 int w_newScreenshot(lua_State *L)
 {
 	love::image::Image *image = luax_getmodule<love::image::Image>(L, "image", MODULE_IMAGE_T);
@@ -1029,6 +1050,10 @@ int w_isSupported(lua_State *L)
 			if (!(GLAD_ARB_draw_instanced || (GLAD_ES_VERSION_2_0 && GLAD_EXT_draw_instanced)))
 				supported = false;
 			break;
+		case Graphics::SUPPORT_SRGB:
+			if (!Canvas::isSRGBSupported())
+				supported = false;
+			break;
 		default:
 			supported = false;
 		}
@@ -1405,6 +1430,8 @@ static const luaL_Reg functions[] =
 	{ "setPointStyle", w_setPointStyle },
 	{ "getPointSize", w_getPointSize },
 	{ "getPointStyle", w_getPointStyle },
+	{ "setWireframe", w_setWireframe },
+	{ "isWireframe", w_isWireframe },
 	{ "newScreenshot", w_newScreenshot },
 	{ "setCanvas", w_setCanvas },
 	{ "getCanvas", w_getCanvas },

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

@@ -85,6 +85,8 @@ int w_setPointStyle(lua_State *L);
 int w_getPointSize(lua_State *L);
 int w_getPointStyle(lua_State *L);
 int w_getMaxPointSize(lua_State *L);
+int w_setWireframe(lua_State *L);
+int w_isWireframe(lua_State *L);
 int w_newScreenshot(lua_State *L);
 int w_setCanvas(lua_State *L);
 int w_getCanvas(lua_State *L);

+ 0 - 16
jni/love/src/modules/graphics/opengl/wrap_Mesh.cpp

@@ -361,20 +361,6 @@ int w_Mesh_hasVertexColors(lua_State *L)
 	return 1;
 }
 
-int w_Mesh_setWireframe(lua_State *L)
-{
-	Mesh *t = luax_checkmesh(L, 1);
-	t->setWireframe(luax_toboolean(L, 2));
-	return 0;
-}
-
-int w_Mesh_isWireframe(lua_State *L)
-{
-	Mesh *t = luax_checkmesh(L, 1);
-	luax_pushboolean(L, t->isWireframe());
-	return 1;
-}
-
 static const luaL_Reg functions[] =
 {
 	{ "setVertex", w_Mesh_setVertex },
@@ -394,8 +380,6 @@ static const luaL_Reg functions[] =
 	{ "getDrawRange", w_Mesh_getDrawRange },
 	{ "setVertexColors", w_Mesh_setVertexColors },
 	{ "hasVertexColors", w_Mesh_hasVertexColors },
-	{ "setWireframe", w_Mesh_setWireframe },
-	{ "isWireframe", w_Mesh_isWireframe },
 
 	// Deprecated since 0.9.1.
 	{ "setImage", w_Mesh_setTexture },

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

@@ -51,8 +51,6 @@ int w_Mesh_setDrawRange(lua_State *L);
 int w_Mesh_getDrawRange(lua_State *L);
 int w_Mesh_setVertexColors(lua_State *L);
 int w_Mesh_hasVertexColors(lua_State *L);
-int w_Mesh_setWireframe(lua_State *L);
-int w_Mesh_isWireframe(lua_State *L);
 
 extern "C" int luaopen_mesh(lua_State *L);
 

+ 16 - 0
jni/love/src/modules/graphics/opengl/wrap_ParticleSystem.cpp

@@ -569,6 +569,20 @@ int w_ParticleSystem_getColors(lua_State *L)
 	return colors.size();
 }
 
+int w_ParticleSystem_setRelativeRotation(lua_State *L)
+{
+	ParticleSystem *t = luax_checkparticlesystem(L, 1);
+	t->setRelativeRotation(luax_toboolean(L, 2));
+	return 0;
+}
+
+int w_ParticleSystem_hasRelativeRotation(lua_State *L)
+{
+	ParticleSystem *t = luax_checkparticlesystem(L, 1);
+	luax_pushboolean(L, t->hasRelativeRotation());
+	return 1;
+}
+
 int w_ParticleSystem_getCount(lua_State *L)
 {
 	ParticleSystem *t = luax_checkparticlesystem(L, 1);
@@ -687,6 +701,8 @@ static const luaL_Reg functions[] =
 	{ "getColors", w_ParticleSystem_getColors },
 	{ "setOffset", w_ParticleSystem_setOffset },
 	{ "getOffset", w_ParticleSystem_getOffset },
+	{ "setRelativeRotation", w_ParticleSystem_setRelativeRotation },
+	{ "hasRelativeRotation", w_ParticleSystem_hasRelativeRotation },
 	{ "getCount", w_ParticleSystem_getCount },
 	{ "start", w_ParticleSystem_start },
 	{ "stop", w_ParticleSystem_stop },

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

@@ -77,6 +77,8 @@ int w_ParticleSystem_setColors(lua_State *L);
 int w_ParticleSystem_getColors(lua_State *L);
 int w_ParticleSystem_setOffset(lua_State *L);
 int w_ParticleSystem_getOffset(lua_State *L);
+int w_ParticleSystem_setRelativeRotation(lua_State *L);
+int w_ParticleSystem_hasRelativeRotation(lua_State *L);
 int w_ParticleSystem_getCount(lua_State *L);
 int w_ParticleSystem_start(lua_State *L);
 int w_ParticleSystem_stop(lua_State *L);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -50,6 +50,7 @@ WindowSettings::WindowSettings()
 	, centered(true)
 	, display(0)
 	, highdpi(false)
+	, sRGB(false)
 {
 }
 
@@ -86,6 +87,7 @@ StringMap<Window::Setting, Window::SETTING_MAX_ENUM>::Entry Window::settingEntri
 	{"centered", SETTING_CENTERED},
 	{"display", SETTING_DISPLAY},
 	{"highdpi", SETTING_HIGHDPI},
+	{"srgb", SETTING_SRGB},
 };
 
 StringMap<Window::Setting, Window::SETTING_MAX_ENUM> Window::settings(Window::settingEntries, sizeof(Window::settingEntries));

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

@@ -57,6 +57,7 @@ public:
 		SETTING_CENTERED,
 		SETTING_DISPLAY,
 		SETTING_HIGHDPI,
+		SETTING_SRGB,
 		SETTING_MAX_ENUM
 	};
 
@@ -157,6 +158,7 @@ struct WindowSettings
 	bool centered; // = true
 	int display; // = 0
 	bool highdpi; // false
+	bool sRGB; // false
 
 }; // WindowSettings
 

+ 18 - 7
jni/love/src/modules/window/sdl/Window.cpp

@@ -89,7 +89,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 	f.minwidth = std::max(f.minwidth, 1);
 	f.minheight = std::max(f.minheight, 1);
 
-	f.display = std::min(std::max(f.display, 0), getDisplayCount());
+	f.display = std::min(std::max(f.display, 0), getDisplayCount() - 1);
 
 	// Use the desktop resolution if a width or height of 0 is specified.
 	if (width == 0 || height == 0)
@@ -161,7 +161,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 	if (!window)
 	{
 		// In Windows and Linux, some GL attributes are set on window creation.
-		setWindowGLAttributes(f.fsaa);
+		setWindowGLAttributes(f.fsaa, f.sRGB);
 
 		const char *title = windowTitle.c_str();
 		int pos = f.centered ? centeredpos : uncenteredpos;
@@ -197,7 +197,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 
 	SDL_RaiseWindow(window);
 
-	if (!setContext(f.fsaa, f.vsync))
+	if (!setContext(f.fsaa, f.vsync, f.sRGB))
 	{
 		created = false;
 		return false;
@@ -216,7 +216,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 		SDL_GL_GetDrawableSize(window, &width, &height);
 #endif
 
-		if (!gfx->setMode(width, height))
+		if (!gfx->setMode(width, height, curMode.settings.sRGB))
 			displayError("Could not set graphics mode", "Unsupported OpenGL version?");
 	}
 
@@ -237,7 +237,7 @@ bool Window::onWindowResize(int width, int height)
 	return true;
 }
 
-bool Window::setContext(int fsaa, bool vsync)
+bool Window::setContext(int fsaa, bool vsync, bool sRGB)
 {
 	// We would normally only need to recreate the context if FSAA changes or
 	// SDL_GL_MakeCurrent is unsuccessful, but in Windows MakeCurrent can
@@ -249,7 +249,7 @@ bool Window::setContext(int fsaa, bool vsync)
 	}
 
 	// Make sure the proper attributes are set.
-	setWindowGLAttributes(fsaa);
+	setWindowGLAttributes(fsaa, sRGB);
 
 	context = SDL_GL_CreateContext(window);
 
@@ -301,7 +301,7 @@ bool Window::setContext(int fsaa, bool vsync)
 	return true;
 }
 
-void Window::setWindowGLAttributes(int fsaa) const
+void Window::setWindowGLAttributes(int fsaa, bool /* sRGB */) const
 {
 	// Set GL window attributes.
 	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
@@ -315,6 +315,15 @@ void Window::setWindowGLAttributes(int fsaa) const
 	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (fsaa > 0) ? 1 : 0);
 	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, (fsaa > 0) ? fsaa : 0);
 
+	/* FIXME: Enable this code but make sure to try to re-create the window and
+	 * context with this disabled, if creation fails with it enabled.
+	 * We can leave this out for now because in practice the framebuffer will
+	 * already be sRGB-capable (on desktops at least.)
+	 #if SDL_VERSION_ATLEAST(2,0,1)
+	 SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, sRGB ? 1 : 0);
+	 #endif
+	 */
+
 	int contextprofile = 0;
 
 	bool tryES2 = false;
@@ -410,6 +419,8 @@ void Window::updateSettings(const WindowSettings &newsettings)
 	else
 #endif
 		SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
+
+	curMode.settings.sRGB = newsettings.sRGB;
 }
 
 void Window::displayError(const std::string &title, const std::string &text) const

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

@@ -90,8 +90,8 @@ public:
 
 private:
 
-	bool setContext(int fsaa, bool vsync);
-	void setWindowGLAttributes(int fsaa) const;
+	bool setContext(int fsaa, bool vsync, bool sRGB);
+	void setWindowGLAttributes(int fsaa, bool sRGB) const;
 
 	// Update the saved window settings based on the window's actual state.
 	void updateSettings(const WindowSettings &newsettings);

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

@@ -96,6 +96,7 @@ 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);
 	settings.highdpi = luax_boolflag(L, 3, settingName(Window::SETTING_HIGHDPI), false);
+	settings.sRGB = luax_boolflag(L, 3, settingName(Window::SETTING_SRGB), false);
 
 	// Display index is 1-based in Lua and 0-based internally.
 	settings.display--;
@@ -151,6 +152,9 @@ 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));
+
 	return 3;
 }
 

+ 2 - 0
jni/love/src/scripts/boot.lua

@@ -313,6 +313,7 @@ function love.init()
 			resizable = false,
 			centered = true,
 			highdpi = false,
+			srgb = false,
 		},
 		modules = {
 			event = true,
@@ -404,6 +405,7 @@ function love.init()
 			centered = c.window.centered,
 			display = c.window.display,
 			highdpi = c.window.highdpi,
+			srgb = c.window.srgb,
 		}), "Could not set window mode")
 		love.window.setTitle(c.window.title or c.title)
 		if c.window.icon then

+ 3 - 0
jni/love/src/scripts/boot.lua.h

@@ -570,6 +570,7 @@ 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,
@@ -705,6 +706,8 @@ 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, 0x7d, 0x29, 0x2c, 0x20, 0x22, 0x43, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 
 	0x65, 0x74, 0x20, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0x29, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x73, 0x65, 0x74, 0x54,