Browse Source

Added support for creating OpenGL ES 2 and 3 contexts, and updated the OpenGL context creation code to be more robust in general.

If the LOVE_GRAPHICS_USE_OPENGLES environment variable is set, then love will try to create an OpenGL ES context before falling back to regular OpenGL. If it's not set (or set to 0), then the opposite happens except when love is run on backends that should always use OpenGL ES.

--HG--
branch : minor
Alex Szpakowski 10 years ago
parent
commit
15538426d7

+ 1 - 24
src/modules/graphics/opengl/Graphics.cpp

@@ -49,7 +49,6 @@ Graphics::Graphics()
 	, height(0)
 	, created(false)
 	, writingToStencil(false)
-	, displayedMinReqWarning(false)
 {
 	states.reserve(10);
 	states.push_back(DisplayState());
@@ -207,30 +206,8 @@ bool Graphics::setMode(int width, int height, bool &sRGB)
 	this->width = width;
 	this->height = height;
 
-	gl.initContext();
-
-	// Does the system meet LOVE's minimum requirements for graphics?
-	if (!(GLAD_ES_VERSION_2_0 || (GLAD_VERSION_2_0 && Shader::isSupported() && Canvas::isSupported()))
-		&& !displayedMinReqWarning)
-	{
-		love::window::Window::MessageBoxType type = love::window::Window::MESSAGEBOX_ERROR;
-
-		std::string 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.c_str(), message.c_str());
-		currentWindow->showMessageBox(title, message, type, true);
-
-		// We should only show the message once, instead of after every setMode.
-		displayedMinReqWarning = true;
-	}
-
 	// Okay, setup OpenGL.
+	gl.initContext();
 	gl.setupContext();
 
 	created = true;

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

@@ -487,8 +487,6 @@ private:
 
 	static const size_t MAX_USER_STACK_DEPTH = 64;
 
-	bool displayedMinReqWarning;
-
 }; // Graphics
 
 } // opengl

+ 178 - 66
src/modules/window/sdl/Window.cpp

@@ -28,6 +28,9 @@
 #include <vector>
 #include <algorithm>
 
