Common: Add DRM display helper class and GBM GL context

This commit is contained in:
Connor McLaughlin 2021-01-31 02:25:05 +10:00
parent b267020d07
commit b09da307b5
15 changed files with 946 additions and 12 deletions

View file

@ -36,6 +36,9 @@ endif()
if(LINUX OR ANDROID)
option(USE_EGL "Support EGL OpenGL context creation" ON)
endif()
if(LINUX AND NOT ANDROID)
option(USE_DRMKMS "Support DRM/KMS display and contexts" OFF)
endif()
# Force EGL when using Wayland
if(USE_WAYLAND)
@ -112,6 +115,11 @@ if(USE_WAYLAND)
find_package(Wayland REQUIRED Egl)
message(STATUS "Wayland support enabled")
endif()
if(USE_DRMKMS)
find_package(GBM REQUIRED)
find_package(Libdrm REQUIRED)
message(STATUS "DRM/KMS support enabled")
endif()
# Set _DEBUG macro for Debug builds.
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG")

View file

@ -0,0 +1,70 @@
# https://fossies.org/linux/misc/xbmc-18.9-Leia.tar.gz/xbmc-18.9-Leia/cmake/modules/FindGBM.cmake?m=t
# FindGBM
# ----------
# Finds the GBM library
#
# This will define the following variables::
#
# GBM_FOUND - system has GBM
# GBM_INCLUDE_DIRS - the GBM include directory
# GBM_LIBRARIES - the GBM libraries
# GBM_DEFINITIONS - the GBM definitions
#
# and the following imported targets::
#
# GBM::GBM - The GBM library
if(PKG_CONFIG_FOUND)
pkg_check_modules(PC_GBM gbm QUIET)
endif()
find_path(GBM_INCLUDE_DIR NAMES gbm.h
PATHS ${PC_GBM_INCLUDEDIR})
find_library(GBM_LIBRARY NAMES gbm
PATHS ${PC_GBM_LIBDIR})
set(GBM_VERSION ${PC_GBM_VERSION})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(GBM
REQUIRED_VARS GBM_LIBRARY GBM_INCLUDE_DIR
VERSION_VAR GBM_VERSION)
include(CheckCSourceCompiles)
set(CMAKE_REQUIRED_LIBRARIES ${GBM_LIBRARY})
check_c_source_compiles("#include <gbm.h>
int main()
{
gbm_bo_map(NULL, 0, 0, 0, 0, GBM_BO_TRANSFER_WRITE, NULL, NULL);
}
" GBM_HAS_BO_MAP)
check_c_source_compiles("#include <gbm.h>
int main()
{
gbm_surface_create_with_modifiers(NULL, 0, 0, 0, NULL, 0);
}
" GBM_HAS_MODIFIERS)
if(GBM_FOUND)
set(GBM_LIBRARIES ${GBM_LIBRARY})
set(GBM_INCLUDE_DIRS ${GBM_INCLUDE_DIR})
set(GBM_DEFINITIONS -DHAVE_GBM=1)
if(GBM_HAS_BO_MAP)
list(APPEND GBM_DEFINITIONS -DHAS_GBM_BO_MAP=1)
endif()
if(GBM_HAS_MODIFIERS)
list(APPEND GBM_DEFINITIONS -DHAS_GBM_MODIFIERS=1)
endif()
if(NOT TARGET GBM::GBM)
add_library(GBM::GBM UNKNOWN IMPORTED)
set_target_properties(GBM::GBM PROPERTIES
IMPORTED_LOCATION "${GBM_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${GBM_INCLUDE_DIR}")
endif()
endif()
mark_as_advanced(GBM_INCLUDE_DIR GBM_LIBRARY)

View file

@ -0,0 +1,33 @@
# - Try to find libevdev
# Once done this will define
# LIBEVDEV_FOUND - System has libevdev
# LIBEVDEV_INCLUDE_DIRS - The libevdev include directories
# LIBEVDEV_LIBRARIES - The libraries needed to use libevdev
find_package(PkgConfig)
pkg_check_modules(PC_LIBEVDEV QUIET libevdev)
FIND_PATH(
LIBEVDEV_INCLUDE_DIR libevdev/libevdev.h
HINTS ${PC_LIBEVDEV_INCLUDEDIR} ${PC_LIBEVDEV_INCLUDE_DIRS}
/usr/include
/usr/local/include
${LIBEVDEV_PATH_INCLUDES}
)
FIND_LIBRARY(
LIBEVDEV_LIBRARY
NAMES evdev libevdev
HINTS ${PC_LIBEVDEV_LIBDIR} ${PC_LIBEVDEV_LIBRARY_DIRS}
PATHS ${ADDITIONAL_LIBRARY_PATHS}
${LIBEVDEV_PATH_LIB}
)
set(LIBEVDEV_LIBRARIES ${LIBEVDEV_LIBRARY} )
set(LIBEVDEV_INCLUDE_DIRS ${LIBEVDEV_INCLUDE_DIR} )
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LIBEVDEV DEFAULT_MSG
LIBEVDEV_LIBRARY LIBEVDEV_INCLUDE_DIR)
mark_as_advanced(LIBEVDEV_INCLUDE_DIR LIBEVDEV_LIBRARY )

View file

@ -0,0 +1,107 @@
# https://raw.githubusercontent.com/KDE/kwin/master/cmake/modules/FindLibdrm.cmake
#.rst:
# FindLibdrm
# -------
#
# Try to find libdrm on a Unix system.
#
# This will define the following variables:
#
# ``Libdrm_FOUND``
# True if (the requested version of) libdrm is available
# ``Libdrm_VERSION``
# The version of libdrm
# ``Libdrm_LIBRARIES``
# This can be passed to target_link_libraries() instead of the ``Libdrm::Libdrm``
# target
# ``Libdrm_INCLUDE_DIRS``
# This should be passed to target_include_directories() if the target is not
# used for linking
# ``Libdrm_DEFINITIONS``
# This should be passed to target_compile_options() if the target is not
# used for linking
#
# If ``Libdrm_FOUND`` is TRUE, it will also define the following imported target:
#
# ``Libdrm::Libdrm``
# The libdrm library
#
# In general we recommend using the imported target, as it is easier to use.
# Bear in mind, however, that if the target is in the link interface of an
# exported library, it must be made available by the package config file.
#=============================================================================
# SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kde.org>
# SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
#
# SPDX-License-Identifier: BSD-3-Clause
#=============================================================================
if(CMAKE_VERSION VERSION_LESS 2.8.12)
message(FATAL_ERROR "CMake 2.8.12 is required by FindLibdrm.cmake")
endif()
if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12)
message(AUTHOR_WARNING "Your project should require at least CMake 2.8.12 to use FindLibdrm.cmake")
endif()
if(NOT WIN32)
# Use pkg-config to get the directories and then use these values
# in the FIND_PATH() and FIND_LIBRARY() calls
find_package(PkgConfig)
pkg_check_modules(PKG_Libdrm QUIET libdrm)
set(Libdrm_DEFINITIONS ${PKG_Libdrm_CFLAGS_OTHER})
set(Libdrm_VERSION ${PKG_Libdrm_VERSION})
find_path(Libdrm_INCLUDE_DIR
NAMES
xf86drm.h
HINTS
${PKG_Libdrm_INCLUDE_DIRS}
)
find_library(Libdrm_LIBRARY
NAMES
drm
HINTS
${PKG_Libdrm_LIBRARY_DIRS}
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Libdrm
FOUND_VAR
Libdrm_FOUND
REQUIRED_VARS
Libdrm_LIBRARY
Libdrm_INCLUDE_DIR
VERSION_VAR
Libdrm_VERSION
)
if(Libdrm_FOUND AND NOT TARGET Libdrm::Libdrm)
add_library(Libdrm::Libdrm UNKNOWN IMPORTED)
set_target_properties(Libdrm::Libdrm PROPERTIES
IMPORTED_LOCATION "${Libdrm_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${Libdrm_DEFINITIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${Libdrm_INCLUDE_DIR}"
INTERFACE_INCLUDE_DIRECTORIES "${Libdrm_INCLUDE_DIR}/libdrm"
)
endif()
mark_as_advanced(Libdrm_LIBRARY Libdrm_INCLUDE_DIR)
# compatibility variables
set(Libdrm_LIBRARIES ${Libdrm_LIBRARY})
set(Libdrm_INCLUDE_DIRS ${Libdrm_INCLUDE_DIR} "${Libdrm_INCLUDE_DIR}/libdrm")
set(Libdrm_VERSION_STRING ${Libdrm_VERSION})
else()
message(STATUS "FindLibdrm.cmake cannot find libdrm on Windows systems.")
set(Libdrm_FOUND FALSE)
endif()
include(FeatureSummary)
set_package_properties(Libdrm PROPERTIES
URL "https://wiki.freedesktop.org/dri/"
DESCRIPTION "Userspace interface to kernel DRM services."
)

