diff --git a/include/Nazara/OpenGLRenderer/Wrapper/Loader.hpp b/include/Nazara/OpenGLRenderer/Wrapper/Loader.hpp index ad48aa92e..13eeafb53 100644 --- a/include/Nazara/OpenGLRenderer/Wrapper/Loader.hpp +++ b/include/Nazara/OpenGLRenderer/Wrapper/Loader.hpp @@ -8,12 +8,14 @@ #define NAZARA_OPENGLRENDERER_LOADER_HPP #include +#include #include +#include #include namespace Nz::GL { - class GLContext; + class Context; using GLFunction = int(*)(); @@ -23,9 +25,10 @@ namespace Nz::GL Loader() = default; virtual ~Loader(); - virtual std::unique_ptr CreateContext() = 0; + virtual std::unique_ptr CreateContext(const ContextParams& params, Context* shareContext = nullptr) const = 0; + virtual std::unique_ptr CreateContext(const ContextParams& params, WindowHandle handle, Context* shareContext = nullptr) const = 0; - virtual GLFunction LoadFunction(const char* name) = 0; + virtual GLFunction LoadFunction(const char* name) const = 0; }; } diff --git a/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp b/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp index 06f4578d0..8c651ff33 100644 --- a/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp +++ b/src/Nazara/OpenGLRenderer/Wrapper/Context.cpp @@ -3,8 +3,96 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include +#include +#include +#include +#include #include namespace Nz::GL { + Context::~Context() = default; + + bool Context::Initialize(const ContextParams& params) + { + if (!Activate()) + { + NazaraError("failed to activate context"); + return false; + } + + const Loader& loader = GetLoader(); + + auto LoadSymbol = [&](auto& func, const char* funcName) + { + func = reinterpret_cast>(loader.LoadFunction(funcName)); + if (!func && !ImplementFallback(funcName) && !func) //< Not a mistake + throw std::runtime_error("failed to load core function " + std::string(funcName)); + }; + + try + { +#define NAZARA_OPENGLRENDERER_FUNC(name, sig) LoadSymbol(name, #name); + NAZARA_OPENGLRENDERER_FOREACH_GLES_FUNC(NAZARA_OPENGLRENDERER_FUNC) +#undef NAZARA_OPENGLRENDERER_FUNC + } + catch (const std::exception& e) + { + NazaraError(e.what()); + return false; + } + + // Retrieve OpenGL version + auto DecodeDigit = [](char c) -> int + { + if (c >= '0' && c <= '9') + return c - '0'; + else + return -1; + }; + + std::string_view versionString = reinterpret_cast(glGetString(GL_VERSION)); + if (versionString.size() > 2 && DecodeDigit(versionString[0]) >= 0 && versionString[1] == '.' && DecodeDigit(versionString[2]) >= 0) + { + m_params.glMajorVersion = DecodeDigit(versionString[0]); + m_params.glMinorVersion = DecodeDigit(versionString[2]); + } + else + NazaraWarning("Failed to decode OpenGL version: " + std::string(versionString)); + + // Load extensions + std::string_view extensionList = reinterpret_cast(glGetString(GL_EXTENSIONS)); + SplitString(extensionList, " ", [&](std::string_view extension) + { + m_supportedExtensions.emplace(extension); + return true; + }); + + // If we requested an OpenGL ES context but cannot create one, check for some compatibility extensions + if (params.type == ContextType::OpenGL_ES && m_params.type != params.type) + { + if (m_supportedExtensions.count("GL_ARB_ES3_2_compatibility")) + { + m_params.type = ContextType::OpenGL_ES; + m_params.glMajorVersion = 3; + m_params.glMinorVersion = 2; + } + else if (m_supportedExtensions.count("GL_ARB_ES3_1_compatibility")) + { + m_params.type = ContextType::OpenGL_ES; + m_params.glMajorVersion = 3; + m_params.glMinorVersion = 1; + } + else if (m_supportedExtensions.count("GL_ARB_ES3_compatibility")) + { + m_params.type = ContextType::OpenGL_ES; + m_params.glMajorVersion = 3; + m_params.glMinorVersion = 0; + } + else + NazaraWarning("desktop support for OpenGL ES is missing, falling back to OpenGL..."); + } + + return true; + } } diff --git a/src/Nazara/OpenGLRenderer/Wrapper/GLContext.cpp b/src/Nazara/OpenGLRenderer/Wrapper/GLContext.cpp deleted file mode 100644 index 23ba56493..000000000 --- a/src/Nazara/OpenGLRenderer/Wrapper/GLContext.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2020 Jérôme Leclercq -// This file is part of the "Nazara Engine - OpenGL Renderer" -// For conditions of distribution and use, see copyright notice in Config.hpp - -#include -#include -#include -#include -#include - -namespace Nz::GL -{ - GLContext::~GLContext() = default; - - bool GLContext::LoadCoreFunctions(Loader& loader) - { - if (!Activate()) - { - NazaraError("failed to activate context"); - return false; - } - - auto LoadSymbol = [&](auto& func, const char* funcName) - { - func = reinterpret_cast>(loader.LoadFunction(funcName)); - if (!func) - throw std::runtime_error("failed to load core function " + std::string(funcName)); - }; - - try - { -#define NAZARA_OPENGLRENDERER_FUNC(name, sig) LoadSymbol(name, #name); - NAZARA_OPENGLRENDERER_FOREACH_GLES_FUNC(NAZARA_OPENGLRENDERER_FUNC) -#undef NAZARA_OPENGLRENDERER_FUNC - } - catch (const std::exception& e) - { - NazaraError(e.what()); - return false; - } - - return true; - } -} diff --git a/src/Nazara/OpenGLRenderer/Wrapper/Win32/WGLContext.cpp b/src/Nazara/OpenGLRenderer/Wrapper/Win32/WGLContext.cpp index edb84f5bd..b46d38e28 100644 --- a/src/Nazara/OpenGLRenderer/Wrapper/Win32/WGLContext.cpp +++ b/src/Nazara/OpenGLRenderer/Wrapper/Win32/WGLContext.cpp @@ -14,7 +14,7 @@ namespace Nz::GL { thread_local WGLContext* s_currentContext = nullptr; - GL::WGLContext::WGLContext(WGLLoader& loader) : + GL::WGLContext::WGLContext(const WGLLoader& loader) : m_loader(loader) { } @@ -48,11 +48,9 @@ namespace Nz::GL return true; } - bool WGLContext::Create(const ContextParams& params) + bool WGLContext::Create(const WGLContext* baseContext, const ContextParams& params, const WGLContext* shareContext) { - Destroy(); - - // Creating a context requires a Window + // Creating a context requires a device context, create window to get one m_window.reset(::CreateWindowA("STATIC", nullptr, WS_DISABLED | WS_POPUP, 0, 0, 1, 1, nullptr, nullptr, GetModuleHandle(nullptr), nullptr)); if (!m_window) { @@ -62,71 +60,19 @@ namespace Nz::GL ::ShowWindow(m_window.get(), FALSE); - m_deviceContext = ::GetDC(m_window.get()); + return Create(baseContext, params, m_window.get(), shareContext); + } + + bool WGLContext::Create(const WGLContext* baseContext, const ContextParams& params, WindowHandle window, const WGLContext* shareContext) + { + m_deviceContext = ::GetDC(static_cast(window)); if (!m_deviceContext) { - NazaraError("failed to retrieve dummy window device context: " + Error::GetLastSystemError()); + NazaraError("failed to retrieve window device context: " + Error::GetLastSystemError()); return false; } - if (!SetPixelFormat(params)) - return false; - - WGLContext* currentContext = s_currentContext; //< Pay TLS cost only once - if (currentContext && currentContext->wglCreateContextAttribsARB) - { - struct OpenGLVersion - { - int major; - int minor; - }; - - std::array supportedVersions = { - { - { 4, 6 }, - { 4, 5 }, - { 4, 4 }, - { 4, 3 }, - { 4, 2 }, - { 4, 1 }, - { 4, 0 }, - { 3, 3 } - } - }; - - for (const OpenGLVersion& version : supportedVersions) - { - std::array attributes = { - WGL_CONTEXT_MAJOR_VERSION_ARB, version.major, - WGL_CONTEXT_MINOR_VERSION_ARB, version.minor, - - WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB - }; - - m_handle = currentContext->wglCreateContextAttribsARB(m_deviceContext, nullptr, attributes.data()); - if (m_handle) - break; - } - - if (!m_handle) - { - NazaraError("failed to create WGL context: " + Error::GetLastSystemError()); - return false; - } - } - else - { - m_handle = m_loader.wglCreateContext(m_deviceContext); - if (!m_handle) - { - NazaraError("failed to create WGL context: " + Error::GetLastSystemError()); - return false; - } - } - - LoadWGLExt(); - - return true; + return CreateInternal(baseContext, params, shareContext); } void WGLContext::Destroy() @@ -151,6 +97,157 @@ namespace Nz::GL m_loader.SwapBuffers(m_deviceContext); } + bool WGLContext::CreateInternal(const WGLContext* baseContext, const ContextParams& params, const WGLContext* shareContext) + { + Destroy(); + + m_params = params; + + if (!SetPixelFormat()) + return false; + + if (baseContext && baseContext->wglCreateContextAttribsARB) + { + struct Version + { + unsigned int major; + unsigned int minor; + }; + + if (params.type == ContextType::OpenGL_ES) + { + if (baseContext->HasPlatformExtension("WGL_EXT_create_context_es_profile")) + { + // Create OpenGL ES context + std::array supportedGL_ESVersions = { + { + { 3, 2 }, + { 3, 1 }, + { 3, 0 } + } + }; + + for (const Version& version : supportedGL_ESVersions) + { + if (params.glMajorVersion != 0) + { + if (version.major > params.glMajorVersion) + continue; + + if (params.glMinorVersion != 0 && version.minor > params.glMinorVersion) + continue; + } + + std::array attributes = { + WGL_CONTEXT_MAJOR_VERSION_ARB, version.major, + WGL_CONTEXT_MINOR_VERSION_ARB, version.minor, + + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB | WGL_CONTEXT_ES_PROFILE_BIT_EXT + }; + + m_handle = baseContext->wglCreateContextAttribsARB(m_deviceContext, nullptr, attributes.data()); + if (m_handle) + break; + } + } + } + + if (!m_handle) + { + // Create OpenGL context + std::array supportedGLVersions = { + { + { 4, 6 }, + { 4, 5 }, + { 4, 4 }, + { 4, 3 }, + { 4, 2 }, + { 4, 1 }, + { 4, 0 }, + { 3, 3 } + } + }; + + for (const Version& version : supportedGLVersions) + { + if (params.glMajorVersion != 0) + { + if (version.major > params.glMajorVersion) + continue; + + if (params.glMinorVersion != 0 && version.minor > params.glMinorVersion) + continue; + } + + std::array attributes = { + WGL_CONTEXT_MAJOR_VERSION_ARB, version.major, + WGL_CONTEXT_MINOR_VERSION_ARB, version.minor, + + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB + }; + + m_handle = baseContext->wglCreateContextAttribsARB(m_deviceContext, nullptr, attributes.data()); + if (m_handle) + { + m_params.type = ContextType::OpenGL; + break; + } + } + } + + if (!m_handle) + { + NazaraError("failed to create WGL context: " + Error::GetLastSystemError()); + return false; + } + } + else + { + m_handle = m_loader.wglCreateContext(m_deviceContext); + if (!m_handle) + { + NazaraError("failed to create WGL context: " + Error::GetLastSystemError()); + return false; + } + + m_params.type = ContextType::OpenGL; + } + + if (shareContext) + { + if (!m_loader.wglShareLists(shareContext->m_handle, m_handle)) + { + NazaraError("failed to share context objects: " + Error::GetLastSystemError()); + return false; + } + } + + LoadWGLExt(); + + return true; + } + + bool WGLContext::ImplementFallback(const std::string_view& function) + { + if (m_params.type == ContextType::OpenGL_ES) + return false; //< Implement fallback only for OpenGL (when emulating OpenGL ES) + + if (function == "glClearDepthf") + { + fallbacks.glClearDepth = reinterpret_cast(m_loader.LoadFunction("glClearDepth")); + if (!fallbacks.glClearDepth) + return false; + + glClearDepthf = [](GLfloat depth) + { + assert(s_currentContext); + s_currentContext->fallbacks.glClearDepth(depth); + }; + } + + return true; + } + void WGLContext::Desactivate() { WGLContext*& currentContext = s_currentContext; //< Pay TLS cost only once @@ -161,6 +258,11 @@ namespace Nz::GL } } + const Loader& WGLContext::GetLoader() + { + return m_loader; + } + bool WGLContext::LoadWGLExt() { if (!Activate()) @@ -186,7 +288,7 @@ namespace Nz::GL { SplitString(extensionString, " ", [&](std::string_view extension) { - m_supportedExtensions.emplace(extension); + m_supportedPlatformExtensions.emplace(extension); return true; }); } @@ -194,14 +296,14 @@ namespace Nz::GL return true; } - bool WGLContext::SetPixelFormat(const ContextParams& params) + bool WGLContext::SetPixelFormat() { PIXELFORMATDESCRIPTOR descriptor = {}; descriptor.nSize = sizeof(PIXELFORMATDESCRIPTOR); descriptor.nVersion = 1; int pixelFormat = 0; - if (params.sampleCount > 1) + if (m_params.sampleCount > 1) { WGLContext* currentContext = s_currentContext; //< Pay TLS cost only once if (currentContext) @@ -215,13 +317,13 @@ namespace Nz::GL WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, WGL_SUPPORT_OPENGL_ARB, GL_TRUE, WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, - WGL_COLOR_BITS_ARB, (params.bitsPerPixel == 32) ? 24 : params.bitsPerPixel, - WGL_ALPHA_BITS_ARB, (params.bitsPerPixel == 32) ? 8 : 0, - WGL_DEPTH_BITS_ARB, params.depthBits, - WGL_STENCIL_BITS_ARB, params.stencilBits, - WGL_DOUBLE_BUFFER_ARB, (params.doubleBuffering) ? GL_TRUE : GL_FALSE, + WGL_COLOR_BITS_ARB, (m_params.bitsPerPixel == 32) ? 24 : m_params.bitsPerPixel, + WGL_ALPHA_BITS_ARB, (m_params.bitsPerPixel == 32) ? 8 : 0, + WGL_DEPTH_BITS_ARB, m_params.depthBits, + WGL_STENCIL_BITS_ARB, m_params.stencilBits, + WGL_DOUBLE_BUFFER_ARB, (m_params.doubleBuffering) ? GL_TRUE : GL_FALSE, WGL_SAMPLE_BUFFERS_ARB, GL_TRUE, - WGL_SAMPLES_ARB, params.sampleCount + WGL_SAMPLES_ARB, m_params.sampleCount }; int& sampleCount = attributes[attributes.size() - 3]; @@ -239,24 +341,26 @@ namespace Nz::GL } while (sampleCount > 1); - if (params.sampleCount != sampleCount) - NazaraWarning("couldn't find a pixel format matching " + std::to_string(params.sampleCount) + " sample count, using " + std::to_string(sampleCount) + " sample(s) instead"); + if (m_params.sampleCount != sampleCount) + NazaraWarning("couldn't find a pixel format matching " + std::to_string(m_params.sampleCount) + " sample count, using " + std::to_string(sampleCount) + " sample(s) instead"); + + m_params.sampleCount = sampleCount; } } } if (pixelFormat == 0) { - descriptor.cColorBits = BYTE((params.bitsPerPixel == 32) ? 24 : params.bitsPerPixel); - descriptor.cDepthBits = BYTE(params.depthBits); - descriptor.cStencilBits = BYTE(params.stencilBits); + descriptor.cColorBits = BYTE((m_params.bitsPerPixel == 32) ? 24 : m_params.bitsPerPixel); + descriptor.cDepthBits = BYTE(m_params.depthBits); + descriptor.cStencilBits = BYTE(m_params.stencilBits); descriptor.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; descriptor.iPixelType = PFD_TYPE_RGBA; - if (params.bitsPerPixel == 32) + if (m_params.bitsPerPixel == 32) descriptor.cAlphaBits = 8; - if (params.doubleBuffering) + if (m_params.doubleBuffering) descriptor.dwFlags |= PFD_DOUBLEBUFFER; pixelFormat = m_loader.ChoosePixelFormat(m_deviceContext, &descriptor); @@ -273,6 +377,13 @@ namespace Nz::GL return false; } + if (DescribePixelFormat(m_deviceContext, pixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &descriptor) != 0) + { + m_params.bitsPerPixel = descriptor.cColorBits + descriptor.cAlphaBits; + m_params.depthBits = descriptor.cDepthBits; + m_params.stencilBits = descriptor.cStencilBits; + } + return true; } } diff --git a/src/Nazara/OpenGLRenderer/Wrapper/Win32/WGLLoader.cpp b/src/Nazara/OpenGLRenderer/Wrapper/Win32/WGLLoader.cpp index b813af9c3..bc37dc061 100644 --- a/src/Nazara/OpenGLRenderer/Wrapper/Win32/WGLLoader.cpp +++ b/src/Nazara/OpenGLRenderer/Wrapper/Win32/WGLLoader.cpp @@ -3,13 +3,15 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include +#include #include #include namespace Nz::GL { WGLLoader::WGLLoader(DynLib& openglLib) : - m_opengl32Lib(openglLib) + m_opengl32Lib(openglLib), + m_baseContext(*this) { if (!m_gdi32Lib.Load("gdi32.dll")) throw std::runtime_error("failed to load gdi32.dll: " + m_gdi32Lib.GetLastError()); @@ -35,31 +37,62 @@ namespace Nz::GL NAZARA_OPENGLRENDERER_FOREACH_WGL_FUNC(NAZARA_OPENGLRENDERER_FUNC, NAZARA_OPENGLRENDERER_EXT_BEGIN, NAZARA_OPENGLRENDERER_EXT_END, NAZARA_OPENGLRENDERER_EXT_FUNC) #undef NAZARA_OPENGLRENDERER_FUNC - // In order to load OpenGL functions, we have to create a context first - WGLContext dummyContext(*this); - - if (!dummyContext.Create({})) - throw std::runtime_error("failed to create load context"); - - WGLContext loadContext(*this); - - if (!loadContext.Create({})) - throw std::runtime_error("failed to create load context"); - - if (!loadContext.LoadCoreFunctions(*this)) - throw std::runtime_error("failed to load OpenGL functions"); - #undef NAZARA_OPENGLRENDERER_EXT_BEGIN #undef NAZARA_OPENGLRENDERER_EXT_END #undef NAZARA_OPENGLRENDERER_EXT_FUNC + + // In order to load OpenGL functions, we have to create a context first + WGLContext loadContext(*this); + + if (!loadContext.Create(nullptr, {})) + throw std::runtime_error("failed to create load context"); + + ContextParams params; + + if (!m_baseContext.Create(&loadContext, params)) + throw std::runtime_error("failed to create load context"); + + if (!m_baseContext.Initialize(params)) + throw std::runtime_error("failed to load OpenGL functions"); } - std::unique_ptr WGLLoader::CreateContext() + std::unique_ptr WGLLoader::CreateContext(const ContextParams& params, Context* shareContext) const { - return {}; //< TODO + auto context = std::make_unique(*this); + if (!context->Create(&m_baseContext, params, static_cast(shareContext))) + { + NazaraError("failed to create context"); + return {}; + } + + if (!context->Initialize(params)) + { + NazaraError("failed to initialize context"); + return {}; + } + + return context; } - GLFunction WGLLoader::LoadFunction(const char* name) + std::unique_ptr WGLLoader::CreateContext(const ContextParams& params, WindowHandle handle, Context* shareContext) const + { + auto context = std::make_unique(*this); + if (!context->Create(&m_baseContext, params, handle, static_cast(shareContext))) + { + NazaraError("failed to create context"); + return {}; + } + + if (!context->Initialize(params)) + { + NazaraError("failed to initialize context"); + return {}; + } + + return context; + } + + GLFunction WGLLoader::LoadFunction(const char* name) const { GLFunction func = reinterpret_cast(wglGetProcAddress(name)); if (!func) //< wglGetProcAddress doesn't work for OpenGL 1.1 functions