mirror of
https://github.com/stenzek/duckstation.git
synced 2025-06-08 12:35:48 +00:00
267 lines
6.1 KiB
C++
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
|