Common: Add DRM display helper class and GBM GL context
This commit is contained in:
@ -11,18 +11,21 @@ Log_SetChannel(GL::Context);
|
||||
|
||||
#if defined(WIN32) && !defined(_M_ARM64)
|
||||
#include "context_wgl.h"
|
||||
#elif defined(__APPLE__)
|
||||
#elif defined(__APPLE__) && !defined(LIBERTRO)
|
||||
#include "context_agl.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_EGL
|
||||
#if defined(USE_X11) || defined(USE_WAYLAND)
|
||||
#if defined(USE_X11) || defined(USE_WAYLAND) || defined(USE_GBM)
|
||||
#if defined(USE_X11)
|
||||
#include "context_egl_x11.h"
|
||||
#endif
|
||||
#if defined(USE_WAYLAND)
|
||||
#include "context_egl_wayland.h"
|
||||
#endif
|
||||
#if defined(USE_GBM)
|
||||
#include "context_egl_gbm.h"
|
||||
#endif
|
||||
#elif defined(ANDROID)
|
||||
#include "context_egl_android.h"
|
||||
#else
|
||||
@ -77,7 +80,7 @@ std::unique_ptr<GL::Context> Context::Create(const WindowInfo& wi, const Version
|
||||
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__)
|
||||
#elif defined(__APPLE__) && !defined(LIBRETRO)
|
||||
context = ContextAGL::Create(wi, versions_to_try, num_versions_to_try);
|
||||
#elif defined(ANDROID)
|
||||
#ifdef USE_EGL
|
||||
@ -105,6 +108,11 @@ std::unique_ptr<GL::Context> Context::Create(const WindowInfo& wi, const Version
|
||||
context = ContextEGLWayland::Create(wi, versions_to_try, num_versions_to_try);
|
||||
#endif
|
||||
|
||||
#if defined(USE_GBM)
|
||||
if (wi.type == WindowInfo::Type::DRM)
|
||||
context = ContextEGLGBM::Create(wi, versions_to_try, num_versions_to_try);
|
||||
#endif
|
||||
|
||||
if (!context)
|
||||
return nullptr;
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#include "context_egl.h"
|
||||
#include "../assert.h"
|
||||
#include "../log.h"
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
Log_SetChannel(GL::ContextEGL);
|
||||
|
||||
namespace GL {
|
||||
@ -33,12 +35,8 @@ bool ContextEGL::Initialize(const Version* versions_to_try, size_t num_versions_
|
||||
return false;
|
||||
}
|
||||
|
||||
m_display = eglGetDisplay(static_cast<EGLNativeDisplayType>(m_wi.display_connection));
|
||||
if (!m_display)
|
||||
{
|
||||
Log_ErrorPrintf("eglGetDisplay() failed: %d", eglGetError());
|
||||
if (!SetDisplay())
|
||||
return false;
|
||||
}
|
||||
|
||||
int egl_major, egl_minor;
|
||||
if (!eglInitialize(m_display, &egl_major, &egl_minor))
|
||||
@ -66,6 +64,18 @@ bool ContextEGL::Initialize(const Version* versions_to_try, size_t num_versions_
|
||||
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));
|
||||
@ -216,6 +226,36 @@ bool ContextEGL::CreatePBufferSurface()
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
bool ContextEGL::CreateContext(const Version& version, EGLContext share_context)
|
||||
{
|
||||
Log_DevPrintf(
|
||||
@ -263,6 +303,9 @@ bool ContextEGL::CreateContext(const Version& version, EGLContext share_context)
|
||||
surface_attribs[nsurface_attribs++] = 5;
|
||||
break;
|
||||
|
||||
case WindowInfo::SurfaceFormat::Auto:
|
||||
break;
|
||||
|
||||
default:
|
||||
UnreachableCode();
|
||||
break;
|
||||
@ -272,13 +315,36 @@ bool ContextEGL::CreateContext(const Version& version, EGLContext share_context)
|
||||
surface_attribs[nsurface_attribs++] = 0;
|
||||
|
||||
EGLint num_configs;
|
||||
EGLConfig config;
|
||||
if (!eglChooseConfig(m_display, surface_attribs, &config, 1, &num_configs) || num_configs == 0)
|
||||
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)
|
||||
@ -297,7 +363,7 @@ bool ContextEGL::CreateContext(const Version& version, EGLContext share_context)
|
||||
return false;
|
||||
}
|
||||
|
||||
m_context = eglCreateContext(m_display, config, share_context, attribs);
|
||||
m_context = eglCreateContext(m_display, config.value(), share_context, attribs);
|
||||
if (!m_context)
|
||||
{
|
||||
Log_ErrorPrintf("eglCreateContext() failed: %d", eglGetError());
|
||||
@ -308,7 +374,7 @@ bool ContextEGL::CreateContext(const Version& version, EGLContext share_context)
|
||||
"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;
|
||||
m_config = config.value();
|
||||
m_version = version;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ public:
|
||||
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);
|
||||
@ -31,6 +32,7 @@ protected:
|
||||
bool CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current);
|
||||
bool CreateSurface();
|
||||
bool CreatePBufferSurface();
|
||||
bool CheckConfigSurfaceFormat(EGLConfig config, WindowInfo::SurfaceFormat format) const;
|
||||
|
||||
EGLDisplay m_display = EGL_NO_DISPLAY;
|
||||
EGLSurface m_surface = EGL_NO_SURFACE;
|
||||
|
||||
253
src/common/gl/context_egl_gbm.cpp
Normal file
253
src/common/gl/context_egl_gbm.cpp
Normal file
@ -0,0 +1,253 @@
|
||||
#include "context_egl_gbm.h"
|
||||
#include "../assert.h"
|
||||
#include "../log.h"
|
||||
#include <drm.h>
|
||||
#include <drm_fourcc.h>
|
||||
#include <gbm.h>
|
||||
Log_SetChannel(GL::ContextEGLGBM);
|
||||
|
||||
namespace GL {
|
||||
ContextEGLGBM::ContextEGLGBM(const WindowInfo& wi) : ContextEGL(wi)
|
||||
{
|
||||
#ifdef CONTEXT_EGL_GBM_USE_PRESENT_THREAD
|
||||
StartPresentThread();
|
||||
#endif
|
||||
}
|
||||
|
||||
ContextEGLGBM::~ContextEGLGBM()
|
||||
{
|
||||
#ifdef CONTEXT_EGL_GBM_USE_PRESENT_THREAD
|
||||
StopPresentThread();
|
||||
Assert(!m_current_present_buffer);
|
||||
#endif
|
||||
|
||||
while (m_num_buffers > 0)
|
||||
{
|
||||
Buffer& buffer = m_buffers[--m_num_buffers];
|
||||
GetDisplay()->RemoveBuffer(buffer.fb_id);
|
||||
}
|
||||
|
||||
if (m_fb_surface)
|
||||
gbm_surface_destroy(m_fb_surface);
|
||||
|
||||
if (m_gbm_device)
|
||||
gbm_device_destroy(m_gbm_device);
|
||||
}
|
||||
|
||||
std::unique_ptr<Context> ContextEGLGBM::Create(const WindowInfo& wi, const Version* versions_to_try,
|
||||
size_t num_versions_to_try)
|
||||
{
|
||||
std::unique_ptr<ContextEGLGBM> context = std::make_unique<ContextEGLGBM>(wi);
|
||||
if (!context->CreateGBMDevice() || !context->Initialize(versions_to_try, num_versions_to_try))
|
||||
return nullptr;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
std::unique_ptr<Context> ContextEGLGBM::CreateSharedContext(const WindowInfo& wi)
|
||||
{
|
||||
std::unique_ptr<ContextEGLGBM> context = std::make_unique<ContextEGLGBM>(wi);
|
||||
context->m_display = m_display;
|
||||
|
||||
if (!context->CreateContextAndSurface(m_version, m_context, false))
|
||||
return nullptr;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
void ContextEGLGBM::ResizeSurface(u32 new_surface_width, u32 new_surface_height)
|
||||
{
|
||||
ContextEGL::ResizeSurface(new_surface_width, new_surface_height);
|
||||
}
|
||||
|
||||
bool ContextEGLGBM::CreateGBMDevice()
|
||||
{
|
||||
Assert(!m_gbm_device);
|
||||
m_gbm_device = gbm_create_device(GetDisplay()->GetCardFD());
|
||||
if (!m_gbm_device)
|
||||
{
|
||||
Log_ErrorPrintf("gbm_create_device() failed: %d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ContextEGLGBM::SetDisplay()
|
||||
{
|
||||
if (!eglGetPlatformDisplayEXT)
|
||||
{
|
||||
Log_ErrorPrintf("eglGetPlatformDisplayEXT() not loaded");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_display = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, m_gbm_device, nullptr);
|
||||
if (!m_display)
|
||||
{
|
||||
Log_ErrorPrintf("eglGetPlatformDisplayEXT() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
EGLNativeWindowType ContextEGLGBM::GetNativeWindow(EGLConfig config)
|
||||
{
|
||||
EGLint visual_id;
|
||||
eglGetConfigAttrib(m_display, config, EGL_NATIVE_VISUAL_ID, &visual_id);
|
||||
|
||||
Assert(!m_fb_surface);
|
||||
m_fb_surface = gbm_surface_create(m_gbm_device, GetDisplay()->GetWidth(), GetDisplay()->GetHeight(),
|
||||
static_cast<u32>(visual_id), GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT);
|
||||
if (!m_fb_surface)
|
||||
{
|
||||
Log_ErrorPrintf("gbm_surface_create() failed: %d", errno);
|
||||
return {};
|
||||
}
|
||||
|
||||
return (EGLNativeWindowType)((void*)m_fb_surface);
|
||||
}
|
||||
|
||||
ContextEGLGBM::Buffer* ContextEGLGBM::LockFrontBuffer()
|
||||
{
|
||||
struct gbm_bo* bo = gbm_surface_lock_front_buffer(m_fb_surface);
|
||||
|
||||
Buffer* buffer = nullptr;
|
||||
for (u32 i = 0; i < m_num_buffers; i++)
|
||||
{
|
||||
if (m_buffers[i].bo == bo)
|
||||
{
|
||||
buffer = &m_buffers[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!buffer)
|
||||
{
|
||||
// haven't tracked this buffer yet
|
||||
Assert(m_num_buffers < MAX_BUFFERS);
|
||||
|
||||
const u32 width = gbm_bo_get_width(bo);
|
||||
const u32 height = gbm_bo_get_height(bo);
|
||||
const u32 stride = gbm_bo_get_stride(bo);
|
||||
const u32 format = gbm_bo_get_format(bo);
|
||||
const u32 handle = gbm_bo_get_handle(bo).u32;
|
||||
|
||||
std::optional<u32> fb_id = GetDisplay()->AddBuffer(width, height, format, handle, stride, 0);
|
||||
if (!fb_id.has_value())
|
||||
return nullptr;
|
||||
|
||||
buffer = &m_buffers[m_num_buffers];
|
||||
buffer->bo = bo;
|
||||
buffer->fb_id = fb_id.value();
|
||||
m_num_buffers++;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void ContextEGLGBM::ReleaseBuffer(Buffer* buffer)
|
||||
{
|
||||
gbm_surface_release_buffer(m_fb_surface, buffer->bo);
|
||||
}
|
||||
|
||||
void ContextEGLGBM::PresentBuffer(Buffer* buffer, bool wait_for_vsync)
|
||||
{
|
||||
GetDisplay()->PresentBuffer(buffer->fb_id, wait_for_vsync);
|
||||
}
|
||||
|
||||
bool ContextEGLGBM::SwapBuffers()
|
||||
{
|
||||
if (!ContextEGL::SwapBuffers())
|
||||
return false;
|
||||
|
||||
#ifdef CONTEXT_EGL_GBM_USE_PRESENT_THREAD
|
||||
std::unique_lock lock(m_present_mutex);
|
||||
m_present_pending.store(true);
|
||||
m_present_cv.notify_one();
|
||||
if (m_vsync)
|
||||
m_present_done_cv.wait(lock, [this]() { return !m_present_pending.load(); });
|
||||
#else
|
||||
Buffer* front_buffer = LockFrontBuffer();
|
||||
if (!front_buffer)
|
||||
return false;
|
||||
|
||||
PresentSurface(front_buffer, m_vsync && m_last_front_buffer);
|
||||
|
||||
if (m_last_front_buffer)
|
||||
ReleaseBuffer(m_last_front_buffer);
|
||||
|
||||
m_last_front_buffer = front_buffer;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ContextEGLGBM::SetSwapInterval(s32 interval)
|
||||
{
|
||||
if (interval < 0 || interval > 1)
|
||||
return false;
|
||||
|
||||
std::unique_lock lock(m_present_mutex);
|
||||
m_vsync = (interval > 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef CONTEXT_EGL_GBM_USE_PRESENT_THREAD
|
||||
|
||||
void ContextEGLGBM::StartPresentThread()
|
||||
{
|
||||
m_present_thread_shutdown.store(false);
|
||||
m_present_thread = std::thread(&ContextEGLGBM::PresentThread, this);
|
||||
}
|
||||
|
||||
void ContextEGLGBM::StopPresentThread()
|
||||
{
|
||||
if (!m_present_thread.joinable())
|
||||
return;
|
||||
|
||||
{
|
||||
std::unique_lock lock(m_present_mutex);
|
||||
m_present_thread_shutdown.store(true);
|
||||
m_present_cv.notify_one();
|
||||
}
|
||||
|
||||
m_present_thread.join();
|
||||
}
|
||||
|
||||
void ContextEGLGBM::PresentThread()
|
||||
{
|
||||
std::unique_lock lock(m_present_mutex);
|
||||
|
||||
while (!m_present_thread_shutdown.load())
|
||||
{
|
||||
m_present_cv.wait(lock);
|
||||
|
||||
if (!m_present_pending.load())
|
||||
continue;
|
||||
|
||||
Buffer* next_buffer = LockFrontBuffer();
|
||||
const bool wait_for_vsync = m_vsync && m_current_present_buffer;
|
||||
|
||||
lock.unlock();
|
||||
PresentBuffer(next_buffer, wait_for_vsync);
|
||||
lock.lock();
|
||||
|
||||
if (m_current_present_buffer)
|
||||
ReleaseBuffer(m_current_present_buffer);
|
||||
|
||||
m_current_present_buffer = next_buffer;
|
||||
m_present_pending.store(false);
|
||||
m_present_done_cv.notify_one();
|
||||
}
|
||||
|
||||
if (m_current_present_buffer)
|
||||
{
|
||||
ReleaseBuffer(m_current_present_buffer);
|
||||
m_current_present_buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace GL
|
||||
76
src/common/gl/context_egl_gbm.h
Normal file
76
src/common/gl/context_egl_gbm.h
Normal file
@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
#include "../drm_display.h"
|
||||
#include "context_egl.h"
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <gbm.h>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#define CONTEXT_EGL_GBM_USE_PRESENT_THREAD 1
|
||||
|
||||
namespace GL {
|
||||
|
||||
class ContextEGLGBM final : public ContextEGL
|
||||
{
|
||||
public:
|
||||
ContextEGLGBM(const WindowInfo& wi);
|
||||
~ContextEGLGBM() 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;
|
||||
|
||||
bool SwapBuffers() override;
|
||||
bool SetSwapInterval(s32 interval) override;
|
||||
|
||||
protected:
|
||||
bool SetDisplay() override;
|
||||
EGLNativeWindowType GetNativeWindow(EGLConfig config) override;
|
||||
|
||||
private:
|
||||
enum : u32
|
||||
{
|
||||
MAX_BUFFERS = 5
|
||||
};
|
||||
|
||||
struct Buffer
|
||||
{
|
||||
struct gbm_bo* bo;
|
||||
u32 fb_id;
|
||||
};
|
||||
|
||||
DRMDisplay* GetDisplay() { return static_cast<DRMDisplay*>(m_wi.display_connection); }
|
||||
|
||||
bool CreateGBMDevice();
|
||||
Buffer* LockFrontBuffer();
|
||||
void ReleaseBuffer(Buffer* buffer);
|
||||
void PresentBuffer(Buffer* buffer, bool wait_for_vsync);
|
||||
|
||||
void StartPresentThread();
|
||||
void StopPresentThread();
|
||||
void PresentThread();
|
||||
|
||||
bool m_vsync = true;
|
||||
|
||||
struct gbm_device* m_gbm_device = nullptr;
|
||||
struct gbm_surface* m_fb_surface = nullptr;
|
||||
|
||||
#ifdef CONTEXT_EGL_GBM_USE_PRESENT_THREAD
|
||||
std::thread m_present_thread;
|
||||
std::mutex m_present_mutex;
|
||||
std::condition_variable m_present_cv;
|
||||
std::atomic_bool m_present_pending{false};
|
||||
std::atomic_bool m_present_thread_shutdown{false};
|
||||
std::condition_variable m_present_done_cv;
|
||||
|
||||
Buffer* m_current_present_buffer = nullptr;
|
||||
#endif
|
||||
|
||||
u32 m_num_buffers = 0;
|
||||
std::array<Buffer, MAX_BUFFERS> m_buffers{};
|
||||
};
|
||||
|
||||
} // namespace GL
|
||||
@ -221,6 +221,9 @@ bool ContextGLX::CreateWindow(int screen)
|
||||
attribs[nattribs++] = 5;
|
||||
break;
|
||||
|
||||
case WindowInfo::SurfaceFormat::Auto:
|
||||
break;
|
||||
|
||||
default:
|
||||
UnreachableCode();
|
||||
break;
|
||||
|
||||
@ -204,6 +204,9 @@ bool ContextWGL::InitializeDC()
|
||||
pfd.cBlueBits = 5;
|
||||
break;
|
||||
|
||||
case WindowInfo::SurfaceFormat::Auto:
|
||||
break;
|
||||
|
||||
default:
|
||||
UnreachableCode();
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user