// 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 #include #include #include #include #include #include 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(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 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, 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 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, 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(m_loader.LoadFunction("glClearDepth")); if (!fallbacks.glClearDepth) return false; glClearDepthf = [](GLfloat depth) { const WGLContext* context = SafeCast(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(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(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 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