diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index e9f04d320..5818729fd 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/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; diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index dfe494824..2dac246cc 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -487,8 +487,6 @@ private: static const size_t MAX_USER_STACK_DEPTH = 64; - bool displayedMinReqWarning; - }; // Graphics } // opengl diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index e8f3fe96a..c14e5b6c3 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/src/modules/window/sdl/Window.cpp @@ -28,6 +28,9 @@ #include #include +// C +#include + 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); - - window = SDL_CreateWindow(title, x, y, width, height, sdlflags); 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); } // Make sure the window keeps any previously set icon. @@ -244,71 +253,7 @@ bool Window::onWindowResize(int width, int height) 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. - if (context) - { - SDL_GL_DeleteContext(context); - context = 0; - } - - // Make sure the proper attributes are set. - setWindowGLAttributes(msaa, sRGB); - - context = SDL_GL_CreateContext(window); - - if (!context && 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); - context = SDL_GL_CreateContext(window); - } - - if (!context) - { - int flags = 0; - SDL_GL_GetAttribute(SDL_GL_CONTEXT_FLAGS, &flags); - if (flags & SDL_GL_CONTEXT_DEBUG_FLAG) - { - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0); - context = SDL_GL_CreateContext(window); - } - } - - if (!context) - { - std::cerr << "Could not set video mode: " << SDL_GetError() << std::endl; - return false; - } - - // Set vertical synchronization. - SDL_GL_SetSwapInterval(vsync ? 1 : 0); - - // Verify MSAA setting. - int buffers; - int samples; - SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &buffers); - SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &samples); - - // Don't fail because of this, but issue a warning. - if ((!buffers && msaa) || (samples != msaa)) - { - std::cerr << "Warning, MSAA setting failed! (Result: buffers: " << buffers << ", samples: " << samples << ")" << std::endl; - msaa = (buffers > 0) ? samples : 0; - } - - curMode.settings.msaa = msaa; - curMode.settings.vsync = SDL_GL_GetSwapInterval() != 0; - - return true; -} - -void Window::setWindowGLAttributes(int msaa, bool /* sRGB */) const +void Window::setGLFramebufferAttributes(int msaa, bool sRGB) { // Set GL window attributes. SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); @@ -323,27 +268,194 @@ void Window::setWindowGLAttributes(int msaa, bool /* sRGB */) const 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 - */ +} + +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 apparently MakeCurrent + // can sometimes claim success but the context will actually be trashed. + if (context) + { + SDL_GL_DeleteContext(context); + context = nullptr; + } + + bool preferGLES = false; + +#ifdef LOVE_GRAPHICS_USE_OPENGLES + preferGLES = true; +#endif + + const char *curdriver = SDL_GetCurrentVideoDriver(); + const char *glesdrivers[] = {"RPI", "Android", "uikit", "winrt"}; + + // We always want to try OpenGL ES first on certain video backends. + for (const char *glesdriver : glesdrivers) + { + if (curdriver && strstr(curdriver, glesdriver) == curdriver) + { + preferGLES = true; + break; + } + } + + 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 *debugenv = SDL_GetHint("LOVE_GRAPHICS_DEBUG"); - if (debugenv && *debugenv == '1') + const char *debughint = SDL_GetHint("LOVE_GRAPHICS_DEBUG"); + bool debug = (debughint != nullptr && debughint[0] != '0'); + + // Different context attribute profiles to try. + std::vector 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) { - 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); + 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) + { + // 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; } - else + + if (!context) { - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0); + 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; } + + // Set vertical synchronization. + SDL_GL_SetSwapInterval(vsync ? 1 : 0); + + // Verify MSAA setting. + int buffers; + int samples; + SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &buffers); + SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &samples); + + // Don't fail because of this, but issue a warning. + if ((!buffers && msaa > 0) || (samples != msaa)) + { + std::cerr << "Warning, MSAA setting failed! (Result: " << samples << "x MSAA)" << std::endl; + msaa = (buffers > 0) ? samples : 0; + } + + curMode.settings.msaa = msaa; + curMode.settings.vsync = SDL_GL_GetSwapInterval() != 0; + + return true; } void Window::updateSettings(const WindowSettings &newsettings) diff --git a/src/modules/window/sdl/Window.h b/src/modules/window/sdl/Window.h index 39f2b44d4..6f61a249e 100644 --- a/src/modules/window/sdl/Window.h +++ b/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