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; +};