diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/windowing/gbm | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/windowing/gbm')
37 files changed, 4157 insertions, 0 deletions
diff --git a/xbmc/windowing/gbm/CMakeLists.txt b/xbmc/windowing/gbm/CMakeLists.txt new file mode 100644 index 0000000..254b2db --- /dev/null +++ b/xbmc/windowing/gbm/CMakeLists.txt @@ -0,0 +1,26 @@ +add_subdirectory(drm) + +set(SOURCES OptionalsReg.cpp + WinSystemGbm.cpp + VideoSyncGbm.cpp + GBMUtils.cpp + WinSystemGbmEGLContext.cpp + GBMDPMSSupport.cpp) + +set(HEADERS OptionalsReg.h + WinSystemGbm.h + VideoSyncGbm.h + GBMUtils.h + WinSystemGbmEGLContext.h + GBMDPMSSupport.h) + +if (OPENGL_FOUND) + list(APPEND SOURCES WinSystemGbmGLContext.cpp) + list(APPEND HEADERS WinSystemGbmGLContext.h) +endif() +if(OPENGLES_FOUND) + list(APPEND SOURCES WinSystemGbmGLESContext.cpp) + list(APPEND HEADERS WinSystemGbmGLESContext.h) +endif() + +core_add_library(windowing_gbm) diff --git a/xbmc/windowing/gbm/GBMDPMSSupport.cpp b/xbmc/windowing/gbm/GBMDPMSSupport.cpp new file mode 100644 index 0000000..6544587 --- /dev/null +++ b/xbmc/windowing/gbm/GBMDPMSSupport.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2009-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GBMDPMSSupport.h" + +#include "ServiceBroker.h" +#include "windowing/gbm/WinSystemGbm.h" + +using namespace KODI::WINDOWING::GBM; + +CGBMDPMSSupport::CGBMDPMSSupport() +{ + m_supportedModes.push_back(OFF); +} + +bool CGBMDPMSSupport::EnablePowerSaving(PowerSavingMode mode) +{ + auto winSystem = dynamic_cast<CWinSystemGbm*>(CServiceBroker::GetWinSystem()); + if (!winSystem) + return false; + + switch (mode) + { + case OFF: + return winSystem->Hide(); + default: + return false; + } +} + +bool CGBMDPMSSupport::DisablePowerSaving() +{ + auto winSystem = dynamic_cast<CWinSystemGbm*>(CServiceBroker::GetWinSystem()); + if (!winSystem) + return false; + + return winSystem->Show(); +} diff --git a/xbmc/windowing/gbm/GBMDPMSSupport.h b/xbmc/windowing/gbm/GBMDPMSSupport.h new file mode 100644 index 0000000..f5fabf8 --- /dev/null +++ b/xbmc/windowing/gbm/GBMDPMSSupport.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2009-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "powermanagement/DPMSSupport.h" + +#include <memory> + +class CGBMDPMSSupport : public CDPMSSupport +{ +public: + CGBMDPMSSupport(); + ~CGBMDPMSSupport() override = default; + +protected: + bool EnablePowerSaving(PowerSavingMode mode) override; + bool DisablePowerSaving() override; +}; diff --git a/xbmc/windowing/gbm/GBMUtils.cpp b/xbmc/windowing/gbm/GBMUtils.cpp new file mode 100644 index 0000000..5267c93 --- /dev/null +++ b/xbmc/windowing/gbm/GBMUtils.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GBMUtils.h" + +#include "utils/log.h" + +#include <mutex> + +using namespace KODI::WINDOWING::GBM; + +namespace +{ +std::once_flag flag; +} + +bool CGBMUtils::CreateDevice(int fd) +{ + auto device = gbm_create_device(fd); + if (!device) + { + CLog::Log(LOGERROR, "CGBMUtils::{} - failed to create device: {}", __FUNCTION__, + strerror(errno)); + return false; + } + + m_device.reset(new CGBMDevice(device)); + + return true; +} + +CGBMUtils::CGBMDevice::CGBMDevice(gbm_device* device) : m_device(device) +{ +} + +bool CGBMUtils::CGBMDevice::CreateSurface( + int width, int height, uint32_t format, const uint64_t* modifiers, const int modifiers_count) +{ + gbm_surface* surface{nullptr}; +#if defined(HAS_GBM_MODIFIERS) + if (modifiers) + { + surface = gbm_surface_create_with_modifiers(m_device, width, height, format, modifiers, + modifiers_count); + } +#endif + if (!surface) + { + surface = gbm_surface_create(m_device, width, height, format, + GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + } + + if (!surface) + { + CLog::Log(LOGERROR, "CGBMUtils::{} - failed to create surface: {}", __FUNCTION__, + strerror(errno)); + return false; + } + + CLog::Log(LOGDEBUG, "CGBMUtils::{} - created surface with size {}x{}", __FUNCTION__, width, + height); + + m_surface.reset(new CGBMSurface(surface)); + + return true; +} + +CGBMUtils::CGBMDevice::CGBMSurface::CGBMSurface(gbm_surface* surface) : m_surface(surface) +{ +} + +CGBMUtils::CGBMDevice::CGBMSurface::CGBMSurfaceBuffer* CGBMUtils::CGBMDevice::CGBMSurface:: + LockFrontBuffer() +{ + m_buffers.emplace(std::make_unique<CGBMSurfaceBuffer>(m_surface)); + + if (!static_cast<bool>(gbm_surface_has_free_buffers(m_surface))) + { + /* + * We want to use call_once here because we want it to be logged the first time that + * we have to release buffers. This means that the maximum amount of buffers had been reached. + * For mesa this should be 4 buffers but it may vary across other implementations. + */ + std::call_once( + flag, [this]() { CLog::Log(LOGDEBUG, "CGBMUtils - using {} buffers", m_buffers.size()); }); + + m_buffers.pop(); + } + + return m_buffers.back().get(); +} + +CGBMUtils::CGBMDevice::CGBMSurface::CGBMSurfaceBuffer::CGBMSurfaceBuffer(gbm_surface* surface) + : m_surface(surface), m_buffer(gbm_surface_lock_front_buffer(surface)) +{ +} + +CGBMUtils::CGBMDevice::CGBMSurface::CGBMSurfaceBuffer::~CGBMSurfaceBuffer() +{ + if (m_surface && m_buffer) + gbm_surface_release_buffer(m_surface, m_buffer); +} diff --git a/xbmc/windowing/gbm/GBMUtils.h b/xbmc/windowing/gbm/GBMUtils.h new file mode 100644 index 0000000..291a93a --- /dev/null +++ b/xbmc/windowing/gbm/GBMUtils.h @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> +#include <queue> + +#include <gbm.h> + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +/** + * @brief A wrapper for gbm c classes to allow OOP and RAII. + * + */ +class CGBMUtils +{ +public: + CGBMUtils(const CGBMUtils&) = delete; + CGBMUtils& operator=(const CGBMUtils&) = delete; + CGBMUtils() = default; + ~CGBMUtils() = default; + + /** + * @brief Create a gbm device for allocating buffers + * + * @param fd The file descriptor for a backend device + * @return true The device creation succeeded + * @return false The device creation failed + */ + bool CreateDevice(int fd); + + /** + * @brief A wrapper for gbm_device to allow OOP and RAII + * + */ + class CGBMDevice + { + public: + CGBMDevice(const CGBMDevice&) = delete; + CGBMDevice& operator=(const CGBMDevice&) = delete; + explicit CGBMDevice(gbm_device* device); + ~CGBMDevice() = default; + + /** + * @brief Create a gbm surface + * + * @param width The width to use for the surface + * @param height The height to use for the surface + * @param format The format to use for the surface + * @param modifiers The modifiers to use for the surface + * @param modifiers_count The amount of modifiers in the modifiers param + * @return true The surface creation succeeded + * @return false The surface creation failed + */ + bool CreateSurface(int width, + int height, + uint32_t format, + const uint64_t* modifiers, + const int modifiers_count); + + /** + * @brief Get the underlying gbm_device + * + * @return gbm_device* A pointer to the underlying gbm_device + */ + gbm_device* Get() const { return m_device; } + + /** + * @brief A wrapper for gbm_surface to allow OOP and RAII + * + */ + class CGBMSurface + { + public: + CGBMSurface(const CGBMSurface&) = delete; + CGBMSurface& operator=(const CGBMSurface&) = delete; + explicit CGBMSurface(gbm_surface* surface); + ~CGBMSurface() = default; + + /** + * @brief Get the underlying gbm_surface + * + * @return gbm_surface* A pointer to the underlying gbm_surface + */ + gbm_surface* Get() const { return m_surface; } + + /** + * @brief A wrapper for gbm_bo to allow OOP and RAII + * + */ + class CGBMSurfaceBuffer + { + public: + CGBMSurfaceBuffer(const CGBMSurfaceBuffer&) = delete; + CGBMSurfaceBuffer& operator=(const CGBMSurfaceBuffer&) = delete; + explicit CGBMSurfaceBuffer(gbm_surface* surface); + ~CGBMSurfaceBuffer(); + + /** + * @brief Get the underlying gbm_bo + * + * @return gbm_bo* A pointer to the underlying gbm_bo + */ + gbm_bo* Get() const { return m_buffer; } + + private: + gbm_surface* m_surface{nullptr}; + gbm_bo* m_buffer{nullptr}; + }; + + /** + * @brief Lock the surface's current front buffer. + * + * @return CGBMSurfaceBuffer* A pointer to a CGBMSurfaceBuffer object + */ + CGBMSurfaceBuffer* LockFrontBuffer(); + + private: + gbm_surface* m_surface{nullptr}; + std::queue<std::unique_ptr<CGBMSurfaceBuffer>> m_buffers; + }; + + /** + * @brief Get the CGBMSurface object + * + * @return CGBMSurface* A pointer to the CGBMSurface object + */ + CGBMDevice::CGBMSurface* GetSurface() const { return m_surface.get(); } + + private: + gbm_device* m_device{nullptr}; + + struct CGBMSurfaceDeleter + { + void operator()(CGBMSurface* p) const + { + if (p) + gbm_surface_destroy(p->Get()); + } + }; + std::unique_ptr<CGBMSurface, CGBMSurfaceDeleter> m_surface; + }; + + /** + * @brief Get the CGBMDevice object + * + * @return CGBMDevice* A pointer to the CGBMDevice object + */ + CGBMUtils::CGBMDevice* GetDevice() const { return m_device.get(); } + +private: + struct CGBMDeviceDeleter + { + void operator()(CGBMDevice* p) const + { + if (p) + gbm_device_destroy(p->Get()); + } + }; + std::unique_ptr<CGBMDevice, CGBMDeviceDeleter> m_device; +}; + +} +} +} diff --git a/xbmc/windowing/gbm/OptionalsReg.cpp b/xbmc/windowing/gbm/OptionalsReg.cpp new file mode 100644 index 0000000..9f5076f --- /dev/null +++ b/xbmc/windowing/gbm/OptionalsReg.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "OptionalsReg.h" + +//----------------------------------------------------------------------------- +// VAAPI +//----------------------------------------------------------------------------- +#if defined (HAVE_LIBVA) +#include <va/va_drm.h> +#include "cores/VideoPlayer/DVDCodecs/Video/VAAPI.h" +#if defined(HAS_GL) +#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGL.h" +#endif +#if defined(HAS_GLES) +#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGLES.h" +#endif + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class CVaapiProxy : public VAAPI::IVaapiWinSystem +{ +public: + CVaapiProxy(int fd) : m_fd(fd) {}; + virtual ~CVaapiProxy() = default; + VADisplay GetVADisplay() override; + void *GetEGLDisplay() override { return eglDisplay; }; + + VADisplay vaDpy; + void *eglDisplay; + +private: + int m_fd{-1}; +}; + +VADisplay CVaapiProxy::GetVADisplay() +{ + return vaGetDisplayDRM(m_fd); +} + +CVaapiProxy* VaapiProxyCreate(int fd) +{ + return new CVaapiProxy(fd); +} + +void VaapiProxyDelete(CVaapiProxy *proxy) +{ + delete proxy; +} + +void VaapiProxyConfig(CVaapiProxy *proxy, void *eglDpy) +{ + proxy->vaDpy = proxy->GetVADisplay(); + proxy->eglDisplay = eglDpy; +} + +void VAAPIRegister(CVaapiProxy *winSystem, bool deepColor) +{ + VAAPI::CDecoder::Register(winSystem, deepColor); +} + +#if defined(HAS_GL) +void VAAPIRegisterRenderGL(CVaapiProxy* winSystem, bool& general, bool& deepColor) +{ + CRendererVAAPIGL::Register(winSystem, winSystem->vaDpy, winSystem->eglDisplay, general, + deepColor); +} +#endif + +#if defined(HAS_GLES) +void VAAPIRegisterRenderGLES(CVaapiProxy* winSystem, bool& general, bool& deepColor) +{ + CRendererVAAPIGLES::Register(winSystem, winSystem->vaDpy, winSystem->eglDisplay, general, + deepColor); +} +#endif +} +} +} + +#else + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class CVaapiProxy +{ +}; + +CVaapiProxy* VaapiProxyCreate(int fd) +{ + return nullptr; +} + +void VaapiProxyDelete(CVaapiProxy *proxy) +{ +} + +void VaapiProxyConfig(CVaapiProxy *proxy, void *eglDpy) +{ + +} + +void VAAPIRegister(CVaapiProxy *winSystem, bool deepColor) +{ + +} + +#if defined(HAS_GL) +void VAAPIRegisterRenderGL(CVaapiProxy* winSystem, bool& general, bool& deepColor) +{ + +} +#endif + +#if defined(HAS_GLES) +void VAAPIRegisterRenderGLES(CVaapiProxy* winSystem, bool& general, bool& deepColor) +{ +} +#endif +} +} +} + +#endif diff --git a/xbmc/windowing/gbm/OptionalsReg.h b/xbmc/windowing/gbm/OptionalsReg.h new file mode 100644 index 0000000..ee459f6 --- /dev/null +++ b/xbmc/windowing/gbm/OptionalsReg.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + + +//----------------------------------------------------------------------------- +// VAAPI +//----------------------------------------------------------------------------- + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ +class CVaapiProxy; + +CVaapiProxy* VaapiProxyCreate(int fd); +void VaapiProxyDelete(CVaapiProxy *proxy); +void VaapiProxyConfig(CVaapiProxy *proxy, void *eglDpy); +void VAAPIRegister(CVaapiProxy *winSystem, bool deepColor); +#if defined(HAS_GL) +void VAAPIRegisterRenderGL(CVaapiProxy* winSystem, bool& general, bool& deepColor); +#endif +#if defined(HAS_GLES) +void VAAPIRegisterRenderGLES(CVaapiProxy* winSystem, bool& general, bool& deepColor); +#endif +} +} +} diff --git a/xbmc/windowing/gbm/VideoLayerBridge.h b/xbmc/windowing/gbm/VideoLayerBridge.h new file mode 100644 index 0000000..7070567 --- /dev/null +++ b/xbmc/windowing/gbm/VideoLayerBridge.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class CVideoLayerBridge +{ +public: + virtual ~CVideoLayerBridge() = default; + virtual void Disable() {} +}; + +} +} +} diff --git a/xbmc/windowing/gbm/VideoSyncGbm.cpp b/xbmc/windowing/gbm/VideoSyncGbm.cpp new file mode 100644 index 0000000..d113c90 --- /dev/null +++ b/xbmc/windowing/gbm/VideoSyncGbm.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "VideoSyncGbm.h" + +#include "ServiceBroker.h" +#include "threads/Thread.h" +#include "utils/TimeUtils.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" +#include "windowing/WinSystem.h" +#include "windowing/gbm/WinSystemGbm.h" +#include "xf86drm.h" +#include "xf86drmMode.h" + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> + +#include <unistd.h> + +CVideoSyncGbm::CVideoSyncGbm(void* clock) + : CVideoSync(clock), m_winSystem(CServiceBroker::GetWinSystem()) +{ + if (!m_winSystem) + throw std::runtime_error("window system not available"); +} + +bool CVideoSyncGbm::Setup(PUPDATECLOCK func) +{ + UpdateClock = func; + m_abort = false; + m_winSystem->Register(this); + CLog::Log(LOGDEBUG, "CVideoSyncGbm::{} setting up", __FUNCTION__); + + auto winSystemGbm = dynamic_cast<KODI::WINDOWING::GBM::CWinSystemGbm*>(m_winSystem); + if (!winSystemGbm) + { + CLog::Log(LOGWARNING, "CVideoSyncGbm::{}: failed to get winSystem", __FUNCTION__); + return false; + } + + auto drm = winSystemGbm->GetDrm(); + if (!drm) + { + CLog::Log(LOGWARNING, "CVideoSyncGbm::{}: failed to get drm", __FUNCTION__); + return false; + } + + auto crtc = drm->GetCrtc(); + if (!crtc) + { + CLog::Log(LOGWARNING, "CVideoSyncGbm::{}: failed to get crtc", __FUNCTION__); + return false; + } + + uint64_t ns = 0; + m_crtcId = crtc->GetCrtcId(); + m_fd = drm->GetFileDescriptor(); + int s = drmCrtcGetSequence(m_fd, m_crtcId, &m_sequence, &ns); + m_offset = CurrentHostCounter() - ns; + if (s != 0) + { + CLog::Log(LOGWARNING, "CVideoSyncGbm::{}: drmCrtcGetSequence failed ({})", __FUNCTION__, s); + return false; + } + + CLog::Log(LOGINFO, "CVideoSyncGbm::{}: opened (fd:{} crtc:{} seq:{} ns:{}:{})", __FUNCTION__, + m_fd, m_crtcId, m_sequence, ns, m_offset + ns); + return true; +} + +void CVideoSyncGbm::Run(CEvent& stopEvent) +{ + /* This shouldn't be very busy and timing is important so increase priority */ + CThread::GetCurrentThread()->SetPriority(ThreadPriority::ABOVE_NORMAL); + + if (m_fd < 0) + { + CLog::Log(LOGWARNING, "CVideoSyncGbm::{}: failed to open device ({})", __FUNCTION__, m_fd); + return; + } + CLog::Log(LOGDEBUG, "CVideoSyncGbm::{}: started {}", __FUNCTION__, m_fd); + + while (!stopEvent.Signaled() && !m_abort) + { + uint64_t sequence = 0, ns = 0; + usleep(1000); + int s = drmCrtcGetSequence(m_fd, m_crtcId, &sequence, &ns); + if (s != 0) + { + CLog::Log(LOGWARNING, "CVideoSyncGbm::{}: drmCrtcGetSequence failed ({})", __FUNCTION__, s); + break; + } + + if (sequence == m_sequence) + continue; + + UpdateClock(sequence - m_sequence, m_offset + ns, m_refClock); + m_sequence = sequence; + } +} + +void CVideoSyncGbm::Cleanup() +{ + CLog::Log(LOGDEBUG, "CVideoSyncGbm::{}: cleaning up", __FUNCTION__); + m_winSystem->Unregister(this); +} + +float CVideoSyncGbm::GetFps() +{ + m_fps = m_winSystem->GetGfxContext().GetFPS(); + CLog::Log(LOGDEBUG, "CVideoSyncGbm::{}: fps:{}", __FUNCTION__, m_fps); + return m_fps; +} + +void CVideoSyncGbm::OnResetDisplay() +{ + m_abort = true; +} + +void CVideoSyncGbm::RefreshChanged() +{ + if (m_fps != m_winSystem->GetGfxContext().GetFPS()) + m_abort = true; +} diff --git a/xbmc/windowing/gbm/VideoSyncGbm.h b/xbmc/windowing/gbm/VideoSyncGbm.h new file mode 100644 index 0000000..610d988 --- /dev/null +++ b/xbmc/windowing/gbm/VideoSyncGbm.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/DispResource.h" +#include "windowing/VideoSync.h" + +#include <atomic> + +class CWinSystemBase; + +class CVideoSyncGbm : public CVideoSync, IDispResource +{ +public: + explicit CVideoSyncGbm(void* clock); + CVideoSyncGbm() = delete; + ~CVideoSyncGbm() override = default; + + // CVideoSync overrides + bool Setup(PUPDATECLOCK func) override; + void Run(CEvent& stopEvent) override; + void Cleanup() override; + float GetFps() override; + void RefreshChanged() override; + + // IDispResource overrides + void OnResetDisplay() override; + +private: + int m_fd = -1; + uint32_t m_crtcId = 0; + uint64_t m_sequence = 0; + uint64_t m_offset = 0; + std::atomic<bool> m_abort{false}; + + CWinSystemBase* m_winSystem; +}; diff --git a/xbmc/windowing/gbm/WinSystemGbm.cpp b/xbmc/windowing/gbm/WinSystemGbm.cpp new file mode 100644 index 0000000..4fd2da4 --- /dev/null +++ b/xbmc/windowing/gbm/WinSystemGbm.cpp @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "WinSystemGbm.h" + +#include "GBMDPMSSupport.h" +#include "OptionalsReg.h" +#include "ServiceBroker.h" +#include "VideoSyncGbm.h" +#include "cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h" +#include "drm/DRMAtomic.h" +#include "drm/DRMLegacy.h" +#include "drm/OffScreenModeSetting.h" +#include "messaging/ApplicationMessenger.h" +#include "settings/DisplaySettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "utils/StringUtils.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" + +#include <mutex> +#include <string.h> + +#ifndef HAVE_HDR_OUTPUT_METADATA +// HDR structs is copied from linux include/linux/hdmi.h +struct hdr_metadata_infoframe +{ + uint8_t eotf; + uint8_t metadata_type; + struct + { + uint16_t x, y; + } display_primaries[3]; + struct + { + uint16_t x, y; + } white_point; + uint16_t max_display_mastering_luminance; + uint16_t min_display_mastering_luminance; + uint16_t max_cll; + uint16_t max_fall; +}; +struct hdr_output_metadata +{ + uint32_t metadata_type; + union + { + struct hdr_metadata_infoframe hdmi_metadata_type1; + }; +}; +#endif + +using namespace KODI::WINDOWING::GBM; + +using namespace std::chrono_literals; + +CWinSystemGbm::CWinSystemGbm() : + m_DRM(nullptr), + m_GBM(new CGBMUtils), + m_libinput(new CLibInputHandler) +{ + m_dpms = std::make_shared<CGBMDPMSSupport>(); + m_libinput->Start(); +} + +bool CWinSystemGbm::InitWindowSystem() +{ + const char* x11 = getenv("DISPLAY"); + const char* wayland = getenv("WAYLAND_DISPLAY"); + if (x11 || wayland) + { + CLog::Log(LOGDEBUG, "CWinSystemGbm::{} - not allowed to run GBM under a window manager", + __FUNCTION__); + return false; + } + + m_DRM = std::make_shared<CDRMAtomic>(); + + if (!m_DRM->InitDrm()) + { + CLog::Log(LOGERROR, "CWinSystemGbm::{} - failed to initialize Atomic DRM", __FUNCTION__); + m_DRM.reset(); + + m_DRM = std::make_shared<CDRMLegacy>(); + + if (!m_DRM->InitDrm()) + { + CLog::Log(LOGERROR, "CWinSystemGbm::{} - failed to initialize Legacy DRM", __FUNCTION__); + m_DRM.reset(); + + m_DRM = std::make_shared<COffScreenModeSetting>(); + if (!m_DRM->InitDrm()) + { + CLog::Log(LOGERROR, "CWinSystemGbm::{} - failed to initialize off screen DRM", + __FUNCTION__); + m_DRM.reset(); + return false; + } + } + } + + if (!m_GBM->CreateDevice(m_DRM->GetFileDescriptor())) + { + m_GBM.reset(); + return false; + } + + auto settingsComponent = CServiceBroker::GetSettingsComponent(); + if (!settingsComponent) + return false; + + auto settings = settingsComponent->GetSettings(); + if (!settings) + return false; + + auto setting = settings->GetSetting(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE); + if (setting) + setting->SetVisible(true); + + setting = settings->GetSetting("videoscreen.limitguisize"); + if (setting) + setting->SetVisible(true); + + CLog::Log(LOGDEBUG, "CWinSystemGbm::{} - initialized DRM", __FUNCTION__); + return CWinSystemBase::InitWindowSystem(); +} + +bool CWinSystemGbm::DestroyWindowSystem() +{ + CLog::Log(LOGDEBUG, "CWinSystemGbm::{} - deinitialized DRM", __FUNCTION__); + + m_libinput.reset(); + + return true; +} + +void CWinSystemGbm::UpdateResolutions() +{ + RESOLUTION_INFO current = m_DRM->GetCurrentMode(); + + auto resolutions = m_DRM->GetModes(); + if (resolutions.empty()) + { + CLog::Log(LOGWARNING, "CWinSystemGbm::{} - Failed to get resolutions", __FUNCTION__); + } + else + { + CDisplaySettings::GetInstance().ClearCustomResolutions(); + + for (auto &res : resolutions) + { + CServiceBroker::GetWinSystem()->GetGfxContext().ResetOverscan(res); + CDisplaySettings::GetInstance().AddResolutionInfo(res); + + if (current.iScreenWidth == res.iScreenWidth && + current.iScreenHeight == res.iScreenHeight && + current.iWidth == res.iWidth && + current.iHeight == res.iHeight && + current.fRefreshRate == res.fRefreshRate && + current.dwFlags == res.dwFlags) + { + CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP) = res; + } + + CLog::Log(LOGINFO, "Found resolution {}x{} with {}x{}{} @ {:f} Hz", res.iWidth, res.iHeight, + res.iScreenWidth, res.iScreenHeight, + res.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "", res.fRefreshRate); + } + } + + CDisplaySettings::GetInstance().ApplyCalibrations(); +} + +bool CWinSystemGbm::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) +{ + return true; +} + +bool CWinSystemGbm::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) +{ + // Notify other subsystems that we will change resolution + OnLostDevice(); + + if(!m_DRM->SetMode(res)) + { + CLog::Log(LOGERROR, "CWinSystemGbm::{} - failed to set DRM mode", __FUNCTION__); + return false; + } + + struct gbm_bo *bo = nullptr; + + if (!std::dynamic_pointer_cast<CDRMAtomic>(m_DRM)) + { + bo = m_GBM->GetDevice()->GetSurface()->LockFrontBuffer()->Get(); + } + + auto result = m_DRM->SetVideoMode(res, bo); + + auto delay = + std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + "videoscreen.delayrefreshchange") * + 100); + if (delay > 0ms) + m_dispResetTimer.Set(delay); + + return result; +} + +bool CWinSystemGbm::DisplayHardwareScalingEnabled() +{ + auto drmAtomic = std::dynamic_pointer_cast<CDRMAtomic>(m_DRM); + if (drmAtomic && drmAtomic->DisplayHardwareScalingEnabled()) + return true; + + return false; +} + +void CWinSystemGbm::UpdateDisplayHardwareScaling(const RESOLUTION_INFO& resInfo) +{ + if (!DisplayHardwareScalingEnabled()) + return; + + //! @todo The PR that made the res struct constant was abandoned due to drama. + // It should be const-corrected and changed here. + RESOLUTION_INFO& resMutable = const_cast<RESOLUTION_INFO&>(resInfo); + + SetFullScreen(true, resMutable, false); +} + +void CWinSystemGbm::FlipPage(bool rendered, bool videoLayer) +{ + if (m_videoLayerBridge && !videoLayer) + { + // disable video plane when video layer no longer is active + m_videoLayerBridge->Disable(); + } + + struct gbm_bo *bo = nullptr; + + if (rendered) + { + bo = m_GBM->GetDevice()->GetSurface()->LockFrontBuffer()->Get(); + } + + m_DRM->FlipPage(bo, rendered, videoLayer); + + if (m_videoLayerBridge && !videoLayer) + { + // delete video layer bridge when video layer no longer is active + m_videoLayerBridge.reset(); + } +} + +bool CWinSystemGbm::UseLimitedColor() +{ + return CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE); +} + +bool CWinSystemGbm::Hide() +{ + bool ret = m_DRM->SetActive(false); + FlipPage(false, false); + return ret; +} + +bool CWinSystemGbm::Show(bool raise) +{ + bool ret = m_DRM->SetActive(true); + FlipPage(false, false); + return ret; +} + +void CWinSystemGbm::Register(IDispResource *resource) +{ + std::unique_lock<CCriticalSection> lock(m_resourceSection); + m_resources.push_back(resource); +} + +void CWinSystemGbm::Unregister(IDispResource *resource) +{ + std::unique_lock<CCriticalSection> lock(m_resourceSection); + std::vector<IDispResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource); + if (i != m_resources.end()) + { + m_resources.erase(i); + } +} + +void CWinSystemGbm::OnLostDevice() +{ + CLog::Log(LOGDEBUG, "{} - notify display change event", __FUNCTION__); + m_dispReset = true; + + std::unique_lock<CCriticalSection> lock(m_resourceSection); + for (auto resource : m_resources) + resource->OnLostDisplay(); +} + +std::unique_ptr<CVideoSync> CWinSystemGbm::GetVideoSync(void* clock) +{ + return std::make_unique<CVideoSyncGbm>(clock); +} + +std::vector<std::string> CWinSystemGbm::GetConnectedOutputs() +{ + return m_DRM->GetConnectedConnectorNames(); +} + +bool CWinSystemGbm::SetHDR(const VideoPicture* videoPicture) +{ + auto settingsComponent = CServiceBroker::GetSettingsComponent(); + if (!settingsComponent) + return false; + + auto settings = settingsComponent->GetSettings(); + if (!settings) + return false; + + if (!settings->GetBool(SETTING_WINSYSTEM_IS_HDR_DISPLAY)) + return false; + + auto drm = std::dynamic_pointer_cast<CDRMAtomic>(m_DRM); + if (!drm) + return false; + + if (!videoPicture) + { + auto connector = drm->GetConnector(); + if (connector->SupportsProperty("HDR_OUTPUT_METADATA")) + { + drm->AddProperty(connector, "HDR_OUTPUT_METADATA", 0); + drm->SetActive(true); + + if (m_hdr_blob_id) + drmModeDestroyPropertyBlob(drm->GetFileDescriptor(), m_hdr_blob_id); + m_hdr_blob_id = 0; + } + + return true; + } + + auto connector = drm->GetConnector(); + if (connector->SupportsProperty("HDR_OUTPUT_METADATA")) + { + hdr_output_metadata hdr_metadata = {}; + + hdr_metadata.metadata_type = DRMPRIME::HDMI_STATIC_METADATA_TYPE1; + hdr_metadata.hdmi_metadata_type1.eotf = DRMPRIME::GetEOTF(*videoPicture); + hdr_metadata.hdmi_metadata_type1.metadata_type = DRMPRIME::HDMI_STATIC_METADATA_TYPE1; + + if (m_hdr_blob_id) + drmModeDestroyPropertyBlob(drm->GetFileDescriptor(), m_hdr_blob_id); + m_hdr_blob_id = 0; + + if (hdr_metadata.hdmi_metadata_type1.eotf) + { + const AVMasteringDisplayMetadata* mdmd = DRMPRIME::GetMasteringDisplayMetadata(*videoPicture); + if (mdmd && mdmd->has_primaries) + { + // Convert to unsigned 16-bit values in units of 0.00002, + // where 0x0000 represents zero and 0xC350 represents 1.0000 + for (int i = 0; i < 3; i++) + { + hdr_metadata.hdmi_metadata_type1.display_primaries[i].x = + std::round(av_q2d(mdmd->display_primaries[i][0]) * 50000.0); + hdr_metadata.hdmi_metadata_type1.display_primaries[i].y = + std::round(av_q2d(mdmd->display_primaries[i][1]) * 50000.0); + + CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - display_primaries[{}].x: {}", + __FUNCTION__, i, hdr_metadata.hdmi_metadata_type1.display_primaries[i].x); + CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - display_primaries[{}].y: {}", + __FUNCTION__, i, hdr_metadata.hdmi_metadata_type1.display_primaries[i].y); + } + hdr_metadata.hdmi_metadata_type1.white_point.x = + std::round(av_q2d(mdmd->white_point[0]) * 50000.0); + hdr_metadata.hdmi_metadata_type1.white_point.y = + std::round(av_q2d(mdmd->white_point[1]) * 50000.0); + + CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - white_point.x: {}", __FUNCTION__, + hdr_metadata.hdmi_metadata_type1.white_point.x); + CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - white_point.y: {}", __FUNCTION__, + hdr_metadata.hdmi_metadata_type1.white_point.y); + } + if (mdmd && mdmd->has_luminance) + { + // Convert to unsigned 16-bit value in units of 1 cd/m2, + // where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2 + hdr_metadata.hdmi_metadata_type1.max_display_mastering_luminance = + std::round(av_q2d(mdmd->max_luminance)); + + // Convert to unsigned 16-bit value in units of 0.0001 cd/m2, + // where 0x0001 represents 0.0001 cd/m2 and 0xFFFF represents 6.5535 cd/m2 + hdr_metadata.hdmi_metadata_type1.min_display_mastering_luminance = + std::round(av_q2d(mdmd->min_luminance) * 10000.0); + + CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - max_display_mastering_luminance: {}", + __FUNCTION__, hdr_metadata.hdmi_metadata_type1.max_display_mastering_luminance); + CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - min_display_mastering_luminance: {}", + __FUNCTION__, hdr_metadata.hdmi_metadata_type1.min_display_mastering_luminance); + } + + const AVContentLightMetadata* clmd = DRMPRIME::GetContentLightMetadata(*videoPicture); + if (clmd) + { + hdr_metadata.hdmi_metadata_type1.max_cll = clmd->MaxCLL; + hdr_metadata.hdmi_metadata_type1.max_fall = clmd->MaxFALL; + + CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - max_cll: {}", __FUNCTION__, + hdr_metadata.hdmi_metadata_type1.max_cll); + CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - max_fall: {}", __FUNCTION__, + hdr_metadata.hdmi_metadata_type1.max_fall); + } + + drmModeCreatePropertyBlob(drm->GetFileDescriptor(), &hdr_metadata, sizeof(hdr_metadata), + &m_hdr_blob_id); + } + + drm->AddProperty(connector, "HDR_OUTPUT_METADATA", m_hdr_blob_id); + drm->SetActive(true); + } + + return true; +} + +bool CWinSystemGbm::IsHDRDisplay() +{ + auto drm = std::dynamic_pointer_cast<CDRMAtomic>(m_DRM); + if (!drm) + return false; + + auto connector = drm->GetConnector(); + if (!connector) + return false; + + //! @todo: improve detection (edid?) + // we have no way to know if the display is actually HDR capable and we blindly set the HDR metadata + return connector->SupportsProperty("HDR_OUTPUT_METADATA"); +} diff --git a/xbmc/windowing/gbm/WinSystemGbm.h b/xbmc/windowing/gbm/WinSystemGbm.h new file mode 100644 index 0000000..b313d33 --- /dev/null +++ b/xbmc/windowing/gbm/WinSystemGbm.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "VideoLayerBridge.h" +#include "drm/DRMUtils.h" +#include "threads/CriticalSection.h" +#include "threads/SystemClock.h" +#include "windowing/WinSystem.h" + +#include "platform/linux/input/LibInputHandler.h" + +#include <utility> + +#include <gbm.h> + +class IDispResource; + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class CWinSystemGbm : public CWinSystemBase +{ +public: + CWinSystemGbm(); + ~CWinSystemGbm() override = default; + + const std::string GetName() override { return "gbm"; } + + bool InitWindowSystem() override; + bool DestroyWindowSystem() override; + + bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override; + bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override; + bool DisplayHardwareScalingEnabled() override; + void UpdateDisplayHardwareScaling(const RESOLUTION_INFO& resInfo) override; + + void FlipPage(bool rendered, bool videoLayer); + + bool CanDoWindowed() override { return false; } + void UpdateResolutions() override; + + bool UseLimitedColor() override; + + bool Hide() override; + bool Show(bool raise = true) override; + void Register(IDispResource* resource) override; + void Unregister(IDispResource* resource) override; + + bool SetHDR(const VideoPicture* videoPicture) override; + bool IsHDRDisplay() override; + + std::shared_ptr<CVideoLayerBridge> GetVideoLayerBridge() const { return m_videoLayerBridge; } + void RegisterVideoLayerBridge(std::shared_ptr<CVideoLayerBridge> bridge) + { + m_videoLayerBridge = std::move(bridge); + }; + + CGBMUtils::CGBMDevice* GetGBMDevice() const { return m_GBM->GetDevice(); } + std::shared_ptr<CDRMUtils> GetDrm() const { return m_DRM; } + + std::vector<std::string> GetConnectedOutputs() override; + +protected: + void OnLostDevice(); + + std::unique_ptr<CVideoSync> GetVideoSync(void* clock) override; + + std::shared_ptr<CDRMUtils> m_DRM; + std::unique_ptr<CGBMUtils> m_GBM; + std::shared_ptr<CVideoLayerBridge> m_videoLayerBridge; + + CCriticalSection m_resourceSection; + std::vector<IDispResource*> m_resources; + + bool m_dispReset = false; + XbmcThreads::EndTime<> m_dispResetTimer; + std::unique_ptr<CLibInputHandler> m_libinput; + +private: + uint32_t m_hdr_blob_id = 0; +}; + +} +} +} diff --git a/xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp b/xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp new file mode 100644 index 0000000..83a5941 --- /dev/null +++ b/xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "WinSystemGbmEGLContext.h" + +#include "OptionalsReg.h" +#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h" +#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h" +#include "utils/log.h" + +using namespace KODI::WINDOWING::GBM; +using namespace KODI::WINDOWING::LINUX; + +bool CWinSystemGbmEGLContext::InitWindowSystemEGL(EGLint renderableType, EGLint apiType) +{ + if (!CWinSystemGbm::InitWindowSystem()) + { + return false; + } + + if (!m_eglContext.CreatePlatformDisplay(m_GBM->GetDevice()->Get(), m_GBM->GetDevice()->Get())) + { + return false; + } + + if (!m_eglContext.InitializeDisplay(apiType)) + { + return false; + } + + auto plane = m_DRM->GetGuiPlane(); + uint32_t visualId = plane != nullptr ? plane->GetFormat() : DRM_FORMAT_XRGB2101010; + + // prefer alpha visual id, fallback to non-alpha visual id + if (!m_eglContext.ChooseConfig(renderableType, CDRMUtils::FourCCWithAlpha(visualId)) && + !m_eglContext.ChooseConfig(renderableType, CDRMUtils::FourCCWithoutAlpha(visualId))) + { + // fallback to 8bit format if no EGL config was found for 10bit + if (plane) + plane->SetFormat(DRM_FORMAT_XRGB8888); + + visualId = plane != nullptr ? plane->GetFormat() : DRM_FORMAT_XRGB8888; + + if (!m_eglContext.ChooseConfig(renderableType, CDRMUtils::FourCCWithAlpha(visualId)) && + !m_eglContext.ChooseConfig(renderableType, CDRMUtils::FourCCWithoutAlpha(visualId))) + { + return false; + } + } + + if (!CreateContext()) + { + return false; + } + + return true; +} + +bool CWinSystemGbmEGLContext::CreateNewWindow(const std::string& name, + bool fullScreen, + RESOLUTION_INFO& res) +{ + //Notify other subsystems that we change resolution + OnLostDevice(); + + if (!DestroyWindow()) + { + return false; + } + + if (!m_DRM->SetMode(res)) + { + CLog::Log(LOGERROR, "CWinSystemGbmEGLContext::{} - failed to set DRM mode", __FUNCTION__); + return false; + } + + uint32_t format = m_eglContext.GetConfigAttrib(EGL_NATIVE_VISUAL_ID); + + std::vector<uint64_t> modifiers; + + auto plane = m_DRM->GetGuiPlane(); + if (plane) + modifiers = plane->GetModifiersForFormat(format); + + if (!m_GBM->GetDevice()->CreateSurface(res.iWidth, res.iHeight, format, modifiers.data(), + modifiers.size())) + { + CLog::Log(LOGERROR, "CWinSystemGbmEGLContext::{} - failed to initialize GBM", __FUNCTION__); + return false; + } + + // This check + the reinterpret cast is for security reason, if the user has outdated platform header files which often is the case + static_assert(sizeof(EGLNativeWindowType) == sizeof(gbm_surface*), "Declaration specifier differs in size"); + + if (!m_eglContext.CreatePlatformSurface( + m_GBM->GetDevice()->GetSurface()->Get(), + reinterpret_cast<khronos_uintptr_t>(m_GBM->GetDevice()->GetSurface()->Get()))) + { + return false; + } + + if (!m_eglContext.BindContext()) + { + return false; + } + + m_bFullScreen = fullScreen; + m_nWidth = res.iWidth; + m_nHeight = res.iHeight; + m_fRefreshRate = res.fRefreshRate; + + CLog::Log(LOGDEBUG, "CWinSystemGbmEGLContext::{} - initialized GBM", __FUNCTION__); + return true; +} + +bool CWinSystemGbmEGLContext::DestroyWindow() +{ + m_eglContext.DestroySurface(); + + CLog::Log(LOGDEBUG, "CWinSystemGbmEGLContext::{} - deinitialized GBM", __FUNCTION__); + return true; +} + +bool CWinSystemGbmEGLContext::DestroyWindowSystem() +{ + CDVDFactoryCodec::ClearHWAccels(); + VIDEOPLAYER::CRendererFactory::ClearRenderer(); + m_eglContext.Destroy(); + + return CWinSystemGbm::DestroyWindowSystem(); +} + +void CWinSystemGbmEGLContext::delete_CVaapiProxy::operator()(CVaapiProxy *p) const +{ + VaapiProxyDelete(p); +} diff --git a/xbmc/windowing/gbm/WinSystemGbmEGLContext.h b/xbmc/windowing/gbm/WinSystemGbmEGLContext.h new file mode 100644 index 0000000..84f863d --- /dev/null +++ b/xbmc/windowing/gbm/WinSystemGbmEGLContext.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "WinSystemGbm.h" +#include "utils/EGLUtils.h" +#include "windowing/linux/WinSystemEGL.h" + +#include <memory> + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class CVaapiProxy; + +class CWinSystemGbmEGLContext : public KODI::WINDOWING::LINUX::CWinSystemEGL, public CWinSystemGbm +{ +public: + ~CWinSystemGbmEGLContext() override = default; + + bool DestroyWindowSystem() override; + bool CreateNewWindow(const std::string& name, + bool fullScreen, + RESOLUTION_INFO& res) override; + bool DestroyWindow() override; + +protected: + CWinSystemGbmEGLContext(EGLenum platform, std::string const& platformExtension) + : CWinSystemEGL{platform, platformExtension} + {} + + /** + * Inheriting classes should override InitWindowSystem() without parameters + * and call this function there with appropriate parameters + */ + bool InitWindowSystemEGL(EGLint renderableType, EGLint apiType); + virtual bool CreateContext() = 0; + + struct delete_CVaapiProxy + { + void operator()(CVaapiProxy *p) const; + }; + std::unique_ptr<CVaapiProxy, delete_CVaapiProxy> m_vaapiProxy; +}; + +} +} +} diff --git a/xbmc/windowing/gbm/WinSystemGbmGLContext.cpp b/xbmc/windowing/gbm/WinSystemGbmGLContext.cpp new file mode 100644 index 0000000..e4ff49c --- /dev/null +++ b/xbmc/windowing/gbm/WinSystemGbmGLContext.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "WinSystemGbmGLContext.h" + +#include "OptionalsReg.h" +#include "cores/RetroPlayer/process/gbm/RPProcessInfoGbm.h" +#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.h" +#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h" +#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h" +#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h" +#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h" +#include "rendering/gl/ScreenshotSurfaceGL.h" +#include "utils/BufferObjectFactory.h" +#include "utils/DMAHeapBufferObject.h" +#include "utils/DumbBufferObject.h" +#include "utils/GBMBufferObject.h" +#include "utils/UDMABufferObject.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" +#include "windowing/WindowSystemFactory.h" + +#include <mutex> + +#include <EGL/eglext.h> + +using namespace KODI::WINDOWING::GBM; + +using namespace std::chrono_literals; + +CWinSystemGbmGLContext::CWinSystemGbmGLContext() +: CWinSystemGbmEGLContext(EGL_PLATFORM_GBM_MESA, "EGL_MESA_platform_gbm") +{} + +void CWinSystemGbmGLContext::Register() +{ + CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem, "gbm"); +} + +std::unique_ptr<CWinSystemBase> CWinSystemGbmGLContext::CreateWinSystem() +{ + return std::make_unique<CWinSystemGbmGLContext>(); +} + +bool CWinSystemGbmGLContext::InitWindowSystem() +{ + VIDEOPLAYER::CRendererFactory::ClearRenderer(); + CDVDFactoryCodec::ClearHWAccels(); + CLinuxRendererGL::Register(); + RETRO::CRPProcessInfoGbm::Register(); + RETRO::CRPProcessInfoGbm::RegisterRendererFactory(new RETRO::CRendererFactoryDMA); + RETRO::CRPProcessInfoGbm::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGL); + + if (!CWinSystemGbmEGLContext::InitWindowSystemEGL(EGL_OPENGL_BIT, EGL_OPENGL_API)) + { + return false; + } + + bool general, deepColor; + m_vaapiProxy.reset(VaapiProxyCreate(m_DRM->GetRenderNodeFileDescriptor())); + VaapiProxyConfig(m_vaapiProxy.get(), m_eglContext.GetEGLDisplay()); + VAAPIRegisterRenderGL(m_vaapiProxy.get(), general, deepColor); + + if (general) + { + VAAPIRegister(m_vaapiProxy.get(), deepColor); + } + + CScreenshotSurfaceGL::Register(); + + CBufferObjectFactory::ClearBufferObjects(); + CDumbBufferObject::Register(); +#if defined(HAS_GBM_BO_MAP) + CGBMBufferObject::Register(); +#endif +#if defined(HAVE_LINUX_MEMFD) && defined(HAVE_LINUX_UDMABUF) + CUDMABufferObject::Register(); +#endif +#if defined(HAVE_LINUX_DMA_HEAP) + CDMAHeapBufferObject::Register(); +#endif + + return true; +} + +bool CWinSystemGbmGLContext::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) +{ + if (res.iWidth != m_nWidth || + res.iHeight != m_nHeight) + { + CLog::Log(LOGDEBUG, "CWinSystemGbmGLContext::{} - resolution changed, creating a new window", + __FUNCTION__); + CreateNewWindow("", fullScreen, res); + } + + if (!m_eglContext.TrySwapBuffers()) + { + CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed"); + throw std::runtime_error("eglSwapBuffers failed"); + } + + CWinSystemGbm::SetFullScreen(fullScreen, res, blankOtherDisplays); + CRenderSystemGL::ResetRenderSystem(res.iWidth, res.iHeight); + + return true; +} + +void CWinSystemGbmGLContext::PresentRender(bool rendered, bool videoLayer) +{ + if (!m_bRenderCreated) + return; + + if (rendered || videoLayer) + { + if (rendered) + { + if (!m_eglContext.TrySwapBuffers()) + { + CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed"); + throw std::runtime_error("eglSwapBuffers failed"); + } + } + CWinSystemGbm::FlipPage(rendered, videoLayer); + + if (m_dispReset && m_dispResetTimer.IsTimePast()) + { + CLog::Log(LOGDEBUG, "CWinSystemGbmGLContext::{} - Sending display reset to all clients", + __FUNCTION__); + m_dispReset = false; + std::unique_lock<CCriticalSection> lock(m_resourceSection); + + for (auto resource : m_resources) + resource->OnResetDisplay(); + } + } + else + { + KODI::TIME::Sleep(10ms); + } +} + +bool CWinSystemGbmGLContext::CreateContext() +{ + const EGLint glMajor = 3; + const EGLint glMinor = 2; + + CEGLAttributesVec contextAttribs; + contextAttribs.Add({{EGL_CONTEXT_MAJOR_VERSION_KHR, glMajor}, + {EGL_CONTEXT_MINOR_VERSION_KHR, glMinor}, + {EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR}}); + + if (!m_eglContext.CreateContext(contextAttribs)) + { + CEGLAttributesVec fallbackContextAttribs; + fallbackContextAttribs.Add({{EGL_CONTEXT_CLIENT_VERSION, 2}}); + + if (!m_eglContext.CreateContext(fallbackContextAttribs)) + { + CLog::Log(LOGERROR, "EGL context creation failed"); + return false; + } + else + { + CLog::Log(LOGWARNING, "Your OpenGL drivers do not support OpenGL {}.{} core profile. Kodi will run in compatibility mode, but performance may suffer.", glMajor, glMinor); + } + } + + return true; +} diff --git a/xbmc/windowing/gbm/WinSystemGbmGLContext.h b/xbmc/windowing/gbm/WinSystemGbmGLContext.h new file mode 100644 index 0000000..8994ce6 --- /dev/null +++ b/xbmc/windowing/gbm/WinSystemGbmGLContext.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "WinSystemGbmEGLContext.h" +#include "rendering/gl/RenderSystemGL.h" +#include "utils/EGLUtils.h" + +#include <memory> + +class CVaapiProxy; + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class CWinSystemGbmGLContext : public CWinSystemGbmEGLContext, public CRenderSystemGL +{ +public: + CWinSystemGbmGLContext(); + ~CWinSystemGbmGLContext() override = default; + + static void Register(); + static std::unique_ptr<CWinSystemBase> CreateWinSystem(); + + // Implementation of CWinSystemBase via CWinSystemGbm + CRenderSystemBase *GetRenderSystem() override { return this; } + bool InitWindowSystem() override; + bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override; + void PresentRender(bool rendered, bool videoLayer) override; +protected: + void SetVSyncImpl(bool enable) override {} + void PresentRenderImpl(bool rendered) override {}; + bool CreateContext() override; +}; + +} +} +} diff --git a/xbmc/windowing/gbm/WinSystemGbmGLESContext.cpp b/xbmc/windowing/gbm/WinSystemGbmGLESContext.cpp new file mode 100644 index 0000000..0d071c3 --- /dev/null +++ b/xbmc/windowing/gbm/WinSystemGbmGLESContext.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "WinSystemGbmGLESContext.h" + +#include "OptionalsReg.h" +#include "cores/RetroPlayer/process/gbm/RPProcessInfoGbm.h" +#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.h" +#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.h" +#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h" +#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h" +#include "cores/VideoPlayer/Process/gbm/ProcessInfoGBM.h" +#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.h" +#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.h" +#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h" +#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h" +#include "rendering/gles/ScreenshotSurfaceGLES.h" +#include "utils/BufferObjectFactory.h" +#include "utils/DMAHeapBufferObject.h" +#include "utils/DumbBufferObject.h" +#include "utils/GBMBufferObject.h" +#include "utils/UDMABufferObject.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" +#include "windowing/WindowSystemFactory.h" + +#include <mutex> + +#include <gbm.h> + +using namespace KODI::WINDOWING::GBM; + +using namespace std::chrono_literals; + +CWinSystemGbmGLESContext::CWinSystemGbmGLESContext() +: CWinSystemGbmEGLContext(EGL_PLATFORM_GBM_MESA, "EGL_MESA_platform_gbm") +{} + +void CWinSystemGbmGLESContext::Register() +{ + CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem, "gbm"); +} + +std::unique_ptr<CWinSystemBase> CWinSystemGbmGLESContext::CreateWinSystem() +{ + return std::make_unique<CWinSystemGbmGLESContext>(); +} + +bool CWinSystemGbmGLESContext::InitWindowSystem() +{ + VIDEOPLAYER::CRendererFactory::ClearRenderer(); + CDVDFactoryCodec::ClearHWAccels(); + CLinuxRendererGLES::Register(); + RETRO::CRPProcessInfoGbm::Register(); + RETRO::CRPProcessInfoGbm::RegisterRendererFactory(new RETRO::CRendererFactoryDMA); + RETRO::CRPProcessInfoGbm::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGLES); + + if (!CWinSystemGbmEGLContext::InitWindowSystemEGL(EGL_OPENGL_ES2_BIT, EGL_OPENGL_ES_API)) + { + return false; + } + + bool general, deepColor; + m_vaapiProxy.reset(GBM::VaapiProxyCreate(m_DRM->GetRenderNodeFileDescriptor())); + GBM::VaapiProxyConfig(m_vaapiProxy.get(), m_eglContext.GetEGLDisplay()); + GBM::VAAPIRegisterRenderGLES(m_vaapiProxy.get(), general, deepColor); + + if (general) + { + GBM::VAAPIRegister(m_vaapiProxy.get(), deepColor); + } + + CRendererDRMPRIMEGLES::Register(); + CRendererDRMPRIME::Register(); + CDVDVideoCodecDRMPRIME::Register(); + VIDEOPLAYER::CProcessInfoGBM::Register(); + + CScreenshotSurfaceGLES::Register(); + + CBufferObjectFactory::ClearBufferObjects(); + CDumbBufferObject::Register(); +#if defined(HAS_GBM_BO_MAP) + CGBMBufferObject::Register(); +#endif +#if defined(HAVE_LINUX_MEMFD) && defined(HAVE_LINUX_UDMABUF) + CUDMABufferObject::Register(); +#endif +#if defined(HAVE_LINUX_DMA_HEAP) + CDMAHeapBufferObject::Register(); +#endif + + return true; +} + +bool CWinSystemGbmGLESContext::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) +{ + if (res.iWidth != m_nWidth || + res.iHeight != m_nHeight) + { + CLog::Log(LOGDEBUG, "CWinSystemGbmGLESContext::{} - resolution changed, creating a new window", + __FUNCTION__); + CreateNewWindow("", fullScreen, res); + } + + if (!m_eglContext.TrySwapBuffers()) + { + CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed"); + throw std::runtime_error("eglSwapBuffers failed"); + } + + CWinSystemGbm::SetFullScreen(fullScreen, res, blankOtherDisplays); + CRenderSystemGLES::ResetRenderSystem(res.iWidth, res.iHeight); + + return true; +} + +void CWinSystemGbmGLESContext::PresentRender(bool rendered, bool videoLayer) +{ + if (!m_bRenderCreated) + return; + + if (rendered || videoLayer) + { + if (rendered) + { + if (!m_eglContext.TrySwapBuffers()) + { + CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed"); + throw std::runtime_error("eglSwapBuffers failed"); + } + } + CWinSystemGbm::FlipPage(rendered, videoLayer); + + if (m_dispReset && m_dispResetTimer.IsTimePast()) + { + CLog::Log(LOGDEBUG, "CWinSystemGbmGLESContext::{} - Sending display reset to all clients", + __FUNCTION__); + m_dispReset = false; + std::unique_lock<CCriticalSection> lock(m_resourceSection); + + for (auto resource : m_resources) + resource->OnResetDisplay(); + } + } + else + { + KODI::TIME::Sleep(10ms); + } +} + +bool CWinSystemGbmGLESContext::CreateContext() +{ + CEGLAttributesVec contextAttribs; + contextAttribs.Add({{EGL_CONTEXT_CLIENT_VERSION, 2}}); + + if (!m_eglContext.CreateContext(contextAttribs)) + { + CLog::Log(LOGERROR, "EGL context creation failed"); + return false; + } + return true; +} diff --git a/xbmc/windowing/gbm/WinSystemGbmGLESContext.h b/xbmc/windowing/gbm/WinSystemGbmGLESContext.h new file mode 100644 index 0000000..8b9de77 --- /dev/null +++ b/xbmc/windowing/gbm/WinSystemGbmGLESContext.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "WinSystemGbmEGLContext.h" +#include "rendering/gles/RenderSystemGLES.h" +#include "utils/EGLUtils.h" + +#include <memory> + +class CVaapiProxy; + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class CWinSystemGbmGLESContext : public CWinSystemGbmEGLContext, public CRenderSystemGLES +{ +public: + CWinSystemGbmGLESContext(); + ~CWinSystemGbmGLESContext() override = default; + + static void Register(); + static std::unique_ptr<CWinSystemBase> CreateWinSystem(); + + // Implementation of CWinSystemBase via CWinSystemGbm + CRenderSystemBase *GetRenderSystem() override { return this; } + bool InitWindowSystem() override; + bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override; + void PresentRender(bool rendered, bool videoLayer) override; +protected: + void SetVSyncImpl(bool enable) override {} + void PresentRenderImpl(bool rendered) override {}; + bool CreateContext() override; +}; + +} +} +} diff --git a/xbmc/windowing/gbm/drm/CMakeLists.txt b/xbmc/windowing/gbm/drm/CMakeLists.txt new file mode 100644 index 0000000..8bd07ea --- /dev/null +++ b/xbmc/windowing/gbm/drm/CMakeLists.txt @@ -0,0 +1,21 @@ +set(SOURCES DRMAtomic.cpp + DRMConnector.cpp + DRMCrtc.cpp + DRMEncoder.cpp + DRMLegacy.cpp + DRMObject.cpp + DRMPlane.cpp + DRMUtils.cpp + OffScreenModeSetting.cpp) + +set(HEADERS DRMAtomic.h + DRMConnector.h + DRMCrtc.h + DRMEncoder.h + DRMLegacy.h + DRMObject.h + DRMPlane.h + DRMUtils.h + OffScreenModeSetting.h) + +core_add_library(windowing_gbm_drm) diff --git a/xbmc/windowing/gbm/drm/DRMAtomic.cpp b/xbmc/windowing/gbm/drm/DRMAtomic.cpp new file mode 100644 index 0000000..5d61a69 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMAtomic.cpp @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DRMAtomic.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "utils/log.h" + +#include <errno.h> +#include <string.h> + +#include <drm_fourcc.h> +#include <drm_mode.h> +#include <unistd.h> + +using namespace KODI::WINDOWING::GBM; + +namespace +{ + +const auto SETTING_VIDEOSCREEN_HW_SCALING_FILTER = "videoscreen.hwscalingfilter"; + +uint32_t GetScalingFactor(uint32_t srcWidth, + uint32_t srcHeight, + uint32_t destWidth, + uint32_t destHeight) +{ + uint32_t factor_W = destWidth / srcWidth; + uint32_t factor_H = destHeight / srcHeight; + if (factor_W != factor_H) + return (factor_W < factor_H) ? factor_W : factor_H; + return factor_W; +} + +} // namespace + +bool CDRMAtomic::SetScalingFilter(CDRMObject* object, const char* name, const char* type) +{ + bool result; + uint64_t value; + std::tie(result, value) = m_gui_plane->GetPropertyValue(name, type); + if (!result) + return false; + + if (!AddProperty(object, name, value)) + return false; + + uint32_t mar_scale_factor = + GetScalingFactor(m_width, m_height, m_mode->hdisplay, m_mode->vdisplay); + AddProperty(object, "CRTC_W", (mar_scale_factor * m_width)); + AddProperty(object, "CRTC_H", (mar_scale_factor * m_height)); + + return true; +} + +void CDRMAtomic::DrmAtomicCommit(int fb_id, int flags, bool rendered, bool videoLayer) +{ + uint32_t blob_id; + + if (flags & DRM_MODE_ATOMIC_ALLOW_MODESET) + { + if (!AddProperty(m_connector, "CRTC_ID", m_crtc->GetCrtcId())) + return; + + if (drmModeCreatePropertyBlob(m_fd, m_mode, sizeof(*m_mode), &blob_id) != 0) + return; + + if (m_active && m_orig_crtc && m_orig_crtc->GetCrtcId() != m_crtc->GetCrtcId()) + { + // if using a different CRTC than the original, disable original to avoid EINVAL + if (!AddProperty(m_orig_crtc, "MODE_ID", 0)) + return; + + if (!AddProperty(m_orig_crtc, "ACTIVE", 0)) + return; + } + + if (!AddProperty(m_crtc, "MODE_ID", blob_id)) + return; + + if (!AddProperty(m_crtc, "ACTIVE", m_active ? 1 : 0)) + return; + } + + if (rendered) + { + AddProperty(m_gui_plane, "FB_ID", fb_id); + AddProperty(m_gui_plane, "CRTC_ID", m_crtc->GetCrtcId()); + AddProperty(m_gui_plane, "SRC_X", 0); + AddProperty(m_gui_plane, "SRC_Y", 0); + AddProperty(m_gui_plane, "SRC_W", m_width << 16); + AddProperty(m_gui_plane, "SRC_H", m_height << 16); + AddProperty(m_gui_plane, "CRTC_X", 0); + AddProperty(m_gui_plane, "CRTC_Y", 0); + //! @todo: disabled until upstream kernel changes are merged + // if (DisplayHardwareScalingEnabled()) + // { + // SetScalingFilter(m_gui_plane, "SCALING_FILTER", "Nearest Neighbor"); + // } + // else + { + AddProperty(m_gui_plane, "CRTC_W", m_mode->hdisplay); + AddProperty(m_gui_plane, "CRTC_H", m_mode->vdisplay); + } + + } + else if (videoLayer && !CServiceBroker::GetGUI()->GetWindowManager().HasVisibleControls()) + { + // disable gui plane when video layer is active and gui has no visible controls + AddProperty(m_gui_plane, "FB_ID", 0); + AddProperty(m_gui_plane, "CRTC_ID", 0); + } + + if (CServiceBroker::GetLogging().CanLogComponent(LOGWINDOWING)) + m_req->LogAtomicRequest(); + + auto ret = drmModeAtomicCommit(m_fd, m_req->Get(), flags | DRM_MODE_ATOMIC_TEST_ONLY, nullptr); + if (ret < 0) + { + CLog::Log(LOGERROR, + "CDRMAtomic::{} - test commit failed: ({}) - falling back to last successful atomic " + "request", + __FUNCTION__, strerror(errno)); + + auto oldRequest = m_atomicRequestQueue.front().get(); + CDRMAtomicRequest::LogAtomicDiff(m_req, oldRequest); + m_req = oldRequest; + + // update the old atomic request with the new fb id to avoid tearing + if (rendered) + AddProperty(m_gui_plane, "FB_ID", fb_id); + } + + ret = drmModeAtomicCommit(m_fd, m_req->Get(), flags, nullptr); + if (ret < 0) + { + CLog::Log(LOGERROR, "CDRMAtomic::{} - atomic commit failed: {}", __FUNCTION__, + strerror(errno)); + } + + if (flags & DRM_MODE_ATOMIC_ALLOW_MODESET) + { + if (drmModeDestroyPropertyBlob(m_fd, blob_id) != 0) + CLog::Log(LOGERROR, "CDRMAtomic::{} - failed to destroy property blob: {}", __FUNCTION__, + strerror(errno)); + } + + if (m_atomicRequestQueue.size() > 1) + m_atomicRequestQueue.pop_back(); + + m_atomicRequestQueue.emplace_back(std::make_unique<CDRMAtomicRequest>()); + m_req = m_atomicRequestQueue.back().get(); +} + +void CDRMAtomic::FlipPage(struct gbm_bo *bo, bool rendered, bool videoLayer) +{ + struct drm_fb *drm_fb = nullptr; + + if (rendered) + { + if (videoLayer) + m_gui_plane->SetFormat(CDRMUtils::FourCCWithAlpha(m_gui_plane->GetFormat())); + else + m_gui_plane->SetFormat(CDRMUtils::FourCCWithoutAlpha(m_gui_plane->GetFormat())); + + drm_fb = CDRMUtils::DrmFbGetFromBo(bo); + if (!drm_fb) + { + CLog::Log(LOGERROR, "CDRMAtomic::{} - Failed to get a new FBO", __FUNCTION__); + return; + } + } + + uint32_t flags = 0; + + if (m_need_modeset) + { + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + m_need_modeset = false; + CLog::Log(LOGDEBUG, "CDRMAtomic::{} - Execute modeset at next commit", __FUNCTION__); + } + + DrmAtomicCommit(!drm_fb ? 0 : drm_fb->fb_id, flags, rendered, videoLayer); +} + +bool CDRMAtomic::InitDrm() +{ + if (!CDRMUtils::OpenDrm(true)) + return false; + + auto ret = drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1); + if (ret) + { + CLog::Log(LOGERROR, "CDRMAtomic::{} - no atomic modesetting support: {}", __FUNCTION__, + strerror(errno)); + return false; + } + + m_atomicRequestQueue.emplace_back(std::make_unique<CDRMAtomicRequest>()); + m_req = m_atomicRequestQueue.back().get(); + + if (!CDRMUtils::InitDrm()) + return false; + + for (auto& plane : m_planes) + { + AddProperty(plane.get(), "FB_ID", 0); + AddProperty(plane.get(), "CRTC_ID", 0); + } + + CLog::Log(LOGDEBUG, "CDRMAtomic::{} - initialized atomic DRM", __FUNCTION__); + + //! @todo: disabled until upstream kernel changes are merged + // if (m_gui_plane->SupportsProperty("SCALING_FILTER")) + // { + // const std::shared_ptr<CSettings> settings = + // CServiceBroker::GetSettingsComponent()->GetSettings(); + // settings->GetSetting(SETTING_VIDEOSCREEN_HW_SCALING_FILTER)->SetVisible(true); + // } + + return true; +} + +void CDRMAtomic::DestroyDrm() +{ + CDRMUtils::DestroyDrm(); +} + +bool CDRMAtomic::SetVideoMode(const RESOLUTION_INFO& res, struct gbm_bo *bo) +{ + m_need_modeset = true; + + return true; +} + +bool CDRMAtomic::SetActive(bool active) +{ + m_need_modeset = true; + m_active = active; + + return true; +} + +bool CDRMAtomic::AddProperty(CDRMObject* object, const char* name, uint64_t value) +{ + return m_req->AddProperty(object, name, value); +} + +bool CDRMAtomic::DisplayHardwareScalingEnabled() +{ + auto settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + + if (settings && settings->GetBool(SETTING_VIDEOSCREEN_HW_SCALING_FILTER)) + return true; + + return false; +} + +CDRMAtomic::CDRMAtomicRequest::CDRMAtomicRequest() : m_atomicRequest(drmModeAtomicAlloc()) +{ +} + +bool CDRMAtomic::CDRMAtomicRequest::AddProperty(CDRMObject* object, const char* name, uint64_t value) +{ + uint32_t propertyId = object->GetPropertyId(name); + if (propertyId == 0) + return false; + + int ret = drmModeAtomicAddProperty(m_atomicRequest.get(), object->GetId(), propertyId, value); + if (ret < 0) + return false; + + m_atomicRequestItems[object][propertyId] = value; + return true; +} + +void CDRMAtomic::CDRMAtomicRequest::LogAtomicDiff(CDRMAtomicRequest* current, + CDRMAtomicRequest* old) +{ + std::map<CDRMObject*, std::map<uint32_t, uint64_t>> atomicDiff; + + for (const auto& object : current->m_atomicRequestItems) + { + auto sameObject = old->m_atomicRequestItems.find(object.first); + if (sameObject != old->m_atomicRequestItems.end()) + { + std::map<uint32_t, uint64_t> propertyDiff; + + std::set_difference(current->m_atomicRequestItems[object.first].begin(), + current->m_atomicRequestItems[object.first].end(), + old->m_atomicRequestItems[object.first].begin(), + old->m_atomicRequestItems[object.first].end(), + std::inserter(propertyDiff, propertyDiff.begin())); + + atomicDiff[object.first] = propertyDiff; + } + else + { + atomicDiff[object.first] = current->m_atomicRequestItems[object.first]; + } + } + + CLog::Log(LOGDEBUG, "CDRMAtomicRequest::{} - DRM Atomic Request Diff:", __FUNCTION__); + + LogAtomicRequest(LOGERROR, atomicDiff); +} + +void CDRMAtomic::CDRMAtomicRequest::LogAtomicRequest() +{ + CLog::Log(LOGDEBUG, "CDRMAtomicRequest::{} - DRM Atomic Request:", __FUNCTION__); + LogAtomicRequest(LOGDEBUG, m_atomicRequestItems); +} + +void CDRMAtomic::CDRMAtomicRequest::LogAtomicRequest( + uint8_t logLevel, std::map<CDRMObject*, std::map<uint32_t, uint64_t>>& atomicRequestItems) +{ + std::string message; + for (const auto& object : atomicRequestItems) + { + message.append("\nObject: " + object.first->GetTypeName() + + "\tID: " + std::to_string(object.first->GetId())); + for (const auto& property : object.second) + message.append("\n Property: " + object.first->GetPropertyName(property.first) + + "\tID: " + std::to_string(property.first) + + "\tValue: " + std::to_string(property.second)); + } + + CLog::Log(logLevel, "{}", message); +} + +void CDRMAtomic::CDRMAtomicRequest::DrmModeAtomicReqDeleter::operator()(drmModeAtomicReqPtr p) const +{ + drmModeAtomicFree(p); +}; diff --git a/xbmc/windowing/gbm/drm/DRMAtomic.h b/xbmc/windowing/gbm/drm/DRMAtomic.h new file mode 100644 index 0000000..ca2cd9a --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMAtomic.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DRMUtils.h" + +#include <cstdint> +#include <deque> +#include <map> +#include <memory> + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class CDRMAtomic : public CDRMUtils +{ +public: + CDRMAtomic() = default; + ~CDRMAtomic() override = default; + void FlipPage(struct gbm_bo* bo, bool rendered, bool videoLayer) override; + bool SetVideoMode(const RESOLUTION_INFO& res, struct gbm_bo* bo) override; + bool SetActive(bool active) override; + bool InitDrm() override; + void DestroyDrm() override; + bool AddProperty(CDRMObject* object, const char* name, uint64_t value); + + bool DisplayHardwareScalingEnabled(); + +private: + void DrmAtomicCommit(int fb_id, int flags, bool rendered, bool videoLayer); + + bool SetScalingFilter(CDRMObject* object, const char* name, const char* type); + + bool m_need_modeset; + bool m_active = true; + + class CDRMAtomicRequest + { + public: + CDRMAtomicRequest(); + ~CDRMAtomicRequest() = default; + CDRMAtomicRequest(const CDRMAtomicRequest& right) = delete; + + drmModeAtomicReqPtr Get() const { return m_atomicRequest.get(); } + + bool AddProperty(CDRMObject* object, const char* name, uint64_t value); + void LogAtomicRequest(); + + static void LogAtomicDiff(CDRMAtomicRequest* current, CDRMAtomicRequest* old); + + private: + static void LogAtomicRequest( + uint8_t logLevel, std::map<CDRMObject*, std::map<uint32_t, uint64_t>>& atomicRequestItems); + + std::map<CDRMObject*, std::map<uint32_t, uint64_t>> m_atomicRequestItems; + + struct DrmModeAtomicReqDeleter + { + void operator()(drmModeAtomicReqPtr p) const; + }; + + std::unique_ptr<drmModeAtomicReq, DrmModeAtomicReqDeleter> m_atomicRequest; + }; + + CDRMAtomicRequest* m_req = nullptr; + std::deque<std::unique_ptr<CDRMAtomicRequest>> m_atomicRequestQueue; +}; + +} +} +} diff --git a/xbmc/windowing/gbm/drm/DRMConnector.cpp b/xbmc/windowing/gbm/drm/DRMConnector.cpp new file mode 100644 index 0000000..fc76b90 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMConnector.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DRMConnector.h" + +#include "utils/XTimeUtils.h" +#include "utils/log.h" + +#include <map> + +using namespace KODI::WINDOWING::GBM; + +using namespace std::chrono_literals; + +namespace +{ + +std::map<int, std::string> connectorTypeNames = { + {DRM_MODE_CONNECTOR_Unknown, "unknown"}, + {DRM_MODE_CONNECTOR_VGA, "VGA"}, + {DRM_MODE_CONNECTOR_DVII, "DVI-I"}, + {DRM_MODE_CONNECTOR_DVID, "DVI-D"}, + {DRM_MODE_CONNECTOR_DVIA, "DVI-A"}, + {DRM_MODE_CONNECTOR_Composite, "composite"}, + {DRM_MODE_CONNECTOR_SVIDEO, "s-video"}, + {DRM_MODE_CONNECTOR_LVDS, "LVDS"}, + {DRM_MODE_CONNECTOR_Component, "component"}, + {DRM_MODE_CONNECTOR_9PinDIN, "9-pin DIN"}, + {DRM_MODE_CONNECTOR_DisplayPort, "DP"}, + {DRM_MODE_CONNECTOR_HDMIA, "HDMI-A"}, + {DRM_MODE_CONNECTOR_HDMIB, "HDMI-B"}, + {DRM_MODE_CONNECTOR_TV, "TV"}, + {DRM_MODE_CONNECTOR_eDP, "eDP"}, + {DRM_MODE_CONNECTOR_VIRTUAL, "Virtual"}, + {DRM_MODE_CONNECTOR_DSI, "DSI"}, + {DRM_MODE_CONNECTOR_DPI, "DPI"}, +}; + +std::map<drmModeConnection, std::string> connectorStatusNames = { + {DRM_MODE_CONNECTED, "connected"}, + {DRM_MODE_DISCONNECTED, "disconnected"}, + {DRM_MODE_UNKNOWNCONNECTION, "unknown"}, +}; + +} // namespace + +CDRMConnector::CDRMConnector(int fd, uint32_t connector) + : CDRMObject(fd), m_connector(drmModeGetConnector(m_fd, connector)) +{ + if (!m_connector) + throw std::runtime_error("drmModeGetConnector failed: " + std::string{strerror(errno)}); + + if (!GetProperties(m_connector->connector_id, DRM_MODE_OBJECT_CONNECTOR)) + throw std::runtime_error("failed to get properties for connector: " + + std::to_string(m_connector->connector_id)); +} + +bool CDRMConnector::CheckConnector() +{ + unsigned retryCnt = 7; + while (!IsConnected() && retryCnt > 0) + { + CLog::Log(LOGDEBUG, "CDRMConnector::{} - connector is disconnected", __FUNCTION__); + retryCnt--; + KODI::TIME::Sleep(1s); + + m_connector.reset(drmModeGetConnector(m_fd, m_connector->connector_id)); + } + + return m_connector->connection == DRM_MODE_CONNECTED; +} + +std::string CDRMConnector::GetType() +{ + auto typeName = connectorTypeNames.find(m_connector->connector_type); + if (typeName == connectorTypeNames.end()) + return connectorTypeNames[DRM_MODE_CONNECTOR_Unknown]; + + return typeName->second; +} + +std::string CDRMConnector::GetStatus() +{ + auto statusName = connectorStatusNames.find(m_connector->connection); + if (statusName == connectorStatusNames.end()) + return connectorStatusNames[DRM_MODE_UNKNOWNCONNECTION]; + + return statusName->second; +} + +std::string CDRMConnector::GetName() +{ + return GetType() + "-" + std::to_string(m_connector->connector_type_id); +} diff --git a/xbmc/windowing/gbm/drm/DRMConnector.h b/xbmc/windowing/gbm/drm/DRMConnector.h new file mode 100644 index 0000000..5a29222 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMConnector.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DRMObject.h" + +#include <string> + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class CDRMConnector : public CDRMObject +{ +public: + explicit CDRMConnector(int fd, uint32_t connector); + CDRMConnector(const CDRMConnector&) = delete; + CDRMConnector& operator=(const CDRMConnector&) = delete; + ~CDRMConnector() = default; + + std::string GetType(); + std::string GetStatus(); + std::string GetName(); + + uint32_t GetEncoderId() const { return m_connector->encoder_id; } + uint32_t* GetConnectorId() const { return &m_connector->connector_id; } + int GetModesCount() const { return m_connector->count_modes; } + drmModeModeInfoPtr GetModeForIndex(int index) const { return &m_connector->modes[index]; } + + bool IsConnected() { return m_connector->connection == DRM_MODE_CONNECTED; } + bool CheckConnector(); + +private: + struct DrmModeConnectorDeleter + { + void operator()(drmModeConnector* p) { drmModeFreeConnector(p); } + }; + + std::unique_ptr<drmModeConnector, DrmModeConnectorDeleter> m_connector; +}; + +} // namespace GBM +} // namespace WINDOWING +} // namespace KODI diff --git a/xbmc/windowing/gbm/drm/DRMCrtc.cpp b/xbmc/windowing/gbm/drm/DRMCrtc.cpp new file mode 100644 index 0000000..426fd95 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMCrtc.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DRMCrtc.h" + +#include <cstring> +#include <errno.h> +#include <stdexcept> +#include <string> + +using namespace KODI::WINDOWING::GBM; + +CDRMCrtc::CDRMCrtc(int fd, uint32_t crtc) : CDRMObject(fd), m_crtc(drmModeGetCrtc(m_fd, crtc)) +{ + if (!m_crtc) + throw std::runtime_error("drmModeGetCrtc failed: " + std::string{strerror(errno)}); + + if (!GetProperties(m_crtc->crtc_id, DRM_MODE_OBJECT_CRTC)) + throw std::runtime_error("failed to get properties for crtc: " + + std::to_string(m_crtc->crtc_id)); +} diff --git a/xbmc/windowing/gbm/drm/DRMCrtc.h b/xbmc/windowing/gbm/drm/DRMCrtc.h new file mode 100644 index 0000000..a1aadc2 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMCrtc.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DRMObject.h" + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class CDRMCrtc : public CDRMObject +{ +public: + explicit CDRMCrtc(int fd, uint32_t crtc); + CDRMCrtc(const CDRMCrtc&) = delete; + CDRMCrtc& operator=(const CDRMCrtc&) = delete; + ~CDRMCrtc() = default; + + uint32_t GetCrtcId() const { return m_crtc->crtc_id; } + uint32_t GetBufferId() const { return m_crtc->buffer_id; } + uint32_t GetX() const { return m_crtc->x; } + uint32_t GetY() const { return m_crtc->y; } + drmModeModeInfoPtr GetMode() const { return &m_crtc->mode; } + bool GetModeValid() const { return m_crtc->mode_valid != 0; } + +private: + struct DrmModeCrtcDeleter + { + void operator()(drmModeCrtc* p) { drmModeFreeCrtc(p); } + }; + + std::unique_ptr<drmModeCrtc, DrmModeCrtcDeleter> m_crtc; +}; + +} // namespace GBM +} // namespace WINDOWING +} // namespace KODI diff --git a/xbmc/windowing/gbm/drm/DRMEncoder.cpp b/xbmc/windowing/gbm/drm/DRMEncoder.cpp new file mode 100644 index 0000000..e4289c6 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMEncoder.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DRMEncoder.h" + +#include <cstring> +#include <errno.h> +#include <stdexcept> +#include <string> + +using namespace KODI::WINDOWING::GBM; + +CDRMEncoder::CDRMEncoder(int fd, uint32_t encoder) + : CDRMObject(fd), m_encoder(drmModeGetEncoder(m_fd, encoder)) +{ + if (!m_encoder) + throw std::runtime_error("drmModeGetEncoder failed: " + std::string{strerror(errno)}); +} diff --git a/xbmc/windowing/gbm/drm/DRMEncoder.h b/xbmc/windowing/gbm/drm/DRMEncoder.h new file mode 100644 index 0000000..aceb276 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMEncoder.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DRMObject.h" + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class CDRMEncoder : public CDRMObject +{ +public: + explicit CDRMEncoder(int fd, uint32_t encoder); + CDRMEncoder(const CDRMEncoder&) = delete; + CDRMEncoder& operator=(const CDRMEncoder&) = delete; + ~CDRMEncoder() = default; + + uint32_t GetEncoderId() const { return m_encoder->encoder_id; } + uint32_t GetCrtcId() const { return m_encoder->crtc_id; } + uint32_t GetPossibleCrtcs() const { return m_encoder->possible_crtcs; } + +private: + struct DrmModeEncoderDeleter + { + void operator()(drmModeEncoder* p) { drmModeFreeEncoder(p); } + }; + + std::unique_ptr<drmModeEncoder, DrmModeEncoderDeleter> m_encoder; +}; + +} // namespace GBM +} // namespace WINDOWING +} // namespace KODI diff --git a/xbmc/windowing/gbm/drm/DRMLegacy.cpp b/xbmc/windowing/gbm/drm/DRMLegacy.cpp new file mode 100644 index 0000000..418d067 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMLegacy.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DRMLegacy.h" + +#include "guilib/gui3d.h" +#include "settings/Settings.h" +#include "utils/log.h" + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <drm_mode.h> +#include <fcntl.h> +#include <poll.h> +#include <unistd.h> + +using namespace KODI::WINDOWING::GBM; + +static int flip_happening = 0; + +bool CDRMLegacy::SetVideoMode(const RESOLUTION_INFO& res, struct gbm_bo *bo) +{ + struct drm_fb *drm_fb = DrmFbGetFromBo(bo); + + auto ret = drmModeSetCrtc(m_fd, m_crtc->GetCrtcId(), drm_fb->fb_id, 0, 0, + m_connector->GetConnectorId(), 1, m_mode); + + if(ret < 0) + { + CLog::Log(LOGERROR, "CDRMLegacy::{} - failed to set crtc mode: {}x{}{} @ {} Hz", __FUNCTION__, + m_mode->hdisplay, m_mode->vdisplay, + m_mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "", m_mode->vrefresh); + + return false; + } + + CLog::Log(LOGDEBUG, "CDRMLegacy::{} - set crtc mode: {}x{}{} @ {} Hz", __FUNCTION__, + m_mode->hdisplay, m_mode->vdisplay, m_mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "", + m_mode->vrefresh); + + return true; +} + +void CDRMLegacy::PageFlipHandler(int fd, unsigned int frame, unsigned int sec, + unsigned int usec, void *data) +{ + (void) fd, (void) frame, (void) sec, (void) usec; + + int *flip_happening = static_cast<int *>(data); + *flip_happening = 0; +} + +bool CDRMLegacy::WaitingForFlip() +{ + if (!flip_happening) + return false; + + struct pollfd drm_fds = + { + m_fd, + POLLIN, + 0, + }; + + drmEventContext drm_evctx{}; + drm_evctx.version = DRM_EVENT_CONTEXT_VERSION; + drm_evctx.page_flip_handler = PageFlipHandler; + + while(flip_happening) + { + auto ret = poll(&drm_fds, 1, -1); + + if(ret < 0) + return true; + + if(drm_fds.revents & (POLLHUP | POLLERR)) + return true; + + if(drm_fds.revents & POLLIN) + drmHandleEvent(m_fd, &drm_evctx); + } + + return false; +} + +bool CDRMLegacy::QueueFlip(struct gbm_bo *bo) +{ + struct drm_fb *drm_fb = DrmFbGetFromBo(bo); + + auto ret = drmModePageFlip(m_fd, m_crtc->GetCrtcId(), drm_fb->fb_id, DRM_MODE_PAGE_FLIP_EVENT, + &flip_happening); + + if(ret) + { + CLog::Log(LOGDEBUG, "CDRMLegacy::{} - failed to queue DRM page flip", __FUNCTION__); + return false; + } + + return true; +} + +void CDRMLegacy::FlipPage(struct gbm_bo *bo, bool rendered, bool videoLayer) +{ + if (rendered || videoLayer) + { + flip_happening = QueueFlip(bo); + WaitingForFlip(); + } +} + +bool CDRMLegacy::InitDrm() +{ + if (!CDRMUtils::OpenDrm(true)) + return false; + + if (!CDRMUtils::InitDrm()) + return false; + + CLog::Log(LOGDEBUG, "CDRMLegacy::{} - initialized legacy DRM", __FUNCTION__); + return true; +} + +bool CDRMLegacy::SetActive(bool active) +{ + if (!m_connector->SetProperty("DPMS", active ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF)) + { + CLog::Log(LOGDEBUG, "CDRMLegacy::{} - failed to set DPMS property", __FUNCTION__); + return false; + } + + return true; +} diff --git a/xbmc/windowing/gbm/drm/DRMLegacy.h b/xbmc/windowing/gbm/drm/DRMLegacy.h new file mode 100644 index 0000000..2b7ff45 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMLegacy.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DRMUtils.h" + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class CDRMLegacy : public CDRMUtils +{ +public: + CDRMLegacy() = default; + ~CDRMLegacy() override = default; + void FlipPage(struct gbm_bo* bo, bool rendered, bool videoLayer) override; + bool SetVideoMode(const RESOLUTION_INFO& res, struct gbm_bo* bo) override; + bool SetActive(bool active) override; + bool InitDrm() override; + +private: + bool WaitingForFlip(); + bool QueueFlip(struct gbm_bo *bo); + static void PageFlipHandler(int fd, unsigned int frame, unsigned int sec, + unsigned int usec, void *data); +}; + +} +} +} diff --git a/xbmc/windowing/gbm/drm/DRMObject.cpp b/xbmc/windowing/gbm/drm/DRMObject.cpp new file mode 100644 index 0000000..599bb61 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMObject.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DRMObject.h" + +#include "utils/log.h" + +#include <algorithm> +#include <array> + +using namespace KODI::WINDOWING::GBM; + +namespace +{ + +constexpr std::array<std::pair<uint32_t, const char*>, 8> DrmModeObjectTypes = { + {{DRM_MODE_OBJECT_CRTC, "crtc"}, + {DRM_MODE_OBJECT_CONNECTOR, "connector"}, + {DRM_MODE_OBJECT_ENCODER, "encoder"}, + {DRM_MODE_OBJECT_MODE, "mode"}, + {DRM_MODE_OBJECT_PROPERTY, "property"}, + {DRM_MODE_OBJECT_FB, "framebuffer"}, + {DRM_MODE_OBJECT_BLOB, "blob"}, + {DRM_MODE_OBJECT_PLANE, "plane"}}}; +} + +CDRMObject::CDRMObject(int fd) : m_fd(fd) +{ +} + +std::string CDRMObject::GetTypeName() const +{ + auto name = std::find_if(DrmModeObjectTypes.begin(), DrmModeObjectTypes.end(), + [this](const auto& p) { return p.first == m_type; }); + if (name != DrmModeObjectTypes.end()) + return name->second; + + return "invalid type"; +} + +std::string CDRMObject::GetPropertyName(uint32_t propertyId) const +{ + auto prop = std::find_if(m_propsInfo.begin(), m_propsInfo.end(), + [&propertyId](const auto& p) { return p->prop_id == propertyId; }); + if (prop != m_propsInfo.end()) + return prop->get()->name; + + return "invalid property"; +} + +uint32_t CDRMObject::GetPropertyId(const std::string& name) const +{ + auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(), + [&name](const auto& prop) { return prop->name == name; }); + + if (property != m_propsInfo.end()) + return property->get()->prop_id; + + return 0; +} + +bool CDRMObject::GetProperties(uint32_t id, uint32_t type) +{ + m_props.reset(drmModeObjectGetProperties(m_fd, id, type)); + if (!m_props) + return false; + + m_id = id; + m_type = type; + + for (uint32_t i = 0; i < m_props->count_props; i++) + m_propsInfo.emplace_back(std::unique_ptr<drmModePropertyRes, DrmModePropertyResDeleter>( + drmModeGetProperty(m_fd, m_props->props[i]))); + + return true; +} + +//! @todo: improve with c++17 +std::tuple<bool, uint64_t> CDRMObject::GetPropertyValue(const std::string& name, + const std::string& valueName) const +{ + auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(), + [&name](const auto& prop) { return prop->name == name; }); + + if (property == m_propsInfo.end()) + return std::make_tuple(false, 0); + + auto prop = property->get(); + + if (!static_cast<bool>(drm_property_type_is(prop, DRM_MODE_PROP_ENUM))) + return std::make_tuple(false, 0); + + for (int j = 0; j < prop->count_enums; j++) + { + if (prop->enums[j].name != valueName) + continue; + + return std::make_tuple(true, prop->enums[j].value); + } + + return std::make_tuple(false, 0); +} + +bool CDRMObject::SetProperty(const std::string& name, uint64_t value) +{ + auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(), + [&name](const auto& prop) { return prop->name == name; }); + + if (property != m_propsInfo.end()) + { + int ret = drmModeObjectSetProperty(m_fd, m_id, m_type, property->get()->prop_id, value); + if (ret == 0) + return true; + } + + return false; +} + +bool CDRMObject::SupportsProperty(const std::string& name) +{ + auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(), + [&name](const auto& prop) { return prop->name == name; }); + + if (property != m_propsInfo.end()) + return true; + + return false; +} diff --git a/xbmc/windowing/gbm/drm/DRMObject.h b/xbmc/windowing/gbm/drm/DRMObject.h new file mode 100644 index 0000000..e2ae326 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMObject.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <vector> + +#include <xf86drmMode.h> + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class CDRMObject +{ +public: + CDRMObject(const CDRMObject&) = delete; + CDRMObject& operator=(const CDRMObject&) = delete; + virtual ~CDRMObject() = default; + + std::string GetTypeName() const; + std::string GetPropertyName(uint32_t propertyId) const; + + uint32_t GetId() const { return m_id; } + uint32_t GetPropertyId(const std::string& name) const; + std::tuple<bool, uint64_t> GetPropertyValue(const std::string& name, + const std::string& valueName) const; + + bool SetProperty(const std::string& name, uint64_t value); + bool SupportsProperty(const std::string& name); + +protected: + explicit CDRMObject(int fd); + + bool GetProperties(uint32_t id, uint32_t type); + + struct DrmModeObjectPropertiesDeleter + { + void operator()(drmModeObjectProperties* p) { drmModeFreeObjectProperties(p); } + }; + + std::unique_ptr<drmModeObjectProperties, DrmModeObjectPropertiesDeleter> m_props; + + struct DrmModePropertyResDeleter + { + void operator()(drmModePropertyRes* p) { drmModeFreeProperty(p); } + }; + + std::vector<std::unique_ptr<drmModePropertyRes, DrmModePropertyResDeleter>> m_propsInfo; + + int m_fd{-1}; + +private: + uint32_t m_id{0}; + uint32_t m_type{0}; +}; + +} // namespace GBM +} // namespace WINDOWING +} // namespace KODI diff --git a/xbmc/windowing/gbm/drm/DRMPlane.cpp b/xbmc/windowing/gbm/drm/DRMPlane.cpp new file mode 100644 index 0000000..90b5660 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMPlane.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DRMPlane.h" + +#include "DRMUtils.h" +#include "utils/DRMHelpers.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +using namespace KODI::WINDOWING::GBM; + +CDRMPlane::CDRMPlane(int fd, uint32_t plane) : CDRMObject(fd), m_plane(drmModeGetPlane(m_fd, plane)) +{ + if (!m_plane) + throw std::runtime_error("drmModeGetPlane failed: " + std::string{strerror(errno)}); + + if (!GetProperties(m_plane->plane_id, DRM_MODE_OBJECT_PLANE)) + throw std::runtime_error("failed to get properties for plane: " + + std::to_string(m_plane->plane_id)); +} + +bool CDRMPlane::SupportsFormat(uint32_t format) +{ + for (uint32_t i = 0; i < m_plane->count_formats; i++) + if (m_plane->formats[i] == format) + return true; + + return false; +} + +bool CDRMPlane::SupportsFormatAndModifier(uint32_t format, uint64_t modifier) +{ + /* + * Some broadcom modifiers have parameters encoded which need to be + * masked out before comparing with reported modifiers. + */ + if (modifier >> 56 == DRM_FORMAT_MOD_VENDOR_BROADCOM) + modifier = fourcc_mod_broadcom_mod(modifier); + + if (modifier == DRM_FORMAT_MOD_LINEAR) + { + if (!SupportsFormat(format)) + { + CLog::Log(LOGDEBUG, "CDRMPlane::{} - format not supported: {}", __FUNCTION__, + DRMHELPERS::FourCCToString(format)); + return false; + } + } + else + { + auto formatModifiers = &m_modifiers_map[format]; + if (formatModifiers->empty()) + { + CLog::Log(LOGDEBUG, "CDRMPlane::{} - format not supported: {}", __FUNCTION__, + DRMHELPERS::FourCCToString(format)); + return false; + } + + auto formatModifier = std::find(formatModifiers->begin(), formatModifiers->end(), modifier); + if (formatModifier == formatModifiers->end()) + { + CLog::Log(LOGDEBUG, "CDRMPlane::{} - modifier ({}) not supported for format ({})", + __FUNCTION__, DRMHELPERS::ModifierToString(modifier), + DRMHELPERS::FourCCToString(format)); + return false; + } + } + + CLog::Log(LOGDEBUG, "CDRMPlane::{} - found plane format ({}) and modifier ({})", __FUNCTION__, + DRMHELPERS::FourCCToString(format), DRMHELPERS::ModifierToString(modifier)); + + return true; +} + +void CDRMPlane::FindModifiers() +{ + auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(), [](auto& prop) { + return StringUtils::EqualsNoCase(prop->name, "IN_FORMATS"); + }); + + uint64_t blob_id = 0; + if (property != m_propsInfo.end()) + blob_id = m_props->prop_values[std::distance(m_propsInfo.begin(), property)]; + + if (blob_id == 0) + return; + + drmModePropertyBlobPtr blob = drmModeGetPropertyBlob(m_fd, blob_id); + if (!blob) + return; + + drm_format_modifier_blob* header = static_cast<drm_format_modifier_blob*>(blob->data); + uint32_t* formats = + reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(header) + header->formats_offset); + drm_format_modifier* mod = reinterpret_cast<drm_format_modifier*>( + reinterpret_cast<char*>(header) + header->modifiers_offset); + + for (uint32_t i = 0; i < header->count_formats; i++) + { + std::vector<uint64_t> modifiers; + for (uint32_t j = 0; j < header->count_modifiers; j++) + { + if (mod[j].formats & 1ULL << i) + modifiers.emplace_back(mod[j].modifier); + } + + m_modifiers_map.emplace(formats[i], modifiers); + } + + if (blob) + drmModeFreePropertyBlob(blob); +} diff --git a/xbmc/windowing/gbm/drm/DRMPlane.h b/xbmc/windowing/gbm/drm/DRMPlane.h new file mode 100644 index 0000000..e077fc3 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMPlane.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DRMObject.h" + +#include <map> + +#include <drm_fourcc.h> + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class CDRMPlane : public CDRMObject +{ +public: + explicit CDRMPlane(int fd, uint32_t plane); + CDRMPlane(const CDRMPlane&) = delete; + CDRMPlane& operator=(const CDRMPlane&) = delete; + ~CDRMPlane() = default; + + uint32_t GetPlaneId() const { return m_plane->plane_id; } + uint32_t GetPossibleCrtcs() const { return m_plane->possible_crtcs; } + + void FindModifiers(); + + void SetFormat(const uint32_t newFormat) { m_format = newFormat; } + uint32_t GetFormat() const { return m_format; } + std::vector<uint64_t>& GetModifiersForFormat(uint32_t format) { return m_modifiers_map[format]; } + + bool SupportsFormat(uint32_t format); + bool SupportsFormatAndModifier(uint32_t format, uint64_t modifier); + +private: + struct DrmModePlaneDeleter + { + void operator()(drmModePlane* p) { drmModeFreePlane(p); } + }; + + std::unique_ptr<drmModePlane, DrmModePlaneDeleter> m_plane; + + std::map<uint32_t, std::vector<uint64_t>> m_modifiers_map; + uint32_t m_format{DRM_FORMAT_XRGB8888}; +}; + +} // namespace GBM +} // namespace WINDOWING +} // namespace KODI diff --git a/xbmc/windowing/gbm/drm/DRMUtils.cpp b/xbmc/windowing/gbm/drm/DRMUtils.cpp new file mode 100644 index 0000000..6b61403 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMUtils.cpp @@ -0,0 +1,740 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DRMUtils.h" + +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/DRMHelpers.h" +#include "utils/StringUtils.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" + +#include "PlatformDefs.h" + +using namespace KODI::WINDOWING::GBM; + +namespace +{ +const std::string SETTING_VIDEOSCREEN_LIMITGUISIZE = "videoscreen.limitguisize"; + +void DrmFbDestroyCallback(gbm_bo* bo, void* data) +{ + drm_fb* fb = static_cast<drm_fb*>(data); + + if (fb->fb_id > 0) + { + CLog::Log(LOGDEBUG, "CDRMUtils::{} - removing framebuffer: {}", __FUNCTION__, fb->fb_id); + int drm_fd = gbm_device_get_fd(gbm_bo_get_device(bo)); + drmModeRmFB(drm_fd, fb->fb_id); + } + + delete fb; +} +} + +CDRMUtils::~CDRMUtils() +{ + DestroyDrm(); +} + +bool CDRMUtils::SetMode(const RESOLUTION_INFO& res) +{ + if (!m_connector->CheckConnector()) + return false; + + m_mode = m_connector->GetModeForIndex(std::atoi(res.strId.c_str())); + m_width = res.iWidth; + m_height = res.iHeight; + + CLog::Log(LOGDEBUG, "CDRMUtils::{} - found crtc mode: {}x{}{} @ {} Hz", __FUNCTION__, + m_mode->hdisplay, m_mode->vdisplay, m_mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "", + m_mode->vrefresh); + + return true; +} + +drm_fb * CDRMUtils::DrmFbGetFromBo(struct gbm_bo *bo) +{ + { + struct drm_fb *fb = static_cast<drm_fb *>(gbm_bo_get_user_data(bo)); + if(fb) + { + if (m_gui_plane->GetFormat() == fb->format) + return fb; + else + DrmFbDestroyCallback(bo, gbm_bo_get_user_data(bo)); + } + } + + struct drm_fb *fb = new drm_fb; + fb->bo = bo; + fb->format = m_gui_plane->GetFormat(); + + uint32_t width, + height, + handles[4] = {0}, + strides[4] = {0}, + offsets[4] = {0}; + + uint64_t modifiers[4] = {0}; + + width = gbm_bo_get_width(bo); + height = gbm_bo_get_height(bo); + +#if defined(HAS_GBM_MODIFIERS) + for (int i = 0; i < gbm_bo_get_plane_count(bo); i++) + { + handles[i] = gbm_bo_get_handle_for_plane(bo, i).u32; + strides[i] = gbm_bo_get_stride_for_plane(bo, i); + offsets[i] = gbm_bo_get_offset(bo, i); + modifiers[i] = gbm_bo_get_modifier(bo); + } +#else + handles[0] = gbm_bo_get_handle(bo).u32; + strides[0] = gbm_bo_get_stride(bo); + memset(offsets, 0, 16); +#endif + + uint32_t flags = 0; + + if (modifiers[0] && modifiers[0] != DRM_FORMAT_MOD_INVALID) + { + flags |= DRM_MODE_FB_MODIFIERS; + CLog::Log(LOGDEBUG, "CDRMUtils::{} - using modifier: {}", __FUNCTION__, + DRMHELPERS::ModifierToString(modifiers[0])); + } + + int ret = drmModeAddFB2WithModifiers(m_fd, + width, + height, + fb->format, + handles, + strides, + offsets, + modifiers, + &fb->fb_id, + flags); + + if(ret < 0) + { + ret = drmModeAddFB2(m_fd, + width, + height, + fb->format, + handles, + strides, + offsets, + &fb->fb_id, + flags); + + if (ret < 0) + { + delete (fb); + CLog::Log(LOGDEBUG, "CDRMUtils::{} - failed to add framebuffer: {} ({})", __FUNCTION__, strerror(errno), errno); + return nullptr; + } + } + + gbm_bo_set_user_data(bo, fb, DrmFbDestroyCallback); + + return fb; +} + +bool CDRMUtils::FindPreferredMode() +{ + if (m_mode) + return true; + + for (int i = 0, area = 0; i < m_connector->GetModesCount(); i++) + { + drmModeModeInfo* current_mode = m_connector->GetModeForIndex(i); + + if(current_mode->type & DRM_MODE_TYPE_PREFERRED) + { + m_mode = current_mode; + CLog::Log(LOGDEBUG, "CDRMUtils::{} - found preferred mode: {}x{}{} @ {} Hz", __FUNCTION__, + m_mode->hdisplay, m_mode->vdisplay, + m_mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "", m_mode->vrefresh); + break; + } + + auto current_area = current_mode->hdisplay * current_mode->vdisplay; + if (current_area > area) + { + m_mode = current_mode; + area = current_area; + } + } + + if(!m_mode) + { + CLog::Log(LOGDEBUG, "CDRMUtils::{} - failed to find preferred mode", __FUNCTION__); + return false; + } + + return true; +} + +bool CDRMUtils::FindPlanes() +{ + for (size_t i = 0; i < m_crtcs.size(); i++) + { + if (!(m_encoder->GetPossibleCrtcs() & (1 << i))) + continue; + + auto videoPlane = std::find_if(m_planes.begin(), m_planes.end(), [&i](auto& plane) { + if (plane->GetPossibleCrtcs() & (1 << i)) + { + return plane->SupportsFormat(DRM_FORMAT_NV12); + } + return false; + }); + + uint32_t videoPlaneId{0}; + + if (videoPlane != m_planes.end()) + videoPlaneId = videoPlane->get()->GetPlaneId(); + + auto guiPlane = + std::find_if(m_planes.begin(), m_planes.end(), [&i, &videoPlaneId](auto& plane) { + if (plane->GetPossibleCrtcs() & (1 << i)) + { + return (plane->GetPlaneId() != videoPlaneId && + (videoPlaneId == 0 || plane->SupportsFormat(DRM_FORMAT_ARGB8888)) && + (plane->SupportsFormat(DRM_FORMAT_XRGB2101010) || + plane->SupportsFormat(DRM_FORMAT_XRGB8888))); + } + return false; + }); + + if (videoPlane != m_planes.end() && guiPlane != m_planes.end()) + { + m_crtc = m_crtcs[i].get(); + m_video_plane = videoPlane->get(); + m_gui_plane = guiPlane->get(); + break; + } + + if (guiPlane != m_planes.end()) + { + if (!m_crtc && m_encoder->GetCrtcId() == m_crtcs[i]->GetCrtcId()) + { + m_crtc = m_crtcs[i].get(); + m_gui_plane = guiPlane->get(); + m_video_plane = nullptr; + } + } + } + + CLog::Log(LOGINFO, "CDRMUtils::{} - using crtc: {}", __FUNCTION__, m_crtc->GetCrtcId()); + + // video plane may not be available + if (m_video_plane) + CLog::Log(LOGDEBUG, "CDRMUtils::{} - using video plane {}", __FUNCTION__, + m_video_plane->GetPlaneId()); + + if (m_gui_plane->SupportsFormat(DRM_FORMAT_XRGB2101010)) + { + m_gui_plane->SetFormat(DRM_FORMAT_XRGB2101010); + CLog::Log(LOGDEBUG, "CDRMUtils::{} - using 10bit gui plane {}", __FUNCTION__, + m_gui_plane->GetPlaneId()); + } + else + { + m_gui_plane->SetFormat(DRM_FORMAT_XRGB8888); + CLog::Log(LOGDEBUG, "CDRMUtils::{} - using gui plane {}", __FUNCTION__, + m_gui_plane->GetPlaneId()); + } + + return true; +} + +void CDRMUtils::PrintDrmDeviceInfo(drmDevicePtr device) +{ + std::string message; + + // clang-format off + message.append(fmt::format("CDRMUtils::{} - DRM Device Info:", __FUNCTION__)); + message.append(fmt::format("\n available_nodes: {:#04x}", device->available_nodes)); + message.append("\n nodes:"); + + for (int i = 0; i < DRM_NODE_MAX; i++) + { + if (device->available_nodes & 1 << i) + message.append(fmt::format("\n nodes[{}]: {}", i, device->nodes[i])); + } + + message.append(fmt::format("\n bustype: {:#04x}", device->bustype)); + + if (device->bustype == DRM_BUS_PCI) + { + message.append("\n pci:"); + message.append(fmt::format("\n domain: {:#04x}", device->businfo.pci->domain)); + message.append(fmt::format("\n bus: {:#02x}", device->businfo.pci->bus)); + message.append(fmt::format("\n dev: {:#02x}", device->businfo.pci->dev)); + message.append(fmt::format("\n func: {:#1}", device->businfo.pci->func)); + + message.append("\n deviceinfo:"); + message.append("\n pci:"); + message.append(fmt::format("\n vendor_id: {:#04x}", device->deviceinfo.pci->vendor_id)); + message.append(fmt::format("\n device_id: {:#04x}", device->deviceinfo.pci->device_id)); + message.append(fmt::format("\n subvendor_id: {:#04x}", device->deviceinfo.pci->subvendor_id)); + message.append(fmt::format("\n subdevice_id: {:#04x}", device->deviceinfo.pci->subdevice_id)); + } + else if (device->bustype == DRM_BUS_USB) + { + message.append("\n usb:"); + message.append(fmt::format("\n bus: {:#03x}", device->businfo.usb->bus)); + message.append(fmt::format("\n dev: {:#03x}", device->businfo.usb->dev)); + + message.append("\n deviceinfo:"); + message.append("\n usb:"); + message.append(fmt::format("\n vendor: {:#04x}", device->deviceinfo.usb->vendor)); + message.append(fmt::format("\n product: {:#04x}", device->deviceinfo.usb->product)); + } + else if (device->bustype == DRM_BUS_PLATFORM) + { + message.append("\n platform:"); + message.append(fmt::format("\n fullname: {}", device->businfo.platform->fullname)); + } + else if (device->bustype == DRM_BUS_HOST1X) + { + message.append("\n host1x:"); + message.append(fmt::format("\n fullname: {}", device->businfo.host1x->fullname)); + } + else + message.append("\n unhandled bus type"); + // clang-format on + + CLog::Log(LOGDEBUG, "{}", message); +} + +bool CDRMUtils::OpenDrm(bool needConnector) +{ + int numDevices = drmGetDevices2(0, nullptr, 0); + if (numDevices <= 0) + { + CLog::Log(LOGERROR, "CDRMUtils::{} - no drm devices found: ({})", __FUNCTION__, + strerror(errno)); + return false; + } + + CLog::Log(LOGDEBUG, "CDRMUtils::{} - drm devices found: {}", __FUNCTION__, numDevices); + + std::vector<drmDevicePtr> devices(numDevices); + + int ret = drmGetDevices2(0, devices.data(), devices.size()); + if (ret < 0) + { + CLog::Log(LOGERROR, "CDRMUtils::{} - drmGetDevices2 return an error: ({})", __FUNCTION__, + strerror(errno)); + return false; + } + + for (const auto device : devices) + { + if (!(device->available_nodes & 1 << DRM_NODE_PRIMARY)) + continue; + + close(m_fd); + m_fd = open(device->nodes[DRM_NODE_PRIMARY], O_RDWR | O_CLOEXEC); + if (m_fd < 0) + continue; + + if (needConnector) + { + auto resources = drmModeGetResources(m_fd); + if (!resources) + continue; + + m_connectors.clear(); + for (int i = 0; i < resources->count_connectors; i++) + m_connectors.emplace_back(std::make_unique<CDRMConnector>(m_fd, resources->connectors[i])); + + drmModeFreeResources(resources); + + if (!FindConnector()) + continue; + } + + CLog::Log(LOGDEBUG, "CDRMUtils::{} - opened device: {}", __FUNCTION__, + device->nodes[DRM_NODE_PRIMARY]); + + PrintDrmDeviceInfo(device); + + const char* renderPath = drmGetRenderDeviceNameFromFd(m_fd); + + if (!renderPath) + renderPath = drmGetDeviceNameFromFd2(m_fd); + + if (!renderPath) + renderPath = drmGetDeviceNameFromFd(m_fd); + + if (renderPath) + { + m_renderDevicePath = renderPath; + m_renderFd = open(renderPath, O_RDWR | O_CLOEXEC); + if (m_renderFd != 0) + CLog::Log(LOGDEBUG, "CDRMUtils::{} - opened render node: {}", __FUNCTION__, renderPath); + } + + drmFreeDevices(devices.data(), devices.size()); + return true; + } + + drmFreeDevices(devices.data(), devices.size()); + return false; +} + +bool CDRMUtils::InitDrm() +{ + if (m_fd < 0) + return false; + + /* caps need to be set before allocating connectors, encoders, crtcs, and planes */ + int ret = drmSetClientCap(m_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + if (ret) + { + CLog::Log(LOGERROR, "CDRMUtils::{} - failed to set universal planes capability: {}", + __FUNCTION__, strerror(errno)); + return false; + } + + ret = drmSetClientCap(m_fd, DRM_CLIENT_CAP_STEREO_3D, 1); + if (ret) + { + CLog::Log(LOGERROR, "CDRMUtils::{} - failed to set stereo 3d capability: {}", __FUNCTION__, + strerror(errno)); + return false; + } + +#if defined(DRM_CLIENT_CAP_ASPECT_RATIO) + ret = drmSetClientCap(m_fd, DRM_CLIENT_CAP_ASPECT_RATIO, 0); + if (ret != 0) + CLog::Log(LOGERROR, "CDRMUtils::{} - aspect ratio capability is not supported: {}", + __FUNCTION__, strerror(errno)); +#endif + + auto resources = drmModeGetResources(m_fd); + if (!resources) + { + CLog::Log(LOGERROR, "CDRMUtils::{} - failed to get drm resources: {}", __FUNCTION__, strerror(errno)); + return false; + } + + m_connectors.clear(); + for (int i = 0; i < resources->count_connectors; i++) + m_connectors.emplace_back(std::make_unique<CDRMConnector>(m_fd, resources->connectors[i])); + + m_encoders.clear(); + for (int i = 0; i < resources->count_encoders; i++) + m_encoders.emplace_back(std::make_unique<CDRMEncoder>(m_fd, resources->encoders[i])); + + m_crtcs.clear(); + for (int i = 0; i < resources->count_crtcs; i++) + m_crtcs.emplace_back(std::make_unique<CDRMCrtc>(m_fd, resources->crtcs[i])); + + drmModeFreeResources(resources); + + auto planeResources = drmModeGetPlaneResources(m_fd); + if (!planeResources) + { + CLog::Log(LOGERROR, "CDRMUtils::{} - failed to get drm plane resources: {}", __FUNCTION__, strerror(errno)); + return false; + } + + m_planes.clear(); + for (uint32_t i = 0; i < planeResources->count_planes; i++) + { + m_planes.emplace_back(std::make_unique<CDRMPlane>(m_fd, planeResources->planes[i])); + m_planes[i]->FindModifiers(); + } + + drmModeFreePlaneResources(planeResources); + + if (!FindConnector()) + return false; + + if (!FindEncoder()) + return false; + + if (!FindCrtc()) + return false; + + if (!FindPlanes()) + return false; + + if (!FindPreferredMode()) + return false; + + ret = drmSetMaster(m_fd); + if (ret < 0) + { + CLog::Log(LOGDEBUG, + "CDRMUtils::{} - failed to set drm master, will try to authorize instead: {}", + __FUNCTION__, strerror(errno)); + + drm_magic_t magic; + + ret = drmGetMagic(m_fd, &magic); + if (ret < 0) + { + CLog::Log(LOGERROR, "CDRMUtils::{} - failed to get drm magic: {}", __FUNCTION__, + strerror(errno)); + return false; + } + + ret = drmAuthMagic(m_fd, magic); + if (ret < 0) + { + CLog::Log(LOGERROR, "CDRMUtils::{} - failed to authorize drm magic: {}", __FUNCTION__, + strerror(errno)); + return false; + } + + CLog::Log(LOGINFO, "CDRMUtils::{} - successfully authorized drm magic", __FUNCTION__); + } + + return true; +} + +bool CDRMUtils::FindConnector() +{ + auto settingsComponent = CServiceBroker::GetSettingsComponent(); + if (!settingsComponent) + return false; + + auto settings = settingsComponent->GetSettings(); + if (!settings) + return false; + + std::vector<std::unique_ptr<CDRMConnector>>::iterator connector; + + std::string connectorName = settings->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR); + if (connectorName != "Default") + { + connector = std::find_if(m_connectors.begin(), m_connectors.end(), + [&connectorName](auto& connector) + { + return connector->GetEncoderId() > 0 && connector->IsConnected() && + connector->GetName() == connectorName; + }); + } + + if (connector == m_connectors.end()) + { + CLog::Log(LOGDEBUG, "CDRMUtils::{} - failed to find specified connector: {}, trying default", + __FUNCTION__, connectorName); + connectorName = "Default"; + } + + if (connectorName == "Default") + { + connector = std::find_if(m_connectors.begin(), m_connectors.end(), + [](auto& connector) + { return connector->GetEncoderId() > 0 && connector->IsConnected(); }); + } + + if (connector == m_connectors.end()) + { + CLog::Log(LOGDEBUG, "CDRMUtils::{} - failed to find connected connector", __FUNCTION__); + return false; + } + + CLog::Log(LOGINFO, "CDRMUtils::{} - using connector: {}", __FUNCTION__, + connector->get()->GetName()); + + m_connector = connector->get(); + return true; +} + +bool CDRMUtils::FindEncoder() +{ + auto encoder = std::find_if(m_encoders.begin(), m_encoders.end(), [this](auto& encoder) { + return encoder->GetEncoderId() == m_connector->GetEncoderId(); + }); + + if (encoder == m_encoders.end()) + { + CLog::Log(LOGDEBUG, "CDRMUtils::{} - failed to find encoder for connector id: {}", __FUNCTION__, + *m_connector->GetConnectorId()); + return false; + } + + CLog::Log(LOGINFO, "CDRMUtils::{} - using encoder: {}", __FUNCTION__, + encoder->get()->GetEncoderId()); + + m_encoder = encoder->get(); + return true; +} + +bool CDRMUtils::FindCrtc() +{ + for (size_t i = 0; i < m_crtcs.size(); i++) + { + if (m_encoder->GetPossibleCrtcs() & (1 << i)) + { + if (m_crtcs[i]->GetCrtcId() == m_encoder->GetCrtcId()) + { + m_orig_crtc = m_crtcs[i].get(); + if (m_orig_crtc->GetModeValid()) + { + m_mode = m_orig_crtc->GetMode(); + CLog::Log(LOGDEBUG, "CDRMUtils::{} - original crtc mode: {}x{}{} @ {} Hz", __FUNCTION__, + m_mode->hdisplay, m_mode->vdisplay, + m_mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "", m_mode->vrefresh); + } + return true; + } + } + } + + return false; +} + +bool CDRMUtils::RestoreOriginalMode() +{ + if(!m_orig_crtc) + { + return false; + } + + auto ret = drmModeSetCrtc(m_fd, m_orig_crtc->GetCrtcId(), m_orig_crtc->GetBufferId(), + m_orig_crtc->GetX(), m_orig_crtc->GetY(), m_connector->GetConnectorId(), + 1, m_orig_crtc->GetMode()); + + if(ret) + { + CLog::Log(LOGERROR, "CDRMUtils::{} - failed to set original crtc mode", __FUNCTION__); + return false; + } + + CLog::Log(LOGDEBUG, "CDRMUtils::{} - set original crtc mode", __FUNCTION__); + + return true; +} + +void CDRMUtils::DestroyDrm() +{ + RestoreOriginalMode(); + + if (drmAuthMagic(m_fd, 0) == EINVAL) + drmDropMaster(m_fd); + + close(m_renderFd); + close(m_fd); + + m_connector = nullptr; + m_encoder = nullptr; + m_crtc = nullptr; + m_orig_crtc = nullptr; + m_video_plane = nullptr; + m_gui_plane = nullptr; +} + +RESOLUTION_INFO CDRMUtils::GetResolutionInfo(drmModeModeInfoPtr mode) +{ + RESOLUTION_INFO res; + res.iScreenWidth = mode->hdisplay; + res.iScreenHeight = mode->vdisplay; + res.iWidth = res.iScreenWidth; + res.iHeight = res.iScreenHeight; + + int limit = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + SETTING_VIDEOSCREEN_LIMITGUISIZE); + if (limit > 0 && res.iScreenWidth > 1920 && res.iScreenHeight > 1080) + { + switch (limit) + { + case 1: // 720p + res.iWidth = 1280; + res.iHeight = 720; + break; + case 2: // 1080p / 720p (>30hz) + res.iWidth = mode->vrefresh > 30 ? 1280 : 1920; + res.iHeight = mode->vrefresh > 30 ? 720 : 1080; + break; + case 3: // 1080p + res.iWidth = 1920; + res.iHeight = 1080; + break; + case 4: // Unlimited / 1080p (>30hz) + res.iWidth = mode->vrefresh > 30 ? 1920 : res.iScreenWidth; + res.iHeight = mode->vrefresh > 30 ? 1080 : res.iScreenHeight; + break; + } + } + + if (mode->clock % 5 != 0) + res.fRefreshRate = static_cast<float>(mode->vrefresh) * (1000.0f/1001.0f); + else + res.fRefreshRate = mode->vrefresh; + res.iSubtitles = res.iHeight; + res.fPixelRatio = 1.0f; + res.bFullScreen = true; + + if (mode->flags & DRM_MODE_FLAG_3D_MASK) + { + if (mode->flags & DRM_MODE_FLAG_3D_TOP_AND_BOTTOM) + res.dwFlags = D3DPRESENTFLAG_MODE3DTB; + else if (mode->flags & DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF) + res.dwFlags = D3DPRESENTFLAG_MODE3DSBS; + } + else if (mode->flags & DRM_MODE_FLAG_INTERLACE) + res.dwFlags = D3DPRESENTFLAG_INTERLACED; + else + res.dwFlags = D3DPRESENTFLAG_PROGRESSIVE; + + res.strMode = + StringUtils::Format("{}x{}{} @ {:.6f} Hz", res.iScreenWidth, res.iScreenHeight, + res.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "", res.fRefreshRate); + return res; +} + +RESOLUTION_INFO CDRMUtils::GetCurrentMode() +{ + return GetResolutionInfo(m_mode); +} + +std::vector<RESOLUTION_INFO> CDRMUtils::GetModes() +{ + std::vector<RESOLUTION_INFO> resolutions; + resolutions.reserve(m_connector->GetModesCount()); + + for (auto i = 0; i < m_connector->GetModesCount(); i++) + { + RESOLUTION_INFO res = GetResolutionInfo(m_connector->GetModeForIndex(i)); + res.strId = std::to_string(i); + resolutions.push_back(res); + } + + return resolutions; +} + +std::vector<std::string> CDRMUtils::GetConnectedConnectorNames() +{ + std::vector<std::string> connectorNames; + for (const auto& connector : m_connectors) + { + if (connector->IsConnected()) + connectorNames.emplace_back(connector->GetName()); + } + + return connectorNames; +} + +uint32_t CDRMUtils::FourCCWithAlpha(uint32_t fourcc) +{ + return (fourcc & 0xFFFFFF00) | static_cast<uint32_t>('A'); +} + +uint32_t CDRMUtils::FourCCWithoutAlpha(uint32_t fourcc) +{ + return (fourcc & 0xFFFFFF00) | static_cast<uint32_t>('X'); +} diff --git a/xbmc/windowing/gbm/drm/DRMUtils.h b/xbmc/windowing/gbm/drm/DRMUtils.h new file mode 100644 index 0000000..5327e35 --- /dev/null +++ b/xbmc/windowing/gbm/drm/DRMUtils.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DRMConnector.h" +#include "DRMCrtc.h" +#include "DRMEncoder.h" +#include "DRMPlane.h" +#include "windowing/Resolution.h" +#include "windowing/gbm/GBMUtils.h" + +#include <vector> + +#include <gbm.h> +#include <xf86drm.h> + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +struct drm_fb +{ + struct gbm_bo *bo = nullptr; + uint32_t fb_id; + uint32_t format; +}; + +class CDRMUtils +{ +public: + CDRMUtils() = default; + virtual ~CDRMUtils(); + virtual void FlipPage(struct gbm_bo* bo, bool rendered, bool videoLayer) {} + virtual bool SetVideoMode(const RESOLUTION_INFO& res, struct gbm_bo* bo) { return false; } + virtual bool SetActive(bool active) { return false; } + virtual bool InitDrm(); + virtual void DestroyDrm(); + + int GetFileDescriptor() const { return m_fd; } + int GetRenderNodeFileDescriptor() const { return m_renderFd; } + const char* GetRenderDevicePath() const { return m_renderDevicePath; } + CDRMPlane* GetVideoPlane() const { return m_video_plane; } + CDRMPlane* GetGuiPlane() const { return m_gui_plane; } + CDRMCrtc* GetCrtc() const { return m_crtc; } + CDRMConnector* GetConnector() const { return m_connector; } + + std::vector<std::string> GetConnectedConnectorNames(); + + virtual RESOLUTION_INFO GetCurrentMode(); + virtual std::vector<RESOLUTION_INFO> GetModes(); + virtual bool SetMode(const RESOLUTION_INFO& res); + + static uint32_t FourCCWithAlpha(uint32_t fourcc); + static uint32_t FourCCWithoutAlpha(uint32_t fourcc); + +protected: + bool OpenDrm(bool needConnector); + drm_fb* DrmFbGetFromBo(struct gbm_bo *bo); + + int m_fd; + CDRMConnector* m_connector{nullptr}; + CDRMEncoder* m_encoder{nullptr}; + CDRMCrtc* m_crtc{nullptr}; + CDRMCrtc* m_orig_crtc{nullptr}; + CDRMPlane* m_video_plane{nullptr}; + CDRMPlane* m_gui_plane{nullptr}; + drmModeModeInfo *m_mode = nullptr; + + int m_width = 0; + int m_height = 0; + + std::vector<std::unique_ptr<CDRMPlane>> m_planes; + +private: + bool FindConnector(); + bool FindEncoder(); + bool FindCrtc(); + bool FindPlanes(); + bool FindPreferredMode(); + bool RestoreOriginalMode(); + RESOLUTION_INFO GetResolutionInfo(drmModeModeInfoPtr mode); + void PrintDrmDeviceInfo(drmDevicePtr device); + + int m_renderFd; + const char* m_renderDevicePath{nullptr}; + + std::vector<std::unique_ptr<CDRMConnector>> m_connectors; + std::vector<std::unique_ptr<CDRMEncoder>> m_encoders; + std::vector<std::unique_ptr<CDRMCrtc>> m_crtcs; +}; + +} +} +} diff --git a/xbmc/windowing/gbm/drm/OffScreenModeSetting.cpp b/xbmc/windowing/gbm/drm/OffScreenModeSetting.cpp new file mode 100644 index 0000000..dc69fdc --- /dev/null +++ b/xbmc/windowing/gbm/drm/OffScreenModeSetting.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "OffScreenModeSetting.h" + +#include "utils/log.h" + +using namespace KODI::WINDOWING::GBM; + +namespace +{ +constexpr int DISPLAY_WIDTH = 1280; +constexpr int DISPLAY_HEIGHT = 720; +constexpr float DISPLAY_REFRESH = 50.0f; +} // namespace + +bool COffScreenModeSetting::InitDrm() +{ + if (!CDRMUtils::OpenDrm(false)) + return false; + + CLog::Log(LOGDEBUG, "COffScreenModeSetting::{} - initialized offscreen DRM", __FUNCTION__); + return true; +} + +std::vector<RESOLUTION_INFO> COffScreenModeSetting::GetModes() +{ + std::vector<RESOLUTION_INFO> resolutions; + resolutions.push_back(GetCurrentMode()); + return resolutions; +} + +RESOLUTION_INFO COffScreenModeSetting::GetCurrentMode() +{ + RESOLUTION_INFO res; + res.iScreenWidth = DISPLAY_WIDTH; + res.iWidth = DISPLAY_WIDTH; + res.iScreenHeight = DISPLAY_HEIGHT; + res.iHeight = DISPLAY_HEIGHT; + res.fRefreshRate = DISPLAY_REFRESH; + res.iSubtitles = res.iHeight; + res.fPixelRatio = 1.0f; + res.bFullScreen = true; + res.strId = "0"; + + return res; +} diff --git a/xbmc/windowing/gbm/drm/OffScreenModeSetting.h b/xbmc/windowing/gbm/drm/OffScreenModeSetting.h new file mode 100644 index 0000000..4270d4e --- /dev/null +++ b/xbmc/windowing/gbm/drm/OffScreenModeSetting.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DRMUtils.h" + +namespace KODI +{ +namespace WINDOWING +{ +namespace GBM +{ + +class COffScreenModeSetting : public CDRMUtils +{ +public: + COffScreenModeSetting() = default; + ~COffScreenModeSetting() override = default; + void FlipPage(struct gbm_bo *bo, bool rendered, bool videoLayer) override {} + bool SetVideoMode(const RESOLUTION_INFO& res, struct gbm_bo *bo) override { return false; } + bool SetActive(bool active) override { return false; } + bool InitDrm() override; + void DestroyDrm() override {} + + RESOLUTION_INFO GetCurrentMode() override; + std::vector<RESOLUTION_INFO> GetModes() override; + bool SetMode(const RESOLUTION_INFO& res) override { return true; } +}; + +} +} +} |