Add basic EGL support

This commit is contained in:
Jérôme Leclercq
2020-09-01 18:46:35 +02:00
parent 07a02ecfcd
commit ef737a8ecd
29 changed files with 1122 additions and 94 deletions

View File

@@ -12,6 +12,10 @@
#include <cassert>
#include <sstream>
#if defined(NAZARA_PLATFORM_WINDOWS) || defined(NAZARA_PLATFORM_LINUX)
#include <Nazara/OpenGLRenderer/Wrapper/EGL/EGLLoader.hpp>
#endif
#ifdef NAZARA_PLATFORM_WINDOWS
#include <Nazara/OpenGLRenderer/Wrapper/WGL/WGLLoader.hpp>
#endif
@@ -50,19 +54,7 @@ namespace Nz
return false;
}
std::unique_ptr<GL::Loader> loader;
#ifdef NAZARA_PLATFORM_WINDOWS
try
{
loader = std::make_unique<GL::WGLLoader>(m_opengl32Lib);
}
catch (const std::exception& e)
{
NazaraWarning(std::string("Failed to load WGL: ") + e.what());
}
#endif
std::unique_ptr<GL::Loader> loader = SelectLoader();
if (!loader)
{
NazaraError("Failed to initialize OpenGL loader");
@@ -76,6 +68,33 @@ namespace Nz
return true;
}
std::unique_ptr<GL::Loader> OpenGLRenderer::SelectLoader()
{
#if defined(NAZARA_PLATFORM_WINDOWS) || defined(NAZARA_PLATFORM_LINUX)
try
{
return std::make_unique<GL::EGLLoader>(m_opengl32Lib);
}
catch (const std::exception& e)
{
NazaraWarning(std::string("Failed to load EGL: ") + e.what());
}
#endif
#ifdef NAZARA_PLATFORM_WINDOWS
try
{
return std::make_unique<GL::WGLLoader>(m_opengl32Lib);
}
catch (const std::exception& e)
{
NazaraWarning(std::string("Failed to load WGL: ") + e.what());
}
#endif
return {};
}
RenderAPI OpenGLRenderer::QueryAPI() const
{
return RenderAPI::OpenGL;

View File

@@ -0,0 +1,340 @@
// 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/EGL/EGLContextBase.hpp>
#include <Nazara/Core/CallOnExit.hpp>
#include <Nazara/Core/Log.hpp>
#include <Nazara/Core/StringExt.hpp>
#include <Nazara/OpenGLRenderer/Wrapper/EGL/EGLLoader.hpp>
#include <array>
#include <cassert>
#include <Nazara/OpenGLRenderer/Debug.hpp>
namespace Nz::GL
{
EGLContextBase::~EGLContextBase()
{
Destroy();
}
bool EGLContextBase::Create(const ContextParams& params, const EGLContextBase* shareContext)
{
Destroy(); //< In case a previous display or surface hasn't been released
m_params = params;
if (!BindAPI())
return false;
m_display = m_loader.eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (!m_display)
{
NazaraError("failed to retrieve default EGL display");
return false;
}
if (!InitDisplay())
return false;
std::size_t configCount;
std::array<EGLConfig, 0xFF> configs;
if (!ChooseConfig(configs.data(), configs.size(), &configCount))
return false;
EGLint surfaceAttributes[] = {
EGL_WIDTH, 1,
EGL_HEIGHT, 1,
EGL_NONE
};
std::size_t configIndex = 0;
for (; configIndex < configCount; ++configIndex)
{
m_surface = m_loader.eglCreatePbufferSurface(m_display, configs[configIndex], surfaceAttributes);
if (m_surface)
break;
}
return CreateInternal(configs[configIndex], shareContext);
}
bool EGLContextBase::Create(const ContextParams& params, WindowHandle window, const EGLContextBase* shareContext)
{
NazaraError("Unexpected context creation call");
return false;
}
void EGLContextBase::Destroy()
{
if (m_handle)
{
assert(m_display);
OnContextRelease();
NotifyContextDestruction(this);
m_loader.eglDestroyContext(m_display, m_handle);
m_handle = nullptr;
}
if (m_surface)
{
assert(m_display);
m_loader.eglDestroySurface(m_display, m_surface);
m_surface = nullptr;
}
if (m_display)
m_loader.eglTerminate(m_display);
}
void EGLContextBase::EnableVerticalSync(bool enabled)
{
// TODO
}
void EGLContextBase::SwapBuffers()
{
m_loader.eglSwapBuffers(m_display, m_surface);
// TODO
}
bool EGLContextBase::BindAPI()
{
if (m_params.type == ContextType::OpenGL_ES)
{
if (m_loader.eglBindAPI(EGL_OPENGL_ES_API) == EGL_TRUE)
return true;
if (m_loader.eglBindAPI(EGL_OPENGL_API) == EGL_TRUE)
{
m_params.type = ContextType::OpenGL;
return true;
}
NazaraError("neither OpenGL nor OpenGL ES are supported");
return false;
}
else
{
if (m_loader.eglBindAPI(EGL_OPENGL_API) != EGL_TRUE)
{
NazaraError("OpenGL is not supported");
return false;
}
return true;
}
}
bool EGLContextBase::ChooseConfig(EGLConfig* configs, std::size_t maxConfigCount, std::size_t* configCount)
{
EGLint configAttributes[] =
{
EGL_BUFFER_SIZE, EGLint(m_params.bitsPerPixel),
EGL_DEPTH_SIZE, m_params.depthBits,
EGL_RENDERABLE_TYPE, (m_params.type == ContextType::OpenGL_ES) ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_BIT,
EGL_SAMPLE_BUFFERS, EGLint((m_params.sampleCount > 1) ? 1 : 0),
EGL_SAMPLES, EGLint((m_params.sampleCount > 1) ? m_params.sampleCount : 0),
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT | EGL_WINDOW_BIT,
EGL_STENCIL_SIZE, EGLint(m_params.stencilBits),
EGL_NONE
};
EGLint numConfig = 0;
if (m_loader.eglChooseConfig(m_display, configAttributes, configs, maxConfigCount, &numConfig) != GL_TRUE)
{
NazaraError(std::string("failed to retrieve compatible EGL configurations: ") + EGLLoader::TranslateError(m_loader.eglGetError()));
return false;
}
if (numConfig == 0)
{
NazaraError("no supported configuration matches required attributes");
return false;
}
*configCount = numConfig;
return true;
}
bool EGLContextBase::CreateInternal(EGLConfig config, const EGLContextBase* shareContext)
{
struct Version
{
unsigned int major;
unsigned int minor;
};
if (m_params.type == ContextType::OpenGL_ES)
{
// Create OpenGL ES context
std::array<Version, 3> supportedGL_ESVersions = {
{
{ 3, 2 },
{ 3, 1 },
{ 3, 0 }
}
};
for (const Version& version : supportedGL_ESVersions)
{
if (m_params.glMajorVersion != 0)
{
if (version.major > m_params.glMajorVersion)
continue;
if (m_params.glMinorVersion != 0 && version.minor > m_params.glMinorVersion)
continue;
}
std::array<int, 2 * 2 + 1> attributes = {
EGL_CONTEXT_MAJOR_VERSION, int(version.major),
EGL_CONTEXT_MINOR_VERSION, int(version.minor),
EGL_NONE
};
m_handle = m_loader.eglCreateContext(m_display, config, (shareContext) ? shareContext->m_handle : nullptr, attributes.data());
if (m_handle)
break;
}
}
else
{
// Create OpenGL ES 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 (m_params.glMajorVersion != 0)
{
if (version.major > m_params.glMajorVersion)
continue;
if (m_params.glMinorVersion != 0 && version.minor > m_params.glMinorVersion)
continue;
}
std::array<int, 3 * 2 + 1> attributes = {
EGL_CONTEXT_MAJOR_VERSION, int(version.major),
EGL_CONTEXT_MINOR_VERSION, int(version.minor),
EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
EGL_NONE
};
m_handle = m_loader.eglCreateContext(m_display, config, (shareContext) ? shareContext->m_handle : nullptr, attributes.data());
if (m_handle)
break;
}
}
if (!m_handle)
{
NazaraError(std::string("failed to create EGL context: ") + EGLLoader::TranslateError(m_loader.eglGetError()));
return false;
}
LoadEGLExt();
return true;
}
bool EGLContextBase::InitDisplay()
{
EGLint major, minor;
if (m_loader.eglInitialize(m_display, &major, &minor) != EGL_TRUE)
{
NazaraError("failed to retrieve default EGL display");
return false;
}
const char* vendor = m_loader.eglQueryString(m_display, EGL_VENDOR);
NazaraNotice("Initialized EGL " + std::to_string(major) + "." + std::to_string(minor) + " display (" + vendor + ")");
return true;
}
bool EGLContextBase::ImplementFallback(const std::string_view& function)
{
if (Context::ImplementFallback(function))
return true;
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)
{
const EGLContextBase* context = static_cast<const EGLContextBase*>(GetCurrentContext());
assert(context);
context->fallbacks.glClearDepth(depth);
};
}
return true;
}
bool EGLContextBase::Activate() const
{
EGLBoolean succeeded = m_loader.eglMakeCurrent(m_display, m_surface, m_surface, m_handle);
if (succeeded != EGL_TRUE)
{
NazaraError("failed to activate context");
return false;
}
return true;
}
void EGLContextBase::Desactivate() const
{
assert(GetCurrentContext() == this);
EGLBoolean succeeded = m_loader.eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (succeeded != EGL_TRUE)
NazaraError("failed to desactivate context");
}
const Loader& EGLContextBase::GetLoader()
{
return m_loader;
}
bool EGLContextBase::LoadEGLExt()
{
if (!Activate())
return false;
const char* extensionString = m_loader.eglQueryString(m_display, EGL_EXTENSIONS);
if (extensionString)
{
SplitString(extensionString, " ", [&](std::string_view extension)
{
m_supportedPlatformExtensions.emplace(extension);
return true;
});
}
return true;
}
}

