Browse Source

OpenGL 2.1 is now required to use love.graphics (resolves issue #779.)

Removed love.graphics.isSupported("canvas", "shader", "npot", "subtractive", and "mipmap"), since those features are all guaranteed by the minimum system requirements.

--HG--
branch : minor
Alex Szpakowski 11 years ago
parent
commit
caa61563cc

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

@@ -158,13 +158,8 @@ StringMap<Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM> Graphics::lineJoins(
 
 StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM>::Entry Graphics::supportEntries[] =
 {
-	{ "canvas", Graphics::SUPPORT_CANVAS },
 	{ "hdrcanvas", Graphics::SUPPORT_HDR_CANVAS },
 	{ "multicanvas", Graphics::SUPPORT_MULTI_CANVAS },
-	{ "shader", Graphics::SUPPORT_SHADER },
-	{ "npot", Graphics::SUPPORT_NPOT },
-	{ "subtractive", Graphics::SUPPORT_SUBTRACTIVE },
-	{ "mipmap", Graphics::SUPPORT_MIPMAP },
 	{ "dxt", Graphics::SUPPORT_DXT },
 	{ "bc5", Graphics::SUPPORT_BC5 },
 	{ "instancing", Graphics::SUPPORT_INSTANCING },

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

@@ -78,13 +78,8 @@ public:
 
 	enum Support
 	{
-		SUPPORT_CANVAS = 1,
-		SUPPORT_HDR_CANVAS,
+		SUPPORT_HDR_CANVAS = 1,
 		SUPPORT_MULTI_CANVAS,
-		SUPPORT_SHADER,
-		SUPPORT_NPOT,
-		SUPPORT_SUBTRACTIVE,
-		SUPPORT_MIPMAP,
 		SUPPORT_DXT,
 		SUPPORT_BC5,
 		SUPPORT_INSTANCING,

+ 2 - 8
src/modules/graphics/opengl/Canvas.cpp

@@ -213,10 +213,7 @@ struct FramebufferStrategyGL3 : public FramebufferStrategy
 		}
 
 		// set up multiple render targets
-		if (GLEE_VERSION_2_0)
-			glDrawBuffers(drawbuffers.size(), &drawbuffers[0]);
-		else if (GLEE_ARB_draw_buffers)
-			glDrawBuffersARB(drawbuffers.size(), &drawbuffers[0]);
+		glDrawBuffers(drawbuffers.size(), &drawbuffers[0]);
 	}
 };
 
@@ -342,10 +339,7 @@ struct FramebufferStrategyPackedEXT : public FramebufferStrategy
 		}
 
 		// set up multiple render targets
-		if (GLEE_VERSION_2_0)
-			glDrawBuffers(drawbuffers.size(), &drawbuffers[0]);
-		else if (GLEE_ARB_draw_buffers)
-			glDrawBuffersARB(drawbuffers.size(), &drawbuffers[0]);
+		glDrawBuffers(drawbuffers.size(), &drawbuffers[0]);
 	}
 };
 

+ 34 - 20
src/modules/graphics/opengl/Graphics.cpp

@@ -55,6 +55,7 @@ Graphics::Graphics()
 	, created(false)
 	, activeStencil(false)
 	, savedState()
+	, displayedMinReqWarning(false)
 {
 	currentWindow = love::window::sdl::Window::createSingleton();
 
@@ -161,16 +162,38 @@ bool Graphics::setMode(int width, int height, bool &sRGB)
 	this->width = width;
 	this->height = height;
 
-	// Okay, setup OpenGL.
 	gl.initContext();
 
+	// Does the system meet LOVE's minimum requirements for graphics?
+	if (!(GLEE_VERSION_2_0 && Shader::isSupported() && Canvas::isSupported())
+		&& !displayedMinReqWarning)
+	{
+		love::window::MessageBoxType type = love::window::MESSAGEBOX_ERROR;
+
+		const char *title = "Minimum system requirements not met!";
+
+		std::string message;
+		message += "Detected OpenGL version: ";
+		message += (const char *) glGetString(GL_VERSION);
+		message += "\nRequired OpenGL version: 2.1."; // -ish
+		message += "\nThe program may crash or have graphical issues.";
+
+		::printf("%s\n%s\n", title, message.c_str());
+		currentWindow->showMessageBox(type, title, message.c_str());
+
+		// We should only show the message once, instead of after every setMode.
+		displayedMinReqWarning = true;
+	}
+
+	// Okay, setup OpenGL.
+	gl.setupContext();
+
 	created = true;
 
 	setViewportSize(width, height);
 
 	// Make sure antialiasing works when set elsewhere
-	if (GLEE_VERSION_1_3 || GLEE_ARB_multisample)
-		glEnable(GL_MULTISAMPLE);
+	glEnable(GL_MULTISAMPLE);
 
 	// Enable blending
 	glEnable(GL_BLEND);
@@ -184,8 +207,7 @@ bool Graphics::setMode(int width, int height, bool &sRGB)
 	glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
 
 	// Auto-generated mipmaps should be the best quality possible
-	if (GLEE_VERSION_1_4 || GLEE_SGIS_generate_mipmap)
-		glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
+	glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
 
 	// Enable textures
 	glEnable(GL_TEXTURE_2D);
@@ -609,21 +631,9 @@ void Graphics::setBlendMode(Graphics::BlendMode mode)
 	switch (mode)
 	{
 	case BLEND_ALPHA:
-		if (GLEE_VERSION_1_4 || GLEE_EXT_blend_func_separate)
-		{
-			state.srcRGB = GL_SRC_ALPHA;
-			state.srcA = GL_ONE;
-			state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA;
-		}
-		else
-		{
-			// Fallback for OpenGL implementations without support for separate blend functions.
-			// 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).
-			state.srcRGB = state.srcA = GL_SRC_ALPHA;
-			state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA;
-		}
+		state.srcRGB = GL_SRC_ALPHA;
+		state.srcA = GL_ONE;
+		state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA;
 		break;
 	case BLEND_MULTIPLICATIVE:
 		state.srcRGB = state.srcA = GL_DST_COLOR;
@@ -986,8 +996,10 @@ love::image::ImageData *Graphics::newScreenshot(love::image::Image *image, bool
 	{
 		delete[] pixels;
 		delete[] screenshot;
+
 		if (curcanvas)
 			curcanvas->startGrab(curcanvas->getAttachedCanvases());
+
 		throw love::Exception("Out of memory.");
 	}
 
@@ -1019,8 +1031,10 @@ love::image::ImageData *Graphics::newScreenshot(love::image::Image *image, bool
 	catch (love::Exception &)
 	{
 		delete[] screenshot;
+
 		if (curcanvas)
 			curcanvas->startGrab(curcanvas->getAttachedCanvases());
+
 		throw;
 	}
 

+ 2 - 0
src/modules/graphics/opengl/Graphics.h

@@ -473,6 +473,8 @@ private:
 
 	DisplayState savedState;
 
+	bool displayedMinReqWarning;
+
 }; // Graphics
 
 } // opengl

+ 28 - 135
src/modules/graphics/opengl/Image.cpp

@@ -39,8 +39,6 @@ float Image::defaultMipmapSharpness = 0.0f;
 Image::Image(love::image::ImageData *data, Texture::Format format)
 	: data(data)
 	, cdata(nullptr)
-	, paddedWidth(width)
-	, paddedHeight(height)
 	, texture(0)
 	, mipmapSharpness(defaultMipmapSharpness)
 	, mipmapsCreated(false)
@@ -58,8 +56,6 @@ Image::Image(love::image::ImageData *data, Texture::Format format)
 Image::Image(love::image::CompressedData *cdata, Texture::Format format)
 	: data(nullptr)
 	, cdata(cdata)
-	, paddedWidth(width)
-	, paddedHeight(height)
 	, texture(0)
 	, mipmapSharpness(defaultMipmapSharpness)
 	, mipmapsCreated(false)
@@ -112,25 +108,10 @@ void Image::drawq(Quad *quad, float x, float y, float angle, float sx, float sy,
 void Image::predraw()
 {
 	bind();
-
-	if (width != paddedWidth || height != paddedHeight)
-	{
-		// NPOT image padded to POT size, so the texcoords should be scaled.
-		glMatrixMode(GL_TEXTURE);
-		glPushMatrix();
-		glScalef(float(width) / float(paddedWidth), float(height) / float(paddedHeight), 0.0f);
-		glMatrixMode(GL_MODELVIEW);
-	}
 }
 
 void Image::postdraw()
 {
-	if (width != paddedWidth || height != paddedHeight)
-	{
-		glMatrixMode(GL_TEXTURE);
-		glPopMatrix();
-		glMatrixMode(GL_MODELVIEW);
-	}
 }
 
 GLuint Image::getGLTexture() const
@@ -148,27 +129,18 @@ void Image::uploadCompressedMipmaps()
 	int count = cdata->getMipmapCount();
 
 	// We have to inform OpenGL if the image doesn't have all mipmap levels.
-	if (GLEE_VERSION_1_2 || GLEE_SGIS_texture_lod)
-	{
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, count - 1);
-	}
-	else if (cdata->getWidth(count-1) > 1 || cdata->getHeight(count-1) > 1)
-	{
-		// Telling OpenGL to ignore certain levels isn't always supported.
-		throw love::Exception("Cannot load mipmaps: "
-		      "compressed image does not have all required levels.");
-	}
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, count - 1);
 
 	for (int i = 1; i < count; i++)
 	{
-		glCompressedTexImage2DARB(GL_TEXTURE_2D,
-		                          i,
-		                          getCompressedFormat(cdata->getFormat()),
-		                          cdata->getWidth(i),
-		                          cdata->getHeight(i),
-		                          0,
-		                          GLsizei(cdata->getSize(i)),
-		                          cdata->getData(i));
+		glCompressedTexImage2D(GL_TEXTURE_2D,
+		                       i,
+		                       getCompressedFormat(cdata->getFormat()),
+		                       cdata->getWidth(i),
+		                       cdata->getHeight(i),
+		                       0,
+		                       GLsizei(cdata->getSize(i)),
+		                       cdata->getData(i));
 	}
 }
 
