437 lines
17 KiB
C++
437 lines
17 KiB
C++
#include <mbgl/util/logging.hpp>
|
|
|
|
#include <cassert>
|
|
|
|
#include <mbgl/gl/headless_backend.hpp>
|
|
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
#define WIN32_LEAN_AND_MEAN 1
|
|
#include <Windows.h>
|
|
#endif
|
|
|
|
#include "gl_functions_wgl.h"
|
|
|
|
namespace mbgl {
|
|
namespace gl {
|
|
|
|
// This class provides a singleton that contains information about the
|
|
// configuration used for instantiating new headless rendering contexts.
|
|
class WGLDisplayConfig {
|
|
private:
|
|
// Key for singleton construction.
|
|
struct Key {
|
|
explicit Key() = default;
|
|
};
|
|
|
|
WNDCLASSEXA helperWindowClass = {sizeof(WNDCLASSEXA)};
|
|
HWND helperWindowHandle = NULL;
|
|
HDC helperWindowDeviceContext = NULL;
|
|
HGLRC helperWindowRenderingContext = NULL;
|
|
|
|
private:
|
|
void CreateHelperWindow() {
|
|
MSG message;
|
|
|
|
helperWindowClass.style = CS_OWNDC;
|
|
helperWindowClass.lpfnWndProc = [](HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
|
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
};
|
|
helperWindowClass.lpszClassName = "WGL Helper Window";
|
|
GetModuleHandleExA(0, NULL, &helperWindowClass.hInstance);
|
|
|
|
if (!RegisterClassExA(&helperWindowClass)) {
|
|
Log::Error(Event::OpenGL, "Failed to register helper window class");
|
|
throw std::runtime_error("Failed to register helper window class");
|
|
}
|
|
|
|
helperWindowHandle = CreateWindowExA(0,
|
|
helperWindowClass.lpszClassName,
|
|
"WGL Helper Window",
|
|
WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
|
|
0,
|
|
0,
|
|
1,
|
|
1,
|
|
NULL,
|
|
NULL,
|
|
helperWindowClass.hInstance,
|
|
NULL);
|
|
|
|
if (!helperWindowHandle) {
|
|
Log::Error(Event::OpenGL, "Failed to create helper window");
|
|
throw std::runtime_error("Failed to create helper window");
|
|
}
|
|
|
|
ShowWindow(helperWindowHandle, SW_HIDE);
|
|
|
|
while (PeekMessage(&message, helperWindowHandle, 0, 0, PM_REMOVE)) {
|
|
DispatchMessage(&message);
|
|
}
|
|
}
|
|
|
|
void DestroyHelperWindow() {
|
|
if (helperWindowDeviceContext) {
|
|
ReleaseDC(helperWindowHandle, helperWindowDeviceContext);
|
|
DeleteDC(helperWindowDeviceContext);
|
|
}
|
|
|
|
DestroyWindow(helperWindowHandle);
|
|
UnregisterClassA(helperWindowClass.lpszClassName, helperWindowClass.hInstance);
|
|
}
|
|
|
|
bool StringInExtensionString(const char* string, const char* extensions) {
|
|
const char* start = extensions;
|
|
|
|
while (true) {
|
|
const char* where;
|
|
const char* terminator;
|
|
|
|
where = strstr(start, string);
|
|
if (!where) return false;
|
|
|
|
terminator = where + strlen(string);
|
|
if (where == start || *(where - 1) == ' ') {
|
|
if (*terminator == ' ' || *terminator == '\0') break;
|
|
}
|
|
|
|
start = terminator;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool extensionSupportedWGL(const char* extension) {
|
|
const char* extensions = NULL;
|
|
|
|
extensions = mbgl::platform::wglGetExtensionsStringARB(wglGetCurrentDC());
|
|
|
|
if (!extensions || !StringInExtensionString(extension, extensions))
|
|
extensions = mbgl::platform::wglGetExtensionsStringEXT();
|
|
|
|
if (!extensions || !StringInExtensionString(extension, extensions))
|
|
extensions = (const char*)wgl_glGetString(GL_EXTENSIONS);
|
|
|
|
if (!extensions) return false;
|
|
|
|
return StringInExtensionString(extension, extensions);
|
|
}
|
|
|
|
public:
|
|
explicit WGLDisplayConfig(Key) {
|
|
HDC dummyDC, previousDC;
|
|
HGLRC dummyRC, previousRC;
|
|
PIXELFORMATDESCRIPTOR pfd;
|
|
|
|
CreateHelperWindow();
|
|
|
|
dummyDC = GetDC(helperWindowHandle);
|
|
|
|
ZeroMemory(&pfd, sizeof(pfd));
|
|
|
|
pfd.nSize = sizeof(pfd);
|
|
pfd.nVersion = 1;
|
|
pfd.dwFlags = PFD_GENERIC_ACCELERATED | PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
|
|
pfd.iPixelType = PFD_TYPE_RGBA;
|
|
pfd.cColorBits = 24;
|
|
pfd.cAlphaBits = 8;
|
|
pfd.cDepthBits = 24;
|
|
pfd.cStencilBits = 8;
|
|
|
|
// Note: we're choosing an arbitrary pixel format, since we're not using
|
|
// the default surface anyway; all rendering will be directed to
|
|
// framebuffers which have their own configuration.
|
|
if (!SetPixelFormat(dummyDC, ChoosePixelFormat(dummyDC, &pfd), &pfd)) {
|
|
Log::Error(Event::OpenGL, "Failed to set pixel format for dummy context");
|
|
throw std::runtime_error("Failed to set pixel format for dummy context");
|
|
}
|
|
|
|
dummyRC = wglCreateContext(dummyDC);
|
|
|
|
if (!dummyRC) {
|
|
Log::Error(Event::OpenGL, "Failed to create dummy context");
|
|
throw std::runtime_error("Failed to create dummy context");
|
|
}
|
|
|
|
previousDC = wglGetCurrentDC();
|
|
previousRC = wglGetCurrentContext();
|
|
|
|
if (!wglMakeCurrent(dummyDC, dummyRC)) {
|
|
wglMakeCurrent(previousDC, previousRC);
|
|
wglDeleteContext(dummyRC);
|
|
|
|
DestroyHelperWindow();
|
|
|
|
Log::Error(Event::OpenGL, "Failed to make dummy context current");
|
|
throw std::runtime_error("Failed to make dummy context current");
|
|
}
|
|
|
|
ARB_multisample = extensionSupportedWGL("WGL_ARB_multisample");
|
|
ARB_create_context = extensionSupportedWGL("WGL_ARB_create_context");
|
|
ARB_create_context_profile = extensionSupportedWGL("WGL_ARB_create_context_profile");
|
|
EXT_colorspace = extensionSupportedWGL("WGL_EXT_colorspace");
|
|
ARB_pixel_format = extensionSupportedWGL("WGL_ARB_pixel_format");
|
|
ARB_ES3_compatibility = extensionSupportedWGL("GL_ARB_ES3_compatibility");
|
|
|
|
if (!ARB_create_context || !ARB_create_context_profile || !ARB_ES3_compatibility) {
|
|
Log::Error(Event::OpenGL, "OpenGL ES 3.0 is unavailable");
|
|
throw std::runtime_error("OpenGL ES 3.0 is unavailable");
|
|
}
|
|
|
|
DestroyHelperWindow();
|
|
|
|
renderingWindowClass.style = CS_OWNDC;
|
|
renderingWindowClass.lpfnWndProc = [](HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
|
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
};
|
|
renderingWindowClass.lpszClassName = "WGL Rendering Window";
|
|
GetModuleHandleExA(0, NULL, &renderingWindowClass.hInstance);
|
|
|
|
if (!RegisterClassExA(&renderingWindowClass)) {
|
|
Log::Error(Event::OpenGL, "Failed to register rendering window class");
|
|
throw std::runtime_error("Failed to register rendering window class");
|
|
}
|
|
}
|
|
|
|
~WGLDisplayConfig() { UnregisterClassA(renderingWindowClass.lpszClassName, renderingWindowClass.hInstance); }
|
|
|
|
static WGLDisplayConfig& create() {
|
|
static WGLDisplayConfig instance = WGLDisplayConfig{Key{}};
|
|
|
|
return instance;
|
|
}
|
|
|
|
public:
|
|
bool ARB_multisample = false;
|
|
bool ARB_create_context = false;
|
|
bool ARB_create_context_profile = false;
|
|
bool EXT_colorspace = false;
|
|
bool ARB_pixel_format = false;
|
|
bool ARB_ES3_compatibility = false;
|
|
WNDCLASSEXA renderingWindowClass = {sizeof(WNDCLASSEXA)};
|
|
};
|
|
|
|
class WGLBackendImpl final : public HeadlessBackend::Impl {
|
|
private:
|
|
WGLDisplayConfig& wglDisplayConfig = WGLDisplayConfig::create();
|
|
|
|
HWND renderingWindowHandle = NULL;
|
|
HDC renderingWindowDeviceContext = NULL;
|
|
HGLRC renderingWindowRenderingContext = NULL;
|
|
|
|
private:
|
|
void CreateRenderingWindow() {
|
|
MSG message;
|
|
|
|
renderingWindowHandle = CreateWindowExA(0,
|
|
wglDisplayConfig.renderingWindowClass.lpszClassName,
|
|
"WGL Render Window",
|
|
WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
|
|
0,
|
|
0,
|
|
1,
|
|
1,
|
|
NULL,
|
|
NULL,
|
|
wglDisplayConfig.renderingWindowClass.hInstance,
|
|
NULL);
|
|
|
|
if (!renderingWindowHandle) {
|
|
Log::Error(Event::OpenGL, "Failed to create helper window");
|
|
throw std::runtime_error("Failed to create helper window");
|
|
}
|
|
|
|
ShowWindow(renderingWindowHandle, SW_HIDE);
|
|
|
|
while (PeekMessage(&message, renderingWindowHandle, 0, 0, PM_REMOVE)) {
|
|
DispatchMessage(&message);
|
|
}
|
|
}
|
|
|
|
void DestroyRenderingWindow() {
|
|
if (renderingWindowDeviceContext && renderingWindowHandle) {
|
|
ReleaseDC(renderingWindowHandle, renderingWindowDeviceContext);
|
|
DeleteDC(renderingWindowDeviceContext);
|
|
renderingWindowDeviceContext = NULL;
|
|
}
|
|
|
|
if (renderingWindowHandle) {
|
|
DestroyWindow(renderingWindowHandle);
|
|
renderingWindowHandle = NULL;
|
|
}
|
|
}
|
|
|
|
HGLRC GetContext() {
|
|
if (!(renderingWindowRenderingContext || CreateRenderingContext())) {
|
|
Log::Error(Event::OpenGL, "Failed to create rendering context");
|
|
throw std::runtime_error("Failed to create rendering context");
|
|
}
|
|
|
|
return renderingWindowRenderingContext;
|
|
}
|
|
|
|
bool CreateRenderingContext() {
|
|
PIXELFORMATDESCRIPTOR pfd;
|
|
int pixelFormat;
|
|
UINT numFormats;
|
|
|
|
if (!renderingWindowHandle) {
|
|
CreateRenderingWindow();
|
|
}
|
|
|
|
if (!renderingWindowDeviceContext) {
|
|
renderingWindowDeviceContext = GetDC(renderingWindowHandle);
|
|
}
|
|
|
|
if (!mbgl::platform::wglChoosePixelFormatARB(renderingWindowDeviceContext,
|
|
std::initializer_list<GLint>({WGL_SUPPORT_OPENGL_ARB,
|
|
GL_TRUE,
|
|
WGL_DOUBLE_BUFFER_ARB,
|
|
GL_TRUE,
|
|
WGL_ACCELERATION_ARB,
|
|
WGL_FULL_ACCELERATION_ARB,
|
|
WGL_PIXEL_TYPE_ARB,
|
|
WGL_TYPE_RGBA_ARB,
|
|
WGL_COLOR_BITS_ARB,
|
|
24,
|
|
WGL_ALPHA_BITS_ARB,
|
|
8,
|
|
WGL_DEPTH_BITS_ARB,
|
|
24,
|
|
WGL_STENCIL_BITS_ARB,
|
|
8,
|
|
NULL})
|
|
.begin(),
|
|
NULL,
|
|
1,
|
|
&pixelFormat,
|
|
&numFormats)) {
|
|
Log::Error(Event::OpenGL, "Failed to choose pixel format for context");
|
|
throw std::runtime_error("Failed to choose pixel format for context");
|
|
}
|
|
|
|
ZeroMemory(&pfd, sizeof(pfd));
|
|
|
|
pfd.nSize = sizeof(pfd);
|
|
|
|
if (!DescribePixelFormat(renderingWindowDeviceContext, pixelFormat, sizeof(pfd), &pfd)) {
|
|
Log::Error(Event::OpenGL, "Failed to retrieve pixel format for context");
|
|
throw std::runtime_error("Failed to retrieve pixel format for context");
|
|
}
|
|
|
|
if (!SetPixelFormat(renderingWindowDeviceContext, pixelFormat, &pfd)) {
|
|
Log::Error(Event::OpenGL, "Failed to set pixel format for context");
|
|
throw std::runtime_error("Failed to set pixel format for context");
|
|
}
|
|
|
|
if (wglDisplayConfig.ARB_create_context) {
|
|
renderingWindowRenderingContext = mbgl::platform::wglCreateContextAttribsARB(
|
|
renderingWindowDeviceContext,
|
|
NULL,
|
|
std::initializer_list<int>({WGL_CONTEXT_MAJOR_VERSION_ARB,
|
|
3,
|
|
WGL_CONTEXT_MINOR_VERSION_ARB,
|
|
0,
|
|
WGL_CONTEXT_PROFILE_MASK_ARB,
|
|
WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
|
|
NULL})
|
|
.begin());
|
|
|
|
if (!renderingWindowRenderingContext) {
|
|
const DWORD error = GetLastError();
|
|
|
|
if (error == (0xc0070000 | ERROR_INVALID_VERSION_ARB)) {
|
|
Log::Error(Event::OpenGL, "Driver does not support OpenGL ES version 2.0");
|
|
throw std::runtime_error("Driver does not support OpenGL ES version 2.0");
|
|
} else if (error == (0xc0070000 | ERROR_INVALID_PROFILE_ARB)) {
|
|
Log::Error(Event::OpenGL, "Driver does not support the requested OpenGL profile");
|
|
throw std::runtime_error("Driver does not support the requested OpenGL profile");
|
|
} else if (error == (0xc0070000 | ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB)) {
|
|
Log::Error(Event::OpenGL,
|
|
"The share context is not compatible with the "
|
|
"requested context");
|
|
throw std::runtime_error(
|
|
"The share context is not compatible with the "
|
|
"requested context");
|
|
} else {
|
|
Log::Error(Event::OpenGL, "Failed to create OpenGL ES context");
|
|
throw std::runtime_error("Failed to create OpenGL ES context");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
} else {
|
|
renderingWindowRenderingContext = wglCreateContext(renderingWindowDeviceContext);
|
|
|
|
if (!renderingWindowRenderingContext) {
|
|
Log::Error(Event::OpenGL, "Failed to create OpenGL context");
|
|
throw std::runtime_error("Failed to create OpenGL context");
|
|
}
|
|
}
|
|
|
|
HGLRC currentRenderingContext = wglGetCurrentContext();
|
|
|
|
wglMakeCurrent(NULL, NULL);
|
|
wglDeleteContext(currentRenderingContext);
|
|
|
|
wglMakeCurrent(renderingWindowDeviceContext, renderingWindowRenderingContext);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DestroyRenderingContext() {
|
|
if (renderingWindowRenderingContext && !wglMakeCurrent(renderingWindowDeviceContext, NULL)) {
|
|
Log::Error(Event::OpenGL, "Failed to make context current");
|
|
// throw std::runtime_error("Failed to make context current");
|
|
return false;
|
|
}
|
|
|
|
if (renderingWindowRenderingContext && !wglDeleteContext(renderingWindowRenderingContext)) {
|
|
Log::Error(Event::OpenGL, "Failed to delete current context");
|
|
// throw std::runtime_error("Failed to delete current context");
|
|
return false;
|
|
}
|
|
|
|
renderingWindowRenderingContext = NULL;
|
|
|
|
return true;
|
|
}
|
|
|
|
public:
|
|
WGLBackendImpl() { auto renderingContext = GetContext(); }
|
|
|
|
~WGLBackendImpl() final {
|
|
if (renderingWindowRenderingContext) {
|
|
DestroyRenderingContext();
|
|
}
|
|
|
|
DestroyRenderingWindow();
|
|
}
|
|
|
|
gl::ProcAddress getExtensionFunctionPointer(const char* name) final {
|
|
return (ProcAddress)::wgl_GetProcAddress(name);
|
|
}
|
|
|
|
void activateContext() final {
|
|
if (renderingWindowRenderingContext &&
|
|
!wglMakeCurrent(renderingWindowDeviceContext, renderingWindowRenderingContext)) {
|
|
Log::Error(Event::OpenGL, "Switching OpenGL context failed");
|
|
// throw std::runtime_error("Switching OpenGL context failed");
|
|
}
|
|
}
|
|
|
|
void deactivateContext() final {
|
|
if (renderingWindowRenderingContext && !wglMakeCurrent(NULL, NULL)) {
|
|
Log::Error(Event::OpenGL, "Removing OpenGL context failed");
|
|
// throw std::runtime_error("Removing OpenGL context failed");
|
|
}
|
|
}
|
|
};
|
|
|
|
void HeadlessBackend::createImpl() {
|
|
assert(!impl);
|
|
impl = std::make_unique<WGLBackendImpl>();
|
|
}
|
|
|
|
} // namespace gl
|
|
} // namespace mbgl
|