415 lines
11 KiB
C++
415 lines
11 KiB
C++
// Copyright (C) 2024 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
|
|
// 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/WGL/WGLContext.hpp>
|
|
#include <Nazara/Core/StringExt.hpp>
|
|
#include <Nazara/OpenGLRenderer/Wrapper/WGL/WGLLoader.hpp>
|
|
#include <NazaraUtils/CallOnExit.hpp>
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <Nazara/OpenGLRenderer/Debug.hpp>
|
|
|
|
namespace Nz::GL
|
|
{
|
|
WGLContext::~WGLContext()
|
|
{
|
|
Destroy();
|
|
}
|
|
|
|
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
|
|
HWNDHandle window(::CreateWindowA("STATIC", nullptr, WS_DISABLED | WS_POPUP, 0, 0, 1, 1, nullptr, nullptr, GetModuleHandle(nullptr), nullptr));
|
|
if (!window)
|
|
{
|
|
NazaraErrorFmt("failed to create dummy window: {0}", Error::GetLastSystemError());
|
|
return false;
|
|
}
|
|
|
|
::ShowWindow(window.get(), FALSE);
|
|
|
|
m_deviceContext = ::GetDC(window.get());
|
|
if (!m_deviceContext)
|
|
{
|
|
NazaraErrorFmt("failed to retrieve dummy window device context: {0}", Error::GetLastSystemError());
|
|
return false;
|
|
}
|
|
|
|
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)
|
|
{
|
|
NazaraAssert(window.type == WindowBackend::Windows, "expected Windows window");
|
|
|
|
Destroy();
|
|
|
|
m_deviceContext = ::GetDC(static_cast<HWND>(window.windows.window));
|
|
if (!m_deviceContext)
|
|
{
|
|
NazaraErrorFmt("failed to retrieve window device context: {0}", Error::GetLastSystemError());
|
|
return false;
|
|
}
|
|
|
|
return CreateInternal(baseContext, params, shareContext);
|
|
}
|
|
|
|
void WGLContext::Destroy()
|
|
{
|
|
if (m_handle)
|
|
{
|
|
OnContextRelease();
|
|
NotifyContextDestruction(this);
|
|
|
|
m_loader.wglDeleteContext(m_handle);
|
|
m_handle = nullptr;
|
|
}
|
|
}
|
|
|
|
PresentModeFlags WGLContext::GetSupportedPresentModes() const
|
|
{
|
|
PresentModeFlags supportedModes = PresentMode::Immediate;
|
|
if (wglSwapIntervalEXT)
|
|
{
|
|
supportedModes |= PresentMode::VerticalSync;
|
|
if (HasPlatformExtension("WGL_EXT_swap_control_tear"))
|
|
supportedModes |= PresentMode::RelaxedVerticalSync;
|
|
}
|
|
|
|
return supportedModes;
|
|
}
|
|
|
|
void WGLContext::SetPresentMode(PresentMode presentMode)
|
|
{
|
|
if (!SetCurrentContext(this))
|
|
return;
|
|
|
|
int interval = 0;
|
|
switch (presentMode)
|
|
{
|
|
case PresentMode::Immediate: interval = 0; break;
|
|
case PresentMode::RelaxedVerticalSync: interval = -1; break; //< WGL_EXT_swap_control_tear
|
|
case PresentMode::VerticalSync: interval = 1; break;
|
|
default: return; // TODO: Unreachable
|
|
}
|
|
|
|
wglSwapIntervalEXT(interval);
|
|
}
|
|
|
|
void WGLContext::SwapBuffers()
|
|
{
|
|
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, int(version.major),
|
|
WGL_CONTEXT_MINOR_VERSION_ARB, int(version.minor),
|
|
|
|
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_ES_PROFILE_BIT_EXT
|
|
};
|
|
|
|
m_handle = baseContext->wglCreateContextAttribsARB(m_deviceContext, (shareContext) ? shareContext->m_handle : 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, int(version.major),
|
|
WGL_CONTEXT_MINOR_VERSION_ARB, int(version.minor),
|
|
|
|
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB
|
|
};
|
|
|
|
m_handle = baseContext->wglCreateContextAttribsARB(m_deviceContext, (shareContext) ? shareContext->m_handle : nullptr, attributes.data());
|
|
if (m_handle)
|
|
{
|
|
m_params.type = ContextType::OpenGL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!m_handle)
|
|
{
|
|
NazaraErrorFmt("failed to create WGL context: {0}", Error::GetLastSystemError());
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_handle = m_loader.wglCreateContext(m_deviceContext);
|
|
if (!m_handle)
|
|
{
|
|
NazaraErrorFmt("failed to create WGL context: {0}", Error::GetLastSystemError());
|
|
return false;
|
|
}
|
|
|
|
if (shareContext)
|
|
{
|
|
if (!m_loader.wglShareLists(shareContext->m_handle, m_handle))
|
|
{
|
|
NazaraErrorFmt("failed to share context objects: {0}", Error::GetLastSystemError());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
m_params.type = ContextType::OpenGL;
|
|
}
|
|
|
|
LoadWGLExt();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WGLContext::ImplementFallback(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 WGLContext* context = SafeCast<const WGLContext*>(GetCurrentContext());
|
|
assert(context);
|
|
context->fallbacks.glClearDepth(depth);
|
|
};
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WGLContext::Activate() const
|
|
{
|
|
bool succeeded = m_loader.wglMakeCurrent(m_deviceContext, m_handle);
|
|
if (!succeeded)
|
|
{
|
|
NazaraErrorFmt("failed to activate context: {0}", Error::GetLastSystemError());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WGLContext::Desactivate() const
|
|
{
|
|
assert(GetCurrentContext() == this);
|
|
m_loader.wglMakeCurrent(nullptr, nullptr);
|
|
}
|
|
|
|
const Loader& WGLContext::GetLoader()
|
|
{
|
|
return m_loader;
|
|
}
|
|
|
|
bool WGLContext::LoadWGLExt()
|
|
{
|
|
if (!SetCurrentContext(this))
|
|
return false;
|
|
|
|
#define NAZARA_OPENGLRENDERER_FUNC(name, sig)
|
|
#define NAZARA_OPENGLRENDERER_EXT_BEGIN(ext)
|
|
#define NAZARA_OPENGLRENDERER_EXT_END()
|
|
#define NAZARA_OPENGLRENDERER_EXT_FUNC(name, sig) name = reinterpret_cast<sig>(m_loader.wglGetProcAddress(#name));
|
|
NAZARA_OPENGLRENDERER_FOREACH_WGL_FUNC(NAZARA_OPENGLRENDERER_FUNC, NAZARA_OPENGLRENDERER_EXT_BEGIN, NAZARA_OPENGLRENDERER_EXT_END, NAZARA_OPENGLRENDERER_EXT_FUNC)
|
|
#undef NAZARA_OPENGLRENDERER_EXT_BEGIN
|
|
#undef NAZARA_OPENGLRENDERER_EXT_END
|
|
#undef NAZARA_OPENGLRENDERER_EXT_FUNC
|
|
#undef NAZARA_OPENGLRENDERER_FUNC
|
|
|
|
const char* extensionString = nullptr;
|
|
if (wglGetExtensionsStringARB)
|
|
extensionString = wglGetExtensionsStringARB(m_deviceContext);
|
|
else if (wglGetExtensionsStringEXT)
|
|
extensionString = wglGetExtensionsStringEXT();
|
|
|
|
if (extensionString)
|
|
{
|
|
SplitString(extensionString, " ", [&](std::string_view extension)
|
|
{
|
|
m_supportedPlatformExtensions.emplace(extension);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WGLContext::SetPixelFormat()
|
|
{
|
|
PIXELFORMATDESCRIPTOR descriptor = {};
|
|
descriptor.nSize = sizeof(PIXELFORMATDESCRIPTOR);
|
|
descriptor.nVersion = 1;
|
|
|
|
int pixelFormat = 0;
|
|
if (m_params.sampleCount > 1)
|
|
{
|
|
const WGLContext* currentContext = SafeCast<const WGLContext*>(GetCurrentContext()); //< Pay TLS cost only once
|
|
if (currentContext)
|
|
{
|
|
// WGL_ARB_pixel_format and WGL_EXT_pixel_format are the same, except for the symbol
|
|
auto wglChoosePixelFormat = (currentContext->wglChoosePixelFormatARB) ? currentContext->wglChoosePixelFormatARB : currentContext->wglChoosePixelFormatEXT;
|
|
|
|
if (wglChoosePixelFormat)
|
|
{
|
|
std::array<int, 10 * 2 + 1> attributes = {
|
|
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
|
|
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
|
|
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
|
|
WGL_COLOR_BITS_ARB, int((m_params.bitsPerPixel == 32) ? 24 : m_params.bitsPerPixel),
|
|
WGL_ALPHA_BITS_ARB, int((m_params.bitsPerPixel == 32) ? 8 : 0),
|
|
WGL_DEPTH_BITS_ARB, int(m_params.depthBits),
|
|
WGL_STENCIL_BITS_ARB, int(m_params.stencilBits),
|
|
WGL_DOUBLE_BUFFER_ARB, int((m_params.doubleBuffering) ? GL_TRUE : GL_FALSE),
|
|
WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
|
|
WGL_SAMPLES_ARB, int(m_params.sampleCount)
|
|
};
|
|
|
|
int& sampleCount = attributes[attributes.size() - 3];
|
|
|
|
do
|
|
{
|
|
UINT formatCount;
|
|
if (currentContext->wglChoosePixelFormatARB(m_deviceContext, attributes.data(), nullptr, 1, &pixelFormat, &formatCount))
|
|
{
|
|
if (formatCount > 0)
|
|
break;
|
|
}
|
|
|
|
sampleCount--;
|
|
}
|
|
while (sampleCount > 1);
|
|
|
|
if (int(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((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 (m_params.bitsPerPixel == 32)
|
|
descriptor.cAlphaBits = 8;
|
|
|
|
if (m_params.doubleBuffering)
|
|
descriptor.dwFlags |= PFD_DOUBLEBUFFER;
|
|
|
|
pixelFormat = m_loader.ChoosePixelFormat(m_deviceContext, &descriptor);
|
|
if (pixelFormat == 0)
|
|
{
|
|
NazaraErrorFmt("failed to choose pixel format: {0}", Error::GetLastSystemError());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!m_loader.SetPixelFormat(m_deviceContext, pixelFormat, &descriptor))
|
|
{
|
|
NazaraErrorFmt("failed to choose pixel format: {0}", Error::GetLastSystemError());
|
|
return false;
|
|
}
|
|
|
|
if (m_loader.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;
|
|
}
|
|
}
|
|
|
|
#include <Nazara/Core/AntiWindows.hpp>
|