+// C
+#include <cstdio>
+
 namespace love
 {
 namespace window
@@ -40,7 +43,8 @@ Window::Window()
 	, created(false)
 	, mouseGrabbed(false)
 	, window(0)
-	, context(0)
+	, context(nullptr)
+	, displayedContextError(false)
 {
 	if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
 		throw love::Exception("%s", SDL_GetError());
@@ -156,10 +160,10 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 		wflags &= testflags;
 
 		if (sdlflags != wflags || width != curMode.width || height != curMode.height
-			|| f.display != curdisplay || f.msaa != curMode.settings.msaa)
+			|| f.display != curdisplay || f.msaa != curMode.settings.msaa || f.sRGB != curMode.settings.sRGB)
 		{
 			SDL_DestroyWindow(window);
-			window = 0;
+			window = nullptr;
 
 			// The old window may have generated pending events which are no
 			// longer relevant. Destroy them all!
@@ -172,20 +176,25 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 		created = false;
 
 		// In Windows and Linux, some GL attributes are set on window creation.
-		setWindowGLAttributes(f.msaa, f.sRGB);
+		setGLFramebufferAttributes(f.msaa, f.sRGB);
 
 		const char *title = windowTitle.c_str();
-
 		window = SDL_CreateWindow(title, x, y, width, height, sdlflags);
 
 		if (!window && f.msaa > 0)
 		{
 			// MSAA might have caused the failure, disable it and try again.
-			SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
-			SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
+			f.msaa = 0;
+			setGLFramebufferAttributes(f.msaa, f.sRGB);
+			window = SDL_CreateWindow(title, x, y, width, height, sdlflags);
+		}
 
+		if (!window && f.sRGB)
+		{
+			// Same with sRGB support.
+			f.sRGB = false;
+			setGLFramebufferAttributes(f.msaa, f.sRGB);
 			window = SDL_CreateWindow(title, x, y, width, height, sdlflags);
-			f.msaa = 0;
 		}
 
 		// Make sure the window keeps any previously set icon.
@@ -244,45 +253,186 @@ bool Window::onWindowResize(int width, int height)
 	return true;
 }
 
+void Window::setGLFramebufferAttributes(int msaa, bool sRGB)
+{
+	// Set GL window attributes.
+	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
+	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
+	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
+	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
+	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
+	SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0);
+
+	// MSAA.
+	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (msaa > 0) ? 1 : 0);
+	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, (msaa > 0) ? msaa : 0);
+
+#if SDL_VERSION_ATLEAST(2,0,1)
+	SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, sRGB ? 1 : 0);
+#endif
+}
+
+void Window::setGLContextAttributes(const ContextAttribs &attribs)
+{
+	int profilemask = 0;
+	int contextflags = 0;
+
+	if (attribs.gles)
+		profilemask |= SDL_GL_CONTEXT_PROFILE_ES;
+	else if (attribs.debug)
+		profilemask |= SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
+
+	if (attribs.debug)
+		contextflags |= SDL_GL_CONTEXT_DEBUG_FLAG;
+
+	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, attribs.versionMajor);
+	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, attribs.versionMinor);
+	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profilemask);
+	SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, contextflags);
+}
+
+bool Window::checkGLVersion(int versionmajor, int versionminor)
+{
+	typedef unsigned char GLubyte;
+	typedef unsigned int GLenum;
+	typedef const GLubyte *(*glGetStringPtr)(GLenum name);
+	const GLenum GL_VERSION_ENUM = 0x1F02;
+
+	// We don't have OpenGL headers or an automatic OpenGL function loader in
+	// this module, so we have to get the glGetString function pointer ourselves.
+	glGetStringPtr glGetStringFunc = (glGetStringPtr) SDL_GL_GetProcAddress("glGetString");
+	if (!glGetStringFunc)
+		return false;
+
+	const char *glversion = (const char *) glGetStringFunc(GL_VERSION_ENUM);
+	if (!glversion)
+		return false;
+
+	int glmajor = 0;
+	int glminor = 0;
+
+	// glGetString(GL_VERSION) returns a string with the format "major.minor",
+	// or "OpenGL ES major.minor" in GLES contexts.
+	if (sscanf(glversion, "%d.%d", &glmajor, &glminor) != 2)
+		return false;
+
+	if (glmajor < versionmajor || (glmajor == versionmajor && glminor < versionminor))
+		return false;
+
+	return true;
+}
+
 bool Window::setContext(int msaa, bool vsync, bool sRGB)
 {
 	// We would normally only need to recreate the context if MSAA changes or
-	// SDL_GL_MakeCurrent is unsuccessful, but in Windows MakeCurrent can
-	// sometimes claim success but the context will actually be trashed.
+	// SDL_GL_MakeCurrent is unsuccessful, but in Windows apparently MakeCurrent
+	// can sometimes claim success but the context will actually be trashed.
 	if (context)
 	{
 		SDL_GL_DeleteContext(context);
-		context = 0;
+		context = nullptr;
 	}
 
-	// Make sure the proper attributes are set.
-	setWindowGLAttributes(msaa, sRGB);
+	bool preferGLES = false;
 
-	context = SDL_GL_CreateContext(window);
+#ifdef LOVE_GRAPHICS_USE_OPENGLES
+	preferGLES = true;
+#endif
+
+	const char *curdriver = SDL_GetCurrentVideoDriver();
+	const char *glesdrivers[] = {"RPI", "Android", "uikit", "winrt"};
 
-	if (!context && msaa > 0)
+	// We always want to try OpenGL ES first on certain video backends.
+	for (const char *glesdriver : glesdrivers)
 	{
-		// MSAA might have caused the failure, disable it and try again.
-		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
-		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
-		context = SDL_GL_CreateContext(window);
+		if (curdriver && strstr(curdriver, glesdriver) == curdriver)
+		{
+			preferGLES = true;
+			break;
+		}
 	}
 
-	if (!context)
+	if (!preferGLES)
+	{
+		const char *gleshint = SDL_GetHint("LOVE_GRAPHICS_USE_OPENGLES");
+		preferGLES = (gleshint != nullptr && gleshint[0] != '0');
+	}
+
+	// Do we want a debug context?
+	const char *debughint = SDL_GetHint("LOVE_GRAPHICS_DEBUG");
+	bool debug = (debughint != nullptr && debughint[0] != '0');
+
+	// Different context attribute profiles to try.
+	std::vector<ContextAttribs> attribslist = {
+		{2, 1, false, debug}, // OpenGL 2.1.
+		{3, 0, true,  debug}, // OpenGL ES 3.
+		{2, 0, true,  debug}, // OpenGL ES 2.
+	};
+
+	// Move OpenGL ES to the front of the list if we should prefer GLES.
+	if (preferGLES)
+		std::rotate(attribslist.begin(), attribslist.begin() + 1, attribslist.end());
+
+	for (ContextAttribs attribs : attribslist)
 	{
-		int flags = 0;
-		SDL_GL_GetAttribute(SDL_GL_CONTEXT_FLAGS, &flags);
-		if (flags & SDL_GL_CONTEXT_DEBUG_FLAG)
+		setGLFramebufferAttributes(msaa, sRGB);
+		setGLContextAttributes(attribs);
+
+		context = SDL_GL_CreateContext(window);
+
+		if (!context && msaa > 0)
+		{
+			// MSAA might have caused the failure, disable it and try again.
+			setGLFramebufferAttributes(0, sRGB);
+			context = SDL_GL_CreateContext(window);
+		}
+
+		if (!context && sRGB)
+		{
+			// Or maybe sRGB isn't supported.
+			setGLFramebufferAttributes(msaa, false);
+			context = SDL_GL_CreateContext(window);
+		}
+
+		if (!context && msaa > 0 && sRGB)
 		{
-			SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
-			SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
+			// Or both are an issue!
+			setGLFramebufferAttributes(0, false);
 			context = SDL_GL_CreateContext(window);
 		}
+
+		if (!context && attribs.debug)
+		{
+			attribs.debug = false;
+			setGLContextAttributes(attribs);
+			context = SDL_GL_CreateContext(window);
+		}
+
+		// Make sure the context's version is at least what we requested.
+		if (context && !checkGLVersion(attribs.versionMajor, attribs.versionMinor))
+		{
+			SDL_GL_DeleteContext(context);
+			context = nullptr;
+		}
+
+		if (context)
+			break;
 	}
 
 	if (!context)
 	{
-		std::cerr << "Could not set video mode: " << SDL_GetError() << std::endl;
+		std::string title = "Unable to initialize OpenGL";
+		std::string message = "This program requires a graphics card and video drivers which support OpenGL 2.1 or OpenGL ES 2.";
+
+		// Display a message box with the error, but only once.
+		if (!displayedContextError)
+		{
+			showMessageBox(title, message, MESSAGEBOX_ERROR, true);
+			displayedContextError = true;
+		}
+
+		std::cerr << title << std::endl << message << std::endl;
 		return false;
 	}
 
@@ -296,9 +446,9 @@ bool Window::setContext(int msaa, bool vsync, bool sRGB)
 	SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &samples);
 
 	// Don't fail because of this, but issue a warning.
