From 5df65e4be5aaa7384c7e3fb7cca0b58cd19e8d2e Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Wed, 30 Mar 2022 15:36:27 +0200 Subject: [PATCH] backends/drm: refactor egl/gbm render surface into its own class This allows sharing most code with overlay plane layers --- src/backends/drm/CMakeLists.txt | 1 + src/backends/drm/egl_gbm_layer.cpp | 334 +------------------ src/backends/drm/egl_gbm_layer.h | 31 +- src/backends/drm/egl_gbm_layer_surface.cpp | 362 +++++++++++++++++++++ src/backends/drm/egl_gbm_layer_surface.h | 85 +++++ 5 files changed, 465 insertions(+), 348 deletions(-) create mode 100644 src/backends/drm/egl_gbm_layer_surface.cpp create mode 100644 src/backends/drm/egl_gbm_layer_surface.h diff --git a/src/backends/drm/CMakeLists.txt b/src/backends/drm/CMakeLists.txt index f67e29019b..c010f727d9 100644 --- a/src/backends/drm/CMakeLists.txt +++ b/src/backends/drm/CMakeLists.txt @@ -28,6 +28,7 @@ set(DRM_SOURCES placeholderinputeventfilter.cpp virtual_egl_gbm_layer.cpp drm_lease_egl_gbm_layer.cpp + egl_gbm_layer_surface.cpp ) add_library(KWinWaylandDrmBackend MODULE ${DRM_SOURCES}) diff --git a/src/backends/drm/egl_gbm_layer.cpp b/src/backends/drm/egl_gbm_layer.cpp index 03d22f8b17..c9d7b96a70 100644 --- a/src/backends/drm/egl_gbm_layer.cpp +++ b/src/backends/drm/egl_gbm_layer.cpp @@ -9,17 +9,13 @@ #include "egl_gbm_layer.h" #include "drm_abstract_output.h" #include "drm_backend.h" +#include "drm_buffer_gbm.h" #include "drm_gpu.h" #include "drm_output.h" #include "drm_pipeline.h" -#include "dumb_swapchain.h" #include "egl_dmabuf.h" #include "egl_gbm_backend.h" -#include "gbm_surface.h" -#include "kwineglimagetexture.h" -#include "kwineglutils_p.h" #include "logging.h" -#include "shadowbuffer.h" #include "surfaceitem_wayland.h" #include "KWaylandServer/linuxdmabufv1clientbuffer.h" @@ -36,7 +32,7 @@ namespace KWin EglGbmLayer::EglGbmLayer(EglGbmBackend *eglBackend, DrmPipeline *pipeline) : DrmPipelineLayer(pipeline) - , m_eglBackend(eglBackend) + , m_surface(pipeline->gpu(), eglBackend) { connect(eglBackend, &EglGbmBackend::aboutToBeDestroyed, this, &EglGbmLayer::destroyResources); } @@ -48,17 +44,7 @@ EglGbmLayer::~EglGbmLayer() void EglGbmLayer::destroyResources() { - if (m_shadowBuffer || m_oldShadowBuffer) { - if (m_gbmSurface) { - m_gbmSurface->makeContextCurrent(); - } else if (m_oldGbmSurface) { - m_oldGbmSurface->makeContextCurrent(); - } - } - m_shadowBuffer.reset(); - m_oldShadowBuffer.reset(); - m_gbmSurface.reset(); - m_oldGbmSurface.reset(); + m_surface.destroyResources(); } OutputLayerBeginFrameInfo EglGbmLayer::beginFrame() @@ -73,88 +59,20 @@ OutputLayerBeginFrameInfo EglGbmLayer::beginFrame() } m_scanoutCandidate.attemptedThisFrame = false; - // gbm surface - if (doesGbmSurfaceFit(m_gbmSurface.data())) { - m_oldGbmSurface.reset(); - } else { - if (doesGbmSurfaceFit(m_oldGbmSurface.data())) { - m_gbmSurface = m_oldGbmSurface; - } else { - if (!createGbmSurface()) { - return {}; - } - // dmabuf might work with the new surface - m_importMode = MultiGpuImportMode::Dmabuf; - } - } - if (!m_gbmSurface->makeContextCurrent()) { - return {}; - } - auto repaintRegion = m_gbmSurface->repaintRegion(); - - // shadow buffer - if (doesShadowBufferFit(m_shadowBuffer.data())) { - m_oldShadowBuffer.reset(); - } else { - if (doesShadowBufferFit(m_oldShadowBuffer.data())) { - m_shadowBuffer = m_oldShadowBuffer; - } else { - if (m_pipeline->pending.bufferTransformation != m_pipeline->pending.sourceTransformation) { - const auto format = m_eglBackend->gbmFormatForDrmFormat(m_gbmSurface->format()).value(); - m_shadowBuffer = QSharedPointer::create(m_pipeline->sourceSize(), format); - if (!m_shadowBuffer->isComplete()) { - return {}; - } - } else { - m_shadowBuffer.reset(); - } - } - } - - GLFramebuffer::pushFramebuffer(m_gbmSurface->fbo()); - if (m_shadowBuffer) { - // the blit after rendering will completely overwrite the back buffer anyways - repaintRegion = QRegion(); - GLFramebuffer::pushFramebuffer(m_shadowBuffer->fbo()); - } - - return OutputLayerBeginFrameInfo{ - .renderTarget = RenderTarget(GLFramebuffer::currentFramebuffer()), - .repaint = repaintRegion, - }; + return m_surface.startRendering(m_pipeline->bufferSize(), m_pipeline->pending.sourceTransformation, m_pipeline->pending.bufferTransformation, m_pipeline->formats()); } void EglGbmLayer::aboutToStartPainting(const QRegion &damagedRegion) { - if (m_gbmSurface && m_gbmSurface->bufferAge() > 0 && !damagedRegion.isEmpty() && m_eglBackend->supportsPartialUpdate()) { - QVector rects = m_pipeline->output()->regionToRects(damagedRegion); - const bool correct = eglSetDamageRegionKHR(m_eglBackend->eglDisplay(), m_gbmSurface->eglSurface(), rects.data(), rects.count() / 4); - if (!correct) { - qCWarning(KWIN_DRM) << "eglSetDamageRegionKHR failed:" << getEglErrorString(); - } - } + m_surface.aboutToStartPainting(m_pipeline->output(), damagedRegion); } void EglGbmLayer::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { Q_UNUSED(renderedRegion) - if (m_shadowBuffer) { - GLFramebuffer::popFramebuffer(); - // TODO handle m_pipeline->pending.bufferTransformation != Rotate0 - m_shadowBuffer->render(m_pipeline->pending.sourceTransformation); - } - GLFramebuffer::popFramebuffer(); - QSharedPointer buffer; - if (m_pipeline->gpu() == m_eglBackend->gpu()) { - buffer = m_gbmSurface->swapBuffersForDrm(damagedRegion); - } else { - if (m_gbmSurface->swapBuffers(damagedRegion)) { - buffer = importBuffer(); - } - } - if (buffer) { - m_currentBuffer = buffer; - m_currentDamage = damagedRegion; + const auto ret = m_surface.endRendering(m_pipeline->pending.bufferTransformation, damagedRegion); + if (ret.has_value()) { + std::tie(m_currentBuffer, m_currentDamage) = ret.value(); } } @@ -165,19 +83,8 @@ QRegion EglGbmLayer::currentDamage() const QSharedPointer EglGbmLayer::testBuffer() { - if (!m_currentBuffer || !doesGbmSurfaceFit(m_gbmSurface.data())) { - if (doesGbmSurfaceFit(m_oldGbmSurface.data())) { - // re-use old surface and buffer without rendering - m_gbmSurface = m_oldGbmSurface; - if (m_gbmSurface->currentBuffer()) { - m_currentBuffer = m_gbmSurface->currentDrmBuffer(); - return m_currentBuffer; - } - } - if (!renderTestBuffer() && m_importMode == MultiGpuImportMode::DumbBufferXrgb8888) { - // try multi-gpu import again, this time with DRM_FORMAT_XRGB8888 - renderTestBuffer(); - } + if (!m_surface.doesSurfaceFit(m_pipeline->sourceSize(), m_pipeline->formats())) { + renderTestBuffer(); } return m_currentBuffer; } @@ -191,230 +98,15 @@ bool EglGbmLayer::renderTestBuffer() return m_currentBuffer != oldBuffer; } -bool EglGbmLayer::createGbmSurface(uint32_t format, const QVector &modifiers) -{ - static bool modifiersEnvSet = false; - static const bool modifiersEnv = qEnvironmentVariableIntValue("KWIN_DRM_USE_MODIFIERS", &modifiersEnvSet) != 0; - const bool allowModifiers = m_eglBackend->gpu()->addFB2ModifiersSupported() && m_pipeline->gpu()->addFB2ModifiersSupported() - && ((m_eglBackend->gpu()->isNVidia() && !modifiersEnvSet) || (modifiersEnvSet && modifiersEnv)); - - const auto size = m_pipeline->bufferSize(); - const auto config = m_eglBackend->config(format); - if (config == EGL_NO_CONFIG_KHR) { - return false; - } - - QSharedPointer gbmSurface; -#if HAVE_GBM_BO_GET_FD_FOR_PLANE - if (!allowModifiers) { -#else - // modifiers have to be disabled with multi-gpu if gbm_bo_get_fd_for_plane is not available - if (!allowModifiers || m_pipeline->gpu() != m_eglBackend->gpu()) { -#endif - int flags = GBM_BO_USE_RENDERING; - if (m_pipeline->gpu() == m_eglBackend->gpu()) { - flags |= GBM_BO_USE_SCANOUT; - } else { - flags |= GBM_BO_USE_LINEAR; - } - gbmSurface = QSharedPointer::create(m_eglBackend->gpu(), size, format, flags, config); - } else { - gbmSurface = QSharedPointer::create(m_eglBackend->gpu(), size, format, modifiers, config); - if (!gbmSurface->isValid()) { - // the egl / gbm implementation may reject the modifier list from another gpu - // as a fallback use linear, to at least make CPU copy more efficient - const QVector linear = {DRM_FORMAT_MOD_LINEAR}; - gbmSurface = QSharedPointer::create(m_eglBackend->gpu(), size, format, linear, config); - } - } - if (gbmSurface->isValid()) { - m_oldGbmSurface = m_gbmSurface; - m_gbmSurface = gbmSurface; - return true; - } else { - return false; - } -} - -bool EglGbmLayer::createGbmSurface() -{ - const auto formats = m_pipeline->formats(); - QVector sortedFormats; - for (auto it = formats.begin(); it != formats.end(); it++) { - const auto format = m_eglBackend->gbmFormatForDrmFormat(it.key()); - if (format.has_value()) { - sortedFormats << format.value(); - } - } - std::sort(sortedFormats.begin(), sortedFormats.end(), [this](const auto &lhs, const auto &rhs) { - if (lhs.drmFormat == rhs.drmFormat) { - // prefer having an alpha channel - return lhs.alphaSize > rhs.alphaSize; - } else if (m_eglBackend->prefer10bpc() && ((lhs.bpp == 30) != (rhs.bpp == 30))) { - // prefer 10bpc / 30bpp formats - return lhs.bpp == 30 ? true : false; - } else { - // fallback - return lhs.drmFormat < rhs.drmFormat; - } - }); - for (const auto &format : qAsConst(sortedFormats)) { - if (m_importMode == MultiGpuImportMode::DumbBufferXrgb8888 && format.drmFormat != DRM_FORMAT_XRGB8888) { - continue; - } - if (formats.contains(format.drmFormat) && createGbmSurface(format.drmFormat, formats[format.drmFormat])) { - return true; - } - } - qCCritical(KWIN_DRM, "Failed to create a gbm surface!"); - return false; -} - -bool EglGbmLayer::doesGbmSurfaceFit(GbmSurface *surf) const -{ - return surf && surf->size() == m_pipeline->bufferSize() - && m_pipeline->formats().contains(surf->format()) - && (m_importMode != MultiGpuImportMode::DumbBufferXrgb8888 || surf->format() == DRM_FORMAT_XRGB8888) - && (surf->modifiers().isEmpty() || m_pipeline->formats().value(surf->format()) == surf->modifiers()); -} - -bool EglGbmLayer::doesShadowBufferFit(ShadowBuffer *buffer) const -{ - if (m_pipeline->pending.bufferTransformation != m_pipeline->pending.sourceTransformation) { - return buffer && buffer->texture()->size() == m_pipeline->sourceSize() && buffer->drmFormat() == m_gbmSurface->format(); - } else { - return buffer == nullptr; - } -} - -bool EglGbmLayer::doesSwapchainFit(DumbSwapchain *swapchain) const -{ - return swapchain && swapchain->size() == m_pipeline->sourceSize() && swapchain->drmFormat() == m_gbmSurface->format(); -} - QSharedPointer EglGbmLayer::texture() const { if (m_scanoutBuffer) { - return m_scanoutBuffer->createTexture(m_eglBackend->eglDisplay()); - } else if (m_shadowBuffer) { - return m_shadowBuffer->texture(); - } else if (GbmBuffer *gbmBuffer = m_gbmSurface->currentBuffer().data()) { - return gbmBuffer->createTexture(m_eglBackend->eglDisplay()); + return m_scanoutBuffer->createTexture(m_surface.eglBackend()->eglDisplay()); } else { - qCWarning(KWIN_DRM) << "Failed to record frame: No gbm buffer!"; - return nullptr; + return m_surface.texture(); } } -QSharedPointer EglGbmLayer::importBuffer() -{ - if (m_importMode == MultiGpuImportMode::Dmabuf) { - if (const auto buffer = importDmabuf()) { - return buffer; - } else { - // don't bother trying again, it will most likely fail every time - m_importMode = MultiGpuImportMode::DumbBuffer; - } - } - if (const auto buffer = importWithCpu()) { - return buffer; - } else if (m_importMode == MultiGpuImportMode::DumbBuffer) { - m_importMode = MultiGpuImportMode::DumbBufferXrgb8888; - return nullptr; - } - if (m_importMode != MultiGpuImportMode::Failed) { - qCCritical(KWIN_DRM, "All multi gpu imports failed!"); - m_importMode = MultiGpuImportMode::Failed; - } - return nullptr; -} - -QSharedPointer EglGbmLayer::importDmabuf() -{ - const auto bo = m_gbmSurface->currentBuffer()->getBo(); - gbm_bo *importedBuffer; -#if HAVE_GBM_BO_GET_FD_FOR_PLANE - if (gbm_bo_get_handle_for_plane(bo, 0).s32 != -1) { - gbm_import_fd_modifier_data data = { - .width = gbm_bo_get_width(bo), - .height = gbm_bo_get_height(bo), - .format = gbm_bo_get_format(bo), - .num_fds = static_cast(gbm_bo_get_plane_count(bo)), - .fds = {}, - .strides = {}, - .offsets = {}, - .modifier = gbm_bo_get_modifier(bo), - }; - for (uint32_t i = 0; i < data.num_fds; i++) { - data.fds[i] = gbm_bo_get_fd_for_plane(bo, i); - if (data.fds[i] < 0) { - qCWarning(KWIN_DRM, "failed to export gbm_bo plane %d as dma-buf: %s", i, strerror(errno)); - for (uint32_t f = 0; f < i; f++) { - close(data.fds[f]); - } - return nullptr; - } - data.strides[i] = gbm_bo_get_stride_for_plane(bo, i); - data.offsets[i] = gbm_bo_get_offset(bo, i); - } - importedBuffer = gbm_bo_import(m_pipeline->gpu()->gbmDevice(), GBM_BO_IMPORT_FD_MODIFIER, &data, GBM_BO_USE_SCANOUT); - } else { -#endif - gbm_import_fd_data data = { - .fd = gbm_bo_get_fd(bo), - .width = gbm_bo_get_width(bo), - .height = gbm_bo_get_height(bo), - .stride = gbm_bo_get_stride(bo), - .format = gbm_bo_get_format(bo), - }; - if (data.fd < 0) { - qCWarning(KWIN_DRM, "failed to export gbm_bo as dma-buf: %s", strerror(errno)); - return nullptr; - } - importedBuffer = gbm_bo_import(m_pipeline->gpu()->gbmDevice(), GBM_BO_IMPORT_FD_MODIFIER, &data, GBM_BO_USE_SCANOUT); -#if HAVE_GBM_BO_GET_FD_FOR_PLANE - } -#endif - if (!importedBuffer) { - qCWarning(KWIN_DRM, "failed to import gbm_bo for multi-gpu usage: %s", strerror(errno)); - return nullptr; - } - const auto buffer = QSharedPointer::create(m_pipeline->gpu(), nullptr, importedBuffer); - return buffer->bufferId() ? buffer : nullptr; -} - -QSharedPointer EglGbmLayer::importWithCpu() -{ - if (doesSwapchainFit(m_importSwapchain.data())) { - m_oldImportSwapchain.reset(); - } else { - if (doesSwapchainFit(m_oldImportSwapchain.data())) { - m_importSwapchain = m_oldImportSwapchain; - } else { - const auto swapchain = QSharedPointer::create(m_pipeline->gpu(), m_pipeline->sourceSize(), m_gbmSurface->format()); - if (swapchain->isEmpty()) { - return nullptr; - } - m_importSwapchain = swapchain; - } - } - - const auto bo = m_gbmSurface->currentBuffer(); - if (!bo->map(GBM_BO_TRANSFER_READ)) { - qCWarning(KWIN_DRM, "mapping a gbm_bo failed: %s", strerror(errno)); - return nullptr; - } - const auto importBuffer = m_importSwapchain->acquireBuffer(); - if (bo->stride() != importBuffer->stride()) { - qCCritical(KWIN_DRM, "stride of gbm_bo (%d) and dumb buffer (%d) don't match!", bo->stride(), importBuffer->stride()); - return nullptr; - } - if (!memcpy(importBuffer->data(), bo->mappedData(), importBuffer->size().height() * importBuffer->stride())) { - return nullptr; - } - return importBuffer; -} - bool EglGbmLayer::scanout(SurfaceItem *surfaceItem) { static bool valid; @@ -468,7 +160,7 @@ void EglGbmLayer::sendDmabufFeedback(KWaylandServer::LinuxDmaBufV1ClientBuffer * if (m_scanoutCandidate.surface->dmabufFeedbackV1()) { QVector scanoutTranches; const auto drmFormats = m_pipeline->formats(); - const auto tranches = m_eglBackend->dmabuf()->tranches(); + const auto tranches = m_surface.eglBackend()->dmabuf()->tranches(); for (const auto &tranche : tranches) { KWaylandServer::LinuxDmaBufV1Feedback::Tranche scanoutTranche; for (auto it = tranche.formatTable.constBegin(); it != tranche.formatTable.constEnd(); it++) { diff --git a/src/backends/drm/egl_gbm_layer.h b/src/backends/drm/egl_gbm_layer.h index 8bcf9899cb..5e62440196 100644 --- a/src/backends/drm/egl_gbm_layer.h +++ b/src/backends/drm/egl_gbm_layer.h @@ -9,6 +9,8 @@ #pragma once #include "drm_layer.h" +#include "egl_gbm_layer_surface.h" + #include #include #include @@ -25,10 +27,8 @@ class LinuxDmaBufV1ClientBuffer; namespace KWin { -class GbmSurface; -class DumbSwapchain; -class ShadowBuffer; class EglGbmBackend; +class EglGbmLayerSurface; class DrmGbmBuffer; class EglGbmLayer : public DrmPipelineLayer @@ -48,27 +48,10 @@ public: QSharedPointer texture() const override; private: - bool createGbmSurface(uint32_t format, const QVector &modifiers); - bool createGbmSurface(); - bool doesGbmSurfaceFit(GbmSurface *surf) const; - bool doesShadowBufferFit(ShadowBuffer *buffer) const; - bool doesSwapchainFit(DumbSwapchain *swapchain) const; void sendDmabufFeedback(KWaylandServer::LinuxDmaBufV1ClientBuffer *failedBuffer); bool renderTestBuffer(); void destroyResources(); - QSharedPointer importBuffer(); - QSharedPointer importDmabuf(); - QSharedPointer importWithCpu(); - - enum class MultiGpuImportMode { - Dmabuf, - DumbBuffer, - DumbBufferXrgb8888, - Failed - }; - MultiGpuImportMode m_importMode = MultiGpuImportMode::Dmabuf; - struct { QPointer surface; @@ -79,14 +62,8 @@ private: QSharedPointer m_scanoutBuffer; QSharedPointer m_currentBuffer; QRegion m_currentDamage; - QSharedPointer m_gbmSurface; - QSharedPointer m_oldGbmSurface; - QSharedPointer m_shadowBuffer; - QSharedPointer m_oldShadowBuffer; - QSharedPointer m_importSwapchain; - QSharedPointer m_oldImportSwapchain; - EglGbmBackend *const m_eglBackend; + EglGbmLayerSurface m_surface; }; } diff --git a/src/backends/drm/egl_gbm_layer_surface.cpp b/src/backends/drm/egl_gbm_layer_surface.cpp new file mode 100644 index 0000000000..891a32ee81 --- /dev/null +++ b/src/backends/drm/egl_gbm_layer_surface.cpp @@ -0,0 +1,362 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2022 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "egl_gbm_layer_surface.h" + +#include "config-kwin.h" +#include "drm_gpu.h" +#include "drm_output.h" +#include "dumb_swapchain.h" +#include "egl_dmabuf.h" +#include "egl_gbm_backend.h" +#include "gbm_surface.h" +#include "kwineglutils_p.h" +#include "logging.h" +#include "shadowbuffer.h" +#include "surfaceitem_wayland.h" + +#include +#include + +#include +#include +#include +#include + +namespace KWin +{ + +EglGbmLayerSurface::EglGbmLayerSurface(DrmGpu *gpu, EglGbmBackend *eglBackend) + : m_gpu(gpu) + , m_eglBackend(eglBackend) +{ +} + +EglGbmLayerSurface::~EglGbmLayerSurface() +{ + destroyResources(); +} + +void EglGbmLayerSurface::destroyResources() +{ + if (m_gbmSurface && (m_shadowBuffer || m_oldShadowBuffer)) { + m_gbmSurface->makeContextCurrent(); + } + m_shadowBuffer.reset(); + m_oldShadowBuffer.reset(); + m_gbmSurface.reset(); + m_oldGbmSurface.reset(); +} + +OutputLayerBeginFrameInfo EglGbmLayerSurface::startRendering(const QSize &bufferSize, DrmPlane::Transformations renderTransform, DrmPlane::Transformations bufferTransform, const QMap> &formats) +{ + // gbm surface + if (doesGbmSurfaceFit(m_gbmSurface.data(), bufferSize, formats)) { + m_oldGbmSurface.reset(); + } else { + if (doesGbmSurfaceFit(m_oldGbmSurface.data(), bufferSize, formats)) { + m_gbmSurface = m_oldGbmSurface; + } else { + if (!createGbmSurface(bufferSize, formats)) { + return {}; + } + // dmabuf might work with the new surface + m_importMode = MultiGpuImportMode::Dmabuf; + m_importSwapchain.reset(); + m_oldImportSwapchain.reset(); + } + } + if (!m_gbmSurface->makeContextCurrent()) { + return {}; + } + + // shadow buffer + const QSize renderSize = (renderTransform & (DrmPlane::Transformation::Rotate90 | DrmPlane::Transformation::Rotate270)) ? m_gbmSurface->size().transposed() : m_gbmSurface->size(); + if (doesShadowBufferFit(m_shadowBuffer.data(), renderSize, renderTransform, bufferTransform)) { + m_oldShadowBuffer.reset(); + } else { + if (doesShadowBufferFit(m_oldShadowBuffer.data(), renderSize, renderTransform, bufferTransform)) { + m_shadowBuffer = m_oldShadowBuffer; + } else { + if (renderTransform != bufferTransform) { + const auto format = m_eglBackend->gbmFormatForDrmFormat(m_gbmSurface->format()); + if (!format.has_value()) { + return {}; + } + m_shadowBuffer = QSharedPointer::create(renderSize, format.value()); + if (!m_shadowBuffer->isComplete()) { + return {}; + } + } else { + m_shadowBuffer.reset(); + } + } + } + + GLFramebuffer::pushFramebuffer(m_gbmSurface->fbo()); + if (m_shadowBuffer) { + GLFramebuffer::pushFramebuffer(m_shadowBuffer->fbo()); + // the blit after rendering will completely overwrite the back buffer anyways + return OutputLayerBeginFrameInfo { + .renderTarget = RenderTarget(m_shadowBuffer->fbo()), + .repaint = {}, + }; + } else { + return OutputLayerBeginFrameInfo { + .renderTarget = RenderTarget(m_gbmSurface->fbo()), + .repaint = m_gbmSurface->repaintRegion(), + }; + } +} + +void EglGbmLayerSurface::aboutToStartPainting(DrmOutput *output, const QRegion &damagedRegion) +{ + if (m_gbmSurface && m_gbmSurface->bufferAge() > 0 && !damagedRegion.isEmpty() && m_eglBackend->supportsPartialUpdate()) { + QVector rects = output->regionToRects(damagedRegion); + const bool correct = eglSetDamageRegionKHR(m_eglBackend->eglDisplay(), m_gbmSurface->eglSurface(), rects.data(), rects.count() / 4); + if (!correct) { + qCWarning(KWIN_DRM) << "eglSetDamageRegionKHR failed:" << getEglErrorString(); + } + } +} + +std::optional, QRegion>> EglGbmLayerSurface::endRendering(DrmPlane::Transformations renderTransform, const QRegion &damagedRegion) +{ + if (m_shadowBuffer) { + GLFramebuffer::popFramebuffer(); + // TODO handle bufferTransform != Rotate0 + m_shadowBuffer->render(renderTransform); + } + GLFramebuffer::popFramebuffer(); + QSharedPointer buffer; + if (m_gpu == m_eglBackend->gpu()) { + buffer = m_gbmSurface->swapBuffersForDrm(damagedRegion); + } else { + if (m_gbmSurface->swapBuffers(damagedRegion)) { + buffer = importBuffer(); + } + } + if (buffer) { + return std::tuple(buffer, damagedRegion); + } else { + return {}; + } +} + +bool EglGbmLayerSurface::createGbmSurface(const QSize &size, uint32_t format, const QVector &modifiers) +{ + static bool modifiersEnvSet = false; + static const bool modifiersEnv = qEnvironmentVariableIntValue("KWIN_DRM_USE_MODIFIERS", &modifiersEnvSet) != 0; + const bool allowModifiers = m_eglBackend->gpu()->addFB2ModifiersSupported() && m_gpu->addFB2ModifiersSupported() + && ((m_eglBackend->gpu()->isNVidia() && !modifiersEnvSet) || (modifiersEnvSet && modifiersEnv)); + + const auto config = m_eglBackend->config(format); + + QSharedPointer gbmSurface; +#if HAVE_GBM_BO_GET_FD_FOR_PLANE + if (!allowModifiers) { +#else + // modifiers have to be disabled with multi-gpu if gbm_bo_get_fd_for_plane is not available + if (!allowModifiers || m_pipeline->gpu() != m_eglBackend->gpu()) { +#endif + int flags = GBM_BO_USE_RENDERING; + if (m_gpu == m_eglBackend->gpu()) { + flags |= GBM_BO_USE_SCANOUT; + } else { + flags |= GBM_BO_USE_LINEAR; + } + gbmSurface = QSharedPointer::create(m_eglBackend->gpu(), size, format, flags, config); + } else { + gbmSurface = QSharedPointer::create(m_eglBackend->gpu(), size, format, modifiers, config); + if (!gbmSurface->isValid()) { + // the egl / gbm implementation may reject the modifier list from another gpu + // as a fallback use linear, to at least make CPU copy more efficient + const QVector linear = {DRM_FORMAT_MOD_LINEAR}; + gbmSurface = QSharedPointer::create(m_eglBackend->gpu(), size, format, linear, config); + } + } + if (gbmSurface->isValid()) { + m_oldGbmSurface = m_gbmSurface; + m_gbmSurface = gbmSurface; + return true; + } else { + return false; + } +} + +bool EglGbmLayerSurface::createGbmSurface(const QSize &size, const QMap> &formats) +{ + const auto tranches = m_eglBackend->dmabuf()->tranches(); + for (const auto &tranche : tranches) { + for (auto it = tranche.formatTable.constBegin(); it != tranche.formatTable.constEnd(); it++) { + const uint32_t &format = it.key(); + if (m_importMode == MultiGpuImportMode::DumbBufferXrgb8888 && format != DRM_FORMAT_XRGB8888) { + continue; + } + if (formats.contains(format) && createGbmSurface(size, format, formats[format])) { + return true; + } + } + } + return false; +} + +bool EglGbmLayerSurface::doesGbmSurfaceFit(GbmSurface *surf, const QSize &size, const QMap> &formats) const +{ + return surf && surf->size() == size + && formats.contains(surf->format()) + && (m_importMode != MultiGpuImportMode::DumbBufferXrgb8888 || surf->format() == DRM_FORMAT_XRGB8888) + && (surf->modifiers().isEmpty() || formats[surf->format()] == surf->modifiers()); +} + +bool EglGbmLayerSurface::doesShadowBufferFit(ShadowBuffer *buffer, const QSize &size, DrmPlane::Transformations renderTransform, DrmPlane::Transformations bufferTransform) const +{ + if (renderTransform != bufferTransform) { + return buffer && buffer->texture()->size() == size && buffer->drmFormat() == m_gbmSurface->format(); + } else { + return buffer == nullptr; + } +} + +QSharedPointer EglGbmLayerSurface::importBuffer() +{ + if (m_importMode == MultiGpuImportMode::Dmabuf) { + if (const auto buffer = importDmabuf()) { + return buffer; + } else { + // don't bother trying again, it will most likely fail every time + m_importMode = MultiGpuImportMode::DumbBuffer; + } + } + if (const auto buffer = importWithCpu()) { + return buffer; + } else if (m_importMode == MultiGpuImportMode::DumbBuffer) { + m_importMode = MultiGpuImportMode::DumbBufferXrgb8888; + return nullptr; + } + if (m_importMode != MultiGpuImportMode::Failed) { + qCCritical(KWIN_DRM, "All multi gpu imports failed!"); + m_importMode = MultiGpuImportMode::Failed; + } + return nullptr; +} + +QSharedPointer EglGbmLayerSurface::importDmabuf() +{ + const auto bo = m_gbmSurface->currentBuffer()->getBo(); + gbm_bo *importedBuffer; +#if HAVE_GBM_BO_GET_FD_FOR_PLANE + if (gbm_bo_get_handle_for_plane(bo, 0).s32 != -1) { + gbm_import_fd_modifier_data data = { + .width = gbm_bo_get_width(bo), + .height = gbm_bo_get_height(bo), + .format = gbm_bo_get_format(bo), + .num_fds = static_cast(gbm_bo_get_plane_count(bo)), + .fds = {}, + .strides = {}, + .offsets = {}, + .modifier = gbm_bo_get_modifier(bo), + }; + for (uint32_t i = 0; i < data.num_fds; i++) { + data.fds[i] = gbm_bo_get_fd_for_plane(bo, i); + if (data.fds[i] < 0) { + qCWarning(KWIN_DRM, "failed to export gbm_bo plane %d as dma-buf: %s", i, strerror(errno)); + for (uint32_t f = 0; f < i; f++) { + close(data.fds[f]); + } + return nullptr; + } + data.strides[i] = gbm_bo_get_stride_for_plane(bo, i); + data.offsets[i] = gbm_bo_get_offset(bo, i); + } + importedBuffer = gbm_bo_import(m_gpu->gbmDevice(), GBM_BO_IMPORT_FD_MODIFIER, &data, GBM_BO_USE_SCANOUT); + } else { +#endif + gbm_import_fd_data data = { + .fd = gbm_bo_get_fd(bo), + .width = gbm_bo_get_width(bo), + .height = gbm_bo_get_height(bo), + .stride = gbm_bo_get_stride(bo), + .format = gbm_bo_get_format(bo), + }; + if (data.fd < 0) { + qCWarning(KWIN_DRM, "failed to export gbm_bo as dma-buf: %s", strerror(errno)); + return nullptr; + } + importedBuffer = gbm_bo_import(m_gpu->gbmDevice(), GBM_BO_IMPORT_FD_MODIFIER, &data, GBM_BO_USE_SCANOUT); +#if HAVE_GBM_BO_GET_FD_FOR_PLANE + } +#endif + if (!importedBuffer) { + qCWarning(KWIN_DRM, "failed to import gbm_bo for multi-gpu usage: %s", strerror(errno)); + return nullptr; + } + const auto buffer = QSharedPointer::create(m_gpu, nullptr, importedBuffer); + return buffer->bufferId() ? buffer : nullptr; +} + +QSharedPointer EglGbmLayerSurface::importWithCpu() +{ + if (doesSwapchainFit(m_importSwapchain.data())) { + m_oldImportSwapchain.reset(); + } else { + if (doesSwapchainFit(m_oldImportSwapchain.data())) { + m_importSwapchain = m_oldImportSwapchain; + } else { + const auto swapchain = QSharedPointer::create(m_gpu, m_gbmSurface->size(), m_gbmSurface->format()); + if (swapchain->isEmpty()) { + return nullptr; + } + m_importSwapchain = swapchain; + } + } + + const auto bo = m_gbmSurface->currentBuffer(); + if (!bo->map(GBM_BO_TRANSFER_READ)) { + qCWarning(KWIN_DRM, "mapping a gbm_bo failed: %s", strerror(errno)); + return nullptr; + } + const auto importBuffer = m_importSwapchain->acquireBuffer(); + if (bo->stride() != importBuffer->stride()) { + qCCritical(KWIN_DRM, "stride of gbm_bo (%d) and dumb buffer (%d) don't match!", bo->stride(), importBuffer->stride()); + return nullptr; + } + if (!memcpy(importBuffer->data(), bo->mappedData(), importBuffer->size().height() * importBuffer->stride())) { + return nullptr; + } + return importBuffer; +} + +bool EglGbmLayerSurface::doesSwapchainFit(DumbSwapchain *swapchain) const +{ + return swapchain && swapchain->size() == m_gbmSurface->size() && swapchain->drmFormat() == m_gbmSurface->format(); +} + +EglGbmBackend *EglGbmLayerSurface::eglBackend() const +{ + return m_eglBackend; +} + +bool EglGbmLayerSurface::doesSurfaceFit(const QSize &size, const QMap> &formats) const +{ + return doesGbmSurfaceFit(m_gbmSurface.data(), size, formats); +} + +QSharedPointer EglGbmLayerSurface::texture() const +{ + if (m_shadowBuffer) { + return m_shadowBuffer->texture(); + } + GbmBuffer *gbmBuffer = m_gbmSurface->currentBuffer().data(); + if (!gbmBuffer) { + qCWarning(KWIN_DRM) << "Failed to record frame: No gbm buffer!"; + return nullptr; + } + return gbmBuffer->createTexture(m_eglBackend->eglDisplay()); +} +} diff --git a/src/backends/drm/egl_gbm_layer_surface.h b/src/backends/drm/egl_gbm_layer_surface.h new file mode 100644 index 0000000000..93b8c811e4 --- /dev/null +++ b/src/backends/drm/egl_gbm_layer_surface.h @@ -0,0 +1,85 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2022 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#pragma once + +#include +#include +#include +#include +#include + +#include "drm_object_plane.h" +#include "outputlayer.h" + +namespace KWaylandServer +{ +class SurfaceInterface; +class LinuxDmaBufV1ClientBuffer; +} + +namespace KWin +{ + +class DrmBuffer; +class GbmSurface; +class DumbSwapchain; +class ShadowBuffer; +class EglGbmBackend; +class SurfaceItem; +class GLTexture; + +class EglGbmLayerSurface : public QObject +{ + Q_OBJECT +public: + EglGbmLayerSurface(DrmGpu *gpu, EglGbmBackend *eglBackend); + ~EglGbmLayerSurface(); + + OutputLayerBeginFrameInfo startRendering(const QSize &bufferSize, DrmPlane::Transformations renderTransform, DrmPlane::Transformations bufferTransform, const QMap> &formats); + void aboutToStartPainting(DrmOutput *output, const QRegion &damagedRegion); + std::optional, QRegion>> endRendering(DrmPlane::Transformations renderTransform, const QRegion &damagedRegion); + + bool doesSurfaceFit(const QSize &size, const QMap> &formats) const; + QSharedPointer texture() const; + void destroyResources(); + EglGbmBackend *eglBackend() const; + +private: + bool createGbmSurface(const QSize &size, uint32_t format, const QVector &modifiers); + bool createGbmSurface(const QSize &size, const QMap> &formats); + bool doesGbmSurfaceFit(GbmSurface *surf, const QSize &size, const QMap> &formats) const; + + bool doesShadowBufferFit(ShadowBuffer *buffer, const QSize &size, DrmPlane::Transformations renderTransform, DrmPlane::Transformations bufferTransform) const; + bool doesSwapchainFit(DumbSwapchain *swapchain) const; + + QSharedPointer importBuffer(); + QSharedPointer importDmabuf(); + QSharedPointer importWithCpu(); + + enum class MultiGpuImportMode { + Dmabuf, + DumbBuffer, + DumbBufferXrgb8888, + Failed + }; + MultiGpuImportMode m_importMode = MultiGpuImportMode::Dmabuf; + + QRegion m_currentDamage; + QSharedPointer m_gbmSurface; + QSharedPointer m_oldGbmSurface; + QSharedPointer m_shadowBuffer; + QSharedPointer m_oldShadowBuffer; + QSharedPointer m_importSwapchain; + QSharedPointer m_oldImportSwapchain; + + DrmGpu *const m_gpu; + EglGbmBackend *const m_eglBackend; +}; + +}