@@ -178,9 +150,6 @@ void Image::createMipmaps()
 	if (!data || isCompressed())
 		return;
 
-	if (!hasMipmapSupport())
-		throw love::Exception("Mipmap filtering is not supported on this system.");
-
 	// Some old drivers claim support for NPOT textures, but fail when creating
 	// mipmaps. We can't detect which systems will do this, so we fail gracefully
 	// for all NPOT images.
@@ -196,7 +165,7 @@ void Image::createMipmaps()
 	// Prevent other threads from changing the ImageData while we upload it.
 	love::thread::Lock lock(data->getMutex());
 
-	if (hasNpot() && (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object))
+	if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object)
 	{
 		if (gl.getVendor() == OpenGL::VENDOR_ATI_AMD)
 		{
@@ -266,18 +235,13 @@ void Image::setWrap(const Texture::Wrap &w)
 
 void Image::setMipmapSharpness(float sharpness)
 {
-	if (hasMipmapSharpnessSupport())
-	{
-		// LOD bias has the range (-maxbias, maxbias)
-		mipmapSharpness = std::min(std::max(sharpness, -maxMipmapSharpness + 0.01f), maxMipmapSharpness - 0.01f);
+	// LOD bias has the range (-maxbias, maxbias)
+	mipmapSharpness = std::min(std::max(sharpness, -maxMipmapSharpness + 0.01f), maxMipmapSharpness - 0.01f);
 
-		bind();
+	bind();
 
-		// negative bias is sharper
-		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -mipmapSharpness);
-	}
-	else
-		mipmapSharpness = 0.0f;
+	// negative bias is sharper
+	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -mipmapSharpness);
 }
 
 float Image::getMipmapSharpness() const
@@ -345,28 +309,18 @@ bool Image::loadVolatile()
 			throw love::Exception("cannot create image: format is not supported on this system.");
 	}
 
-	if (hasMipmapSharpnessSupport() && maxMipmapSharpness == 0.0f)
+	if (maxMipmapSharpness == 0.0f)
 		glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &maxMipmapSharpness);
 
 	glGenTextures(1, &texture);
 	gl.bindTexture(texture);
 
-	filter.anisotropy = gl.setTextureFilter(filter);
+	gl.setTextureFilter(filter);
 	gl.setTextureWrap(wrap);
 	setMipmapSharpness(mipmapSharpness);
 
-	paddedWidth = width;
-	paddedHeight = height;
-
-	if (!hasNpot())
-	{
-		// NPOT textures will be padded to POT dimensions if necessary.
-		paddedWidth = next_p2(width);
-		paddedHeight = next_p2(height);
-	}
-
 	// Use a default texture if the size is too big for the system.
-	if (paddedWidth > gl.getMaxTextureSize() || paddedHeight > gl.getMaxTextureSize())
+	if (width > gl.getMaxTextureSize() || height > gl.getMaxTextureSize())
 	{
 		uploadDefaultTexture();
 		return true;
@@ -379,10 +333,7 @@ bool Image::loadVolatile()
 
 	while (glGetError() != GL_NO_ERROR); // Clear errors.
 
-	if (hasNpot() || (width == paddedWidth && height == paddedHeight))
-		uploadTexture();
-	else
-		uploadTexturePadded();
+	uploadTexture();
 
 	GLenum glerr = glGetError();
 	if (glerr != GL_NO_ERROR)
@@ -395,51 +346,19 @@ bool Image::loadVolatile()
 	return true;
 }
 