View file

@ -142,6 +142,14 @@ if(USE_X11)
target_link_libraries(common PRIVATE "${X11_LIBRARIES}")
endif()
if(USE_DRMKMS)
target_sources(common PRIVATE
drm_display.cpp
drm_display.h
)
target_link_libraries(common PUBLIC Libdrm::Libdrm)
endif()
if(USE_EGL)
target_sources(common PRIVATE
gl/context_egl.cpp
@ -161,6 +169,14 @@ if(USE_EGL)
gl/context_egl_android.h
)
endif()
if(USE_DRMKMS)
target_compile_definitions(common PRIVATE "-DUSE_GBM=1")
target_sources(common PRIVATE
gl/context_egl_gbm.cpp
gl/context_egl_gbm.h
)
target_link_libraries(common PUBLIC GBM::GBM)
endif()
endif()
if(USE_X11)

249
src/common/drm_display.cpp Normal file
View file

@ -0,0 +1,249 @@
#include "drm_display.h"
#include "common/assert.h"
#include "common/log.h"
#include "common/string.h"
#include "file_system.h"
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
Log_SetChannel(DRMDisplay);
DRMDisplay::DRMDisplay(int card /*= 1*/) : m_card_id(card) {}
DRMDisplay::~DRMDisplay()
{
if (m_connector)
drmModeFreeConnector(m_connector);
if (m_card_fd >= 0)
close(m_card_fd);
}
// https://gist.github.com/Miouyouyou/89e9fe56a2c59bce7d4a18a858f389ef
static uint32_t find_crtc_for_encoder(const drmModeRes* resources, const drmModeEncoder* encoder)
{
int i;
for (i = 0; i < resources->count_crtcs; i++)
{
/* possible_crtcs is a bitmask as described here:
* https://dvdhrm.wordpress.com/2012/09/13/linux-drm-mode-setting-api
*/
const uint32_t crtc_mask = 1 << i;
const uint32_t crtc_id = resources->crtcs[i];
if (encoder->possible_crtcs & crtc_mask)
{
return crtc_id;
}
}
/* no match found */
return -1;
}
static uint32_t find_crtc_for_connector(int card_fd, const drmModeRes* resources, const drmModeConnector* connector)
{
int i;
for (i = 0; i < connector->count_encoders; i++)
{
const uint32_t encoder_id = connector->encoders[i];
drmModeEncoder* encoder = drmModeGetEncoder(card_fd, encoder_id);
if (encoder)
{
const uint32_t crtc_id = find_crtc_for_encoder(resources, encoder);
drmModeFreeEncoder(encoder);
if (crtc_id != 0)
{
return crtc_id;
}
}
}
/* no match found */
return -1;
}
bool DRMDisplay::Initialize()
{
if (m_card_id < 0)
{
for (int i = 0; i < 10; i++)
{
if (TryOpeningCard(i))
return true;
}
return false;
}
return TryOpeningCard(m_card_id);
}
bool DRMDisplay::TryOpeningCard(int card)
{
if (m_card_fd >= 0)
close(m_card_fd);
m_card_fd = open(TinyString::FromFormat("/dev/dri/card%d", card), O_RDWR);
if (m_card_fd < 0)
{
Log_ErrorPrintf("open(/dev/dri/card%d) failed: %d (%s)", card, errno, strerror(errno));
return false;
}
drmModeRes* resources = drmModeGetResources(m_card_fd);
if (!resources)
{
Log_ErrorPrintf("drmModeGetResources() failed: %d (%s)", errno, strerror(errno));
return false;
}
Assert(!m_connector);
for (int i = 0; i < resources->count_connectors; i++)
{
drmModeConnector* next_connector = drmModeGetConnector(m_card_fd, resources->connectors[i]);
if (next_connector->connection == DRM_MODE_CONNECTED)
{
m_connector = next_connector;
break;
}
drmModeFreeConnector(next_connector);
}
if (!m_connector)
{
Log_ErrorPrintf("No connector found");
drmModeFreeResources(resources);
return false;
}
for (int i = 0; i < m_connector->count_modes; i++)
{
drmModeModeInfo* next_mode = &m_connector->modes[i];
if (next_mode->type & DRM_MODE_TYPE_PREFERRED)
{
m_mode = next_mode;
break;
}
if (!m_mode || (next_mode->hdisplay * next_mode->vdisplay) > (m_mode->hdisplay * m_mode->vdisplay))
{
m_mode = next_mode;
}
}
if (!m_mode)
{
Log_ErrorPrintf("No mode found");
drmModeFreeResources(resources);
return false;
}
drmModeEncoder* encoder = nullptr;
for (int i = 0; i < resources->count_encoders; i++)
{
drmModeEncoder* next_encoder = drmModeGetEncoder(m_card_fd, resources->encoders[i]);
if (next_encoder->encoder_id == m_connector->encoder_id)
{
encoder = next_encoder;
m_crtc_id = encoder->crtc_id;
break;
}
drmModeFreeEncoder(next_encoder);
}
if (encoder)
{
drmModeFreeEncoder(encoder);
}
else
{
m_crtc_id = find_crtc_for_connector(m_card_fd, resources, m_connector);
if (m_crtc_id == 0)
{
Log_ErrorPrintf("No CRTC found");
drmModeFreeResources(resources);
return false;
}
}
drmModeFreeResources(resources);
m_card_id = card;
return true;
}
std::optional<u32> DRMDisplay::AddBuffer(u32 width, u32 height, u32 format, u32 handle, u32 pitch, u32 offset)
{
uint32_t bo_handles[4] = {handle, 0, 0, 0};
uint32_t pitches[4] = {pitch, 0, 0, 0};
uint32_t offsets[4] = {offset, 0, 0, 0};
u32 fb_id;
int res = drmModeAddFB2(m_card_fd, width, height, format, bo_handles, pitches, offsets, &fb_id, 0);
if (res != 0)
{
Log_ErrorPrintf("drmModeAddFB2() failed: %d", res);
return std::nullopt;
}
return fb_id;
}
void DRMDisplay::RemoveBuffer(u32 fb_id)
{
drmModeRmFB(m_card_fd, fb_id);
}
void DRMDisplay::PresentBuffer(u32 fb_id, bool wait_for_vsync)
{
if (!wait_for_vsync)
{
u32 connector_id = m_connector->connector_id;
int res = drmModeSetCrtc(m_card_fd, m_crtc_id, fb_id, 0, 0, &connector_id, 1, m_mode);
if (res != 0)
Log_ErrorPrintf("drmModeSetCrtc() failed: %d", res);
return;
}
bool waiting_for_flip = true;
drmEventContext event_ctx = {};
event_ctx.version = DRM_EVENT_CONTEXT_VERSION;
event_ctx.page_flip_handler = [](int fd, unsigned int frame, unsigned int sec, unsigned int usec, void* data) {
*reinterpret_cast<bool*>(data) = false;
};
int res = drmModePageFlip(m_card_fd, m_crtc_id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, &waiting_for_flip);
if (res != 0)
{
Log_ErrorPrintf("drmModePageFlip() failed: %d", res);
return;
}
while (waiting_for_flip)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(m_card_fd, &fds);
int res = select(m_card_fd + 1, &fds, nullptr, nullptr, nullptr);
if (res < 0)
{
Log_ErrorPrintf("select() failed: %d", errno);
break;
}
else if (res == 0)
{
continue;
}
drmHandleEvent(m_card_fd, &event_ctx);
}
}

