diff --git a/CMakeLists.txt b/CMakeLists.txt
index a95ad255d..fc93e954d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,6 +4,14 @@ project(duckstation C CXX)
# Pull in modules.
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules/")
+# Platform detection.
+if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+ set(LINUX TRUE)
+ set(SUPPORTS_X11 TRUE)
+endif()
+
+
+# Global options.
if(NOT ANDROID)
option(BUILD_SDL_FRONTEND "Build the SDL frontend" ON)
option(BUILD_QT_FRONTEND "Build the Qt frontend" ON)
@@ -12,6 +20,15 @@ if(NOT ANDROID)
endif()
+# OpenGL context creation methods.
+if(SUPPORTS_X11)
+ option(USE_X11 "Support X11 window system" ON)
+endif()
+if(LINUX OR ANDROID)
+ option(USE_EGL "Support EGL OpenGL context creation" ON)
+endif()
+
+
# Common include/library directories on Windows.
if(WIN32)
set(SDL2_FOUND TRUE)
@@ -40,12 +57,12 @@ if(NOT ANDROID)
endif()
endif()
-if(ANDROID)
+if(USE_EGL)
find_package(EGL REQUIRED)
-else()
- find_package(OpenGL COMPONENTS EGL GLX OpenGL)
endif()
-
+if(USE_X11)
+ find_package(X11 REQUIRED)
+endif()
# Set _DEBUG macro for Debug builds.
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG")
diff --git a/dep/glad/CMakeLists.txt b/dep/glad/CMakeLists.txt
index 57aa62eb6..f951c4cec 100644
--- a/dep/glad/CMakeLists.txt
+++ b/dep/glad/CMakeLists.txt
@@ -2,7 +2,13 @@ set(SRCS
src/glad.c
)
-add_library(glad ${SRCS})
+# Linking as a static library breaks on macOS, see https://github.com/libigl/libigl/issues/751
+if(APPLE)
+ add_library(glad OBJECT ${SRCS})
+else()
+ add_library(glad ${SRCS})
+endif()
+
target_include_directories(glad PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_include_directories(glad INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(glad PRIVATE Threads::Threads "${CMAKE_DL_LIBS}")
@@ -14,8 +20,7 @@ else()
target_sources(glad PRIVATE src/glad_egl.c)
target_link_libraries(glad PRIVATE EGL::EGL)
endif()
- if(USE_GLX)
+ if(SUPPORTS_X11)
target_sources(glad PRIVATE src/glad_glx.c)
- target_link_options(glad PRIVATE OpenGL::GLX)
endif()
endif()
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index c52df7f64..791a47e86 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -25,6 +25,8 @@ add_library(common
fifo_queue.h
file_system.cpp
file_system.h
+ gl/context.cpp
+ gl/context.h
gl/program.cpp
gl/program.h
gl/shader_cache.cpp
@@ -69,6 +71,8 @@ target_link_libraries(common PRIVATE glad libcue Threads::Threads cubeb libchdr)
if(WIN32)
target_sources(common PRIVATE
+ gl/context_wgl.cpp
+ gl/context_wgl.h
d3d11/shader_cache.cpp
d3d11/shader_cache.h
d3d11/shader_compiler.cpp
@@ -87,3 +91,49 @@ endif()
if(ANDROID)
target_link_libraries(common PRIVATE log)
endif()
+
+if(USE_X11)
+ target_sources(common PRIVATE
+ gl/x11_window.cpp
+ gl/x11_window.h
+ )
+ target_compile_definitions(common PRIVATE "-DUSE_X11=1")
+ target_include_directories(common PRIVATE "${X11_INCLUDE_DIR}")
+ target_link_libraries(common PRIVATE "${X11_LIBRARIES}")
+endif()
+
+if(USE_EGL)
+ target_sources(common PRIVATE
+ gl/context_egl.cpp
+ gl/context_egl.h
+ )
+ target_compile_definitions(common PRIVATE "-DUSE_EGL=1")
+
+ if(SUPPORTS_X11)
+ target_sources(common PRIVATE
+ gl/context_egl_x11.cpp
+ gl/context_egl_x11.h
+ )
+ endif()
+ if(ANDROID)
+ target_sources(common PRIVATE
+ gl/context_egl_android.cpp
+ gl/context_egl_android.h
+ )
+ endif()
+endif()
+
+if(SUPPORTS_X11)
+ target_sources(common PRIVATE
+ gl/context_glx.cpp
+ gl/context_glx.h
+ )
+ target_compile_definitions(common PRIVATE "-DUSE_GLX=1")
+endif()
+
+if(APPLE)
+ target_sources(common PRIVATE
+ gl/context_agl.mm
+ gl/context_agl.h
+ )
+endif()
diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj
index b098824f2..4174eb0f9 100644
--- a/src/common/common.vcxproj
+++ b/src/common/common.vcxproj
@@ -52,6 +52,8 @@
+
+
@@ -74,6 +76,7 @@
+
@@ -91,6 +94,8 @@
+
+
diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters
index f709f26c8..b9f894ac8 100644
--- a/src/common/common.vcxproj.filters
+++ b/src/common/common.vcxproj.filters
@@ -58,6 +58,13 @@
+
+ gl
+
+
+ gl
+
+
@@ -111,6 +118,12 @@
gl
+
+ gl
+
+
+ gl
+
diff --git a/src/common/gl/context.cpp b/src/common/gl/context.cpp
new file mode 100644
index 000000000..1e36f96a7
--- /dev/null
+++ b/src/common/gl/context.cpp
@@ -0,0 +1,190 @@
+#include "context.h"
+#include "../log.h"
+#include "glad.h"
+#include
+#ifdef __APPLE__
+#include
+#else
+#include
+#endif
+Log_SetChannel(GL::Context);
+
+#if defined(WIN32)
+#include "context_wgl.h"
+#elif defined(__APPLE__)
+#include "context_agl.h"
+#endif
+
+#ifdef USE_EGL
+#if defined(USE_X11)
+#include "context_egl_x11.h"
+#elif defined(ANDROID)
+#include "context_egl_android.h"
+#else
+#error Unknown EGL platform
+#endif
+#endif
+
+#ifdef USE_GLX
+#include "context_glx.h"
+#endif
+
+namespace GL {
+
+static bool ShouldPreferESContext()
+{
+#ifndef _MSC_VER
+ const char* value = std::getenv("PREFER_GLES_CONTEXT");
+ return (value && std::strcmp(value, "1") == 0);
+#else
+ char buffer[2] = {};
+ size_t buffer_size = sizeof(buffer);
+ getenv_s(&buffer_size, buffer, "PREFER_GLES_CONTEXT");
+ return (std::strcmp(buffer, "1") == 0);
+#endif
+}
+
+Context::Context(const WindowInfo& wi) : m_wi(wi) {}
+
+Context::~Context() = default;
+
+std::unique_ptr Context::Create(const WindowInfo& wi, const Version* versions_to_try,
+ size_t num_versions_to_try)
+{
+ if (ShouldPreferESContext())
+ {
+ // move ES versions to the front
+ Version* new_versions_to_try = static_cast(alloca(sizeof(Version) * num_versions_to_try));
+ size_t count = 0;
+ for (size_t i = 0; i < num_versions_to_try; i++)
+ {
+ if (versions_to_try[i].profile == Profile::ES)
+ new_versions_to_try[count++] = versions_to_try[i];
+ }
+ for (size_t i = 0; i < num_versions_to_try; i++)
+ {
+ if (versions_to_try[i].profile != Profile::ES)
+ new_versions_to_try[count++] = versions_to_try[i];
+ }
+ versions_to_try = new_versions_to_try;
+ }
+
+ std::unique_ptr context;
+#if defined(WIN32)
+ context = ContextWGL::Create(wi, versions_to_try, num_versions_to_try);
+#elif defined(__APPLE__)
+ context = ContextAGL::Create(wi, versions_to_try, num_versions_to_try);
+#else
+ if (wi.type == WindowInfo::Type::X11)
+ {
+#ifdef USE_EGL
+ const char* use_egl_x11 = std::getenv("USE_EGL_X11");
+ if (use_egl_x11 && std::strcmp(use_egl_x11, "1") == 0)
+ context = ContextEGLX11::Create(wi, versions_to_try, num_versions_to_try);
+ else
+ context = ContextGLX::Create(wi, versions_to_try, num_versions_to_try);
+#else
+ context = ContextGLX::Create(wi, versions_to_try, num_versions_to_try);
+#endif
+ }
+#endif
+
+ if (!context)
+ return nullptr;
+
+ Log_InfoPrintf("Created a %s context", context->IsGLES() ? "OpenGL ES" : "OpenGL");
+
+ // TODO: Not thread-safe.
+ static Context* context_being_created;
+ context_being_created = context.get();
+
+ // load up glad
+ if (!context->IsGLES())
+ {
+ if (!gladLoadGLLoader([](const char* name) { return context_being_created->GetProcAddress(name); }))
+ {
+ Log_ErrorPrintf("Failed to load GL functions for GLAD");
+ return nullptr;
+ }
+ }
+ else
+ {
+ if (!gladLoadGLES2Loader([](const char* name) { return context_being_created->GetProcAddress(name); }))
+ {
+ Log_ErrorPrintf("Failed to load GLES functions for GLAD");
+ return nullptr;
+ }
+ }
+
+ const char* gl_vendor = reinterpret_cast(glGetString(GL_VENDOR));
+ const char* gl_renderer = reinterpret_cast(glGetString(GL_RENDERER));
+ const char* gl_version = reinterpret_cast(glGetString(GL_VERSION));
+ Log_InfoPrintf("GL_VENDOR: %s", gl_vendor);
+ Log_InfoPrintf("GL_RENDERER: %s", gl_renderer);
+ Log_InfoPrintf("GL_VERSION: %s", gl_version);
+
+ return context;
+}
+
+const std::array& Context::GetAllDesktopVersionsList()
+{
+ static constexpr std::array vlist = {{{Profile::Core, 4, 6},
+ {Profile::Core, 4, 5},
+ {Profile::Core, 4, 4},
+ {Profile::Core, 4, 3},
+ {Profile::Core, 4, 2},
+ {Profile::Core, 4, 1},
+ {Profile::Core, 4, 0},
+ {Profile::Core, 3, 3},
+ {Profile::Core, 3, 2},
+ {Profile::Core, 3, 1},
+ {Profile::Core, 3, 0}}};
+ return vlist;
+}
+
+const std::array& Context::GetAllDesktopVersionsListWithFallback()
+{
+ static constexpr std::array vlist = {{{Profile::Core, 4, 6},
+ {Profile::Core, 4, 5},
+ {Profile::Core, 4, 4},
+ {Profile::Core, 4, 3},
+ {Profile::Core, 4, 2},
+ {Profile::Core, 4, 1},
+ {Profile::Core, 4, 0},
+ {Profile::Core, 3, 3},
+ {Profile::Core, 3, 2},
+ {Profile::Core, 3, 1},
+ {Profile::Core, 3, 0},
+ {Profile::NoProfile, 0, 0}}};
+ return vlist;
+}
+
+const std::array& Context::GetAllESVersionsList()
+{
+ static constexpr std::array vlist = {
+ {{Profile::ES, 3, 2}, {Profile::ES, 3, 1}, {Profile::ES, 3, 0}, {Profile::ES, 2, 0}}};
+ return vlist;
+}
+
+const std::array& Context::GetAllVersionsList()
+{
+ static constexpr std::array vlist = {{{Profile::Core, 4, 6},
+ {Profile::Core, 4, 5},
+ {Profile::Core, 4, 4},
+ {Profile::Core, 4, 3},
+ {Profile::Core, 4, 2},
+ {Profile::Core, 4, 1},
+ {Profile::Core, 4, 0},
+ {Profile::Core, 3, 3},
+ {Profile::Core, 3, 2},
+ {Profile::Core, 3, 1},
+ {Profile::Core, 3, 0},
+ {Profile::ES, 3, 2},
+ {Profile::ES, 3, 1},
+ {Profile::ES, 3, 0},
+ {Profile::ES, 2, 0},
+ {Profile::NoProfile, 0, 0}}};
+ return vlist;
+}
+
+} // namespace GL
diff --git a/src/common/gl/context.h b/src/common/gl/context.h
new file mode 100644
index 000000000..695ca5df6
--- /dev/null
+++ b/src/common/gl/context.h
@@ -0,0 +1,66 @@
+#pragma once
+#include "../types.h"
+#include "../window_info.h"
+#include
+#include
+
+namespace GL {
+class Context
+{
+public:
+ Context(const WindowInfo& wi);
+ virtual ~Context();
+
+ enum class Profile
+ {
+ NoProfile,
+ Core,
+ ES
+ };
+
+ struct Version
+ {
+ Profile profile;
+ int major_version;
+ int minor_version;
+ };
+
+ ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_wi; }
+ ALWAYS_INLINE bool IsGLES() const { return (m_version.profile == Profile::ES); }
+ ALWAYS_INLINE u32 GetSurfaceWidth() const { return m_wi.surface_width; }
+ ALWAYS_INLINE u32 GetSurfaceHeight() const { return m_wi.surface_height; }
+ ALWAYS_INLINE WindowInfo::SurfaceFormat GetSurfaceFormat() const { return m_wi.surface_format; }
+
+ virtual void* GetProcAddress(const char* name) = 0;
+ virtual bool ChangeSurface(const WindowInfo& new_wi) = 0;
+ virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) = 0;
+ virtual bool SwapBuffers() = 0;
+ virtual bool MakeCurrent() = 0;
+ virtual bool DoneCurrent() = 0;
+ virtual bool SetSwapInterval(s32 interval) = 0;
+ virtual std::unique_ptr CreateSharedContext(const WindowInfo& wi) = 0;
+
+ static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try,
+ size_t num_versions_to_try);
+
+ template
+ static std::unique_ptr Create(const WindowInfo& wi, const std::array& versions_to_try)
+ {
+ return Create(wi, versions_to_try.data(), versions_to_try.size());
+ }
+
+ static std::unique_ptr Create(const WindowInfo& wi) { return Create(wi, GetAllVersionsList()); }
+
+ static const std::array& GetAllDesktopVersionsList();
+ static const std::array& GetAllDesktopVersionsListWithFallback();
+ static const std::array& GetAllESVersionsList();
+ static const std::array& GetAllVersionsList();
+
+protected:
+#ifdef WIN32
+#endif
+
+ WindowInfo m_wi;
+ Version m_version = {};
+};
+} // namespace GL
diff --git a/src/common/gl/context_agl.h b/src/common/gl/context_agl.h
new file mode 100644
index 000000000..f5a9df280
--- /dev/null
+++ b/src/common/gl/context_agl.h
@@ -0,0 +1,48 @@
+#pragma once
+#include "context.h"
+#include
+
+#if defined(__APPLE__) && defined(__OBJC__)
+#import
+#else
+struct NSOpenGLContext;
+struct NSOpenGLPixelFormat;
+struct NSView;
+#endif
+
+namespace GL {
+
+class ContextAGL final : public Context
+{
+public:
+ ContextAGL(const WindowInfo& wi);
+ ~ContextAGL() override;
+
+ static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try,
+ size_t num_versions_to_try);
+
+ void* GetProcAddress(const char* name) override;
+ bool ChangeSurface(const WindowInfo& new_wi) override;
+ void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
+ bool SwapBuffers() override;
+ bool MakeCurrent() override;
+ bool DoneCurrent() override;
+ bool SetSwapInterval(s32 interval) override;
+ std::unique_ptr CreateSharedContext(const WindowInfo& wi) override;
+
+private:
+ ALWAYS_INLINE NSView* GetView() const { return static_cast(m_wi.window_handle); }
+
+ bool Initialize(const Version* versions_to_try, size_t num_versions_to_try);
+ bool CreateContext(NSOpenGLContext* share_context, int profile, bool make_current);
+ void BindContextToView();
+
+ // returns true if dimensions have changed
+ bool UpdateDimensions();
+
+ NSOpenGLContext* m_context = nullptr;
+ NSOpenGLPixelFormat* m_pixel_format = nullptr;
+ void* m_opengl_module_handle = nullptr;
+};
+
+} // namespace GL
\ No newline at end of file
diff --git a/src/common/gl/context_agl.mm b/src/common/gl/context_agl.mm
new file mode 100644
index 000000000..1af1a0854
--- /dev/null
+++ b/src/common/gl/context_agl.mm
@@ -0,0 +1,214 @@
+#include "context_agl.h"
+#include "../assert.h"
+#include "../log.h"
+#include "glad.h"
+#include
+Log_SetChannel(GL::ContextAGL);
+
+namespace GL {
+ContextAGL::ContextAGL(const WindowInfo& wi) : Context(wi)
+{
+ m_opengl_module_handle = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL", RTLD_NOW);
+ if (!m_opengl_module_handle)
+ Log_ErrorPrint("Could not open OpenGL.framework, function lookups will probably fail");
+}
+
+ContextAGL::~ContextAGL()
+{
+ if ([NSOpenGLContext currentContext] == m_context)
+ [NSOpenGLContext clearCurrentContext];
+
+ if (m_context)
+ [m_context release];
+
+ if (m_pixel_format)
+ [m_pixel_format release];
+
+ if (m_opengl_module_handle)
+ dlclose(m_opengl_module_handle);
+}
+
+std::unique_ptr ContextAGL::Create(const WindowInfo& wi, const Version* versions_to_try,
+ size_t num_versions_to_try)
+{
+ std::unique_ptr context = std::make_unique(wi);
+ if (!context->Initialize(versions_to_try, num_versions_to_try))
+ return nullptr;
+
+ return context;
+}
+
+bool ContextAGL::Initialize(const Version* versions_to_try, size_t num_versions_to_try)
+{
+ for (size_t i = 0; i < num_versions_to_try; i++)
+ {
+ const Version& cv = versions_to_try[i];
+ if (cv.profile == Profile::NoProfile && CreateContext(nullptr, NSOpenGLProfileVersionLegacy, true))
+ {
+ // we already have the dummy context, so just use that
+ m_version = cv;
+ return true;
+ }
+ else if (cv.profile == Profile::Core)
+ {
+ if (cv.major_version > 4 || cv.minor_version > 1)
+ continue;
+
+ const NSOpenGLPixelFormatAttribute profile = (cv.major_version > 3 || cv.minor_version > 2) ? NSOpenGLProfileVersion4_1Core : NSOpenGLProfileVersion3_2Core;
+ if (CreateContext(nullptr, static_cast(profile), true))
+ {
+ m_version = cv;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void* ContextAGL::GetProcAddress(const char* name)
+{
+ void* addr = m_opengl_module_handle ? dlsym(m_opengl_module_handle, name) : nullptr;
+ if (addr)
+ return addr;
+
+ return dlsym(RTLD_NEXT, name);
+}
+
+bool ContextAGL::ChangeSurface(const WindowInfo& new_wi)
+{
+ m_wi = new_wi;
+ BindContextToView();
+ return true;
+}
+
+void ContextAGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/)
+{
+ UpdateDimensions();
+}
+
+bool ContextAGL::UpdateDimensions()
+{
+ const NSSize window_size = [GetView() frame].size;
+ const CGFloat window_scale = [[GetView() window] backingScaleFactor];
+ const u32 new_width = static_cast(static_cast(window_size.width) * window_scale);
+ const u32 new_height = static_cast(static_cast(window_size.height) * window_scale);
+
+ if (m_wi.surface_width == new_width && m_wi.surface_height == new_height)
+ return false;
+
+ m_wi.surface_width = new_width;
+ m_wi.surface_height = new_height;
+
+ dispatch_block_t block = ^{
+ [m_context update];
+ };
+
+ if ([NSThread isMainThread])
+ block();
+ else
+ dispatch_sync(dispatch_get_main_queue(), block);
+
+ return true;
+}
+
+bool ContextAGL::SwapBuffers()
+{
+ [m_context flushBuffer];
+ return true;
+}
+
+bool ContextAGL::MakeCurrent()
+{
+ [m_context makeCurrentContext];
+ return true;
+}
+
+bool ContextAGL::DoneCurrent()
+{
+ [NSOpenGLContext clearCurrentContext];
+ return true;
+}
+
+bool ContextAGL::SetSwapInterval(s32 interval)
+{
+ GLint gl_interval = static_cast(interval);
+ [m_context setValues:&gl_interval forParameter:NSOpenGLCPSwapInterval];
+ return true;
+}
+
+std::unique_ptr ContextAGL::CreateSharedContext(const WindowInfo& wi)
+{
+ std::unique_ptr context = std::make_unique(wi);
+
+ context->m_context = [[NSOpenGLContext alloc] initWithFormat:m_pixel_format shareContext:m_context];
+ if (context->m_context == nil)
+ return nullptr;
+
+ context->m_version = m_version;
+ context->m_pixel_format = m_pixel_format;
+ [context->m_pixel_format retain];
+
+ if (wi.type == WindowInfo::Type::MacOS)
+ context->BindContextToView();
+
+ return context;
+}
+
+bool ContextAGL::CreateContext(NSOpenGLContext* share_context, int profile, bool make_current)
+{
+ if (m_context)
+ {
+ [m_context release];
+ m_context = nullptr;
+ }
+
+ if (m_pixel_format)
+ [m_pixel_format release];
+
+ const std::array attribs = {{
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAOpenGLProfile,
+ static_cast(profile),
+ NSOpenGLPFAAccelerated,
+ 0}};
+ m_pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs.data()];
+ if (m_pixel_format == nil)
+ {
+ Log_ErrorPrintf("Failed to initialize pixel format");
+ return false;
+ }
+
+ m_context = [[NSOpenGLContext alloc] initWithFormat:m_pixel_format shareContext:nil];
+ if (m_context == nil)
+ return false;
+
+ if (m_wi.type == WindowInfo::Type::MacOS)
+ BindContextToView();
+
+ if (make_current)
+ [m_context makeCurrentContext];
+
+ return true;
+}
+
+void ContextAGL::BindContextToView()
+{
+ NSView* const view = GetView();
+ NSWindow* const window = [view window];
+ [view setWantsBestResolutionOpenGLSurface:YES];
+
+ UpdateDimensions();
+
+ dispatch_block_t block = ^{
+ [window makeFirstResponder:view];
+ [m_context setView:view];
+ [window makeKeyAndOrderFront:nil];
+ };
+
+ if ([NSThread isMainThread])
+ block();
+ else
+ dispatch_sync(dispatch_get_main_queue(), block);
+}
+} // namespace GL
diff --git a/src/common/gl/context_egl.cpp b/src/common/gl/context_egl.cpp
new file mode 100644
index 000000000..2f90c068d
--- /dev/null
+++ b/src/common/gl/context_egl.cpp
@@ -0,0 +1,285 @@
+#include "context_egl.h"
+#include "../assert.h"
+#include "../log.h"
+Log_SetChannel(GL::ContextEGL);
+
+namespace GL {
+ContextEGL::ContextEGL(const WindowInfo& wi) : Context(wi) {}
+
+ContextEGL::~ContextEGL()
+{
+ if (eglGetCurrentContext() == m_context)
+ eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+ if (m_context)
+ eglDestroyContext(m_display, m_context);
+}
+
+std::unique_ptr ContextEGL::Create(const WindowInfo& wi, const Version* versions_to_try,
+ size_t num_versions_to_try)
+{
+ std::unique_ptr context = std::make_unique(wi);
+ if (!context->Initialize(versions_to_try, num_versions_to_try))
+ return nullptr;
+
+ return context;
+}
+
+bool ContextEGL::Initialize(const Version* versions_to_try, size_t num_versions_to_try)
+{
+ if (!gladLoadEGL())
+ {
+ Log_ErrorPrintf("Loading GLAD EGL functions failed");
+ return false;
+ }
+
+ m_display = eglGetDisplay(static_cast(m_wi.display_connection));
+ if (!m_display)
+ {
+ Log_ErrorPrintf("eglGetDisplay() failed: %d", eglGetError());
+ return false;
+ }
+
+ int egl_major, egl_minor;
+ if (!eglInitialize(m_display, &egl_major, &egl_minor))
+ {
+ Log_ErrorPrintf("eglInitialize() failed: %d", eglGetError());
+ return false;
+ }
+ Log_InfoPrintf("EGL Version: %d.%d", egl_major, egl_minor);
+
+ for (size_t i = 0; i < num_versions_to_try; i++)
+ {
+ if (CreateContextAndSurface(versions_to_try[i], nullptr, true))
+ return true;
+ }
+
+ return false;
+}
+
+void* ContextEGL::GetProcAddress(const char* name)
+{
+ return reinterpret_cast(eglGetProcAddress(name));
+}
+
+bool ContextEGL::ChangeSurface(const WindowInfo& new_wi)
+{
+ const bool was_current = (eglGetCurrentContext() == m_context);
+ if (was_current)
+ eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+ if (m_surface != EGL_NO_SURFACE)
+ {
+ eglDestroySurface(m_display, m_surface);
+ m_surface = EGL_NO_SURFACE;
+ }
+
+ m_wi = new_wi;
+ if (m_wi.type != WindowInfo::Type::Surfaceless && !CreateSurface())
+ return false;
+
+ if (was_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context))
+ {
+ Log_ErrorPrintf("Failed to make context current again after surface change");
+ return false;
+ }
+
+ return true;
+}
+
+void ContextEGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/)
+{
+ EGLint surface_width, surface_height;
+ if (eglQuerySurface(m_display, m_surface, EGL_WIDTH, &surface_width) &&
+ eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &surface_height))
+ {
+ m_wi.surface_width = static_cast(surface_width);
+ m_wi.surface_height = static_cast(surface_height);
+ }
+ else
+ {
+ Log_ErrorPrintf("eglQuerySurface() failed: %d", eglGetError());
+ m_wi.surface_width = new_surface_width;
+ m_wi.surface_height = new_surface_height;
+ }
+}
+
+bool ContextEGL::SwapBuffers()
+{
+ return eglSwapBuffers(m_display, m_surface);
+}
+
+bool ContextEGL::MakeCurrent()
+{
+ if (!eglMakeCurrent(m_display, m_surface, m_surface, m_context))
+ {
+ Log_ErrorPrintf("eglMakeCurrent() failed: %d", eglGetError());
+ return false;
+ }
+
+ return true;
+}
+
+bool ContextEGL::DoneCurrent()
+{
+ return eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+}
+
+bool ContextEGL::SetSwapInterval(s32 interval)
+{
+ return eglSwapInterval(m_display, interval);
+}
+
+std::unique_ptr ContextEGL::CreateSharedContext(const WindowInfo& wi)
+{
+ std::unique_ptr context = std::make_unique(wi);
+ context->m_display = m_display;
+
+ if (!context->CreateContextAndSurface(m_version, m_context, false))
+ return nullptr;
+
+ return context;
+}
+
+EGLNativeWindowType ContextEGL::GetNativeWindow(EGLConfig config)
+{
+ return {};
+}
+
+bool ContextEGL::CreateSurface()
+{
+ EGLNativeWindowType native_window = GetNativeWindow(m_config);
+ m_surface = eglCreateWindowSurface(m_display, m_config, native_window, nullptr);
+ if (!m_surface)
+ {
+ Log_ErrorPrintf("eglCreateWindowSurface() failed: %d", eglGetError());
+ return false;
+ }
+
+ // Some implementations may require the size to be queried at runtime.
+ EGLint surface_width, surface_height;
+ if (eglQuerySurface(m_display, m_surface, EGL_WIDTH, &surface_width) &&
+ eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &surface_height))
+ {
+ m_wi.surface_width = static_cast(surface_width);
+ m_wi.surface_height = static_cast(surface_height);
+ }
+ else
+ {
+ Log_ErrorPrintf("eglQuerySurface() failed: %d", eglGetError());
+ }
+
+ return true;
+}
+
+bool ContextEGL::CreateContext(const Version& version, EGLContext share_context)
+{
+ int surface_attribs[16] = {
+ EGL_RENDERABLE_TYPE,
+ (version.profile == Profile::ES) ?
+ ((version.major_version >= 3) ? EGL_OPENGL_ES3_BIT :
+ ((version.major_version == 2) ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES_BIT)) :
+ EGL_OPENGL_BIT,
+ EGL_SURFACE_TYPE,
+ (m_wi.type != WindowInfo::Type::Surfaceless) ? EGL_WINDOW_BIT : 0,
+ };
+ int nsurface_attribs = 4;
+
+ switch (m_wi.surface_format)
+ {
+ case WindowInfo::SurfaceFormat::RGB8:
+ surface_attribs[nsurface_attribs++] = EGL_RED_SIZE;
+ surface_attribs[nsurface_attribs++] = 8;
+ surface_attribs[nsurface_attribs++] = EGL_GREEN_SIZE;
+ surface_attribs[nsurface_attribs++] = 8;
+ surface_attribs[nsurface_attribs++] = EGL_BLUE_SIZE;
+ surface_attribs[nsurface_attribs++] = 8;
+ break;
+
+ case WindowInfo::SurfaceFormat::RGBA8:
+ surface_attribs[nsurface_attribs++] = EGL_RED_SIZE;
+ surface_attribs[nsurface_attribs++] = 8;
+ surface_attribs[nsurface_attribs++] = EGL_GREEN_SIZE;
+ surface_attribs[nsurface_attribs++] = 8;
+ surface_attribs[nsurface_attribs++] = EGL_BLUE_SIZE;
+ surface_attribs[nsurface_attribs++] = 8;
+ surface_attribs[nsurface_attribs++] = EGL_ALPHA_SIZE;
+ surface_attribs[nsurface_attribs++] = 8;
+ break;
+
+ case WindowInfo::SurfaceFormat::RGB565:
+ surface_attribs[nsurface_attribs++] = EGL_RED_SIZE;
+ surface_attribs[nsurface_attribs++] = 5;
+ surface_attribs[nsurface_attribs++] = EGL_GREEN_SIZE;
+ surface_attribs[nsurface_attribs++] = 6;
+ surface_attribs[nsurface_attribs++] = EGL_BLUE_SIZE;
+ surface_attribs[nsurface_attribs++] = 5;
+ break;
+
+ default:
+ UnreachableCode();
+ break;
+ }
+
+ surface_attribs[nsurface_attribs++] = EGL_NONE;
+ surface_attribs[nsurface_attribs++] = 0;
+
+ EGLint num_configs;
+ EGLConfig config;
+ if (!eglChooseConfig(m_display, surface_attribs, &config, 1, &num_configs) || num_configs == 0)
+ {
+ Log_ErrorPrintf("eglChooseConfig() failed: %d", eglGetError());
+ return false;
+ }
+
+ int attribs[8];
+ int nattribs = 0;
+ if (version.profile != Profile::NoProfile)
+ {
+ attribs[nattribs++] = EGL_CONTEXT_MAJOR_VERSION;
+ attribs[nattribs++] = version.major_version;
+ attribs[nattribs++] = EGL_CONTEXT_MINOR_VERSION;
+ attribs[nattribs++] = version.minor_version;
+ }
+ attribs[nattribs++] = EGL_NONE;
+ attribs[nattribs++] = 0;
+
+ eglBindAPI((version.profile == Profile::ES) ? EGL_OPENGL_ES_API : EGL_OPENGL_API);
+ m_context = eglCreateContext(m_display, config, share_context, attribs);
+ if (!m_context)
+ return false;
+
+ m_config = config;
+ m_version = version;
+ return true;
+}
+
+bool ContextEGL::CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current)
+{
+ if (!CreateContext(version, share_context))
+ return false;
+
+ if (m_wi.type != WindowInfo::Type::Surfaceless && !CreateSurface())
+ {
+ Log_ErrorPrintf("Failed to create surface for context");
+ eglDestroyContext(m_display, m_context);
+ m_context = EGL_NO_CONTEXT;
+ return false;
+ }
+
+ if (make_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context))
+ {
+ Log_ErrorPrintf("eglMakeCurrent() failed: %d", eglGetError());
+ if (m_surface != EGL_NO_SURFACE)
+ {
+ eglDestroySurface(m_display, m_surface);
+ m_surface = EGL_NO_SURFACE;
+ }
+ eglDestroyContext(m_display, m_context);
+ m_context = EGL_NO_CONTEXT;
+ return false;
+ }
+
+ return true;
+}
+} // namespace GL
diff --git a/src/common/gl/context_egl.h b/src/common/gl/context_egl.h
new file mode 100644
index 000000000..d70752328
--- /dev/null
+++ b/src/common/gl/context_egl.h
@@ -0,0 +1,42 @@
+#pragma once
+#include "context.h"
+#include "glad_egl.h"
+#include "x11_window.h"
+
+namespace GL {
+
+class ContextEGL : public Context
+{
+public:
+ ContextEGL(const WindowInfo& wi);
+ ~ContextEGL() override;
+
+ static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try,
+ size_t num_versions_to_try);
+
+ void* GetProcAddress(const char* name) override;
+ virtual bool ChangeSurface(const WindowInfo& new_wi) override;
+ virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
+ bool SwapBuffers() override;
+ bool MakeCurrent() override;
+ bool DoneCurrent() override;
+ bool SetSwapInterval(s32 interval) override;
+ virtual std::unique_ptr CreateSharedContext(const WindowInfo& wi) override;
+
+protected:
+ virtual EGLNativeWindowType GetNativeWindow(EGLConfig config);
+
+ bool Initialize(const Version* versions_to_try, size_t num_versions_to_try);
+ bool CreateDisplay();
+ bool CreateContext(const Version& version, EGLContext share_context);
+ bool CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current);
+ bool CreateSurface();
+
+ EGLDisplay m_display = EGL_NO_DISPLAY;
+ EGLSurface m_surface = EGL_NO_SURFACE;
+ EGLContext m_context = EGL_NO_CONTEXT;
+
+ EGLConfig m_config = {};
+};
+
+} // namespace GL
diff --git a/src/common/gl/context_egl_android.cpp b/src/common/gl/context_egl_android.cpp
new file mode 100644
index 000000000..fdd146641
--- /dev/null
+++ b/src/common/gl/context_egl_android.cpp
@@ -0,0 +1,44 @@
+#include "context_egl_android.h"
+#include "../log.h"
+Log_SetChannel(GL::ContextEGLAndroid);
+
+namespace GL {
+ContextEGLX11::ContextEGLAndroid(const WindowInfo& wi) : ContextEGL(wi) {}
+ContextEGLX11::~ContextEGLAndroid() = default;
+
+std::unique_ptr ContextEGLAndroid::Create(const WindowInfo& wi, const Version* versions_to_try,
+ size_t num_versions_to_try)
+{
+ std::unique_ptr context = std::make_unique(wi);
+ if (!context->Initialize(versions_to_try, num_versions_to_try))
+ return nullptr;
+
+ return context;
+}
+
+std::unique_ptr ContextEGLAndroid::CreateSharedContext(const WindowInfo& wi)
+{
+ std::unique_ptr context = std::make_unique(wi);
+ context->m_display = m_display;
+
+ if (!context->CreateContextAndSurface(m_version, m_context, false))
+ return nullptr;
+
+ return context;
+}
+
+EGLNativeWindowType ContextEGLAndroid::GetNativeWindow(EGLConfig config)
+{
+ X11InhibitErrors ei;
+
+ EGLint native_visual_id = 0;
+ if (!eglGetConfigAttrib(m_display, m_config, EGL_NATIVE_VISUAL_ID, &native_visual_id))
+ {
+ Log_ErrorPrintf("Failed to get X11 visual ID");
+ return false;
+ }
+
+ ANativeWindow_setBuffersGeometry(static_cast(m_wi.window_handle), 0, 0, static_cast(native_visual_id));
+ return static_cast(m_wi.window_handle);
+}
+} // namespace GL
diff --git a/src/common/gl/context_egl_android.h b/src/common/gl/context_egl_android.h
new file mode 100644
index 000000000..cb452c9b0
--- /dev/null
+++ b/src/common/gl/context_egl_android.h
@@ -0,0 +1,27 @@
+#pragma once
+#include "context_egl.h"
+#include "x11_window.h"
+
+namespace GL {
+
+class ContextEGLAndroid final : public ContextEGL
+{
+public:
+ ContextEGLAndroid(const WindowInfo& wi);
+ ~ContextEGLAndroid() override;
+
+ static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try,
+ size_t num_versions_to_try);
+
+ std::unique_ptr CreateSharedContext(const WindowInfo& wi) override;
+
+protected:
+ EGLNativeWindowType GetNativeWindow(EGLConfig config) override;
+
+private:
+ ALWAYS_INLINE Display* GetDisplay() const { return static_cast(m_wi.display_connection); }
+
+ X11Window m_window;
+};
+
+} // namespace GL
diff --git a/src/common/gl/context_egl_x11.cpp b/src/common/gl/context_egl_x11.cpp
new file mode 100644
index 000000000..c7310e312
--- /dev/null
+++ b/src/common/gl/context_egl_x11.cpp
@@ -0,0 +1,69 @@
+#include "context_egl_x11.h"
+#include "../log.h"
+Log_SetChannel(GL::ContextEGLX11);
+
+namespace GL {
+ContextEGLX11::ContextEGLX11(const WindowInfo& wi) : ContextEGL(wi) {}
+ContextEGLX11::~ContextEGLX11() = default;
+
+std::unique_ptr ContextEGLX11::Create(const WindowInfo& wi, const Version* versions_to_try,
+ size_t num_versions_to_try)
+{
+ std::unique_ptr context = std::make_unique(wi);
+ if (!context->Initialize(versions_to_try, num_versions_to_try))
+ return nullptr;
+
+ return context;
+}
+
+std::unique_ptr ContextEGLX11::CreateSharedContext(const WindowInfo& wi)
+{
+ std::unique_ptr context = std::make_unique(wi);
+ context->m_display = m_display;
+
+ if (!context->CreateContextAndSurface(m_version, m_context, false))
+ return nullptr;
+
+ return context;
+}
+
+void ContextEGLX11::ResizeSurface(u32 new_surface_width, u32 new_surface_height)
+{
+ m_window.Resize();
+ ContextEGL::ResizeSurface(new_surface_width, new_surface_height);
+}
+
+EGLNativeWindowType ContextEGLX11::GetNativeWindow(EGLConfig config)
+{
+ X11InhibitErrors ei;
+
+ EGLint native_visual_id = 0;
+ if (!eglGetConfigAttrib(m_display, m_config, EGL_NATIVE_VISUAL_ID, &native_visual_id))
+ {
+ Log_ErrorPrintf("Failed to get X11 visual ID");
+ return false;
+ }
+
+ XVisualInfo vi_query = {};
+ vi_query.visualid = native_visual_id;
+
+ int num_vis;
+ XVisualInfo* vi = XGetVisualInfo(static_cast(m_wi.display_connection), VisualIDMask, &vi_query, &num_vis);
+ if (num_vis <= 0 || !vi)
+ {
+ Log_ErrorPrintf("Failed to query visual from X11");
+ return false;
+ }
+
+ m_window.Destroy();
+ if (!m_window.Create(GetDisplay(), static_cast(reinterpret_cast(m_wi.window_handle)), vi))
+ {
+ Log_ErrorPrintf("Faild to create X11 child window");
+ XFree(vi);
+ return false;
+ }
+
+ XFree(vi);
+ return static_cast(m_window.GetWindow());
+}
+} // namespace GL
diff --git a/src/common/gl/context_egl_x11.h b/src/common/gl/context_egl_x11.h
new file mode 100644
index 000000000..7def8bfbc
--- /dev/null
+++ b/src/common/gl/context_egl_x11.h
@@ -0,0 +1,28 @@
+#pragma once
+#include "context_egl.h"
+#include "x11_window.h"
+
+namespace GL {
+
+class ContextEGLX11 final : public ContextEGL
+{
+public:
+ ContextEGLX11(const WindowInfo& wi);
+ ~ContextEGLX11() override;
+
+ static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try,
+ size_t num_versions_to_try);
+
+ std::unique_ptr CreateSharedContext(const WindowInfo& wi) override;
+ void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
+
+protected:
+ EGLNativeWindowType GetNativeWindow(EGLConfig config) override;
+
+private:
+ ALWAYS_INLINE Display* GetDisplay() const { return static_cast(m_wi.display_connection); }
+
+ X11Window m_window;
+};
+
+} // namespace GL
diff --git a/src/common/gl/context_glx.cpp b/src/common/gl/context_glx.cpp
new file mode 100644
index 000000000..a00875bbc
--- /dev/null
+++ b/src/common/gl/context_glx.cpp
@@ -0,0 +1,306 @@
+#include "context_glx.h"
+#include "../assert.h"
+#include "../log.h"
+Log_SetChannel(GL::ContextGLX);
+
+namespace GL {
+ContextGLX::ContextGLX(const WindowInfo& wi) : Context(wi) {}
+
+ContextGLX::~ContextGLX()
+{
+ if (glXGetCurrentContext() == m_context)
+ glXMakeCurrent(GetDisplay(), None, nullptr);
+
+ if (m_context)
+ glXDestroyContext(GetDisplay(), m_context);
+}
+
+std::unique_ptr ContextGLX::Create(const WindowInfo& wi, const Version* versions_to_try,
+ size_t num_versions_to_try)
+{
+ std::unique_ptr context = std::make_unique(wi);
+ if (!context->Initialize(versions_to_try, num_versions_to_try))
+ return nullptr;
+
+ return context;
+}
+
+bool ContextGLX::Initialize(const Version* versions_to_try, size_t num_versions_to_try)
+{
+ const int screen = DefaultScreen(GetDisplay());
+ if (!gladLoadGLX(GetDisplay(), screen))
+ {
+ Log_ErrorPrintf("Loading GLAD GLX functions failed");
+ return false;
+ }
+
+ if (m_wi.type == WindowInfo::Type::X11)
+ {
+ if (!CreateWindow(screen))
+ return false;
+ }
+ else
+ {
+ Panic("Create pbuffer");
+ }
+
+ for (size_t i = 0; i < num_versions_to_try; i++)
+ {
+ const Version& cv = versions_to_try[i];
+ if (cv.profile == Profile::NoProfile && CreateAnyContext(nullptr, true))
+ {
+ m_version = cv;
+ return true;
+ }
+ else if (cv.profile != Profile::NoProfile && CreateVersionContext(cv, nullptr, true))
+ {
+ m_version = cv;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void* ContextGLX::GetProcAddress(const char* name)
+{
+ return reinterpret_cast(glXGetProcAddress(reinterpret_cast(name)));
+}
+
+bool ContextGLX::ChangeSurface(const WindowInfo& new_wi)
+{
+ const bool was_current = (glXGetCurrentContext() == m_context);
+ if (was_current)
+ glXMakeCurrent(GetDisplay(), None, nullptr);
+
+ m_window.Destroy();
+ m_wi = new_wi;
+
+ if (new_wi.type == WindowInfo::Type::X11)
+ {
+ const int screen = DefaultScreen(GetDisplay());
+ if (!CreateWindow(screen))
+ return false;
+ }
+
+ if (was_current && !glXMakeCurrent(GetDisplay(), GetDrawable(), m_context))
+ {
+ Log_ErrorPrintf("Failed to make context current again after surface change");
+ return false;
+ }
+
+ return true;
+}
+
+void ContextGLX::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/)
+{
+ m_window.Resize();
+ m_wi.surface_width = m_window.GetWidth();
+ m_wi.surface_height = m_window.GetHeight();
+}
+
+bool ContextGLX::SwapBuffers()
+{
+ glXSwapBuffers(GetDisplay(), GetDrawable());
+ return true;
+}
+
+bool ContextGLX::MakeCurrent()
+{
+ return (glXMakeCurrent(GetDisplay(), GetDrawable(), m_context) == True);
+}
+
+bool ContextGLX::DoneCurrent()
+{
+ return (glXMakeCurrent(GetDisplay(), None, nullptr) == True);
+}
+
+bool ContextGLX::SetSwapInterval(s32 interval)
+{
+ if (GLAD_GLX_EXT_swap_control)
+ {
+ glXSwapIntervalEXT(GetDisplay(), GetDrawable(), interval);
+ return true;
+ }
+ else if (GLAD_GLX_MESA_swap_control)
+ {
+ return (glXSwapIntervalMESA(static_cast(std::max(interval, 0))) != 0);
+ }
+ else if (GLAD_GLX_SGI_swap_control)
+ {
+ return (glXSwapIntervalSGI(interval) != 0);
+ }
+ else
+ {
+ return false;
+ }
+}
+
+std::unique_ptr ContextGLX::CreateSharedContext(const WindowInfo& wi)
+{
+ std::unique_ptr context = std::make_unique(wi);
+ if (wi.type == WindowInfo::Type::X11)
+ {
+ const int screen = DefaultScreen(context->GetDisplay());
+ if (!context->CreateWindow(screen))
+ return nullptr;
+ }
+ else
+ {
+ Panic("Create pbuffer");
+ }
+
+ if (m_version.profile == Profile::NoProfile)
+ {
+ if (!context->CreateAnyContext(m_context, false))
+ return nullptr;
+ }
+ else
+ {
+ if (!context->CreateVersionContext(m_version, m_context, false))
+ return nullptr;
+ }
+
+ context->m_version = m_version;
+ return context;
+}
+
+bool ContextGLX::CreateWindow(int screen)
+{
+ int attribs[32] = {GLX_X_RENDERABLE, True, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
+ GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, GLX_DOUBLEBUFFER, True};
+ int nattribs = 8;
+
+ switch (m_wi.surface_format)
+ {
+ case WindowInfo::SurfaceFormat::RGB8:
+ attribs[nattribs++] = GLX_RED_SIZE;
+ attribs[nattribs++] = 8;
+ attribs[nattribs++] = GLX_GREEN_SIZE;
+ attribs[nattribs++] = 8;
+ attribs[nattribs++] = GLX_BLUE_SIZE;
+ attribs[nattribs++] = 8;
+ break;
+
+ case WindowInfo::SurfaceFormat::RGBA8:
+ attribs[nattribs++] = GLX_RED_SIZE;
+ attribs[nattribs++] = 8;
+ attribs[nattribs++] = GLX_GREEN_SIZE;
+ attribs[nattribs++] = 8;
+ attribs[nattribs++] = GLX_BLUE_SIZE;
+ attribs[nattribs++] = 8;
+ attribs[nattribs++] = GLX_ALPHA_SIZE;
+ attribs[nattribs++] = 8;
+ break;
+
+ case WindowInfo::SurfaceFormat::RGB565:
+ attribs[nattribs++] = GLX_RED_SIZE;
+ attribs[nattribs++] = 5;
+ attribs[nattribs++] = GLX_GREEN_SIZE;
+ attribs[nattribs++] = 6;
+ attribs[nattribs++] = GLX_BLUE_SIZE;
+ attribs[nattribs++] = 5;
+ break;
+
+ default:
+ UnreachableCode();
+ break;
+ }
+
+ attribs[nattribs++] = None;
+ attribs[nattribs++] = 0;
+
+ int fbcount = 0;
+ GLXFBConfig* fbc = glXChooseFBConfig(GetDisplay(), screen, attribs, &fbcount);
+ if (!fbc || !fbcount)
+ {
+ Log_ErrorPrintf("glXChooseFBConfig() failed");
+ return false;
+ }
+ m_fb_config = *fbc;
+ XFree(fbc);
+
+ if (!GLAD_GLX_VERSION_1_3)
+ {
+ Log_ErrorPrintf("GLX Version 1.3 is required");
+ return false;
+ }
+
+ const XVisualInfo* vi = glXGetVisualFromFBConfig(GetDisplay(), m_fb_config);
+ if (!vi)
+ {
+ Log_ErrorPrintf("glXGetVisualFromFBConfig() failed");
+ return false;
+ }
+
+ return m_window.Create(GetDisplay(), static_cast(reinterpret_cast(m_wi.window_handle)), vi);
+}
+
+bool ContextGLX::CreateAnyContext(GLXContext share_context, bool make_current)
+{
+ X11InhibitErrors ie;
+
+ m_context = glXCreateContext(GetDisplay(), m_vi, share_context, True);
+ if (!m_context || ie.HadError())
+ {
+ Log_ErrorPrintf("glxCreateContext() failed");
+ return false;
+ }
+
+ if (make_current)
+ {
+ if (!glXMakeCurrent(GetDisplay(), GetDrawable(), m_context))
+ {
+ Log_ErrorPrintf("glXMakeCurrent() failed");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool ContextGLX::CreateVersionContext(const Version& version, GLXContext share_context, bool make_current)
+{
+ // we need create context attribs
+ if (!GLAD_GLX_VERSION_1_3)
+ {
+ Log_ErrorPrint("Missing GLX version 1.3.");
+ return false;
+ }
+
+ int attribs[32];
+ int nattribs = 0;
+ attribs[nattribs++] = GLX_CONTEXT_PROFILE_MASK_ARB;
+ attribs[nattribs++] =
+ ((version.profile == Profile::ES) ?
+ ((version.major_version >= 2) ? GLX_CONTEXT_ES2_PROFILE_BIT_EXT : GLX_CONTEXT_ES_PROFILE_BIT_EXT) :
+ GLX_CONTEXT_CORE_PROFILE_BIT_ARB);
+ attribs[nattribs++] = GLX_CONTEXT_MAJOR_VERSION_ARB;
+ attribs[nattribs++] = version.major_version;
+ attribs[nattribs++] = GLX_CONTEXT_MINOR_VERSION_ARB;
+ attribs[nattribs++] = version.minor_version;
+ attribs[nattribs++] = None;
+ attribs[nattribs++] = 0;
+
+ X11InhibitErrors ie;
+ m_context = glXCreateContextAttribsARB(GetDisplay(), m_fb_config, share_context, True, attribs);
+ XSync(GetDisplay(), False);
+ if (ie.HadError())
+ m_context = nullptr;
+ if (!m_context)
+ return false;
+
+ if (make_current)
+ {
+ if (!glXMakeCurrent(GetDisplay(), GetDrawable(), m_context))
+ {
+ Log_ErrorPrint("glXMakeCurrent() failed");
+ glXDestroyContext(GetDisplay(), m_context);
+ m_context = nullptr;
+ return false;
+ }
+ }
+
+ return true;
+}
+} // namespace GL
diff --git a/src/common/gl/context_glx.h b/src/common/gl/context_glx.h
new file mode 100644
index 000000000..89a1fbc86
--- /dev/null
+++ b/src/common/gl/context_glx.h
@@ -0,0 +1,41 @@
+#pragma once
+#include "context.h"
+#include "glad_glx.h"
+#include "x11_window.h"
+
+namespace GL {
+
+class ContextGLX final : public Context
+{
+public:
+ ContextGLX(const WindowInfo& wi);
+ ~ContextGLX() override;
+
+ static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try,
+ size_t num_versions_to_try);
+
+ void* GetProcAddress(const char* name) override;
+ bool ChangeSurface(const WindowInfo& new_wi) override;
+ void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
+ bool SwapBuffers() override;
+ bool MakeCurrent() override;
+ bool DoneCurrent() override;
+ bool SetSwapInterval(s32 interval) override;
+ std::unique_ptr CreateSharedContext(const WindowInfo& wi) override;
+
+private:
+ ALWAYS_INLINE Display* GetDisplay() const { return static_cast(m_wi.display_connection); }
+ ALWAYS_INLINE GLXDrawable GetDrawable() const { return static_cast(m_window.GetWindow()); }
+
+ bool Initialize(const Version* versions_to_try, size_t num_versions_to_try);
+ bool CreateWindow(int screen);
+ bool CreateAnyContext(GLXContext share_context, bool make_current);
+ bool CreateVersionContext(const Version& version, GLXContext share_context, bool make_current);
+
+ GLXContext m_context = nullptr;
+ GLXFBConfig m_fb_config = {};
+ XVisualInfo* m_vi = nullptr;
+ X11Window m_window;
+};
+
+} // namespace GL
diff --git a/src/common/gl/context_wgl.cpp b/src/common/gl/context_wgl.cpp
new file mode 100644
index 000000000..7dcde8f5e
--- /dev/null
+++ b/src/common/gl/context_wgl.cpp
@@ -0,0 +1,345 @@
+#include "context_wgl.h"
+#include "../assert.h"
+#include "../log.h"
+#include "glad.h"
+#include "glad_wgl.h"
+Log_SetChannel(GL::ContextWGL);
+
+// TODO: get rid of this
+#pragma comment(lib, "opengl32.lib")
+
+static void* GetProcAddressCallback(const char* name)
+{
+ void* addr = wglGetProcAddress(name);
+ if (addr)
+ return addr;
+
+ // try opengl32.dll
+ return ::GetProcAddress(GetModuleHandleA("opengl32.dll"), name);
+}
+
+namespace GL {
+ContextWGL::ContextWGL(const WindowInfo& wi) : Context(wi) {}
+
+ContextWGL::~ContextWGL()
+{
+ if (wglGetCurrentContext() == m_rc)
+ wglMakeCurrent(m_dc, nullptr);
+
+ if (m_rc)
+ wglDeleteContext(m_rc);
+
+ if (m_dc)
+ ReleaseDC(GetHWND(), m_dc);
+}
+
+std::unique_ptr ContextWGL::Create(const WindowInfo& wi, const Version* versions_to_try,
+ size_t num_versions_to_try)
+{
+ std::unique_ptr context = std::make_unique(wi);
+ if (!context->Initialize(versions_to_try, num_versions_to_try))
+ return nullptr;
+
+ return context;
+}
+
+bool ContextWGL::Initialize(const Version* versions_to_try, size_t num_versions_to_try)
+{
+ if (m_wi.type == WindowInfo::Type::Win32)
+ {
+ if (!InitializeDC())
+ return false;
+ }
+ else
+ {
+ Panic("Create pbuffer");
+ }
+
+ // Everything including core/ES requires a dummy profile to load the WGL extensions.
+ if (!CreateAnyContext(nullptr, true))
+ return false;
+
+ for (size_t i = 0; i < num_versions_to_try; i++)
+ {
+ const Version& cv = versions_to_try[i];
+ if (cv.profile == Profile::NoProfile)
+ {
+ // we already have the dummy context, so just use that
+ m_version = cv;
+ return true;
+ }
+ else if (CreateVersionContext(cv, nullptr, true))
+ {
+ m_version = cv;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void* ContextWGL::GetProcAddress(const char* name)
+{
+ return GetProcAddressCallback(name);
+}
+
+bool ContextWGL::ChangeSurface(const WindowInfo& new_wi)
+{
+ const bool was_current = (wglGetCurrentContext() == m_rc);
+
+ if (m_dc)
+ {
+ ReleaseDC(GetHWND(), m_dc);
+ m_dc = {};
+ }
+
+ m_wi = new_wi;
+ if (!InitializeDC())
+ return false;
+
+ if (was_current && !wglMakeCurrent(m_dc, m_rc))
+ {
+ Log_ErrorPrintf("Failed to make context current again after surface change: 0x%08X", GetLastError());
+ return false;
+ }
+
+ return true;
+}
+
+void ContextWGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/)
+{
+ RECT client_rc = {};
+ GetClientRect(GetHWND(), &client_rc);
+ m_wi.surface_width = static_cast(client_rc.right - client_rc.left);
+ m_wi.surface_height = static_cast(client_rc.bottom - client_rc.top);
+}
+
+bool ContextWGL::SwapBuffers()
+{
+ return ::SwapBuffers(m_dc);
+}
+
+bool ContextWGL::MakeCurrent()
+{
+ if (!wglMakeCurrent(m_dc, m_rc))
+ {
+ Log_ErrorPrintf("wglMakeCurrent() failed: 0x%08X", GetLastError());
+ return false;
+ }
+
+ return true;
+}
+
+bool ContextWGL::DoneCurrent()
+{
+ return wglMakeCurrent(m_dc, nullptr);
+}
+
+bool ContextWGL::SetSwapInterval(s32 interval)
+{
+ if (!GLAD_WGL_EXT_swap_control)
+ return false;
+
+ return wglSwapIntervalEXT(interval);
+}
+
+std::unique_ptr ContextWGL::CreateSharedContext(const WindowInfo& wi)
+{
+ std::unique_ptr context = std::make_unique(wi);
+ if (wi.type == WindowInfo::Type::Win32)
+ {
+ if (!context->InitializeDC())
+ return nullptr;
+ }
+ else
+ {
+ Panic("Create pbuffer");
+ }
+
+ if (m_version.profile == Profile::NoProfile)
+ {
+ if (!context->CreateAnyContext(m_rc, false))
+ return nullptr;
+ }
+ else
+ {
+ if (!context->CreateVersionContext(m_version, m_rc, false))
+ return nullptr;
+ }
+
+ context->m_version = m_version;
+ return context;
+}
+
+bool ContextWGL::InitializeDC()
+{
+ PIXELFORMATDESCRIPTOR pfd = {};
+ pfd.nSize = sizeof(pfd);
+ pfd.nVersion = 1;
+ pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
+ pfd.iPixelType = PFD_TYPE_RGBA;
+ pfd.dwLayerMask = PFD_MAIN_PLANE;
+
+ switch (m_wi.surface_format)
+ {
+ case WindowInfo::SurfaceFormat::RGB8:
+ pfd.cColorBits = 32;
+ pfd.cRedBits = 8;
+ pfd.cGreenBits = 8;
+ pfd.cBlueBits = 8;
+ break;
+
+ case WindowInfo::SurfaceFormat::RGBA8:
+ pfd.cColorBits = 32;
+ pfd.cRedBits = 8;
+ pfd.cGreenBits = 8;
+ pfd.cBlueBits = 8;
+ pfd.cAlphaBits = 8;
+ break;
+
+ case WindowInfo::SurfaceFormat::RGB565:
+ pfd.cColorBits = 16;
+ pfd.cRedBits = 5;
+ pfd.cGreenBits = 6;
+ pfd.cBlueBits = 5;
+ break;
+
+ default:
+ UnreachableCode();
+ break;
+ }
+
+ m_dc = GetDC(GetHWND());
+ if (!m_dc)
+ {
+ Log_ErrorPrintf("GetDC() failed: 0x%08X", GetLastError());
+ return false;
+ }
+
+ const int pf = ChoosePixelFormat(m_dc, &pfd);
+ if (pf == 0)
+ {
+ Log_ErrorPrintf("ChoosePixelFormat() failed: 0x%08X", GetLastError());
+ return false;
+ }
+
+ if (!SetPixelFormat(m_dc, pf, &pfd))
+ {
+ Log_ErrorPrintf("SetPixelFormat() failed: 0x%08X", GetLastError());
+ return false;
+ }
+
+ return true;
+}
+
+bool ContextWGL::CreateAnyContext(HGLRC share_context, bool make_current)
+{
+ m_rc = wglCreateContext(m_dc);
+ if (!m_rc)
+ {
+ Log_ErrorPrintf("wglCreateContext() failed: 0x%08X", GetLastError());
+ return false;
+ }
+
+ if (make_current)
+ {
+ if (!wglMakeCurrent(m_dc, m_rc))
+ {
+ Log_ErrorPrintf("wglMakeCurrent() failed: 0x%08X", GetLastError());
+ return false;
+ }
+
+ // re-init glad-wgl
+ if (!gladLoadWGLLoader([](const char* name) -> void* { return wglGetProcAddress(name); }, m_dc))
+ {
+ Log_ErrorPrintf("Loading GLAD WGL functions failed");
+ return false;
+ }
+ }
+
+ if (share_context && !wglShareLists(share_context, m_rc))
+ {
+ Log_ErrorPrintf("wglShareLists() failed: 0x%08X", GetLastError());
+ return false;
+ }
+
+ return true;
+}
+
+bool ContextWGL::CreateVersionContext(const Version& version, HGLRC share_context, bool make_current)
+{
+ // we need create context attribs
+ if (!GLAD_WGL_ARB_create_context)
+ {
+ Log_ErrorPrint("Missing GLAD_WGL_ARB_create_context.");
+ return false;
+ }
+
+ HGLRC new_rc;
+ if (version.profile == Profile::Core)
+ {
+ const int attribs[] = {WGL_CONTEXT_PROFILE_MASK_ARB,
+ WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
+ WGL_CONTEXT_MAJOR_VERSION_ARB,
+ version.major_version,
+ WGL_CONTEXT_MINOR_VERSION_ARB,
+ version.minor_version,
+#ifdef _DEBUG
+ WGL_CONTEXT_FLAGS_ARB,
+ WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB,
+#else
+ WGL_CONTEXT_FLAGS_ARB,
+ WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
+#endif
+ 0,
+ 0};
+
+ new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs);
+ }
+ else if (version.profile == Profile::ES)
+ {
+ const int attribs[] = {
+ WGL_CONTEXT_PROFILE_MASK_ARB,
+ ((version.major_version >= 2) ? WGL_CONTEXT_ES2_PROFILE_BIT_EXT : WGL_CONTEXT_ES_PROFILE_BIT_EXT),
+ WGL_CONTEXT_MAJOR_VERSION_ARB,
+ version.major_version,
+ WGL_CONTEXT_MINOR_VERSION_ARB,
+ version.minor_version,
+ 0,
+ 0};
+
+ new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs);
+ }
+ else
+ {
+ Log_ErrorPrintf("Unknown profile");
+ return false;
+ }
+
+ if (!new_rc)
+ return false;
+
+ // destroy and swap contexts
+ if (m_rc)
+ {
+ if (!wglMakeCurrent(m_dc, make_current ? new_rc : nullptr))
+ {
+ Log_ErrorPrintf("wglMakeCurrent() failed: 0x%08X", GetLastError());
+ wglDeleteContext(new_rc);
+ return false;
+ }
+
+ // re-init glad-wgl
+ if (make_current && !gladLoadWGLLoader([](const char* name) -> void* { return wglGetProcAddress(name); }, m_dc))
+ {
+ Log_ErrorPrintf("Loading GLAD WGL functions failed");
+ return false;
+ }
+
+ wglDeleteContext(m_rc);
+ }
+
+ m_rc = new_rc;
+ return true;
+}
+} // namespace GL
diff --git a/src/common/gl/context_wgl.h b/src/common/gl/context_wgl.h
new file mode 100644
index 000000000..6303235e2
--- /dev/null
+++ b/src/common/gl/context_wgl.h
@@ -0,0 +1,38 @@
+#pragma once
+#include "../windows_headers.h"
+#include "context.h"
+#include
+
+namespace GL {
+
+class ContextWGL final : public Context
+{
+public:
+ ContextWGL(const WindowInfo& wi);
+ ~ContextWGL() override;
+
+ static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try,
+ size_t num_versions_to_try);
+
+ void* GetProcAddress(const char* name) override;
+ bool ChangeSurface(const WindowInfo& new_wi) override;
+ void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
+ bool SwapBuffers() override;
+ bool MakeCurrent() override;
+ bool DoneCurrent() override;
+ bool SetSwapInterval(s32 interval) override;
+ std::unique_ptr CreateSharedContext(const WindowInfo& wi) override;
+
+private:
+ ALWAYS_INLINE HWND GetHWND() const { return static_cast(m_wi.window_handle); }
+
+ bool Initialize(const Version* versions_to_try, size_t num_versions_to_try);
+ bool InitializeDC();
+ bool CreateAnyContext(HGLRC share_context, bool make_current);
+ bool CreateVersionContext(const Version& version, HGLRC share_context, bool make_current);
+
+ HDC m_dc = {};
+ HGLRC m_rc = {};
+};
+
+} // namespace GL
\ No newline at end of file
diff --git a/src/common/gl/x11_window.cpp b/src/common/gl/x11_window.cpp
new file mode 100644
index 000000000..1913dd54f
--- /dev/null
+++ b/src/common/gl/x11_window.cpp
@@ -0,0 +1,93 @@
+#include "x11_window.h"
+#include "../assert.h"
+#include
+namespace GL {
+X11Window::X11Window() = default;
+
+X11Window::~X11Window()
+{
+ Destroy();
+}
+
+bool X11Window::Create(Display* display, Window parent_window, const XVisualInfo* vi)
+{
+ m_display = display;
+ m_parent_window = parent_window;
+ XSync(m_display, True);
+
+ XWindowAttributes parent_wa = {};
+ XGetWindowAttributes(m_display, m_parent_window, &parent_wa);
+ m_width = static_cast(parent_wa.width);
+ m_height = static_cast(parent_wa.height);
+
+ // Failed X calls terminate the process so no need to check for errors.
+ // We could swap the error handler out here as well.
+ m_colormap = XCreateColormap(m_display, m_parent_window, vi->visual, AllocNone);
+
+ XSetWindowAttributes wa = {};
+ wa.colormap = m_colormap;
+
+ m_window = XCreateWindow(m_display, m_parent_window, 0, 0, m_width, m_height, 0, vi->depth, InputOutput, vi->visual,
+ CWColormap, &wa);
+ XMapWindow(m_display, m_window);
+ XSync(m_display, True);
+
+ return true;
+}
+
+void X11Window::Destroy()
+{
+ if (m_window)
+ {
+ XUnmapWindow(m_display, m_window);
+ XDestroyWindow(m_display, m_window);
+ m_window = {};
+ }
+
+ if (m_colormap)
+ {
+ XFreeColormap(m_display, m_colormap);
+ m_colormap = {};
+ }
+}
+
+void X11Window::Resize(u32 width, u32 height)
+{
+ if (width != 0 && height != 0)
+ {
+ m_width = width;
+ m_height = height;
+ }
+ else
+ {
+ XWindowAttributes parent_wa = {};
+ XGetWindowAttributes(m_display, m_parent_window, &parent_wa);
+ m_width = static_cast(parent_wa.width);
+ m_height = static_cast(parent_wa.height);
+ }
+
+ XResizeWindow(m_display, m_window, m_width, m_height);
+}
+
+static X11InhibitErrors* s_current_error_inhibiter;
+
+X11InhibitErrors::X11InhibitErrors()
+{
+ Assert(!s_current_error_inhibiter);
+ m_old_handler = XSetErrorHandler(ErrorHandler);
+ s_current_error_inhibiter = this;
+}
+
+X11InhibitErrors::~X11InhibitErrors()
+{
+ Assert(s_current_error_inhibiter == this);
+ s_current_error_inhibiter = nullptr;
+ XSetErrorHandler(m_old_handler);
+}
+
+int X11InhibitErrors::ErrorHandler(Display* display, XErrorEvent* ee)
+{
+ s_current_error_inhibiter->m_had_error = true;
+ return 0;
+}
+} // namespace GL
diff --git a/src/common/gl/x11_window.h b/src/common/gl/x11_window.h
new file mode 100644
index 000000000..4752980d3
--- /dev/null
+++ b/src/common/gl/x11_window.h
@@ -0,0 +1,48 @@
+#pragma once
+#include "../types.h"
+#include
+#include
+
+namespace GL {
+class X11Window
+{
+public:
+ X11Window();
+ ~X11Window();
+
+ ALWAYS_INLINE Window GetWindow() const { return m_window; }
+ ALWAYS_INLINE u32 GetWidth() const { return m_width; }
+ ALWAYS_INLINE u32 GetHeight() const { return m_height; }
+
+ bool Create(Display* display, Window parent_window, const XVisualInfo* vi);
+ void Destroy();
+
+ // Setting a width/height of 0 will use parent dimensions.
+ void Resize(u32 width = 0, u32 height = 0);
+
+private:
+ Display* m_display = nullptr;
+ Window m_parent_window = {};
+ Window m_window = {};
+ Colormap m_colormap = {};
+ u32 m_width = 0;
+ u32 m_height = 0;
+};
+
+// Helper class for managing X errors
+class X11InhibitErrors
+{
+public:
+ X11InhibitErrors();
+ ~X11InhibitErrors();
+
+ ALWAYS_INLINE bool HadError() const { return m_had_error; }
+
+private:
+ static int ErrorHandler(Display* display, XErrorEvent* ee);
+
+ XErrorHandler m_old_handler = {};
+ bool m_had_error = false;
+};
+
+} // namespace GL
diff --git a/src/common/window_info.h b/src/common/window_info.h
new file mode 100644
index 000000000..413f637dc
--- /dev/null
+++ b/src/common/window_info.h
@@ -0,0 +1,31 @@
+#pragma once
+
+// Contains the information required to create a graphics context in a window.
+struct WindowInfo
+{
+ enum class Type
+ {
+ Surfaceless,
+ Win32,
+ X11,
+ Wayland,
+ MacOS,
+ Android
+ };
+
+ enum class SurfaceFormat
+ {
+ None,
+ RGB8,
+ RGBA8,
+ RGB565,
+ Count
+ };
+
+ Type type = Type::Surfaceless;
+ void* display_connection = nullptr;
+ void* window_handle = nullptr;
+ u32 surface_width = 0;
+ u32 surface_height = 0;
+ SurfaceFormat surface_format = SurfaceFormat::RGB8;
+};