-void Image::uploadTexturePadded()
-{
-	if (isCompressed() && cdata)
-	{
-		// Padded textures don't really work if they're compressed...
-		throw love::Exception("Cannot create image: "
-		                      "compressed NPOT images are not supported on this system.");
-	}
-	else if (data)
-	{
-		GLenum iformat = (format == FORMAT_SRGB) ? GL_SRGB8_ALPHA8 : GL_RGBA8;
-		glTexImage2D(GL_TEXTURE_2D,
-		             0,
-		             iformat,
-		             (GLsizei)paddedWidth,
-		             (GLsizei)paddedHeight,
-		             0,
-		             GL_RGBA,
-		             GL_UNSIGNED_BYTE,
-		             0);
-
-		glTexSubImage2D(GL_TEXTURE_2D,
-		                0,
-		                0, 0,
-		                (GLsizei)width,
-		                (GLsizei)height,
-		                GL_RGBA,
-		                GL_UNSIGNED_BYTE,
-		                data->getData());
-	}
-}
-
 void Image::uploadTexture()
 {
 	if (isCompressed() && cdata)
 	{
 		GLenum format = getCompressedFormat(cdata->getFormat());
-		glCompressedTexImage2DARB(GL_TEXTURE_2D,
-		                          0,
-		                          format,
-		                          cdata->getWidth(0),
-		                          cdata->getHeight(0),
-		                          0,
-		                          GLsizei(cdata->getSize(0)),
-		                          cdata->getData(0));
+		glCompressedTexImage2D(GL_TEXTURE_2D,
+		                       0,
+		                       format,
+		                       cdata->getWidth(0),
+		                       cdata->getHeight(0),
+		                       0,
+		                       GLsizei(cdata->getSize(0)),
+		                       cdata->getData(0));
 	}
 	else if (data)
 	{
@@ -488,10 +407,7 @@ bool Image::refresh()
 
 	while (glGetError() != GL_NO_ERROR); // Clear errors.
 
-	if (hasNpot() || (width == paddedWidth && height == paddedHeight))
-		uploadTexture();
-	else
-		uploadTexturePadded();
+	uploadTexture();
 
 	if (glGetError() != GL_NO_ERROR)
 		uploadDefaultTexture();
@@ -610,36 +526,13 @@ GLenum Image::getCompressedFormat(image::CompressedData::Format cformat) const
 	}
 }
 
-bool Image::hasNpot()
-{
-	return GLEE_VERSION_2_0 || GLEE_ARB_texture_non_power_of_two;
-}
-
 bool Image::hasAnisotropicFilteringSupport()
 {
 	return GLEE_EXT_texture_filter_anisotropic;
 }
 
-bool Image::hasMipmapSupport()
-{
-	return GLEE_VERSION_1_4 || GLEE_SGIS_generate_mipmap;
-}
-
-bool Image::hasMipmapSharpnessSupport()
-{
-	return GLEE_VERSION_1_4;
-}
-
-bool Image::hasCompressedTextureSupport()
-{
-	return GLEE_VERSION_1_3 || GLEE_ARB_texture_compression;
-}
-
 bool Image::hasCompressedTextureSupport(image::CompressedData::Format format)
 {
-	if (!hasCompressedTextureSupport())
-		return false;
-
 	switch (format)
 	{
 	case image::CompressedData::FORMAT_DXT1:

+ 0 - 10
src/modules/graphics/opengl/Image.h

@@ -127,14 +127,8 @@ public:
 	static void setDefaultMipmapFilter(FilterMode f);
 	static FilterMode getDefaultMipmapFilter();
 
-	static bool hasNpot();
 	static bool hasAnisotropicFilteringSupport();
-	static bool hasMipmapSupport();
-	static bool hasMipmapSharpnessSupport();
-
-	static bool hasCompressedTextureSupport();
 	static bool hasCompressedTextureSupport(image::CompressedData::Format format);
-
 	static bool hasSRGBSupport();
 
 private:
@@ -151,9 +145,6 @@ private:
 	// null if raw ImageData was used to create the texture.
 	love::image::CompressedData *cdata;
 
-	// Real dimensions of the texture, if it was auto-padded to POT size.
-	int paddedWidth, paddedHeight;
-
 	// OpenGL texture identifier.
 	GLuint texture;
 
@@ -175,7 +166,6 @@ private:
 
 	void preload();
 
-	void uploadTexturePadded();
 	void uploadTexture();
 
 	void uploadCompressedMipmaps();

+ 32 - 90
src/modules/graphics/opengl/OpenGL.cpp

@@ -57,6 +57,14 @@ void OpenGL::initContext()
 	initOpenGLFunctions();
 	initVendor();
 
+	contextInitialized = true;
+}
+
+void OpenGL::setupContext()
+{
+	if (!contextInitialized)
+		return;
+
 	// Store the current color so we don't have to get it through GL later.
 	GLfloat glcolor[4];
 	glGetFloatv(GL_CURRENT_COLOR, glcolor);
@@ -80,37 +88,28 @@ void OpenGL::initContext()
 	glGetIntegerv(GL_SCISSOR_BOX, (GLint *) &state.scissor.x);
 	state.scissor.y = state.viewport.h - (state.scissor.y + state.scissor.h);
 
-	// Initialize multiple texture unit support for shaders, if available.
+	// Initialize multiple texture unit support for shaders.
 	state.textureUnits.clear();
-	if (Shader::isSupported())
-	{
-		GLint maxtextureunits;
-		glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxtextureunits);
 
-		state.textureUnits.resize(maxtextureunits, 0);
+	GLint maxtextureunits;
+	glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxtextureunits);
 
-		GLenum curgltextureunit;
-		glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint *) &curgltextureunit);
+	state.textureUnits.resize(maxtextureunits, 0);
 
-		state.curTextureUnit = (int) curgltextureunit - GL_TEXTURE0;
+	GLenum curgltextureunit;
+	glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint *) &curgltextureunit);
 
-		// Retrieve currently bound textures for each texture unit.
-		for (size_t i = 0; i < state.textureUnits.size(); i++)
-		{
-			glActiveTexture(GL_TEXTURE0 + i);
-			glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint *) &state.textureUnits[i]);
-		}
+	state.curTextureUnit = (int) curgltextureunit - GL_TEXTURE0;
 
-		glActiveTexture(curgltextureunit);
-	}
-	else
+	// Retrieve currently bound textures for each texture unit.
+	for (size_t i = 0; i < state.textureUnits.size(); i++)
 	{
-		// Multitexturing not supported, so we only have 1 texture unit.
-		state.textureUnits.resize(1, 0);
-		state.curTextureUnit = 0;
-		glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint *) &state.textureUnits[0]);
+		glActiveTexture(GL_TEXTURE0 + i);
+		glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint *) &state.textureUnits[i]);
 	}
 
+	glActiveTexture(curgltextureunit);
+
 	BlendState blend = {GL_ONE, GL_ONE, GL_ZERO, GL_ZERO, GL_FUNC_ADD};
 	setBlendState(blend);
 
@@ -118,8 +117,6 @@ void OpenGL::initContext()
 	createDefaultTexture();
 
 	state.lastPseudoInstanceID = -1;
-
-	contextInitialized = true;
 }
 
 void OpenGL::deInitContext()
