File indexing completed on 2025-04-20 10:57:37

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "drm_virtual_egl_layer.h"
0010 #include "drm_abstract_output.h"
0011 #include "drm_backend.h"
0012 #include "drm_dumb_swapchain.h"
0013 #include "drm_egl_backend.h"
0014 #include "drm_gbm_surface.h"
0015 #include "drm_gpu.h"
0016 #include "drm_logging.h"
0017 #include "drm_output.h"
0018 #include "drm_pipeline.h"
0019 #include "drm_shadow_buffer.h"
0020 #include "drm_virtual_output.h"
0021 #include "egl_dmabuf.h"
0022 #include "kwineglutils_p.h"
0023 #include "scene/surfaceitem_wayland.h"
0024 #include "wayland/linuxdmabufv1clientbuffer.h"
0025 #include "wayland/surface_interface.h"
0026 
0027 #include <QRegion>
0028 #include <drm_fourcc.h>
0029 #include <errno.h>
0030 #include <gbm.h>
0031 #include <unistd.h>
0032 
0033 namespace KWin
0034 {
0035 
0036 VirtualEglGbmLayer::VirtualEglGbmLayer(EglGbmBackend *eglBackend, DrmVirtualOutput *output)
0037     : m_output(output)
0038     , m_eglBackend(eglBackend)
0039 {
0040 }
0041 
0042 void VirtualEglGbmLayer::aboutToStartPainting(const QRegion &damagedRegion)
0043 {
0044     if (m_gbmSurface && m_gbmSurface->bufferAge() > 0 && !damagedRegion.isEmpty() && m_eglBackend->supportsPartialUpdate()) {
0045         const QRegion region = damagedRegion & m_output->geometry();
0046 
0047         QVector<EGLint> rects = m_output->regionToRects(region);
0048         const bool correct = eglSetDamageRegionKHR(m_eglBackend->eglDisplay(), m_gbmSurface->eglSurface(), rects.data(), rects.count() / 4);
0049         if (!correct) {
0050             qCWarning(KWIN_DRM) << "eglSetDamageRegionKHR failed:" << getEglErrorString();
0051         }
0052     }
0053 }
0054 
0055 std::optional<OutputLayerBeginFrameInfo> VirtualEglGbmLayer::beginFrame()
0056 {
0057     // gbm surface
0058     if (doesGbmSurfaceFit(m_gbmSurface.get())) {
0059         m_oldGbmSurface.reset();
0060     } else {
0061         if (doesGbmSurfaceFit(m_oldGbmSurface.get())) {
0062             m_gbmSurface = m_oldGbmSurface;
0063         } else {
0064             if (!createGbmSurface()) {
0065                 return std::nullopt;
0066             }
0067         }
0068     }
0069     if (!m_gbmSurface->makeContextCurrent()) {
0070         return std::nullopt;
0071     }
0072     return OutputLayerBeginFrameInfo{
0073         .renderTarget = RenderTarget(m_gbmSurface->fbo()),
0074         .repaint = m_gbmSurface->repaintRegion(),
0075     };
0076 }
0077 
0078 bool VirtualEglGbmLayer::endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
0079 {
0080     const auto buffer = m_gbmSurface->swapBuffers(damagedRegion);
0081     if (buffer) {
0082         m_currentBuffer = buffer;
0083         m_currentDamage = damagedRegion;
0084     }
0085     return buffer != nullptr;
0086 }
0087 
0088 QRegion VirtualEglGbmLayer::currentDamage() const
0089 {
0090     return m_currentDamage;
0091 }
0092 
0093 bool VirtualEglGbmLayer::createGbmSurface()
0094 {
0095     static bool modifiersEnvSet = false;
0096     static const bool modifiersEnv = qEnvironmentVariableIntValue("KWIN_DRM_USE_MODIFIERS", &modifiersEnvSet) != 0;
0097     const bool allowModifiers = !modifiersEnvSet || modifiersEnv;
0098 
0099     const auto tranches = m_eglBackend->dmabuf()->tranches();
0100     for (const auto &tranche : tranches) {
0101         for (auto it = tranche.formatTable.constBegin(); it != tranche.formatTable.constEnd(); it++) {
0102             const auto size = m_output->pixelSize();
0103             const auto config = m_eglBackend->config(it.key());
0104             const auto format = it.key();
0105             const auto modifiers = it.value();
0106 
0107             if (allowModifiers && !modifiers.isEmpty()) {
0108                 const auto ret = GbmSurface::createSurface(m_eglBackend, size, format, modifiers, config);
0109                 if (const auto surface = std::get_if<std::shared_ptr<GbmSurface>>(&ret)) {
0110                     m_oldGbmSurface = m_gbmSurface;
0111                     m_gbmSurface = *surface;
0112                     return true;
0113                 } else if (std::get<GbmSurface::Error>(ret) != GbmSurface::Error::ModifiersUnsupported) {
0114                     continue;
0115                 }
0116             }
0117             const auto ret = GbmSurface::createSurface(m_eglBackend, size, format, GBM_BO_USE_RENDERING, config);
0118             if (const auto surface = std::get_if<std::shared_ptr<GbmSurface>>(&ret)) {
0119                 m_oldGbmSurface = m_gbmSurface;
0120                 m_gbmSurface = *surface;
0121                 return true;
0122             }
0123         }
0124     }
0125     return false;
0126 }
0127 
0128 bool VirtualEglGbmLayer::doesGbmSurfaceFit(GbmSurface *surf) const
0129 {
0130     return surf && surf->size() == m_output->pixelSize();
0131 }
0132 
0133 std::shared_ptr<GLTexture> VirtualEglGbmLayer::texture() const
0134 {
0135     if (!m_currentBuffer) {
0136         qCWarning(KWIN_DRM) << "Failed to record frame: No gbm buffer!";
0137         return nullptr;
0138     }
0139     return m_eglBackend->importBufferObjectAsTexture(m_currentBuffer->bo());
0140 }
0141 
0142 bool VirtualEglGbmLayer::scanout(SurfaceItem *surfaceItem)
0143 {
0144     static bool valid;
0145     static const bool directScanoutDisabled = qEnvironmentVariableIntValue("KWIN_DRM_NO_DIRECT_SCANOUT", &valid) == 1 && valid;
0146     if (directScanoutDisabled) {
0147         return false;
0148     }
0149 
0150     SurfaceItemWayland *item = qobject_cast<SurfaceItemWayland *>(surfaceItem);
0151     if (!item || !item->surface()) {
0152         return false;
0153     }
0154     const auto buffer = qobject_cast<KWaylandServer::LinuxDmaBufV1ClientBuffer *>(item->surface()->buffer());
0155     if (!buffer || buffer->size() != m_output->pixelSize()) {
0156         return false;
0157     }
0158     const auto scanoutBuffer = GbmBuffer::importBuffer(m_output->gpu(), buffer);
0159     if (!scanoutBuffer) {
0160         return false;
0161     }
0162     // damage tracking for screen casting
0163     m_currentDamage = m_scanoutSurface == item->surface() ? surfaceItem->damage() : infiniteRegion();
0164     surfaceItem->resetDamage();
0165     // ensure the pixmap is updated when direct scanout ends
0166     surfaceItem->destroyPixmap();
0167     m_scanoutSurface = item->surface();
0168     m_currentBuffer = scanoutBuffer;
0169     return true;
0170 }
0171 
0172 void VirtualEglGbmLayer::releaseBuffers()
0173 {
0174     m_currentBuffer.reset();
0175     m_gbmSurface.reset();
0176     m_oldGbmSurface.reset();
0177 }
0178 }