#include "context_egl_gbm.h" #include "../assert.h" #include "../log.h" #include #include #include 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 ContextEGLGBM::Create(const WindowInfo& wi, const Version* versions_to_try, size_t num_versions_to_try) { std::unique_ptr context = std::make_unique(wi); if (!context->CreateDisplay(wi) || !context->CreateGBMDevice() || !context->Initialize(versions_to_try, num_versions_to_try)) { return nullptr; } return context; } std::unique_ptr ContextEGLGBM::CreateSharedContext(const WindowInfo& wi) { std::unique_ptr context = std::make_unique(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(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 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