@@ -158,31 +155,6 @@ void OpenGL::initVendor()
 
 void OpenGL::initOpenGLFunctions()
 {
-	// The functionality of the core and ARB VBOs are identical, so we can
-	// assign the pointers of the core functions to the names of the ARB
-	// functions, if the latter isn't supported but the former is.
-	if (GLEE_VERSION_1_5 && !GLEE_ARB_vertex_buffer_object)
-	{
-		glBindBufferARB = (GLEEPFNGLBINDBUFFERARBPROC) glBindBuffer;
-		glBufferDataARB = (GLEEPFNGLBUFFERDATAARBPROC) glBufferData;
-		glBufferSubDataARB = (GLEEPFNGLBUFFERSUBDATAARBPROC) glBufferSubData;
-		glDeleteBuffersARB = (GLEEPFNGLDELETEBUFFERSARBPROC) glDeleteBuffers;
-		glGenBuffersARB = (GLEEPFNGLGENBUFFERSARBPROC) glGenBuffers;
-		glGetBufferParameterivARB = (GLEEPFNGLGETBUFFERPARAMETERIVARBPROC) glGetBufferParameteriv;
-		glGetBufferPointervARB = (GLEEPFNGLGETBUFFERPOINTERVARBPROC) glGetBufferPointerv;
-		glGetBufferSubDataARB = (GLEEPFNGLGETBUFFERSUBDATAARBPROC) glGetBufferSubData;
-		glIsBufferARB = (GLEEPFNGLISBUFFERARBPROC) glIsBuffer;
-		glMapBufferARB = (GLEEPFNGLMAPBUFFERARBPROC) glMapBuffer;
-		glUnmapBufferARB = (GLEEPFNGLUNMAPBUFFERARBPROC) glUnmapBuffer;
-	}
-
-	// Same deal for compressed textures.
-	if (GLEE_VERSION_1_3 && !GLEE_ARB_texture_compression)
-	{
-		glCompressedTexImage2DARB = (GLEEPFNGLCOMPRESSEDTEXIMAGE2DARBPROC) glCompressedTexImage2D;
-		glCompressedTexSubImage2DARB = (GLEEPFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) glCompressedTexSubImage2D;
-		glGetCompressedTexImageARB = (GLEEPFNGLGETCOMPRESSEDTEXIMAGEARBPROC) glGetCompressedTexImage;
-	}
 }
 
 void OpenGL::initMaxValues()
@@ -195,18 +167,13 @@ void OpenGL::initMaxValues()
 
 	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
 
-	if (Canvas::isSupported() && (GLEE_VERSION_2_0 || GLEE_ARB_draw_buffers))
-	{
-		int maxattachments = 0;
-		glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxattachments);
+	int maxattachments = 0;
+	glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxattachments);
 
-		int maxdrawbuffers = 0;
-		glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxdrawbuffers);
+	int maxdrawbuffers = 0;
+	glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxdrawbuffers);
 
-		maxRenderTargets = std::min(maxattachments, maxdrawbuffers);
-	}
-	else
-		maxRenderTargets = 0;
+	maxRenderTargets = std::min(maxattachments, maxdrawbuffers);
 }
 
 void OpenGL::createDefaultTexture()
@@ -369,28 +336,8 @@ OpenGL::Viewport OpenGL::getScissor() const
 
 void OpenGL::setBlendState(const BlendState &blend)
 {
-	if (GLEE_VERSION_1_4 || GLEE_ARB_imaging)
-		glBlendEquation(blend.func);
-	else if (GLEE_EXT_blend_minmax && GLEE_EXT_blend_subtract)
-		glBlendEquationEXT(blend.func);
-	else
-	{
-		if (blend.func == GL_FUNC_REVERSE_SUBTRACT)
-			throw love::Exception("This graphics card does not support the subtractive blend mode!");
-		// GL_FUNC_ADD is the default even without access to glBlendEquation, so that'll still work.
-	}
-
-	if (blend.srcRGB == blend.srcA && blend.dstRGB == blend.dstA)
-		glBlendFunc(blend.srcRGB, blend.dstRGB);
-	else
-	{
-		if (GLEE_VERSION_1_4)
-			glBlendFuncSeparate(blend.srcRGB, blend.dstRGB, blend.srcA, blend.dstA);
-		else if (GLEE_EXT_blend_func_separate)
-			glBlendFuncSeparateEXT(blend.srcRGB, blend.dstRGB, blend.srcA, blend.dstA);
-		else
-			throw love::Exception("This graphics card does not support separated rgb and alpha blend functions!");
-	}
+	glBlendEquation(blend.func);
+	glBlendFuncSeparate(blend.srcRGB, blend.dstRGB, blend.srcA, blend.dstA);
 
 	state.blend = blend;
 }
@@ -406,12 +353,7 @@ void OpenGL::setTextureUnit(int textureunit)
 		throw love::Exception("Invalid texture unit index (%d).", textureunit);
 
 	if (textureunit != state.curTextureUnit)
-	{
-		if (state.textureUnits.size() > 1)
-			glActiveTexture(GL_TEXTURE0 + textureunit);
-		else
-			throw love::Exception("Multitexturing is not supported.");
-	}
+		glActiveTexture(GL_TEXTURE0 + textureunit);
 
 	state.curTextureUnit = textureunit;
 }
@@ -457,7 +399,7 @@ void OpenGL::deleteTexture(GLuint texture)
 	glDeleteTextures(1, &texture);
 }
 
-float OpenGL::setTextureFilter(graphics::Texture::Filter &f)
+void OpenGL::setTextureFilter(graphics::Texture::Filter &f)
 {
 	GLint gmin, gmag;
 
@@ -502,8 +444,8 @@ float OpenGL::setTextureFilter(graphics::Texture::Filter &f)
 		f.anisotropy = std::min(std::max(f.anisotropy, 1.0f), maxAnisotropy);
 		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, f.anisotropy);
 	}
-
-	return f.anisotropy;
+	else
+		f.anisotropy = 1.0f;
 }
 
 graphics::Texture::Filter OpenGL::getTextureFilter()

+ 10 - 4
src/modules/graphics/opengl/OpenGL.h

@@ -104,11 +104,16 @@ public:
 	OpenGL();
 
 	/**
-	 * Initializes some required context state based on current and default
-	 * OpenGL state. Call this directly after creating an OpenGL context!
+	 * Initializes the active OpenGL context.
 	 **/
 	void initContext();
 
+	/**
+	 * Sets up some required context state based on current and default OpenGL
+	 * state. Call this directly after initializing an OpenGL context!
+	 **/
+	void setupContext();
+
 	/**
 	 * Marks current context state as invalid and deletes OpenGL objects owned
 	 * by this class instance. Call this directly before potentially deleting
@@ -214,9 +219,10 @@ public:
 
 	/**
 	 * Sets the texture filter mode for the currently bound texture.
-	 * Returns the actual amount of anisotropic filtering set.
+	 * The anisotropy parameter of the argument is set to the actual amount of
+	 * anisotropy that was used.
 	 **/
