aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/duckstation/gl/context_egl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/duckstation/gl/context_egl.cpp')
-rw-r--r--src/frontend/duckstation/gl/context_egl.cpp432
1 files changed, 432 insertions, 0 deletions
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