View File

@@ -0,0 +1,158 @@
// 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/EGL/EGLLoader.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/OpenGLRenderer/Wrapper/EGL/EGLContextBase.hpp>
#ifdef NAZARA_PLATFORM_LINUX
#include <Nazara/OpenGLRenderer/Wrapper/Linux/EGLContextX11.hpp>
#include <Nazara/OpenGLRenderer/Wrapper/Linux/EGLContextWayland.hpp>
#endif
#ifdef NAZARA_PLATFORM_WINDOWS
#include <Nazara/OpenGLRenderer/Wrapper/Win32/EGLContextWin32.hpp>
#endif
#include <Nazara/OpenGLRenderer/Debug.hpp>
namespace Nz::GL
{
EGLLoader::EGLLoader(DynLib& /*openglLib*/)
{
if (!m_eglLib.Load("libEGL"))
throw std::runtime_error("failed to load gdi32.dll: " + m_eglLib.GetLastError());
auto LoadSymbol = [](DynLib& lib, auto& func, const char* funcName)
{
func = reinterpret_cast<std::decay_t<decltype(func)>>(lib.GetSymbol(funcName));
if (!func)
throw std::runtime_error("failed to load core function " + std::string(funcName));
};
// Load gdi32 functions
#define NAZARA_OPENGLRENDERER_EXT_BEGIN(ext)
#define NAZARA_OPENGLRENDERER_EXT_END()
#define NAZARA_OPENGLRENDERER_EXT_FUNC(name, sig) //< Ignore extensions
// Load base WGL functions
#define NAZARA_OPENGLRENDERER_FUNC(name, sig) LoadSymbol(m_eglLib, name, #name);
NAZARA_OPENGLRENDERER_FOREACH_EGL_FUNC(NAZARA_OPENGLRENDERER_FUNC, NAZARA_OPENGLRENDERER_EXT_BEGIN, NAZARA_OPENGLRENDERER_EXT_END, NAZARA_OPENGLRENDERER_EXT_FUNC)
#undef NAZARA_OPENGLRENDERER_FUNC
#undef NAZARA_OPENGLRENDERER_EXT_BEGIN
#undef NAZARA_OPENGLRENDERER_EXT_END
#undef NAZARA_OPENGLRENDERER_EXT_FUNC
// Try to create a dummy context in order to check EGL support (FIXME: is this really necessary?)
ContextParams params;
EGLContextBase baseContext(nullptr, *this);
if (!baseContext.Create(params))
throw std::runtime_error("failed to create load context");
if (!baseContext.Initialize(params))
throw std::runtime_error("failed to load OpenGL functions");
}
std::unique_ptr<Context> EGLLoader::CreateContext(const OpenGLDevice* device, const ContextParams& params, Context* shareContext) const
{
std::unique_ptr<EGLContextBase> context;
#ifdef NAZARA_PLATFORM_WINDOWS
// On Windows context sharing seems to work only with window contexts
context = std::make_unique<EGLContextWin32>(device, *this);
#else
context = std::make_unique<EGLContextBase>(device, *this);
#endif
if (!context->Create(params, static_cast<EGLContextBase*>(shareContext)))
{
NazaraError("failed to create context");
return {};
}
if (!context->Initialize(params))
{
NazaraError("failed to initialize context");
return {};
}
return context;
}
std::unique_ptr<Context> EGLLoader::CreateContext(const OpenGLDevice* device, const ContextParams& params, WindowHandle handle, Context* shareContext) const
{
std::unique_ptr<EGLContextBase> context;
switch (handle.type)
{
case WindowManager::Invalid:
break;
case WindowManager::X11:
#ifdef NAZARA_PLATFORM_LINUX
context = std::make_unique<EGLContextX11>(device, *this);
#endif
break;
case WindowManager::Windows:
#ifdef NAZARA_PLATFORM_WINDOWS
context = std::make_unique<EGLContextWin32>(device, *this);
#endif
break;
case WindowManager::Wayland:
#ifdef NAZARA_PLATFORM_LINUX
context = std::make_unique<EGLContextWayland>(device, *this);
#endif
break;
}
if (!context)
{
NazaraError("unsupported window type");
return {};
}
if (!context->Create(params, handle, /*static_cast<EGLContextBase*>(shareContext)*/nullptr))
{
NazaraError("failed to create context");
return {};
}
if (!context->Initialize(params))
{
NazaraError("failed to initialize context");
return {};
}
return context;
}
GLFunction EGLLoader::LoadFunction(const char* name) const
{
return eglGetProcAddress(name);
}
const char* EGLLoader::TranslateError(EGLint errorId)
{
switch (errorId)
{
case EGL_SUCCESS: return "The last function succeeded without error.";
case EGL_NOT_INITIALIZED: return "EGL is not initialized, or could not be initialized, for the specified EGL display connection.";
case EGL_BAD_ACCESS: return "EGL cannot access a requested resource.";
case EGL_BAD_ALLOC: return "EGL failed to allocate resources for the requested operation.";
case EGL_BAD_ATTRIBUTE: return "An unrecognized attribute or attribute value was passed in the attribute list.";
case EGL_BAD_CONTEXT: return "An EGLContext argument does not name a valid EGL rendering context.";
case EGL_BAD_CONFIG: return "An EGLConfig argument does not name a valid EGL frame buffer configuration.";
case EGL_BAD_CURRENT_SURFACE: return "The current surface of the calling thread is a window, pixel buffer or pixmap that is no longer valid.";
case EGL_BAD_DISPLAY: return "An EGLDisplay argument does not name a valid EGL display connection.";
case EGL_BAD_SURFACE: return "An EGLSurface argument does not name a valid surface (window, pixel buffer or pixmap) configured for GL rendering.";
case EGL_BAD_MATCH: return "Arguments are inconsistent.";
case EGL_BAD_PARAMETER: return "One or more argument values are invalid.";
case EGL_BAD_NATIVE_PIXMAP: return "A NativePixmapType argument does not refer to a valid native pixmap.";
case EGL_BAD_NATIVE_WINDOW: return "A NativeWindowType argument does not refer to a valid native window.";
case EGL_CONTEXT_LOST: return "A power management event has occurred.";
default: return "Invalid or unknown error.";
}
}
}