-	float setTextureFilter(graphics::Texture::Filter &f);
+	void setTextureFilter(graphics::Texture::Filter &f);
 
 	/**
 	 * Returns the texture filter mode for the currently bound texture.

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

@@ -734,11 +734,7 @@ const std::map<std::string, Object *> &Shader::getBoundRetainables() const
 
 std::string Shader::getGLSLVersion()
 {
-	const char *tmp = nullptr;
-
-	// GL_SHADING_LANGUAGE_VERSION isn't available in OpenGL < 2.0.
-	if (GLEE_VERSION_2_0 || GLEE_ARB_shading_language_100)
-		tmp = (const char *) glGetString(GL_SHADING_LANGUAGE_VERSION);
+	const char *tmp = (const char *) glGetString(GL_SHADING_LANGUAGE_VERSION);
 
 	if (tmp == nullptr)
 		return "0.0";
@@ -755,7 +751,7 @@ std::string Shader::getGLSLVersion()
 
 bool Shader::isSupported()
 {
-	return GLEE_VERSION_2_0 && getGLSLVersion() >= "1.2";
+	return getGLSLVersion() >= "1.2";
 }
 
 StringMap<Shader::ShaderType, Shader::TYPE_MAX_ENUM>::Entry Shader::typeNameEntries[] =

+ 24 - 91
src/modules/graphics/opengl/VertexBuffer.cpp

@@ -44,16 +44,7 @@ namespace opengl
 
 VertexBuffer *VertexBuffer::Create(size_t size, GLenum target, GLenum usage, MemoryBacking backing)
 {
-	try
-	{
-		// Try to create a VBO.
-		return new VBO(size, target, usage, backing);
-	}
-	catch(const love::Exception &)
-	{
-		// VBO not supported ... create regular array.
-		return new VertexArray(size, target, usage, backing);
-	}
+	return new VertexBuffer(size, target, usage, backing);
 }
 
 VertexBuffer::VertexBuffer(size_t size, GLenum target, GLenum usage, MemoryBacking backing)
@@ -63,68 +54,10 @@ VertexBuffer::VertexBuffer(size_t size, GLenum target, GLenum usage, MemoryBacki
 	, target(target)
 	, usage(usage)
 	, backing(backing)
-{
-}
-
-VertexBuffer::~VertexBuffer()
-{
-}
-
-// VertexArray
-
-VertexArray::VertexArray(size_t size, GLenum target, GLenum usage, MemoryBacking backing)
-	: VertexBuffer(size, target, usage, backing)
-	, buf(new char[size])
-{
-}
-
-VertexArray::~VertexArray()
-{
-	delete [] buf;
-}
-
-void *VertexArray::map()
-{
-	is_mapped = true;
-	return buf;
-}
-
-void VertexArray::unmap()
-{
-	is_mapped = false;
-}
-
-void VertexArray::bind()
-{
-	is_bound = true;
-}
-
-void VertexArray::unbind()
-{
-	is_bound = false;
-}
-
-void VertexArray::fill(size_t offset, size_t size, const void *data)
-{
-	memcpy(buf + offset, data, size);
-}
-
-const void *VertexArray::getPointer(size_t offset) const
-{
-	return buf + offset;
-}
-
-// VBO
-
-VBO::VBO(size_t size, GLenum target, GLenum usage, MemoryBacking backing)
-	: VertexBuffer(size, target, usage, backing)
 	, vbo(0)
 	, memory_map(0)
 	, is_dirty(true)
 {
-	if (!(GLEE_ARB_vertex_buffer_object || GLEE_VERSION_1_5))
-		throw love::Exception("Not supported");
-
 	if (getMemoryBacking() == BACKING_FULL)
 		memory_map = malloc(getSize());
 
@@ -137,7 +70,7 @@ VBO::VBO(size_t size, GLenum target, GLenum usage, MemoryBacking backing)
 	}
 }
 
-VBO::~VBO()
+VertexBuffer::~VertexBuffer()
 {
 	if (vbo != 0)
 		unload(false);
@@ -146,7 +79,7 @@ VBO::~VBO()
 		free(memory_map);
 }
 
-void *VBO::map()
+void *VertexBuffer::map()
 {
 	if (is_mapped)
 		return memory_map;
@@ -160,7 +93,7 @@ void *VBO::map()
 
 	if (is_dirty)
 	{
-		glGetBufferSubDataARB(getTarget(), 0, (GLsizeiptr) getSize(), memory_map);
+		glGetBufferSubData(getTarget(), 0, (GLsizeiptr) getSize(), memory_map);
 		is_dirty = false;
 	}
 
@@ -169,7 +102,7 @@ void *VBO::map()
 	return memory_map;
 }
 
-void VBO::unmap()
+void VertexBuffer::unmap()
 {
 	if (!is_mapped)
 		return;
@@ -178,36 +111,36 @@ void VBO::unmap()
 	// bound here.
 	if (!is_bound)
 	{
-		glBindBufferARB(getTarget(), vbo);
+		glBindBuffer(getTarget(), vbo);
 		is_bound = true;
 	}
 
 	// "orphan" current buffer to avoid implicit synchronisation on the GPU:
 	// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
-	glBufferDataARB(getTarget(), (GLsizeiptr) getSize(), NULL,       getUsage());
-	glBufferDataARB(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage());
+	glBufferData(getTarget(), (GLsizeiptr) getSize(), NULL,       getUsage());
+	glBufferData(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage());
 
 	is_mapped = false;
 }
 
-void VBO::bind()
+void VertexBuffer::bind()
 {
 	if (!is_mapped)
 	{
-		glBindBufferARB(getTarget(), vbo);
+		glBindBuffer(getTarget(), vbo);
 		is_bound = true;
 	}
 }
 
-void VBO::unbind()
+void VertexBuffer::unbind()
 {
 	if (is_bound)
-		glBindBufferARB(getTarget(), 0);
+		glBindBuffer(getTarget(), 0);
 
 	is_bound = false;
 }
 
-void VBO::fill(size_t offset, size_t size, const void *data)
+void VertexBuffer::fill(size_t offset, size_t size, const void *data)
 {
 	if (is_mapped || getMemoryBacking() == BACKING_FULL)
 		memcpy(static_cast<char *>(memory_map) + offset, data, size);
@@ -217,7 +150,7 @@ void VBO::fill(size_t offset, size_t size, const void *data)
 		// Not all systems have access to some faster paths...
 		if (GLEE_APPLE_flush_buffer_range)
 		{
-			void *mapdata = glMapBufferARB(getTarget(), GL_WRITE_ONLY);
+			void *mapdata = glMapBuffer(getTarget(), GL_WRITE_ONLY);
 
 			if (mapdata)
 			{
@@ -228,12 +161,12 @@ void VBO::fill(size_t offset, size_t size, const void *data)
 				glFlushMappedBufferRangeAPPLE(getTarget(), (GLintptr) offset, (GLsizei) size);
 			}
 
-			glUnmapBufferARB(getTarget());
+			glUnmapBuffer(getTarget());
 		}
 		else
 		{
 			// Fall back to a possibly slower SubData (more chance of syncing.)
-			glBufferSubDataARB(getTarget(), (GLintptr) offset, (GLsizeiptr) size, data);
+			glBufferSubData(getTarget(), (GLintptr) offset, (GLsizeiptr) size, data);
 		}
 
 		if (getMemoryBacking() != BACKING_FULL)
@@ -241,24 +174,24 @@ void VBO::fill(size_t offset, size_t size, const void *data)
 	}
 }
 
-const void *VBO::getPointer(size_t offset) const
+const void *VertexBuffer::getPointer(size_t offset) const
 {
 	return BUFFER_OFFSET(offset);
 }
 
-bool VBO::loadVolatile()
+bool VertexBuffer::loadVolatile()
 {
 	return load(true);
 }
 
-void VBO::unloadVolatile()
+void VertexBuffer::unloadVolatile()
 {
 	unload(true);
 }
 
-bool VBO::load(bool restore)
+bool VertexBuffer::load(bool restore)
 {
-	glGenBuffersARB(1, &vbo);
+	glGenBuffers(1, &vbo);
 
 	VertexBuffer::Bind bind(*this);
 
@@ -275,13 +208,13 @@ bool VBO::load(bool restore)
 		glBufferParameteriAPPLE(getTarget(), GL_BUFFER_FLUSHING_UNMAP_APPLE, GL_FALSE);
 
 	// Note that if 'src' is '0', no data will be copied.
-	glBufferDataARB(getTarget(), (GLsizeiptr) getSize(), src, getUsage());
+	glBufferData(getTarget(), (GLsizeiptr) getSize(), src, getUsage());
 	GLenum err = glGetError();
 
 	return (GL_NO_ERROR == err);
 }
 
-void VBO::unload(bool save)
+void VertexBuffer::unload(bool save)
 {
 	// Save data before unloading, if we need to.
 	if (save && getMemoryBacking() == BACKING_PARTIAL)
@@ -294,7 +227,7 @@ void VBO::unload(bool save)
 		is_mapped = mapped;
 	}
 
-	glDeleteBuffersARB(1, &vbo);
+	glDeleteBuffers(1, &vbo);
 	vbo = 0;
 }
 

+ 41 - 108
src/modules/graphics/opengl/VertexBuffer.h

@@ -36,15 +36,12 @@ namespace opengl
 {
 
 /**
- * VertexBuffer is an abstraction over VBOs (Vertex Buffer Objects), which
- * falls back to regular vertex arrays if VBOs are not supported.
- *
- * This allows code to take advantage of VBOs where available, but still
- * work on older systems where it's *not* available. Everyone's happy.
+ * VertexBuffer is a thin abstraction over VBOs (Vertex Buffer Objects) and
+ * other OpenGL Buffer Objects.
  *
  * The class is (for now) meant for internal use.
  */
