diff --git a/CMakeLists.txt b/CMakeLists.txt index 44468e094..29373e497 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/CMakeModules/FindGBM.cmake b/CMakeModules/FindGBM.cmake new file mode 100644 index 000000000..1447b5edd --- /dev/null +++ b/CMakeModules/FindGBM.cmake @@ -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 + + 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 + + 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) diff --git a/CMakeModules/FindLIBEVDEV.cmake b/CMakeModules/FindLIBEVDEV.cmake new file mode 100644 index 000000000..f01e41a56 --- /dev/null +++ b/CMakeModules/FindLIBEVDEV.cmake @@ -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 ) diff --git a/CMakeModules/FindLibdrm.cmake b/CMakeModules/FindLibdrm.cmake new file mode 100644 index 000000000..2df135812 --- /dev/null +++ b/CMakeModules/FindLibdrm.cmake @@ -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 +# SPDX-FileCopyrightText: 2014 Martin Gräßlin +# +# 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." +) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index eba94c682..e216ad60c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -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) diff --git a/src/common/drm_display.cpp b/src/common/drm_display.cpp new file mode 100644 index 000000000..3fd180d65 --- /dev/null +++ b/src/common/drm_display.cpp @@ -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 +#include +#include +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 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(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); + } +} diff --git a/src/common/drm_display.h b/src/common/drm_display.h new file mode 100644 index 000000000..04692405c --- /dev/null +++ b/src/common/drm_display.h @@ -0,0 +1,38 @@ +#pragma once +#include "core/types.h" +#include +#include +#include +#include + +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 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; +}; diff --git a/src/common/gl/context.cpp b/src/common/gl/context.cpp index aecabae23..2370f986b 100644 --- a/src/common/gl/context.cpp +++ b/src/common/gl/context.cpp @@ -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 Context::Create(const WindowInfo& wi, const Version std::unique_ptr 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 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; diff --git a/src/common/gl/context_egl.cpp b/src/common/gl/context_egl.cpp index 2ee212907..6a034da39 100644 --- a/src/common/gl/context_egl.cpp +++ b/src/common/gl/context_egl.cpp @@ -1,6 +1,8 @@ #include "context_egl.h" #include "../assert.h" #include "../log.h" +#include +#include 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(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(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(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 configs(static_cast(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(num_configs)); + + std::optional 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; } diff --git a/src/common/gl/context_egl.h b/src/common/gl/context_egl.h index ce228980a..368588664 100644 --- a/src/common/gl/context_egl.h +++ b/src/common/gl/context_egl.h @@ -23,6 +23,7 @@ public: virtual std::unique_ptr 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; diff --git a/src/common/gl/context_egl_gbm.cpp b/src/common/gl/context_egl_gbm.cpp new file mode 100644 index 000000000..3a27f684b --- /dev/null +++ b/src/common/gl/context_egl_gbm.cpp @@ -0,0 +1,253 @@ +#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 + + 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 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->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(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(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 = 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 diff --git a/src/common/gl/context_egl_gbm.h b/src/common/gl/context_egl_gbm.h new file mode 100644 index 000000000..dfc121e2f --- /dev/null +++ b/src/common/gl/context_egl_gbm.h @@ -0,0 +1,76 @@ +#pragma once +#include "../drm_display.h" +#include "context_egl.h" +#include +#include +#include +#include +#include + +#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 Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try); + + std::unique_ptr 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(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 m_buffers{}; +}; + +} // namespace GL diff --git a/src/common/gl/context_glx.cpp b/src/common/gl/context_glx.cpp index a46037b75..464993a39 100644 --- a/src/common/gl/context_glx.cpp +++ b/src/common/gl/context_glx.cpp @@ -221,6 +221,9 @@ bool ContextGLX::CreateWindow(int screen) attribs[nattribs++] = 5; break; + case WindowInfo::SurfaceFormat::Auto: + break; + default: UnreachableCode(); break; diff --git a/src/common/gl/context_wgl.cpp b/src/common/gl/context_wgl.cpp index 857084e78..c24e37027 100644 --- a/src/common/gl/context_wgl.cpp +++ b/src/common/gl/context_wgl.cpp @@ -204,6 +204,9 @@ bool ContextWGL::InitializeDC() pfd.cBlueBits = 5; break; + case WindowInfo::SurfaceFormat::Auto: + break; + default: UnreachableCode(); break; diff --git a/src/common/window_info.h b/src/common/window_info.h index 0c5943efd..cf79cd70d 100644 --- a/src/common/window_info.h +++ b/src/common/window_info.h @@ -12,11 +12,13 @@ struct WindowInfo Wayland, MacOS, Android, + DRM, }; enum class SurfaceFormat { None, + Auto, RGB8, RGBA8, RGB565,