View File

@@ -0,0 +1,23 @@
// 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/Linux/EGLContextWayland.hpp>
#include <Nazara/OpenGLRenderer/Wrapper/EGL/EGLLoader.hpp>
#include <Nazara/OpenGLRenderer/Debug.hpp>
namespace Nz::GL
{
bool EGLContextWayland::Create(const ContextParams& params, WindowHandle window, const EGLContextBase* shareContext)
{
assert(window.type == WindowManager::Wayland);
NazaraError("Wayland is not yet supported");
return false;
}
void EGLContextWayland::Destroy()
{
EGLContextBase::Destroy();
}
}

View File

@@ -0,0 +1,23 @@
// 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/Linux/EGLContextX11.hpp>
#include <Nazara/OpenGLRenderer/Wrapper/EGL/EGLLoader.hpp>
#include <Nazara/OpenGLRenderer/Debug.hpp>
namespace Nz::GL
{
bool EGLContextX11::Create(const ContextParams& params, WindowHandle window, const EGLContextBase* shareContext)
{
assert(window.type == WindowManager::X11);
NazaraError("X11 is not yet supported");
return false;
}
void EGLContextX11::Destroy()
{
EGLContextBase::Destroy();
}
}

View File

@@ -20,23 +20,27 @@ namespace Nz::GL
bool WGLContext::Create(const WGLContext* baseContext, const ContextParams& params, const WGLContext* shareContext)
{
// 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)
HWNDHandle window(::CreateWindowA("STATIC", nullptr, WS_DISABLED | WS_POPUP, 0, 0, 1, 1, nullptr, nullptr, GetModuleHandle(nullptr), nullptr));
if (!window)
{
NazaraError("failed to create dummy window: " + Error::GetLastSystemError());
return false;
}
::ShowWindow(m_window.get(), FALSE);
::ShowWindow(window.get(), FALSE);
m_deviceContext = ::GetDC(m_window.get());
m_deviceContext = ::GetDC(window.get());
if (!m_deviceContext)
{
NazaraError("failed to retrieve dummy window device context: " + Error::GetLastSystemError());
return false;
}
return CreateInternal(baseContext, params, shareContext);
if (!CreateInternal(baseContext, params, shareContext))
return false;
m_window = std::move(window);
return true;
}
bool WGLContext::Create(const WGLContext* baseContext, const ContextParams& params, WindowHandle window, const WGLContext* shareContext)