-class VertexBuffer
+class VertexBuffer : public Volatile
 {
 public:
 
@@ -61,11 +58,7 @@ public:
 	};
 
 	/**
-	 * Create a new VertexBuffer (either a plain vertex array, or a VBO),
-	 * based on what's supported on the system.
-	 *
-	 * If VBOs are not supported, a plain vertex array will automatically
-	 * be created and returned instead.
+	 * Create a new VertexBuffer.
 	 *
 	 * @param size The size of the VertexBuffer (in bytes).
 	 * @param target GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER.
@@ -86,7 +79,7 @@ public:
 	VertexBuffer(size_t size, GLenum target, GLenum usage, MemoryBacking backing = BACKING_PARTIAL);
 
 	/**
-	 * Destructor. Does nothing, but must be declared virtual.
+	 * Destructor.
 	 */
 	virtual ~VertexBuffer();
 
@@ -145,7 +138,7 @@ public:
 	 *
 	 * @return A pointer to memory which represents the buffer.
 	 */
-	virtual void *map() = 0;
+	virtual void *map();
 
 	/**
 	 * Unmap a previously mapped VertexBuffer. The buffer must be unmapped
@@ -153,18 +146,18 @@ public:
 	 *
 	 * The VertexBuffer must be bound to use this function.
 	 */
-	virtual void unmap() = 0;
+	virtual void unmap();
 
 	/**
 	 * Bind the VertexBuffer to its specified target.
 	 * (GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, etc).
 	 */
-	virtual void bind() = 0;
+	virtual void bind();
 
 	/**
 	 * Unbind a prevously bound VertexBuffer.
 	 */
-	virtual void unbind() = 0;
+	virtual void unbind();
 
 	/**
 	 * Fill a portion of the buffer with data.
@@ -175,7 +168,7 @@ public:
 	 * @param size The size of the incoming data.
 	 * @param data Pointer to memory to copy data from.
 	 */
-	virtual void fill(size_t offset, size_t size, const void *data) = 0;
+	virtual void fill(size_t offset, size_t size, const void *data);
 
 	/**
 	 * Get a pointer which represents the specified byte offset.
@@ -183,7 +176,11 @@ public:
 	 * @param offset The byte offset. (0 is first byte).
 	 * @return A pointer which represents the offset.
 	 */
-	virtual const void *getPointer(size_t offset) const = 0;
+	virtual const void *getPointer(size_t offset) const;
+
+	// Implements Volatile.
+	virtual bool loadVolatile();
+	virtual void unloadVolatile();
 
 	/**
 	 * This helper class can bind a VertexArray temporarily, and
@@ -214,11 +211,13 @@ public:
 
 		// VertexBuffer to work on.
 		VertexBuffer &buf;
-	};
+
+	}; // Bind
 
 	class Mapper
 	{
 	public:
+
 		/**
 		 * Memory-maps a VertexBuffer.
 		 */
@@ -245,11 +244,29 @@ public:
 		}
 
 	private:
+
 		VertexBuffer &buf;
 		void *elems;
-	};
 
-protected:
+	}; // Mapper
+
+private:
+
+	/**
+	 * Creates the VBO, and optionally restores data we saved earlier.
+	 *
+	 * @param restore True to restore data previously saved with 'unload'.
+	 * @return True on success, false otherwise.
+	 */
+	bool load(bool restore);
+
+	/**
+	 * Optionally save the data in the VBO, then delete it.
+	 *
+	 * @param save True to save the data before deleting.
+	 */
+	void unload(bool save);
+
 
 	// Whether the buffer is currently bound.
 	bool is_bound;
@@ -257,8 +274,6 @@ protected:
 	// Whether the buffer is currently mapped to main memory.
 	bool is_mapped;
 
-private:
-
 	// The size of the buffer, in bytes.
 	size_t size;
 
@@ -270,90 +285,6 @@ private:
 
 	//
 	MemoryBacking backing;
