This commit is contained in:
wheremyfoodat 2023-09-30 01:33:27 +03:00
parent 42cbcc1380
commit 5d9ded47da
35 changed files with 37343 additions and 21800 deletions

179
third_party/duckstation/gl/context.cpp vendored Normal file
View file

@ -0,0 +1,179 @@
#include "context.h"
#include "duckstation_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)
#include "context_wgl.h"
#elif defined(__APPLE__)
#include "context_agl.h"
#else
#ifdef WAYLAND_ENABLED
#include "context_egl_wayland.h"
#endif
#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)
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);
}
#ifdef WAYLAND_ENABLED
if (wi.type == WindowInfo::Type::Wayland)
context = ContextEGLWayland::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();
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

76
third_party/duckstation/gl/context.h vendored Normal file
View file

@ -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

View file

@ -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

View file

@ -0,0 +1,214 @@
#include "context_agl.h"
#include "duckstation_compat.h"
#include "duckstation_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

View file

@ -0,0 +1,432 @@
#include "context_egl.h"
#include "duckstation_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

View file

@ -0,0 +1,48 @@
#pragma once
#include "context.h"
#include <glad/gl.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

View file

@ -0,0 +1,86 @@
#include "context_egl_wayland.h"
#include "duckstation_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

View file

@ -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

View file

@ -0,0 +1,69 @@
#include "context_egl_x11.h"
#include "duckstation_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

View file

@ -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

View file

@ -0,0 +1,328 @@
#include "context_glx.h"
#include "duckstation_compat.h"
#include "duckstation_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

View file

@ -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

View file

@ -0,0 +1,452 @@
#include "context_wgl.h"
#include "duckstation_compat.h"
#include "duckstation_log.h"
#include "duckstation_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

View file

@ -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

8
third_party/duckstation/gl/loader.h vendored Normal file
View file

@ -0,0 +1,8 @@
#pragma once
// Fix glad.h including windows.h
#ifdef _WIN32
#include "windows_headers.h"
#endif
#include <glad/gl.h>

View file

@ -0,0 +1,101 @@
#include "x11_window.h"
#include "duckstation_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

48
third_party/duckstation/gl/x11_window.h vendored Normal file
View file

@ -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