OpenGL: Fix contexts & loaders

This commit is contained in:
Lynix 2020-04-19 01:38:19 +02:00
parent 4dc8920a73
commit 9dd208c3cf
5 changed files with 337 additions and 146 deletions

View File

@ -8,12 +8,14 @@
#define NAZARA_OPENGLRENDERER_LOADER_HPP #define NAZARA_OPENGLRENDERER_LOADER_HPP
#include <Nazara/Prerequisites.hpp> #include <Nazara/Prerequisites.hpp>
#include <Nazara/Platform/WindowHandle.hpp>
#include <Nazara/OpenGLRenderer/Config.hpp> #include <Nazara/OpenGLRenderer/Config.hpp>
#include <Nazara/OpenGLRenderer/Wrapper/Context.hpp>
#include <memory> #include <memory>
namespace Nz::GL namespace Nz::GL
{ {
class GLContext; class Context;
using GLFunction = int(*)(); using GLFunction = int(*)();
@ -23,9 +25,10 @@ namespace Nz::GL
Loader() = default; Loader() = default;
virtual ~Loader(); virtual ~Loader();
virtual std::unique_ptr<GLContext> CreateContext() = 0; virtual std::unique_ptr<Context> CreateContext(const ContextParams& params, Context* shareContext = nullptr) const = 0;
virtual std::unique_ptr<Context> 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;
}; };
} }

View File

@ -3,8 +3,96 @@
// For conditions of distribution and use, see copyright notice in Config.hpp // For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/OpenGLRenderer/Wrapper/Context.hpp> #include <Nazara/OpenGLRenderer/Wrapper/Context.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/StringExt.hpp>
#include <Nazara/OpenGLRenderer/Wrapper/Loader.hpp>
#include <stdexcept>
#include <Nazara/OpenGLRenderer/Debug.hpp> #include <Nazara/OpenGLRenderer/Debug.hpp>
namespace Nz::GL 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<std::decay_t<decltype(func)>>(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<const char*>(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<const char*>(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;
}
} }

View File

@ -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 <Nazara/OpenGLRenderer/Wrapper/GLContext.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/OpenGLRenderer/Wrapper/Loader.hpp>
#include <stdexcept>
#include <Nazara/OpenGLRenderer/Debug.hpp>
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<std::decay_t<decltype(func)>>(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;
}
}

View File