-};
-
-/**
- * Implementation of VertexBuffer which uses plain arrays to store the data.
- *
- * This implementation should be supported everywhere, and acts as a fallback
- * on systems which do not support VBOs.
- */
-class VertexArray : public VertexBuffer
-{
-public:
-
-	/**
-	 * @copydoc VertexBuffer(int, GLenum, GLenum, Backing)
-	 */
-	VertexArray(size_t size, GLenum target, GLenum usage, MemoryBacking backing);
-
-	/**
-	 * Frees the data we've allocated.
-	 */
-	virtual ~VertexArray();
-
-	// Implements VertexBuffer.
-	virtual void *map();
-	virtual void unmap();
-	virtual void bind();
-	virtual void unbind();
-	virtual void fill(size_t offset, size_t size, const void *data);
-	virtual const void *getPointer(size_t offset) const ;
-
-private:
-	// Holds the data.
-	char *buf;
-};
-
-/**
- * Vertex Buffer Object (VBO) implementation of VertexBuffer.
- *
- * This will be used on all systems that support it. It's in general
- * faster than vertex arrays, but especially in use-cases where there
- * is no need to update the data every frame.
- **/
-class VBO : public VertexBuffer, public Volatile
-{
-public:
-
-	/**
-	 * @copydoc VertexBuffer(size_t, GLenum, GLenum, Backing)
-	 **/
-	VBO(size_t size, GLenum target, GLenum usage, MemoryBacking backing);
-
-	/**
-	 * Deletes the VBOs from OpenGL.
-	 **/
-	virtual ~VBO();
-
-	// Implements VertexBuffer.
-	virtual void *map();
-	virtual void unmap();
-	virtual void bind();
-	virtual void unbind();
-	virtual void fill(size_t offset, size_t size, const void *data);
-	virtual const void *getPointer(size_t offset) const ;
-
-	// Implements Volatile.
-	bool loadVolatile();
-	void unloadVolatile();
-
-private:
-
-	/**
-	 * Creates the VBO, and optionally restores data we saved earlier.
-	 *
-	 * @param restore True to restore data previously saved with 'unload'.
-	 * @return True on success, false otherwise.
-	 */
-	bool load(bool restore);
-
-	/**
-	 * Optionally save the data in the VBO, then delete it.
-	 *
-	 * @param save True to save the data before deleting.
-	 */
-	void unload(bool save);
 
 	// The VBO identifier. Assigned by OpenGL.
 	GLuint vbo;
@@ -365,7 +296,9 @@ private:
 	// Set if the buffer was modified while operating on gpu memory
 	// and needs to be synchronized.
 	bool is_dirty;
-};
+
+}; // VertexBuffer
+
 
 /**
  * VertexIndex manages one shared VertexBuffer that stores the indices for an

+ 0 - 23
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -344,9 +344,6 @@ int w_newCanvas(lua_State *L)
 
 int w_newShader(lua_State *L)
 {
-	if (!Shader::isSupported())
-		return luaL_error(L, "Sorry, your graphics card does not support shaders.");
-
 	// clamp stack to 2 elements
 	lua_settop(L, 2);
 
@@ -946,10 +943,6 @@ int w_isSupported(lua_State *L)
 
 		switch (support)
 		{
-		case Graphics::SUPPORT_CANVAS:
-			if (!Canvas::isSupported())
-				supported = false;
-			break;
 		case Graphics::SUPPORT_HDR_CANVAS:
 			if (!Canvas::isHDRSupported())
 				supported = false;
@@ -958,22 +951,6 @@ int w_isSupported(lua_State *L)
 			if (!Canvas::isMultiCanvasSupported())
 				supported = false;
 			break;
-		case Graphics::SUPPORT_SHADER:
-			if (!Shader::isSupported())
-				supported = false;
-			break;
-		case Graphics::SUPPORT_NPOT:
-			if (!Image::hasNpot())
-				supported = false;
-			break;
-		case Graphics::SUPPORT_SUBTRACTIVE:
-			if (!((GLEE_VERSION_1_4 || GLEE_ARB_imaging) || (GLEE_EXT_blend_minmax && GLEE_EXT_blend_subtract)))
-				supported = false;
-			break;
-		case Graphics::SUPPORT_MIPMAP:
-			if (!Image::hasMipmapSupport())
-				supported = false;
-			break;
 		case Graphics::SUPPORT_DXT:
 			if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_DXT5))
 				supported = false;

+ 19 - 0
src/modules/window/Window.cpp

@@ -74,6 +74,16 @@ bool Window::getConstant(Window::Setting in, const char *&out)
 	return settings.find(in, out);
 }
 
+bool Window::getConstant(const char *in, MessageBoxType &out)
+{
+	return messageBoxTypes.find(in, out);
+}
+
+bool Window::getConstant(MessageBoxType in, const char *&out)
+{
+	return messageBoxTypes.find(in, out);
+}
+
 StringMap<Window::Setting, Window::SETTING_MAX_ENUM>::Entry Window::settingEntries[] =
 {
 	{"fullscreen", SETTING_FULLSCREEN},
@@ -100,5 +110,14 @@ StringMap<Window::FullscreenType, Window::FULLSCREEN_TYPE_MAX_ENUM>::Entry Windo
 
 StringMap<Window::FullscreenType, Window::FULLSCREEN_TYPE_MAX_ENUM> Window::fullscreenTypes(Window::fullscreenTypeEntries, sizeof(Window::fullscreenTypeEntries));
 
+StringMap<MessageBoxType, MESSAGEBOX_MAX_ENUM>::Entry Window::messageBoxTypeEntries[] =
+{
+	{"error", MESSAGEBOX_ERROR},
+	{"warning", MESSAGEBOX_WARNING},
+	{"info", MESSAGEBOX_INFO},
+};
+
+StringMap<MessageBoxType, MESSAGEBOX_MAX_ENUM> Window::messageBoxTypes(Window::messageBoxTypeEntries, sizeof(Window::messageBoxTypeEntries));
+
 } // window
 } // love

+ 16 - 0
src/modules/window/Window.h

@@ -39,6 +39,14 @@ namespace window
 // whole thing here because it uses the Window::Type enum.
 struct WindowSettings;
 
+enum MessageBoxType
+{
+	MESSAGEBOX_ERROR,
+	MESSAGEBOX_WARNING,
+	MESSAGEBOX_INFO,
+	MESSAGEBOX_MAX_ENUM
+};
+
 class Window : public Module
 {
 public:
@@ -119,6 +127,8 @@ public:
 
 	virtual const void *getHandle() const = 0;
 
+	virtual void showMessageBox(MessageBoxType type, const char *title, const char *message) = 0;
+
 	//virtual static Window *createSingleton() = 0;
 	//virtual static Window *getSingleton() = 0;
 	// No virtual statics, of course, but you are supposed to implement these statics.
@@ -129,6 +139,9 @@ public:
 	static bool getConstant(const char *in, FullscreenType &out);
 	static bool getConstant(FullscreenType in, const char *&out);
 
+	static bool getConstant(const char *in, MessageBoxType &out);
+	static bool getConstant(MessageBoxType in, const char *&out);
+
 protected:
 
 	static Window *singleton;
@@ -141,6 +154,9 @@ private:
 	static StringMap<FullscreenType, FULLSCREEN_TYPE_MAX_ENUM>::Entry fullscreenTypeEntries[];
 	static StringMap<FullscreenType, FULLSCREEN_TYPE_MAX_ENUM> fullscreenTypes;
 
+	static StringMap<MessageBoxType, MESSAGEBOX_MAX_ENUM>::Entry messageBoxTypeEntries[];
+	static StringMap<MessageBoxType, MESSAGEBOX_MAX_ENUM> messageBoxTypes;
+
 }; // Window
 
 struct WindowSettings

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

@@ -654,6 +654,27 @@ const void *Window::getHandle() const
 	return window;
 }
 
+void Window::showMessageBox(MessageBoxType type, const char *title, const char *message)
+{
+	SDL_MessageBoxFlags sdlflags;
+
+	switch (type)
+	{
+	case MESSAGEBOX_ERROR:
+		sdlflags = SDL_MESSAGEBOX_ERROR;
+		break;
+	case MESSAGEBOX_WARNING:
+		sdlflags = SDL_MESSAGEBOX_WARNING;
+		break;
+	case MESSAGEBOX_INFO:
+	default:
+		sdlflags = SDL_MESSAGEBOX_INFORMATION;
+		break;
+	}
+
+	SDL_ShowSimpleMessageBox(sdlflags, title, message, window);
+}
+
 love::window::Window *Window::createSingleton()
 {
 	if (!singleton)

+ 2 - 0
src/modules/window/sdl/Window.h

@@ -83,6 +83,8 @@ public:
 
 	const void *getHandle() const;
 
+	void showMessageBox(MessageBoxType type, const char *title, const char *message);
+
 	static love::window::Window *createSingleton();
 	static love::window::Window *getSingleton();
 

+ 2 - 14
src/scripts/boot.lua

@@ -1331,13 +1331,7 @@ function love.nogame()
 	local create_rain
 
 	function love.load()
-		-- Subtractive blending isn't supported on some ancient systems, so
-		-- we should make sure it still looks decent in that case.
-		if love.graphics.isSupported("subtractive") then
-			love.graphics.setBackgroundColor(137, 194, 218)
-		else
-			love.graphics.setBackgroundColor(11, 88, 123)
-		end
+		love.graphics.setBackgroundColor(137, 194, 218)
 
 		local win_w = love.graphics.getWidth()
 		local win_h = love.graphics.getHeight()
@@ -1429,18 +1423,12 @@ function love.nogame()
 	end
 
 	local function draw_grid()
-		local blendmode = "subtractive"
-		if not love.graphics.isSupported("subtractive") then
-			-- We also change the background color in this case, so it looks OK.
-			blendmode = "additive"
-		end
-
 		local y = rain.spacing_y * rain.t
 
 		local small_y = -rain.spacing_y + y / 2
 		local big_y = -rain.spacing_y + y
 
-		love.graphics.setBlendMode(blendmode)
+		love.graphics.setBlendMode("subtractive")
 		love.graphics.setColor(255, 255, 255, 128)
 		love.graphics.draw(rain.batch, -rain.spacing_x, small_y, 0, 0.5, 0.5)
 

+ 5 - 33
src/scripts/boot.lua.h

@@ -4850,25 +4850,9 @@ const unsigned char boot_lua[] =
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x61, 0x69, 0x6e, 0x0a,
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6c, 0x6f, 0x61, 
 	0x64, 0x28, 0x29, 0x0a,
-	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x53, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x20, 0x62, 
-	0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x73, 0x6e, 0x27, 0x74, 0x20, 0x73, 0x75, 0x70, 0x70, 
-	0x6f, 0x72, 0x74, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x61, 0x6e, 0x63, 0x69, 
-	0x65, 0x6e, 0x74, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x2c, 0x20, 0x73, 0x6f, 0x0a,
-	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x77, 0x65, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6d, 0x61, 0x6b, 
-	0x65, 0x20, 0x73, 0x75, 0x72, 0x65, 0x20, 0x69, 0x74, 0x20, 0x73, 0x74, 0x69, 0x6c, 0x6c, 0x20, 0x6c, 0x6f, 
-	0x6f, 0x6b, 0x73, 0x20, 0x64, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x61, 0x74, 
-	0x20, 0x63, 0x61, 0x73, 0x65, 0x2e, 0x0a,
-	0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 
-	0x2e, 0x69, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x28, 0x22, 0x73, 0x75, 0x62, 0x74, 
-	0x72, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
-	0x09, 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, 
-	0x31, 0x33, 0x37, 0x2c, 0x20, 0x31, 0x39, 0x34, 0x2c, 0x20, 0x32, 0x31, 0x38, 0x29, 0x0a,
-	0x09, 0x09, 0x65, 0x6c, 0x73, 0x65, 0x0a,
-	0x09, 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, 
-	0x31, 0x31, 0x2c, 0x20, 0x38, 0x38, 0x2c, 0x20, 0x31, 0x32, 0x33, 0x29, 0x0a,
-	0x09, 0x09, 0x65, 0x6e, 0x64, 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, 0x31, 
+	0x33, 0x37, 0x2c, 0x20, 0x31, 0x39, 0x34, 0x2c, 0x20, 0x32, 0x31, 0x38, 0x29, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x77, 0x69, 0x6e, 0x5f, 0x77, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 
 	0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x57, 0x69, 0x64, 
 	0x74, 0x68, 0x28, 0x29, 0x0a,
@@ -5024,18 +5008,6 @@ const unsigned char boot_lua[] =
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x72, 
 	0x61, 0x77, 0x5f, 0x67, 0x72, 0x69, 0x64, 0x28, 0x29, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x62, 0x6c, 0x65, 0x6e, 0x64, 0x6d, 0x6f, 0x64, 0x65, 0x20, 
-	0x3d, 0x20, 0x22, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x0a,
-	0x09, 0x09, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 
-	0x68, 0x69, 0x63, 0x73, 0x2e, 0x69, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x28, 0x22, 
-	0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
-	0x09, 0x09, 0x09, 0x2d, 0x2d, 0x20, 0x57, 0x65, 0x20, 0x61, 0x6c, 0x73, 0x6f, 0x20, 0x63, 0x68, 0x61, 0x6e, 
-	0x67, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x20, 
-	0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x63, 0x61, 0x73, 0x65, 
-	0x2c, 0x20, 0x73, 0x6f, 0x20, 0x69, 0x74, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x73, 0x20, 0x4f, 0x4b, 0x2e, 0x0a,
-	0x09, 0x09, 0x09, 0x62, 0x6c, 0x65, 0x6e, 0x64, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x61, 0x64, 
-	0x64, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0x0a,
-	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x79, 0x20, 0x3d, 0x20, 0x72, 0x61, 0x69, 0x6e, 0x2e, 0x73, 
 	0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x79, 0x20, 0x2a, 0x20, 0x72, 0x61, 0x69, 0x6e, 0x2e, 0x74, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x5f, 0x79, 0x20, 0x3d, 0x20, 
@@ -5044,8 +5016,8 @@ const unsigned char boot_lua[] =
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x62, 0x69, 0x67, 0x5f, 0x79, 0x20, 0x3d, 0x20, 0x2d, 0x72, 
 	0x61, 0x69, 0x6e, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x79, 0x20, 0x2b, 0x20, 0x79, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 
-	0x74, 0x42, 0x6c, 0x65, 0x6e, 0x64, 0x4d, 0x6f, 0x64, 0x65, 0x28, 0x62, 0x6c, 0x65, 0x6e, 0x64, 0x6d, 0x6f, 
-	0x64, 0x65, 0x29, 0x0a,
+	0x74, 0x42, 0x6c, 0x65, 0x6e, 0x64, 0x4d, 0x6f, 0x64, 0x65, 0x28, 0x22, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 
+	0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x29, 0x0a,
 	0x09, 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, 0x31, 0x32, 0x38, 0x29, 0x0a,