diff options
Diffstat (limited to 'src/frontend/duckstation')
23 files changed, 2601 insertions, 0 deletions
diff --git a/src/frontend/duckstation/duckstation_compat.h b/src/frontend/duckstation/duckstation_compat.h new file mode 100644 index 0000000..a661e92 --- /dev/null +++ b/src/frontend/duckstation/duckstation_compat.h @@ -0,0 +1,17 @@ +#ifndef DUCKSTATION_COMPAT_H +#define DUCKSTATION_COMPAT_H + +#include "../types.h" + +#include <assert.h> + +#define ALWAYS_INLINE __attribute__((always_inline)) inline + +#define AssertMsg(cond, msg) assert(cond && msg) +#define Assert(cond) assert(cond) + +#define Panic(msg) assert(false && msg) + +#define UnreachableCode() __builtin_unreachable + +#endif
\ No newline at end of file diff --git a/src/frontend/duckstation/gl/context.cpp b/src/frontend/duckstation/gl/context.cpp new file mode 100644 index 0000000..98e6bd1 --- /dev/null +++ b/src/frontend/duckstation/gl/context.cpp @@ -0,0 +1,175 @@ +#include "context.h" +#include "../log.h" +#include "loader.h" +#include <cstdlib> +#include <cstring> +#ifdef __APPLE__ +#include <stdlib.h> +#else +#include <malloc.h> +#endif +Log_SetChannel(GL::Context); + +#if defined(_WIN32) && !defined(_M_ARM64) +#include "context_wgl.h" +#elif defined(__APPLE__) +#include "context_agl.h" +#else +#include "context_egl_wayland.h" +#include "context_egl_x11.h" +#include "context_glx.h" +#endif + +namespace GL { + +static bool ShouldPreferESContext() +{ +#ifndef _MSC_VER + const char* value = std::getenv("PREFER_GLES_CONTEXT"); + return (value && 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::vector<Context::FullscreenModeInfo> Context::EnumerateFullscreenModes() +{ + return {}; +} + +std::unique_ptr<GL::Context> 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<Version*>(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> context; +#if defined(_WIN32) && !defined(_M_ARM64) + 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) + { + 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); + } + + if (wi.type == WindowInfo::Type::Wayland) + context = ContextEGLWayland::Create(wi, versions_to_try, num_versions_to_try); +#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(); + + if (!gladLoadGLLoader([](const char* name) { return context_being_created->GetProcAddress(name); })) + { + Log_ErrorPrintf("Failed to load GL functions for GLAD"); + return nullptr; + } + + const char* gl_vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR)); + const char* gl_renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER)); + const char* gl_version = reinterpret_cast<const char*>(glGetString(GL_VERSION)); + const char* gl_shading_language_version = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION)); + Log_InfoPrintf("GL_VENDOR: %s", gl_vendor); + Log_InfoPrintf("GL_RENDERER: %s", gl_renderer); + Log_InfoPrintf("GL_VERSION: %s", gl_version); + Log_InfoPrintf("GL_SHADING_LANGUAGE_VERSION: %s", gl_shading_language_version); + + return context; +} + +const std::array<Context::Version, 11>& Context::GetAllDesktopVersionsList() +{ + static constexpr std::array<Version, 11> 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::Version, 12>& Context::GetAllDesktopVersionsListWithFallback() +{ + static constexpr std::array<Version, 12> 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::Version, 4>& Context::GetAllESVersionsList() +{ + static constexpr std::array<Version, 4> vlist = { + {{Profile::ES, 3, 2}, {Profile::ES, 3, 1}, {Profile::ES, 3, 0}, {Profile::ES, 2, 0}}}; + return vlist; +} + +const std::array<Context::Version, 16>& Context::GetAllVersionsList() +{ + static constexpr std::array<Version, 16> 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/frontend/duckstation/gl/context.h b/src/frontend/duckstation/gl/context.h new file mode 100644 index 0000000..41d8a2c --- /dev/null +++ b/src/frontend/duckstation/gl/context.h @@ -0,0 +1,76 @@ +#pragma once +#include "../duckstation_compat.h" +#include "../window_info.h" +#include <array> +#include <memory> +#include <vector> + +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; + }; + + struct FullscreenModeInfo + { + u32 width; + u32 height; + float refresh_rate; + }; + + 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<Context> CreateSharedContext(const WindowInfo& wi) = 0; + + virtual std::vector<FullscreenModeInfo> EnumerateFullscreenModes(); + + static std::unique_ptr<Context> Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try); + + template<size_t N> + static std::unique_ptr<Context> Create(const WindowInfo& wi, const std::array<Version, N>& versions_to_try) + { + return Create(wi, versions_to_try.data(), versions_to_try.size()); + } + + static std::unique_ptr<Context> Create(const WindowInfo& wi) { return Create(wi, GetAllVersionsList()); } + + static const std::array<Version, 11>& GetAllDesktopVersionsList(); + static const std::array<Version, 12>& GetAllDesktopVersionsListWithFallback(); + static const std::array<Version, 4>& GetAllESVersionsList(); + static const std::array<Version, 16>& GetAllVersionsList(); + +protected: +#ifdef _WIN32 +#endif + + WindowInfo m_wi; + Version m_version = {}; +}; +} // namespace GL diff --git a/src/frontend/duckstation/gl/context_agl.h b/src/frontend/duckstation/gl/context_agl.h new file mode 100644 index 0000000..459bf2f --- /dev/null +++ b/src/frontend/duckstation/gl/context_agl.h @@ -0,0 +1,49 @@ +#pragma once +#include "context.h" +#include "loader.h" + +#if defined(__APPLE__) && defined(__OBJC__) +#import <AppKit/AppKit.h> +#else +struct NSOpenGLContext; +struct NSOpenGLPixelFormat; +struct NSView; +#define __bridge +#endif + +namespace GL { + +class ContextAGL final : public Context +{ +public: + ContextAGL(const WindowInfo& wi); + ~ContextAGL() override; + + static std::unique_ptr<Context> 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<Context> CreateSharedContext(const WindowInfo& wi) override; + +private: + ALWAYS_INLINE NSView* GetView() const { return static_cast<NSView*>((__bridge NSView*)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 diff --git a/src/frontend/duckstation/gl/context_agl.mm b/src/frontend/duckstation/gl/context_agl.mm new file mode 100644 index 0000000..f833518 --- /dev/null +++ b/src/frontend/duckstation/gl/context_agl.mm @@ -0,0 +1,214 @@ +#include "context_agl.h" +#include "../duckstation_compat.h" +#include "../log.h" +#include "loader.h" +#include <dlfcn.h> +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<Context> ContextAGL::Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try) +{ + std::unique_ptr<ContextAGL> context = std::make_unique<ContextAGL>(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<int>(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<u32>(static_cast<CGFloat>(window_size.width) * window_scale); + const u32 new_height = static_cast<u32>(static_cast<CGFloat>(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<GLint>(interval); + [m_context setValues:&gl_interval forParameter:NSOpenGLCPSwapInterval]; + return true; +} + +std::unique_ptr<Context> ContextAGL::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr<ContextAGL> context = std::make_unique<ContextAGL>(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<NSOpenGLPixelFormatAttribute, 5> attribs = {{ + NSOpenGLPFADoubleBuffer, + NSOpenGLPFAOpenGLProfile, + static_cast<NSOpenGLPixelFormatAttribute>(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/frontend/duckstation/gl/context_egl.cpp b/src/frontend/duckstation/gl/context_egl.cpp new file mode 100644 index 0000000..d251a39 --- /dev/null +++ b/src/frontend/duckstation/gl/context_egl.cpp @@ -0,0 +1,432 @@ +#include "context_egl.h" +#include "../log.h" +#include "../duckstation_compat.h" +#include <optional> +#include <vector> +#include <cstring> +Log_SetChannel(GL::ContextEGL); + +namespace GL { +ContextEGL::ContextEGL(const WindowInfo& wi) : Context(wi) {} + +ContextEGL::~ContextEGL() +{ + DestroySurface(); + DestroyContext(); +} + +std::unique_ptr<Context> ContextEGL::Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try) +{ + std::unique_ptr<ContextEGL> context = std::make_unique<ContextEGL>(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; + } + + if (!SetDisplay()) + 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); + + const char* extensions = eglQueryString(m_display, EGL_EXTENSIONS); + if (extensions) + { + Log_InfoPrintf("EGL Extensions: %s", extensions); + m_supports_surfaceless = std::strstr(extensions, "EGL_KHR_surfaceless_context") != nullptr; + } + if (!m_supports_surfaceless) + Log_WarningPrint("EGL implementation does not support surfaceless contexts, emulating with pbuffers"); + + for (size_t i = 0; i < num_versions_to_try; i++) + { + if (CreateContextAndSurface(versions_to_try[i], nullptr, true)) + return true; + } + + return false; +} + +bool ContextEGL::SetDisplay() +{ + m_display = eglGetDisplay(static_cast<EGLNativeDisplayType>(m_wi.display_connection)); + if (!m_display) + { + Log_ErrorPrintf("eglGetDisplay() failed: %d", eglGetError()); + return false; + } + + return true; +} + +void* ContextEGL::GetProcAddress(const char* name) +{ + return reinterpret_cast<void*>(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 (!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*/) +{ + if (new_surface_width == 0 && 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<u32>(surface_width); + m_wi.surface_height = static_cast<u32>(surface_height); + return; + } + 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<Context> ContextEGL::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr<ContextEGL> context = std::make_unique<ContextEGL>(wi); + context->m_display = m_display; + context->m_supports_surfaceless = m_supports_surfaceless; + + if (!context->CreateContextAndSurface(m_version, m_context, false)) + return nullptr; + + return context; +} + +EGLNativeWindowType ContextEGL::GetNativeWindow(EGLConfig config) +{ + return {}; +} + +bool ContextEGL::CreateSurface() +{ + if (m_wi.type == WindowInfo::Type::Surfaceless) + { + if (m_supports_surfaceless) + return true; + else + return CreatePBufferSurface(); + } + + 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<u32>(surface_width); + m_wi.surface_height = static_cast<u32>(surface_height); + } + else + { + Log_ErrorPrintf("eglQuerySurface() failed: %d", eglGetError()); + } + + return true; +} + +bool ContextEGL::CreatePBufferSurface() +{ + const u32 width = std::max<u32>(m_wi.surface_width, 1); + const u32 height = std::max<u32>(m_wi.surface_height, 1); + + // TODO: Format + EGLint attrib_list[] = { + EGL_WIDTH, static_cast<EGLint>(width), EGL_HEIGHT, static_cast<EGLint>(height), EGL_NONE, + }; + + m_surface = eglCreatePbufferSurface(m_display, m_config, attrib_list); + if (!m_surface) + { + Log_ErrorPrintf("eglCreatePbufferSurface() failed: %d", eglGetError()); + return false; + } + + Log_DevPrintf("Created %ux%u pbuffer surface", width, height); + return true; +} + +bool ContextEGL::CheckConfigSurfaceFormat(EGLConfig config, WindowInfo::SurfaceFormat format) const +{ + int red_size, green_size, blue_size, alpha_size; + if (!eglGetConfigAttrib(m_display, config, EGL_RED_SIZE, &red_size) || + !eglGetConfigAttrib(m_display, config, EGL_GREEN_SIZE, &green_size) || + !eglGetConfigAttrib(m_display, config, EGL_BLUE_SIZE, &blue_size) || + !eglGetConfigAttrib(m_display, config, EGL_ALPHA_SIZE, &alpha_size)) + { + return false; + } + + switch (format) + { + case WindowInfo::SurfaceFormat::Auto: + return true; + + case WindowInfo::SurfaceFormat::RGB8: + return (red_size == 8 && green_size == 8 && blue_size == 8); + + case WindowInfo::SurfaceFormat::RGBA8: + return (red_size == 8 && green_size == 8 && blue_size == 8 && alpha_size == 8); + + case WindowInfo::SurfaceFormat::RGB565: + return (red_size == 5 && green_size == 6 && blue_size == 5); + + default: + return false; + } +} + +void ContextEGL::DestroyContext() +{ + if (eglGetCurrentContext() == m_context) + eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (m_context != EGL_NO_CONTEXT) + { + eglDestroyContext(m_display, m_context); + m_context = EGL_NO_CONTEXT; + } +} + +void ContextEGL::DestroySurface() +{ + if (eglGetCurrentSurface(EGL_DRAW) == m_surface) + 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; + } +} + +bool ContextEGL::CreateContext(const Version& version, EGLContext share_context) +{ + Log_DevPrintf( + "Trying version %u.%u (%s)", version.major_version, version.minor_version, + version.profile == Context::Profile::ES ? "ES" : (version.profile == Context::Profile::Core ? "Core" : "None")); + 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; + + case WindowInfo::SurfaceFormat::Auto: + break; + + default: + UnreachableCode(); + break; + } + + surface_attribs[nsurface_attribs++] = EGL_NONE; + surface_attribs[nsurface_attribs++] = 0; + + EGLint num_configs; + if (!eglChooseConfig(m_display, surface_attribs, nullptr, 0, &num_configs) || num_configs == 0) + { + Log_ErrorPrintf("eglChooseConfig() failed: %d", eglGetError()); + return false; + } + + std::vector<EGLConfig> configs(static_cast<u32>(num_configs)); + if (!eglChooseConfig(m_display, surface_attribs, configs.data(), num_configs, &num_configs)) + { + Log_ErrorPrintf("eglChooseConfig() failed: %d", eglGetError()); + return false; + } + configs.resize(static_cast<u32>(num_configs)); + + std::optional<EGLConfig> config; + for (EGLConfig check_config : configs) + { + if (CheckConfigSurfaceFormat(check_config, m_wi.surface_format)) + { + config = check_config; + break; + } + } + + if (!config.has_value()) + { + Log_WarningPrintf("No EGL configs matched exactly, using first."); + config = configs.front(); + } + + 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; + + if (!eglBindAPI((version.profile == Profile::ES) ? EGL_OPENGL_ES_API : EGL_OPENGL_API)) + { + Log_ErrorPrintf("eglBindAPI(%s) failed", (version.profile == Profile::ES) ? "EGL_OPENGL_ES_API" : "EGL_OPENGL_API"); + return false; + } + + m_context = eglCreateContext(m_display, config.value(), share_context, attribs); + if (!m_context) + { + Log_ErrorPrintf("eglCreateContext() failed: %d", eglGetError()); + return false; + } + + Log_InfoPrintf( + "Got version %u.%u (%s)", version.major_version, version.minor_version, + version.profile == Context::Profile::ES ? "ES" : (version.profile == Context::Profile::Core ? "Core" : "None")); + + m_config = config.value(); + 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 (!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/frontend/duckstation/gl/context_egl.h b/src/frontend/duckstation/gl/context_egl.h new file mode 100644 index 0000000..c5eff20 --- /dev/null +++ b/src/frontend/duckstation/gl/context_egl.h @@ -0,0 +1,48 @@ +#pragma once +#include "context.h" +#include "../../glad/glad_egl.h" + +namespace GL { + +class ContextEGL : public Context +{ +public: + ContextEGL(const WindowInfo& wi); + ~ContextEGL() override; + + static std::unique_ptr<Context> 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<Context> CreateSharedContext(const WindowInfo& wi) override; + +protected: + virtual bool SetDisplay(); + 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(); + bool CreatePBufferSurface(); + bool CheckConfigSurfaceFormat(EGLConfig config, WindowInfo::SurfaceFormat format) const; + void DestroyContext(); + void DestroySurface(); + + EGLDisplay m_display = EGL_NO_DISPLAY; + EGLSurface m_surface = EGL_NO_SURFACE; + EGLContext m_context = EGL_NO_CONTEXT; + + EGLConfig m_config = {}; + + bool m_supports_surfaceless = false; +}; + +} // namespace GL diff --git a/src/frontend/duckstation/gl/context_egl_wayland.cpp b/src/frontend/duckstation/gl/context_egl_wayland.cpp new file mode 100644 index 0000000..16532e8 --- /dev/null +++ b/src/frontend/duckstation/gl/context_egl_wayland.cpp @@ -0,0 +1,86 @@ +#include "context_egl_wayland.h" +#include "../log.h" +#include <dlfcn.h> +Log_SetChannel(ContextEGLWayland); + +namespace GL { +static const char* WAYLAND_EGL_MODNAME = "libwayland-egl.so.1"; + +ContextEGLWayland::ContextEGLWayland(const WindowInfo& wi) : ContextEGL(wi) {} +ContextEGLWayland::~ContextEGLWayland() +{ + if (m_wl_window) + m_wl_egl_window_destroy(m_wl_window); + if (m_wl_module) + dlclose(m_wl_module); +} + +std::unique_ptr<Context> ContextEGLWayland::Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try) +{ + std::unique_ptr<ContextEGLWayland> context = std::make_unique<ContextEGLWayland>(wi); + if (!context->LoadModule() || !context->Initialize(versions_to_try, num_versions_to_try)) + return nullptr; + + return context; +} + +std::unique_ptr<Context> ContextEGLWayland::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr<ContextEGLWayland> context = std::make_unique<ContextEGLWayland>(wi); + context->m_display = m_display; + + if (!context->LoadModule() || !context->CreateContextAndSurface(m_version, m_context, false)) + return nullptr; + + return context; +} + +void ContextEGLWayland::ResizeSurface(u32 new_surface_width, u32 new_surface_height) +{ + if (m_wl_window) + m_wl_egl_window_resize(m_wl_window, new_surface_width, new_surface_height, 0, 0); + + ContextEGL::ResizeSurface(new_surface_width, new_surface_height); +} + +EGLNativeWindowType ContextEGLWayland::GetNativeWindow(EGLConfig config) +{ + if (m_wl_window) + { + m_wl_egl_window_destroy(m_wl_window); + m_wl_window = nullptr; + } + + m_wl_window = + m_wl_egl_window_create(static_cast<wl_surface*>(m_wi.window_handle), m_wi.surface_width, m_wi.surface_height); + if (!m_wl_window) + return {}; + + return reinterpret_cast<EGLNativeWindowType>(m_wl_window); +} + +bool ContextEGLWayland::LoadModule() +{ + m_wl_module = dlopen(WAYLAND_EGL_MODNAME, RTLD_NOW | RTLD_GLOBAL); + if (!m_wl_module) + { + Log_ErrorPrintf("Failed to load %s.", WAYLAND_EGL_MODNAME); + return false; + } + + m_wl_egl_window_create = + reinterpret_cast<decltype(m_wl_egl_window_create)>(dlsym(m_wl_module, "wl_egl_window_create")); + m_wl_egl_window_destroy = + reinterpret_cast<decltype(m_wl_egl_window_destroy)>(dlsym(m_wl_module, "wl_egl_window_destroy")); + m_wl_egl_window_resize = + reinterpret_cast<decltype(m_wl_egl_window_resize)>(dlsym(m_wl_module, "wl_egl_window_resize")); + if (!m_wl_egl_window_create || !m_wl_egl_window_destroy || !m_wl_egl_window_resize) + { + Log_ErrorPrintf("Failed to load one or more functions from %s.", WAYLAND_EGL_MODNAME); + return false; + } + + return true; +} +} // namespace GL diff --git a/src/frontend/duckstation/gl/context_egl_wayland.h b/src/frontend/duckstation/gl/context_egl_wayland.h new file mode 100644 index 0000000..4682f42 --- /dev/null +++ b/src/frontend/duckstation/gl/context_egl_wayland.h @@ -0,0 +1,33 @@ +#pragma once +#include "context_egl.h" +#include <wayland-egl.h> + +namespace GL { + +class ContextEGLWayland final : public ContextEGL +{ +public: + ContextEGLWayland(const WindowInfo& wi); + ~ContextEGLWayland() override; + + static std::unique_ptr<Context> Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try); + + std::unique_ptr<Context> 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: + bool LoadModule(); + + wl_egl_window* m_wl_window = nullptr; + + void* m_wl_module = nullptr; + wl_egl_window* (*m_wl_egl_window_create)(struct wl_surface* surface, int width, int height); + void (*m_wl_egl_window_destroy)(struct wl_egl_window* egl_window); + void (*m_wl_egl_window_resize)(struct wl_egl_window* egl_window, int width, int height, int dx, int dy); +}; + +} // namespace GL diff --git a/src/frontend/duckstation/gl/context_egl_x11.cpp b/src/frontend/duckstation/gl/context_egl_x11.cpp new file mode 100644 index 0000000..c7310e3 --- /dev/null +++ b/src/frontend/duckstation/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<Context> ContextEGLX11::Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try) +{ + std::unique_ptr<ContextEGLX11> context = std::make_unique<ContextEGLX11>(wi); + if (!context->Initialize(versions_to_try, num_versions_to_try)) + return nullptr; + + return context; +} + +std::unique_ptr<Context> ContextEGLX11::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr<ContextEGLX11> context = std::make_unique<ContextEGLX11>(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<Display*>(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<Window>(reinterpret_cast<uintptr_t>(m_wi.window_handle)), vi)) + { + Log_ErrorPrintf("Faild to create X11 child window"); + XFree(vi); + return false; + } + + XFree(vi); + return static_cast<EGLNativeWindowType>(m_window.GetWindow()); +} +} // namespace GL diff --git a/src/frontend/duckstation/gl/context_egl_x11.h b/src/frontend/duckstation/gl/context_egl_x11.h new file mode 100644 index 0000000..7def8bf --- /dev/null +++ b/src/frontend/duckstation/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<Context> Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try); + + std::unique_ptr<Context> 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<Display*>(m_wi.display_connection); } + + X11Window m_window; +}; + +} // namespace GL diff --git a/src/frontend/duckstation/gl/context_glx.cpp b/src/frontend/duckstation/gl/context_glx.cpp new file mode 100644 index 0000000..bd32039 --- /dev/null +++ b/src/frontend/duckstation/gl/context_glx.cpp @@ -0,0 +1,328 @@ +#include "context_glx.h" +#include "../duckstation_compat.h" +#include "../log.h" +#include <dlfcn.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); + + if (m_vi) + XFree(m_vi); + + if (m_libGL_handle) + dlclose(m_libGL_handle); +} + +std::unique_ptr<Context> ContextGLX::Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try) +{ + std::unique_ptr<ContextGLX> context = std::make_unique<ContextGLX>(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) +{ + // We need libGL loaded, because GLAD loads its own, then releases it. + m_libGL_handle = dlopen("libGL.so.1", RTLD_NOW | RTLD_GLOBAL); + if (!m_libGL_handle) + { + m_libGL_handle = dlopen("libGL.so", RTLD_NOW | RTLD_GLOBAL); + if (!m_libGL_handle) + { + Log_ErrorPrintf("Failed to load libGL.so: %s", dlerror()); + return false; + } + } + + 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<void*>(glXGetProcAddress(reinterpret_cast<const GLubyte*>(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(new_surface_width, new_surface_height); + 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<u32>(std::max(interval, 0))) != 0); + } + else if (GLAD_GLX_SGI_swap_control) + { + return (glXSwapIntervalSGI(interval) != 0); + } + else + { + return false; + } +} + +std::unique_ptr<Context> ContextGLX::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr<ContextGLX> context = std::make_unique<ContextGLX>(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; + + case WindowInfo::SurfaceFormat::Auto: + 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; + } + + m_vi = glXGetVisualFromFBConfig(GetDisplay(), m_fb_config); + if (!m_vi) + { + Log_ErrorPrintf("glXGetVisualFromFBConfig() failed"); + return false; + } + + return m_window.Create(GetDisplay(), static_cast<Window>(reinterpret_cast<uintptr_t>(m_wi.window_handle)), m_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/frontend/duckstation/gl/context_glx.h b/src/frontend/duckstation/gl/context_glx.h new file mode 100644 index 0000000..c8416e5 --- /dev/null +++ b/src/frontend/duckstation/gl/context_glx.h @@ -0,0 +1,44 @@ +#pragma once +#include "context.h" +#include "../../glad/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<Context> 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<Context> CreateSharedContext(const WindowInfo& wi) override; + +private: + ALWAYS_INLINE Display* GetDisplay() const { return static_cast<Display*>(m_wi.display_connection); } + ALWAYS_INLINE GLXDrawable GetDrawable() const { return static_cast<GLXDrawable>(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; + + // GLAD releases its reference to libGL.so, so we need to maintain our own. + void* m_libGL_handle = nullptr; +}; + +} // namespace GL diff --git a/src/frontend/duckstation/gl/context_wgl.cpp b/src/frontend/duckstation/gl/context_wgl.cpp new file mode 100644 index 0000000..03c18e8 --- /dev/null +++ b/src/frontend/duckstation/gl/context_wgl.cpp @@ -0,0 +1,452 @@ +#include "context_wgl.h" +#include "../duckstation_compat.h" +#include "../log.h" +#include "../scoped_guard.h" +#include "loader.h" +Log_SetChannel(GL::ContextWGL); + +// TODO: get rid of this +#pragma comment(lib, "opengl32.lib") + +static void* GetProcAddressCallback(const char* name) +{ + void* addr = reinterpret_cast<void*>(wglGetProcAddress(name)); + if (addr) + return addr; + + // try opengl32.dll + return reinterpret_cast<void*>(::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); + + ReleaseDC(); +} + +std::unique_ptr<Context> ContextWGL::Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try) +{ + std::unique_ptr<ContextWGL> context = std::make_unique<ContextWGL>(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 + { + Log_ErrorPrint("ContextWGL must always start with a valid surface."); + return false; + } + + // 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); + + ReleaseDC(); + + 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<u32>(client_rc.right - client_rc.left); + m_wi.surface_height = static_cast<u32>(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<Context> ContextWGL::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr<ContextWGL> context = std::make_unique<ContextWGL>(wi); + if (wi.type == WindowInfo::Type::Win32) + { + if (!context->InitializeDC()) + return nullptr; + } + else + { + Log_ErrorPrint("PBuffer not implemented"); + return nullptr; + } + + 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; +} + +HDC ContextWGL::GetDCAndSetPixelFormat(HWND hwnd) +{ + 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; + pfd.cRedBits = 8; + pfd.cGreenBits = 8; + pfd.cBlueBits = 8; + pfd.cColorBits = 24; + + HDC hDC = ::GetDC(hwnd); + if (!hDC) + { + Log_ErrorPrintf("GetDC() failed: 0x%08X", GetLastError()); + return {}; + } + + if (!m_pixel_format.has_value()) + { + const int pf = ChoosePixelFormat(hDC, &pfd); + if (pf == 0) + { + Log_ErrorPrintf("ChoosePixelFormat() failed: 0x%08X", GetLastError()); + ::ReleaseDC(hwnd, hDC); + return {}; + } + + m_pixel_format = pf; + } + + if (!SetPixelFormat(hDC, m_pixel_format.value(), &pfd)) + { + Log_ErrorPrintf("SetPixelFormat() failed: 0x%08X", GetLastError()); + ::ReleaseDC(hwnd, hDC); + return {}; + } + + return hDC; +} + +bool ContextWGL::InitializeDC() +{ + if (m_wi.type == WindowInfo::Type::Win32) + { + m_dc = GetDCAndSetPixelFormat(GetHWND()); + if (!m_dc) + { + Log_ErrorPrint("Failed to get DC for window"); + return false; + } + + return true; + } + else if (m_wi.type == WindowInfo::Type::Surfaceless) + { + return CreatePBuffer(); + } + else + { + Log_ErrorPrintf("Unknown window info type %u", static_cast<unsigned>(m_wi.type)); + return false; + } +} + +void ContextWGL::ReleaseDC() +{ + if (m_pbuffer) + { + wglReleasePbufferDCARB(m_pbuffer, m_dc); + m_dc = {}; + + wglDestroyPbufferARB(m_pbuffer); + m_pbuffer = {}; + + ::ReleaseDC(m_dummy_window, m_dummy_dc); + m_dummy_dc = {}; + + DestroyWindow(m_dummy_window); + m_dummy_window = {}; + } + else if (m_dc) + { + ::ReleaseDC(GetHWND(), m_dc); + m_dc = {}; + } +} + +bool ContextWGL::CreatePBuffer() +{ + static bool window_class_registered = false; + static const wchar_t* window_class_name = L"ContextWGLPBuffer"; + + if (!window_class_registered) + { + WNDCLASSEXW wc = {}; + wc.cbSize = sizeof(WNDCLASSEXW); + wc.style = 0; + wc.lpfnWndProc = DefWindowProcW; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = GetModuleHandle(nullptr); + wc.hIcon = NULL; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wc.lpszMenuName = NULL; + wc.lpszClassName = window_class_name; + wc.hIconSm = NULL; + + if (!RegisterClassExW(&wc)) + { + Log_ErrorPrint("(ContextWGL::CreatePBuffer) RegisterClassExW() failed"); + return false; + } + + window_class_registered = true; + } + + HWND hwnd = CreateWindowExW(0, window_class_name, window_class_name, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL); + if (!hwnd) + { + Log_ErrorPrint("(ContextWGL::CreatePBuffer) CreateWindowEx() failed"); + return false; + } + + ScopedGuard hwnd_guard([hwnd]() { DestroyWindow(hwnd); }); + + HDC hdc = GetDCAndSetPixelFormat(hwnd); + if (!hdc) + return false; + + ScopedGuard hdc_guard([hdc, hwnd]() { ::ReleaseDC(hwnd, hdc); }); + + static constexpr const int pb_attribs[] = {0, 0}; + + AssertMsg(m_pixel_format.has_value(), "Has pixel format for pbuffer"); + HPBUFFERARB pbuffer = wglCreatePbufferARB(hdc, m_pixel_format.value(), 1, 1, pb_attribs); + if (!pbuffer) + { + Log_ErrorPrint("(ContextWGL::CreatePBuffer) wglCreatePbufferARB() failed"); + return false; + } + + ScopedGuard pbuffer_guard([pbuffer]() { wglDestroyPbufferARB(pbuffer); }); + + m_dc = wglGetPbufferDCARB(pbuffer); + if (!m_dc) + { + Log_ErrorPrint("(ContextWGL::CreatePbuffer) wglGetPbufferDCARB() failed"); + return false; + } + + m_dummy_window = hwnd; + m_dummy_dc = hdc; + m_pbuffer = pbuffer; + + pbuffer_guard.Cancel(); + hdc_guard.Cancel(); + hwnd_guard.Cancel(); + 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 reinterpret_cast<void*>(wglGetProcAddress(name)); }, m_dc)) + { + Log_ErrorPrint("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) + { + if ((version.major_version >= 2 && !GLAD_WGL_EXT_create_context_es2_profile) || + (version.major_version < 2 && !GLAD_WGL_EXT_create_context_es_profile)) + { + Log_ErrorPrint("WGL_EXT_create_context_es_profile not supported"); + return false; + } + + 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_ErrorPrint("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 reinterpret_cast<void*>(wglGetProcAddress(name)); }, m_dc)) + { + Log_ErrorPrint("Loading GLAD WGL functions failed"); + return false; + } + + wglDeleteContext(m_rc); + } + + m_rc = new_rc; + return true; +} +} // namespace GL diff --git a/src/frontend/duckstation/gl/context_wgl.h b/src/frontend/duckstation/gl/context_wgl.h new file mode 100644 index 0000000..d742333 --- /dev/null +++ b/src/frontend/duckstation/gl/context_wgl.h @@ -0,0 +1,53 @@ +#pragma once +#include "../windows_headers.h" + +#include "context.h" +#include "../../glad/glad_wgl.h" +#include "loader.h" +#include <optional> + +namespace GL { + +class ContextWGL final : public Context +{ +public: + ContextWGL(const WindowInfo& wi); + ~ContextWGL() override; + + static std::unique_ptr<Context> 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<Context> CreateSharedContext(const WindowInfo& wi) override; + +private: + ALWAYS_INLINE HWND GetHWND() const { return static_cast<HWND>(m_wi.window_handle); } + + HDC GetDCAndSetPixelFormat(HWND hwnd); + + bool Initialize(const Version* versions_to_try, size_t num_versions_to_try); + bool InitializeDC(); + void ReleaseDC(); + bool CreatePBuffer(); + 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 = {}; + + // Can't change pixel format once it's set for a RC. + std::optional<int> m_pixel_format; + + // Dummy window for creating a PBuffer off when we're surfaceless. + HWND m_dummy_window = {}; + HDC m_dummy_dc = {}; + HPBUFFERARB m_pbuffer = {}; +}; + +} // namespace GL
\ No newline at end of file diff --git a/src/frontend/duckstation/gl/loader.h b/src/frontend/duckstation/gl/loader.h new file mode 100644 index 0000000..0c2ba12 --- /dev/null +++ b/src/frontend/duckstation/gl/loader.h @@ -0,0 +1,8 @@ +#pragma once + +// Fix glad.h including windows.h +#ifdef _WIN32 +#include "../windows_headers.h" +#endif + +#include "../../glad/glad.h" diff --git a/src/frontend/duckstation/gl/x11_window.cpp b/src/frontend/duckstation/gl/x11_window.cpp new file mode 100644 index 0000000..bf09fcc --- /dev/null +++ b/src/frontend/duckstation/gl/x11_window.cpp @@ -0,0 +1,101 @@ +#include "x11_window.h" +#include "../log.h" +#include "../duckstation_compat.h" +#include <cstdio> +Log_SetChannel(X11Window); + +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<u32>(parent_wa.width); + m_height = static_cast<u32>(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<u32>(parent_wa.width); + m_height = static_cast<u32>(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) +{ + char error_string[256] = {}; + XGetErrorText(display, ee->error_code, error_string, sizeof(error_string)); + Log_WarningPrintf("X11 Error: %s (Error %u Minor %u Request %u)", error_string, ee->error_code, ee->minor_code, + ee->request_code); + + s_current_error_inhibiter->m_had_error = true; + return 0; +} +} // namespace GL diff --git a/src/frontend/duckstation/gl/x11_window.h b/src/frontend/duckstation/gl/x11_window.h new file mode 100644 index 0000000..aff38b7 --- /dev/null +++ b/src/frontend/duckstation/gl/x11_window.h @@ -0,0 +1,48 @@ +#pragma once +#include "../duckstation_compat.h" +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +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/frontend/duckstation/log.h b/src/frontend/duckstation/log.h new file mode 100644 index 0000000..a945bb3 --- /dev/null +++ b/src/frontend/duckstation/log.h @@ -0,0 +1,46 @@ +#ifndef LOG_H +#define LOG_H + +#include <stdio.h> + +#define Log_SetChannel(ChannelName) +#define Log_ErrorPrint(msg) puts(msg "\n"); +#define Log_ErrorPrintf(...) do { printf(__VA_ARGS__); putchar('\n'); } while (0) +#define Log_WarningPrint(msg) puts(msg) +#define Log_WarningPrintf(...) do { printf(__VA_ARGS__); putchar('\n'); } while (0) +#define Log_PerfPrint(msg) puts(msg) +#define Log_PerfPrintf(...) do { printf(__VA_ARGS__); putchar('\n'); } while (0) +#define Log_InfoPrint(msg) puts(msg) +#define Log_InfoPrintf(...) do { printf(__VA_ARGS__); putchar('\n'); } while (0) +#define Log_VerbosePrint(msg) puts(msg) +#define Log_VerbosePrintf(...) do { printf(__VA_ARGS__); putchar('\n'); } while (0) +#define Log_DevPrint(msg) puts(msg) +#define Log_DevPrintf(...) do { printf(__VA_ARGS__); putchar('\n'); } while (0) +#define Log_ProfilePrint(msg) puts(msg) +#define Log_ProfilePrintf(...) do { printf(__VA_ARGS__); putchar('\n'); } while (0) + +#ifdef _DEBUG +#define Log_DebugPrint(msg) puts(msg) +#define Log_DebugPrintf(...) do { printf(__VA_ARGS__); putchar('\n'); } while (0) +#define Log_TracePrint(msg) puts(msg) +#define Log_TracePrintf(...) do { printf(__VA_ARGS__); putchar('\n'); } while (0) +#else +#define Log_DebugPrint(msg) \ + do \ + { \ + } while (0) +#define Log_DebugPrintf(...) \ + do \ + { \ + } while (0) +#define Log_TracePrint(msg) \ + do \ + { \ + } while (0) +#define Log_TracePrintf(...) \ + do \ + { \ + } while (0) +#endif + +#endif
\ No newline at end of file diff --git a/src/frontend/duckstation/scoped_guard.h b/src/frontend/duckstation/scoped_guard.h new file mode 100644 index 0000000..89f35d9 --- /dev/null +++ b/src/frontend/duckstation/scoped_guard.h @@ -0,0 +1,34 @@ +#pragma once +#include <optional> +#include <utility> + +/// ScopedGuard provides an object which runs a function (usually a lambda) when +/// it goes out of scope. This can be useful for releasing resources or handles +/// which do not normally have C++ types to automatically release. +template<typename T> +class ScopedGuard final +{ +public: + ALWAYS_INLINE ScopedGuard(T&& func) : m_func(std::forward<T>(func)) {} + ALWAYS_INLINE ScopedGuard(ScopedGuard&& other) : m_func(std::move(other.m_func)) { other.m_func = nullptr; } + ALWAYS_INLINE ~ScopedGuard() { Invoke(); } + + ScopedGuard(const ScopedGuard&) = delete; + void operator=(const ScopedGuard&) = delete; + + /// Prevents the function from being invoked when we go out of scope. + ALWAYS_INLINE void Cancel() { m_func.reset(); } + + /// Explicitly fires the function. + ALWAYS_INLINE void Invoke() + { + if (!m_func.has_value()) + return; + + m_func.value()(); + m_func.reset(); + } + +private: + std::optional<T> m_func; +}; diff --git a/src/frontend/duckstation/window_info.cpp b/src/frontend/duckstation/window_info.cpp new file mode 100644 index 0000000..5176dad --- /dev/null +++ b/src/frontend/duckstation/window_info.cpp @@ -0,0 +1,191 @@ +#include "window_info.h" +#include "common/log.h" +Log_SetChannel(WindowInfo); + +#if defined(_WIN32) + +#include "common/windows_headers.h" +#include <dwmapi.h> + +static bool GetRefreshRateFromDWM(HWND hwnd, float* refresh_rate) +{ + static HMODULE dwm_module = nullptr; + static HRESULT(STDAPICALLTYPE * is_composition_enabled)(BOOL * pfEnabled) = nullptr; + static HRESULT(STDAPICALLTYPE * get_timing_info)(HWND hwnd, DWM_TIMING_INFO * pTimingInfo) = nullptr; + static bool load_tried = false; + if (!load_tried) + { + load_tried = true; + dwm_module = LoadLibrary("dwmapi.dll"); + if (dwm_module) + { + std::atexit([]() { + FreeLibrary(dwm_module); + dwm_module = nullptr; + }); + is_composition_enabled = + reinterpret_cast<decltype(is_composition_enabled)>(GetProcAddress(dwm_module, "DwmIsCompositionEnabled")); + get_timing_info = + reinterpret_cast<decltype(get_timing_info)>(GetProcAddress(dwm_module, "DwmGetCompositionTimingInfo")); + } + } + + BOOL composition_enabled; + if (!is_composition_enabled || FAILED(is_composition_enabled(&composition_enabled) || !get_timing_info)) + return false; + + DWM_TIMING_INFO ti = {}; + ti.cbSize = sizeof(ti); + HRESULT hr = get_timing_info(nullptr, &ti); + if (SUCCEEDED(hr)) + { + if (ti.rateRefresh.uiNumerator == 0 || ti.rateRefresh.uiDenominator == 0) + return false; + + *refresh_rate = static_cast<float>(ti.rateRefresh.uiNumerator) / static_cast<float>(ti.rateRefresh.uiDenominator); + return true; + } + + return false; +} + +static bool GetRefreshRateFromMonitor(HWND hwnd, float* refresh_rate) +{ + HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + if (!mon) + return false; + + MONITORINFOEXW mi = {}; + mi.cbSize = sizeof(mi); + if (GetMonitorInfoW(mon, &mi)) + { + DEVMODEW dm = {}; + dm.dmSize = sizeof(dm); + + // 0/1 are reserved for "defaults". + if (EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm) && dm.dmDisplayFrequency > 1) + { + *refresh_rate = static_cast<float>(dm.dmDisplayFrequency); + return true; + } + } + + return false; +} + +bool WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate) +{ + if (wi.type != Type::Win32 || !wi.window_handle) + return false; + + // Try DWM first, then fall back to integer values. + const HWND hwnd = static_cast<HWND>(wi.window_handle); + return GetRefreshRateFromDWM(hwnd, refresh_rate) || GetRefreshRateFromMonitor(hwnd, refresh_rate); +} + +#else + +#ifdef USE_X11 + +#include "common/scoped_guard.h" +#include "gl/x11_window.h" +#include <X11/extensions/Xrandr.h> + +static bool GetRefreshRateFromXRandR(const WindowInfo& wi, float* refresh_rate) +{ + Display* display = static_cast<Display*>(wi.display_connection); + Window window = static_cast<Window>(reinterpret_cast<uintptr_t>(wi.window_handle)); + if (!display || !window) + return false; + + GL::X11InhibitErrors inhibiter; + + XRRScreenResources* res = XRRGetScreenResources(display, window); + if (!res) + { + Log_ErrorPrint("XRRGetScreenResources() failed"); + return false; + } + + ScopedGuard res_guard([res]() { XRRFreeScreenResources(res); }); + + int num_monitors; + XRRMonitorInfo* mi = XRRGetMonitors(display, window, True, &num_monitors); + if (num_monitors < 0) + { + Log_ErrorPrint("XRRGetMonitors() failed"); + return false; + } + else if (num_monitors > 1) + { + Log_WarningPrintf("XRRGetMonitors() returned %d monitors, using first", num_monitors); + } + + ScopedGuard mi_guard([mi]() { XRRFreeMonitors(mi); }); + if (mi->noutput <= 0) + { + Log_ErrorPrint("Monitor has no outputs"); + return false; + } + else if (mi->noutput > 1) + { + Log_WarningPrintf("Monitor has %d outputs, using first", mi->noutput); + } + + XRROutputInfo* oi = XRRGetOutputInfo(display, res, mi->outputs[0]); + if (!oi) + { + Log_ErrorPrint("XRRGetOutputInfo() failed"); + return false; + } + + ScopedGuard oi_guard([oi]() { XRRFreeOutputInfo(oi); }); + + XRRCrtcInfo* ci = XRRGetCrtcInfo(display, res, oi->crtc); + if (!ci) + { + Log_ErrorPrint("XRRGetCrtcInfo() failed"); + return false; + } + + ScopedGuard ci_guard([ci]() { XRRFreeCrtcInfo(ci); }); + + XRRModeInfo* mode = nullptr; + for (int i = 0; i < res->nmode; i++) + { + if (res->modes[i].id == ci->mode) + { + mode = &res->modes[i]; + break; + } + } + if (!mode) + { + Log_ErrorPrintf("Failed to look up mode %d (of %d)", static_cast<int>(ci->mode), res->nmode); + return false; + } + + if (mode->dotClock == 0 || mode->hTotal == 0 || mode->vTotal == 0) + { + Log_ErrorPrintf("Modeline is invalid: %ld/%d/%d", mode->dotClock, mode->hTotal, mode->vTotal); + return false; + } + + *refresh_rate = + static_cast<double>(mode->dotClock) / (static_cast<double>(mode->hTotal) * static_cast<double>(mode->vTotal)); + return true; +} + +#endif // USE_X11 + +bool WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate) +{ +#if defined(USE_X11) + if (wi.type == WindowInfo::Type::X11) + return GetRefreshRateFromXRandR(wi, refresh_rate); +#endif + + return false; +} + +#endif
\ No newline at end of file diff --git a/src/frontend/duckstation/window_info.h b/src/frontend/duckstation/window_info.h new file mode 100644 index 0000000..44912ca --- /dev/null +++ b/src/frontend/duckstation/window_info.h @@ -0,0 +1,43 @@ +#pragma once +#include "../types.h" + +// Contains the information required to create a graphics context in a window. +struct WindowInfo +{ + enum class Type + { + Surfaceless, + Win32, + X11, + Wayland, + MacOS, + Android, + Display, + }; + + enum class SurfaceFormat + { + None, + Auto, + RGB8, + RGBA8, + RGB565, + Count + }; + + Type type = Type::Surfaceless; + void* display_connection = nullptr; + void* window_handle = nullptr; + u32 surface_width = 0; + u32 surface_height = 0; + float surface_refresh_rate = 0.0f; + float surface_scale = 1.0f; + SurfaceFormat surface_format = SurfaceFormat::RGB8; + + // Needed for macOS. +#ifdef __APPLE__ + void* surface_handle = nullptr; +#endif + + static bool QueryRefreshRateForWindow(const WindowInfo& wi, float* refresh_rate); +}; diff --git a/src/frontend/duckstation/windows_headers.h b/src/frontend/duckstation/windows_headers.h new file mode 100644 index 0000000..6ff6ed3 --- /dev/null +++ b/src/frontend/duckstation/windows_headers.h @@ -0,0 +1,26 @@ +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#ifndef NOMINMAX +#define NOMINMAX 1 +#endif + +// require vista+ +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT _WIN32_WINNT_VISTA + +#include <windows.h> + +#if defined(CreateDirectory) +#undef CreateDirectory +#endif +#if defined(CopyFile) +#undef CopyFile +#endif +#if defined(DeleteFile) +#undef DeleteFile +#endif |