38
src/common/drm_display.h Normal file
View file

@ -0,0 +1,38 @@
#pragma once
#include "core/types.h"
#include <array>
#include <optional>
#include <xf86drm.h>
#include <xf86drmMode.h>
class DRMDisplay
{
public:
DRMDisplay(int card = -1);
~DRMDisplay();
bool Initialize();
int GetCardID() const { return m_card_id; }
int GetCardFD() const { return m_card_fd; }
u32 GetWidth() const { return m_mode->hdisplay; }
u32 GetHeight() const { return m_mode->vdisplay; }
std::optional<u32> AddBuffer(u32 width, u32 height, u32 format, u32 handle, u32 pitch, u32 offset);
void RemoveBuffer(u32 fb_id);
void PresentBuffer(u32 fb_id, bool wait_for_vsync);
private:
enum : u32
{
MAX_BUFFERS = 5
};
bool TryOpeningCard(int card);
int m_card_id = 0;
int m_card_fd = -1;
u32 m_crtc_id = 0;
drmModeConnector* m_connector = nullptr;
drmModeModeInfo* m_mode = nullptr;
};

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View 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

View 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

View file

@ -221,6 +221,9 @@ bool ContextGLX::CreateWindow(int screen)
attribs[nattribs++] = 5;
break;
case WindowInfo::SurfaceFormat::Auto:
break;
default:
UnreachableCode();
break;

View file

@ -204,6 +204,9 @@ bool ContextWGL::InitializeDC()
pfd.cBlueBits = 5;
break;
case WindowInfo::SurfaceFormat::Auto:
break;
default:
UnreachableCode();
break;

View file

@ -12,11 +12,13 @@ struct WindowInfo
Wayland,
MacOS,
Android,
DRM,
};
enum class SurfaceFormat
{
None,
Auto,
RGB8,
RGBA8,
RGB565,