@ -14,7 +14,7 @@ namespace Nz::GL
{ {
thread_local WGLContext* s_currentContext = nullptr; thread_local WGLContext* s_currentContext = nullptr;
GL::WGLContext::WGLContext(WGLLoader& loader) : GL::WGLContext::WGLContext(const WGLLoader& loader) :
m_loader(loader) m_loader(loader)
{ {
} }
@ -48,11 +48,9 @@ namespace Nz::GL
return true; 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 device context, create window to get one
// Creating a context requires a Window
m_window.reset(::CreateWindowA("STATIC", nullptr, WS_DISABLED | WS_POPUP, 0, 0, 1, 1, nullptr, nullptr, GetModuleHandle(nullptr), nullptr)); m_window.reset(::CreateWindowA("STATIC", nullptr, WS_DISABLED | WS_POPUP, 0, 0, 1, 1, nullptr, nullptr, GetModuleHandle(nullptr), nullptr));
if (!m_window) if (!m_window)
{ {
@ -62,71 +60,19 @@ namespace Nz::GL
::ShowWindow(m_window.get(), FALSE); ::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<HWND>(window));
if (!m_deviceContext) if (!m_deviceContext)
{ {
NazaraError("failed to retrieve dummy window device context: " + Error::GetLastSystemError()); NazaraError("failed to retrieve window device context: " + Error::GetLastSystemError());
return false; return false;
} }
if (!SetPixelFormat(params)) return CreateInternal(baseContext, params, shareContext);
return false;
WGLContext* currentContext = s_currentContext; //< Pay TLS cost only once
if (currentContext && currentContext->wglCreateContextAttribsARB)
{
struct OpenGLVersion
{
int major;
int minor;
};
std::array<OpenGLVersion, 8> supportedVersions = {
{
{ 4, 6 },
{ 4, 5 },
{ 4, 4 },
{ 4, 3 },
{ 4, 2 },
{ 4, 1 },
{ 4, 0 },
{ 3, 3 }
}
};
for (const OpenGLVersion& version : supportedVersions)
{
std::array<int, 3 * 2 + 1> 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;
} }
void WGLContext::Destroy() void WGLContext::Destroy()
@ -151,6 +97,157 @@ namespace Nz::GL
m_loader.SwapBuffers(m_deviceContext); 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<Version, 3> 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<int, 3 * 2 + 1> 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<Version, 8> 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<int, 3 * 2 + 1> 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<Fallback::glClearDepthProc>(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() void WGLContext::Desactivate()
{ {
WGLContext*& currentContext = s_currentContext; //< Pay TLS cost only once 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() bool WGLContext::LoadWGLExt()
{ {
if (!Activate()) if (!Activate())
@ -186,7 +288,7 @@ namespace Nz::GL
{ {
SplitString(extensionString, " ", [&](std::string_view extension) SplitString(extensionString, " ", [&](std::string_view extension)
{ {
m_supportedExtensions.emplace(extension); m_supportedPlatformExtensions.emplace(extension);
return true; return true;
}); });
} }
@ -194,14 +296,14 @@ namespace Nz::GL
return true; return true;
} }
bool WGLContext::SetPixelFormat(const ContextParams& params) bool WGLContext::SetPixelFormat()
{ {
PIXELFORMATDESCRIPTOR descriptor = {}; PIXELFORMATDESCRIPTOR descriptor = {};
descriptor.nSize = sizeof(PIXELFORMATDESCRIPTOR); descriptor.nSize = sizeof(PIXELFORMATDESCRIPTOR);
descriptor.nVersion = 1; descriptor.nVersion = 1;
int pixelFormat = 0; int pixelFormat = 0;
if (params.sampleCount > 1) if (m_params.sampleCount > 1)
{ {
WGLContext* currentContext = s_currentContext; //< Pay TLS cost only once WGLContext* currentContext = s_currentContext; //< Pay TLS cost only once
if (currentContext) if (currentContext)
@ -215,13 +317,13 @@ namespace Nz::GL
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_SUPPORT_OPENGL_ARB, GL_TRUE, WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
WGL_COLOR_BITS_ARB, (params.bitsPerPixel == 32) ? 24 : params.bitsPerPixel, WGL_COLOR_BITS_ARB, (m_params.bitsPerPixel == 32) ? 24 : m_params.bitsPerPixel,
WGL_ALPHA_BITS_ARB, (params.bitsPerPixel == 32) ? 8 : 0, WGL_ALPHA_BITS_ARB, (m_params.bitsPerPixel == 32) ? 8 : 0,
WGL_DEPTH_BITS_ARB, params.depthBits, WGL_DEPTH_BITS_ARB, m_params.depthBits,
WGL_STENCIL_BITS_ARB, params.stencilBits, WGL_STENCIL_BITS_ARB, m_params.stencilBits,
WGL_DOUBLE_BUFFER_ARB, (params.doubleBuffering) ? GL_TRUE : GL_FALSE, WGL_DOUBLE_BUFFER_ARB, (m_params.doubleBuffering) ? GL_TRUE : GL_FALSE,
WGL_SAMPLE_BUFFERS_ARB, GL_TRUE, WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
WGL_SAMPLES_ARB, params.sampleCount WGL_SAMPLES_ARB, m_params.sampleCount
}; };
int& sampleCount = attributes[attributes.size() - 3]; int& sampleCount = attributes[attributes.size() - 3];
@ -239,24 +341,26 @@ namespace Nz::GL
} }
while (sampleCount > 1); while (sampleCount > 1);
if (params.sampleCount != sampleCount) if (m_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"); 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) if (pixelFormat == 0)
{ {
descriptor.cColorBits = BYTE((params.bitsPerPixel == 32) ? 24 : params.bitsPerPixel); descriptor.cColorBits = BYTE((m_params.bitsPerPixel == 32) ? 24 : m_params.bitsPerPixel);
descriptor.cDepthBits = BYTE(params.depthBits); descriptor.cDepthBits = BYTE(m_params.depthBits);
descriptor.cStencilBits = BYTE(params.stencilBits); descriptor.cStencilBits = BYTE(m_params.stencilBits);
descriptor.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; descriptor.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
descriptor.iPixelType = PFD_TYPE_RGBA; descriptor.iPixelType = PFD_TYPE_RGBA;
if (params.bitsPerPixel == 32) if (m_params.bitsPerPixel == 32)
descriptor.cAlphaBits = 8; descriptor.cAlphaBits = 8;
if (params.doubleBuffering) if (m_params.doubleBuffering)
descriptor.dwFlags |= PFD_DOUBLEBUFFER; descriptor.dwFlags |= PFD_DOUBLEBUFFER;
pixelFormat = m_loader.ChoosePixelFormat(m_deviceContext, &descriptor); pixelFormat = m_loader.ChoosePixelFormat(m_deviceContext, &descriptor);
@ -273,6 +377,13 @@ namespace Nz::GL
return false; 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; return true;
} }
} }

View File

@ -3,13 +3,15 @@
// For conditions of distribution and use, see copyright notice in Config.hpp // For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/OpenGLRenderer/Wrapper/Win32/WGLLoader.hpp> #include <Nazara/OpenGLRenderer/Wrapper/Win32/WGLLoader.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/OpenGLRenderer/Wrapper/Win32/WGLContext.hpp> #include <Nazara/OpenGLRenderer/Wrapper/Win32/WGLContext.hpp>
#include <Nazara/OpenGLRenderer/Debug.hpp> #include <Nazara/OpenGLRenderer/Debug.hpp>
namespace Nz::GL namespace Nz::GL
{ {
WGLLoader::WGLLoader(DynLib& openglLib) : WGLLoader::WGLLoader(DynLib& openglLib) :
m_opengl32Lib(openglLib) m_opengl32Lib(openglLib),
m_baseContext(*this)
{ {
if (!m_gdi32Lib.Load("gdi32.dll")) if (!m_gdi32Lib.Load("gdi32.dll"))
throw std::runtime_error("failed to load gdi32.dll: " + m_gdi32Lib.GetLastError()); 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) NAZARA_OPENGLRENDERER_FOREACH_WGL_FUNC(NAZARA_OPENGLRENDERER_FUNC, NAZARA_OPENGLRENDERER_EXT_BEGIN, NAZARA_OPENGLRENDERER_EXT_END, NAZARA_OPENGLRENDERER_EXT_FUNC)
#undef NAZARA_OPENGLRENDERER_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_BEGIN
#undef NAZARA_OPENGLRENDERER_EXT_END #undef NAZARA_OPENGLRENDERER_EXT_END
#undef NAZARA_OPENGLRENDERER_EXT_FUNC #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<GLContext> WGLLoader::CreateContext() std::unique_ptr<Context> WGLLoader::CreateContext(const ContextParams& params, Context* shareContext) const
{ {
return {}; //< TODO auto context = std::make_unique<WGLContext>(*this);
if (!context->Create(&m_baseContext, params, static_cast<WGLContext*>(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<Context> WGLLoader::CreateContext(const ContextParams& params, WindowHandle handle, Context* shareContext) const
{
auto context = std::make_unique<WGLContext>(*this);
if (!context->Create(&m_baseContext, params, handle, static_cast<WGLContext*>(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<GLFunction>(wglGetProcAddress(name)); GLFunction func = reinterpret_cast<GLFunction>(wglGetProcAddress(name));
if (!func) //< wglGetProcAddress doesn't work for OpenGL 1.1 functions if (!func) //< wglGetProcAddress doesn't work for OpenGL 1.1 functions