View File

@@ -0,0 +1,83 @@
// 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/Win32/EGLContextWin32.hpp>
#include <Nazara/OpenGLRenderer/Wrapper/EGL/EGLLoader.hpp>
#include <Nazara/OpenGLRenderer/Debug.hpp>
namespace Nz::GL
{
bool EGLContextWin32::Create(const ContextParams& params, const EGLContextBase* shareContext)
{
// It seems context sharing between pbuffer context and window context doesn't work, create an hidden window to handle this
HWNDHandle window(::CreateWindowA("STATIC", nullptr, WS_DISABLED | WS_POPUP, 0, 0, 1, 1, nullptr, nullptr, GetModuleHandle(nullptr), nullptr));
if (!window)
{
NazaraError("failed to create dummy window: " + Error::GetLastSystemError());
return false;
}
::ShowWindow(window.get(), FALSE);
WindowHandle windowHandle;
windowHandle.type = WindowManager::Windows;
windowHandle.windows.window = window.get();
if (!Create(params, windowHandle, shareContext))
return false;
m_ownedWindow = std::move(window);
return true;
}
bool EGLContextWin32::Create(const ContextParams& params, WindowHandle window, const EGLContextBase* shareContext)
{
assert(window.type == WindowManager::Windows);
Destroy(); //< In case a previous display or surface hasn't been released
m_params = params;
if (!BindAPI())
return false;
HWND windowHandle = static_cast<HWND>(window.windows.window);
HDC deviceContext = ::GetDC(windowHandle);
if (!deviceContext)
{
NazaraError("failed to retrieve window device context: " + Error::GetLastSystemError());
return false;
}
m_display = m_loader.eglGetDisplay(deviceContext);
if (!InitDisplay())
return false;
std::size_t configCount;
std::array<EGLConfig, 0xFF> configs;
if (!ChooseConfig(configs.data(), configs.size(), &configCount))
return false;
EGLint surfaceAttributes[] = {
EGL_NONE
};
std::size_t configIndex = 0;
for (; configIndex < configCount; ++configIndex)
{
m_surface = m_loader.eglCreateWindowSurface(m_display, configs[configIndex], windowHandle, surfaceAttributes);
if (m_surface)
break;
}
return CreateInternal(configs[configIndex], shareContext);
}
void EGLContextWin32::Destroy()
{
EGLContextBase::Destroy();
m_ownedWindow.reset();
}
}