-	if ((!buffers && msaa) || (samples != msaa))
+	if ((!buffers && msaa > 0) || (samples != msaa))
 	{
-		std::cerr << "Warning, MSAA setting failed! (Result: buffers: " << buffers << ", samples: " << samples << ")" << std::endl;
+		std::cerr << "Warning, MSAA setting failed! (Result: " << samples << "x MSAA)" << std::endl;
 		msaa = (buffers > 0) ? samples : 0;
 	}
 
@@ -308,44 +458,6 @@ bool Window::setContext(int msaa, bool vsync, bool sRGB)
 	return true;
 }
 
-void Window::setWindowGLAttributes(int msaa, bool /* sRGB */) const
-{
-	// Set GL window attributes.
-	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
-	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
-	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
-	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
-	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
-	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
-	SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0);
-
-	// MSAA.
-	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (msaa > 0) ? 1 : 0);
-	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, (msaa > 0) ? msaa : 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
-	 */
-
-	// Do we want a debug context?
-	const char *debugenv = SDL_GetHint("LOVE_GRAPHICS_DEBUG");
-	if (debugenv && *debugenv == '1')
-	{
-		SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
-		SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
-	}
-	else
-	{
-		SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
-		SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
-	}
-}
-
 void Window::updateSettings(const WindowSettings &newsettings)
 {
 	Uint32 wflags = SDL_GetWindowFlags(window);

+ 13 - 1
src/modules/window/sdl/Window.h

@@ -103,8 +103,18 @@ public:
 
 private:
 
+	struct ContextAttribs
+	{
+		int versionMajor;
+		int versionMinor;
+		bool gles;
+		bool debug;
+	};
+
+	void setGLFramebufferAttributes(int msaa, bool sRGB);
+	void setGLContextAttributes(const ContextAttribs &attribs);
+	bool checkGLVersion(int versionmajor, int versionminor);
 	bool setContext(int msaa, bool vsync, bool sRGB);
-	void setWindowGLAttributes(int msaa, bool sRGB) const;
 
 	// Update the saved window settings based on the window's actual state.
 	void updateSettings(const WindowSettings &newsettings);
@@ -129,6 +139,8 @@ private:
 	SDL_Window *window;
 	SDL_GLContext context;
 
+	bool displayedContextError;
+
 }; // Window
 
 } // sdl