duckstation/src/common/gl/context_egl_gbm.cpp
Connor McLaughlin 3e9fdf22bf NoGUI: Move DRM display to GL context
We don't use it for Vulkan.
2021-02-04 19:39:19 +10:00

267 lines
6.1 KiB
C++

#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
// We have to destroy the context before the surface/device.
// Leaving it to the base class would be too late.
DestroySurface();
DestroyContext();
while (m_num_buffers > 0)
{
Buffer& buffer = m_buffers[--m_num_buffers];
m_drm_display.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->CreateDisplay(wi) || !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(m_drm_display.GetCardFD());
if (!m_gbm_device)
{
Log_ErrorPrintf("gbm_create_device() failed: %d", errno);
return false;
}
return true;
}
bool ContextEGLGBM::CreateDisplay(const WindowInfo& wi)
{
return m_drm_display.Initialize();
}
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, m_drm_display.GetWidth(), m_drm_display.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 = m_drm_display.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)
{
m_drm_display.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