diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 835344e29..9572bded2 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -32,6 +32,7 @@ #include "Video.h" #include "Text.h" #include "common/deprecation.h" +#include "common/config.h" // C++ #include @@ -106,6 +107,36 @@ love::Type Graphics::type("graphics", &Module::type); Graphics::DefaultShaderCode Graphics::defaultShaderCode[Shader::STANDARD_MAX_ENUM][Shader::LANGUAGE_MAX_ENUM][2]; +namespace opengl { extern love::graphics::Graphics *createInstance(); } +#if defined(LOVE_MACOS) || defined(LOVE_IOS) +namespace metal { extern love::graphics::Graphics *createInstance(); } +#endif + +Graphics *Graphics::createInstance(const std::vector &renderers) +{ + Graphics *instance = Module::getInstance(M_GRAPHICS); + + if (instance != nullptr) + instance->retain(); + else + { + for (auto renderer : renderers) + { + if (renderer == RENDERER_OPENGL) + instance = opengl::createInstance(); +#if defined(LOVE_MACOS) || defined(LOVE_IOS) + if (renderer == RENDERER_METAL) + instance = metal::createInstance(); +#endif + + if (instance != nullptr) + break; + } + } + + return instance; +} + Graphics::Graphics() : width(0) , height(0) @@ -241,7 +272,7 @@ ShaderStage *Graphics::newShaderStage(ShaderStage::StageType stage, const std::s if (s == nullptr) { - s = newShaderStageInternal(stage, cachekey, source, getRenderer() == RENDERER_OPENGLES); + s = newShaderStageInternal(stage, cachekey, source, usesGLSLES()); if (!cachekey.empty()) cachedShaderStages[stage][cachekey] = s; } diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index e8f146cf1..a9aec2cd4 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -151,7 +151,7 @@ public: enum Renderer { RENDERER_OPENGL = 0, - RENDERER_OPENGLES, + RENDERER_METAL, RENDERER_MAX_ENUM }; @@ -797,6 +797,11 @@ public: **/ virtual Renderer getRenderer() const = 0; + /** + * Whether shaders will use GLSL ES or not (mobile shaders). + **/ + virtual bool usesGLSLES() const = 0; + /** * Returns system-dependent renderer information. * Returned strings can vary greatly between systems! Do not rely on it for @@ -853,6 +858,8 @@ public: return (T *) scratchBuffer.data(); } + static Graphics *createInstance(const std::vector &renderers); + static bool getConstant(const char *in, DrawMode &out); static bool getConstant(DrawMode in, const char *&out); static std::vector getConstants(DrawMode); diff --git a/src/modules/graphics/metal/Graphics.h b/src/modules/graphics/metal/Graphics.h index 44afdf03d..d7aa86f63 100644 --- a/src/modules/graphics/metal/Graphics.h +++ b/src/modules/graphics/metal/Graphics.h @@ -88,6 +88,7 @@ public: bool isCanvasFormatSupported(PixelFormat format, bool readable) const override; bool isImageFormatSupported(PixelFormat format, bool sRGB) const override; Renderer getRenderer() const override; + bool usesGLSLES() const override; RendererInfo getRendererInfo() const override; Shader::Language getShaderLanguageTarget() const override; diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index 06dde099a..7becbc481 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -133,6 +133,22 @@ static MTLBlendFactor getMTLBlendFactor(BlendFactor factor) return MTLBlendFactorZero; } +love::graphics::Graphics *createInstance() +{ + love::graphics::Graphics *instance = nullptr; + + try + { + instance = new Graphics(); + } + catch (love::Exception &e) + { + printf("Cannot create Metal renderer: %s\n", e.what()); + } + + return instance; +} + Graphics::Graphics() : device(nil) , commandQueue(nil) @@ -935,6 +951,30 @@ void Graphics::setBlendState(const BlendState &blend) } } +Graphics::Renderer Graphics::getRenderer() const +{ + return RENDERER_METAL; +} + +bool Graphics::usesGLSLES() const +{ +#ifdef LOVE_IOS + return true; +#else + return false; +#endif +} + +Graphics::RendererInfo Graphics::getRendererInfo() const +{ + RendererInfo info; + info.name = "Metal"; + info.version = "1"; // TODO + info.vendor = ""; // TODO + info.device = device.name.UTF8String; + return info; +} + } // metal } // graphics } // love diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index d636ed1eb..11278fdad 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -88,6 +88,22 @@ static GLenum getGLBlendFactor(BlendFactor factor) return 0; } +love::graphics::Graphics *createInstance() +{ + love::graphics::Graphics *instance = nullptr; + + try + { + instance = new Graphics(); + } + catch (love::Exception &e) + { + printf("Cannot create OpenGL renderer: %s\n", e.what()); + } + + return instance; +} + Graphics::Graphics() : windowHasStencil(false) , mainVAO(0) @@ -1322,7 +1338,12 @@ void Graphics::setWireframe(bool enable) Graphics::Renderer Graphics::getRenderer() const { - return GLAD_ES_VERSION_2_0 ? RENDERER_OPENGLES : RENDERER_OPENGL; + return RENDERER_OPENGL; +} + +bool Graphics::usesGLSLES() const +{ + return GLAD_ES_VERSION_2_0; } Graphics::RendererInfo Graphics::getRendererInfo() const diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 4e68acd2d..914a971e8 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -108,6 +108,7 @@ public: bool isCanvasFormatSupported(PixelFormat format, bool readable) const override; bool isImageFormatSupported(PixelFormat format, bool sRGB) const override; Renderer getRenderer() const override; + bool usesGLSLES() const override; RendererInfo getRendererInfo() const override; Shader::Language getShaderLanguageTarget() const override; diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index f42938b1f..e25762744 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -1363,7 +1363,7 @@ static int w_getShaderSource(lua_State *L, int startidx, bool gles, std::string int w_newShader(lua_State *L) { - bool gles = instance()->getRenderer() == Graphics::RENDERER_OPENGLES; + bool gles = instance()->usesGLSLES(); std::string vertexsource, pixelsource; w_getShaderSource(L, 1, gles, vertexsource, pixelsource); @@ -3146,13 +3146,22 @@ static const lua_CFunction types[] = extern "C" int luaopen_love_graphics(lua_State *L) { - Graphics *instance = instance(); + Graphics *instance = nullptr; + + { + std::vector renderers; +#if defined(LOVE_MACOS) || defined(LOVE_IOS) + renderers.push_back(Graphics::RENDERER_METAL); +#endif + renderers.push_back(Graphics::RENDERER_OPENGL); + instance = Graphics::createInstance(renderers); + } + if (instance == nullptr) { - luax_catchexcept(L, [&](){ instance = new love::graphics::opengl::Graphics(); }); + printf("Cannot create graphics: no supported renderer on this system.\n"); + return luaL_error(L, "Cannot create graphics: no supported renderer on this system."); } - else - instance->retain(); WrappedModule w; w.module = instance; diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index 5e9d2d505..8f6af1f12 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/src/modules/window/sdl/Window.cpp @@ -63,7 +63,10 @@ Window::Window() : open(false) , mouseGrabbed(false) , window(nullptr) - , context(nullptr) + , glcontext(nullptr) +#if defined(LOVE_MACOS) || defined(LOVE_IOS) + , metalView(nullptr) +#endif , displayedWindowError(false) , hasSDL203orEarlier(false) , contextAttribs() @@ -82,9 +85,7 @@ Window::Window() Window::~Window() { close(false); - graphics.set(nullptr); - SDL_QuitSubSystem(SDL_INIT_VIDEO); } @@ -288,9 +289,9 @@ std::vector Window::getContextAttribsList() const return attribslist; } -bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, int msaa, bool stencil, int depth) +bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, graphics::Graphics::Renderer renderer, int msaa, bool stencil, int depth) { - std::vector attribslist = getContextAttribsList(); + bool needsglcontext = (windowflags & SDL_WINDOW_OPENGL) != 0; std::string windowerror; std::string contexterror; @@ -302,12 +303,12 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla // Also, apparently some Intel drivers on Windows give back a Microsoft // OpenGL 1.1 software renderer context when high MSAA values are requested! - const auto create = [&](ContextAttribs attribs) -> bool + const auto create = [&](const ContextAttribs *attribs) -> bool { - if (context) + if (glcontext) { - SDL_GL_DeleteContext(context); - context = nullptr; + SDL_GL_DeleteContext(glcontext); + glcontext = nullptr; } if (window) @@ -325,80 +326,112 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla return false; } - context = SDL_GL_CreateContext(window); - - if (!context) - contexterror = std::string(SDL_GetError()); - - // Make sure the context's version is at least what we requested. - if (context && !checkGLVersion(attribs, glversion)) + if (attribs != nullptr) { - SDL_GL_DeleteContext(context); - context = nullptr; - } + glcontext = SDL_GL_CreateContext(window); - if (!context) - { - SDL_DestroyWindow(window); - window = nullptr; - return false; + if (!glcontext) + contexterror = std::string(SDL_GetError()); + + // Make sure the context's version is at least what we requested. + if (glcontext && !checkGLVersion(*attribs, glversion)) + { + SDL_GL_DeleteContext(glcontext); + glcontext = nullptr; + } + + if (!glcontext) + { + SDL_DestroyWindow(window); + window = nullptr; + return false; + } } return true; }; - // Try each context profile in order. - for (ContextAttribs attribs : attribslist) + if (renderer == graphics::Graphics::RENDERER_OPENGL) { - int curMSAA = msaa; - bool curSRGB = love::graphics::isGammaCorrect(); + std::vector attribslist = getContextAttribsList(); - setGLFramebufferAttributes(curMSAA, curSRGB, stencil, depth); - setGLContextAttributes(attribs); - - windowerror.clear(); - contexterror.clear(); - - create(attribs); - - if (!window && curMSAA > 0) + // Try each context profile in order. + for (ContextAttribs attribs : attribslist) { - // The MSAA setting could have caused the failure. - setGLFramebufferAttributes(0, curSRGB, stencil, depth); - if (create(attribs)) - curMSAA = 0; - } + int curMSAA = msaa; + bool curSRGB = love::graphics::isGammaCorrect(); - if (!window && curSRGB) - { - // same with sRGB. - setGLFramebufferAttributes(curMSAA, false, stencil, depth); - if (create(attribs)) - curSRGB = false; - } + setGLFramebufferAttributes(curMSAA, curSRGB, stencil, depth); + setGLContextAttributes(attribs); - if (!window && curMSAA > 0 && curSRGB) - { - // Or both! - setGLFramebufferAttributes(0, false, stencil, depth); - if (create(attribs)) + windowerror.clear(); + contexterror.clear(); + + create(&attribs); + + if (!window && curMSAA > 0) { - curMSAA = 0; - curSRGB = false; + // The MSAA setting could have caused the failure. + setGLFramebufferAttributes(0, curSRGB, stencil, depth); + if (create(&attribs)) + curMSAA = 0; + } + + if (!window && curSRGB) + { + // same with sRGB. + setGLFramebufferAttributes(curMSAA, false, stencil, depth); + if (create(&attribs)) + curSRGB = false; + } + + if (!window && curMSAA > 0 && curSRGB) + { + // Or both! + setGLFramebufferAttributes(0, false, stencil, depth); + if (create(&attribs)) + { + curMSAA = 0; + curSRGB = false; + } + } + + if (window && glcontext) + { + // Store the successful context attributes so we can re-use them in + // subsequent calls to createWindowAndContext. + contextAttribs = attribs; + love::graphics::setGammaCorrect(curSRGB); + break; } } + } +#if defined(LOVE_MACOS) || defined(LOVE_IOS) + else if (renderer == graphics::Graphics::RENDERER_METAL) + { + if (create(nullptr) && window != nullptr) + metalView = SDL_Metal_CreateView(window); - if (window && context) + if (metalView == nullptr && window != nullptr) { - // Store the successful context attributes so we can re-use them in - // subsequent calls to createWindowAndContext. - contextAttribs = attribs; - love::graphics::setGammaCorrect(curSRGB); - break; + SDL_DestroyWindow(window); + window = nullptr; + // TODO: error message } } +#endif + else + { + create(nullptr); + } - if (!context || !window) + bool failed = window == nullptr; + failed |= (needsglcontext && !glcontext); +#if defined(LOVE_MACOS) || defined(LOVE_IOS) + +#endif + + if (failed) { std::string title = "Unable to create OpenGL window"; std::string message = "This program requires a graphics card and video drivers which support OpenGL 2.1 or OpenGL ES 2."; @@ -435,6 +468,8 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) if (graphics.get() && graphics->isCanvasActive()) throw love::Exception("love.window.setMode cannot be called while a Canvas is active in love.graphics."); + auto renderer = graphics->getRenderer(); + WindowSettings f; if (settings) @@ -454,7 +489,10 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) height = mode.h; } - Uint32 sdlflags = SDL_WINDOW_OPENGL; + Uint32 sdlflags = 0; + + if (renderer == graphics::Graphics::RENDERER_OPENGL) + sdlflags |= SDL_WINDOW_OPENGL; // On Android we always must have fullscreen type FULLSCREEN_TYPE_DESKTOP #ifdef LOVE_ANDROID @@ -515,7 +553,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) close(); - if (!createWindowAndContext(x, y, width, height, sdlflags, f.msaa, f.stencil, f.depth)) + if (!createWindowAndContext(x, y, width, height, sdlflags, renderer, f.msaa, f.stencil, f.depth)) return false; // Make sure the window keeps any previously set icon. @@ -670,12 +708,20 @@ void Window::close(bool allowExceptions) graphics->unSetMode(); } - if (context) + if (glcontext) { - SDL_GL_DeleteContext(context); - context = nullptr; + SDL_GL_DeleteContext(glcontext); + glcontext = nullptr; } +#if defined(LOVE_MACOS) || defined(LOVE_IOS) + if (metalView) + { + SDL_Metal_DestroyView(metalView); + metalView = nullptr; + } +#endif + if (window) { SDL_DestroyWindow(window); @@ -726,7 +772,7 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype) if (SDL_SetWindowFullscreen(window, sdlflags) == 0) { - SDL_GL_MakeCurrent(window, context); + SDL_GL_MakeCurrent(window, glcontext); updateSettings(newsettings, true); // Apparently this gets un-set when we exit fullscreen (at least in OS X). @@ -960,7 +1006,7 @@ love::image::ImageData *Window::getIcon() void Window::setVSync(int vsync) { - if (context == nullptr) + if (glcontext == nullptr) return; SDL_GL_SetSwapInterval(vsync); @@ -973,7 +1019,7 @@ void Window::setVSync(int vsync) int Window::getVSync() const { - return context != nullptr ? SDL_GL_GetSwapInterval() : 0; + return glcontext != nullptr ? SDL_GL_GetSwapInterval() : 0; } void Window::setDisplaySleepEnabled(bool enable) diff --git a/src/modules/window/sdl/Window.h b/src/modules/window/sdl/Window.h index e48a8bca8..4928c406c 100644 --- a/src/modules/window/sdl/Window.h +++ b/src/modules/window/sdl/Window.h @@ -23,6 +23,8 @@ // LOVE #include "window/Window.h" +#include "common/config.h" +#include "graphics/Graphics.h" // SDL #include @@ -143,7 +145,7 @@ private: void setGLContextAttributes(const ContextAttribs &attribs); bool checkGLVersion(const ContextAttribs &attribs, std::string &outversion); std::vector getContextAttribsList() const; - bool createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, int msaa, bool stencil, int depth); + bool createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, graphics::Graphics::Renderer renderer, int msaa, bool stencil, int depth); // Update the saved window settings based on the window's actual state. void updateSettings(const WindowSettings &newsettings, bool updateGraphicsViewport); @@ -164,7 +166,11 @@ private: bool mouseGrabbed; SDL_Window *window; - SDL_GLContext context; + + SDL_GLContext glcontext; +#if defined(LOVE_MACOS) || defined(LOVE_IOS) + SDL_MetalView metalView; +#endif bool displayedWindowError; bool hasSDL203orEarlier;