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

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_egl_layer_surface.h"
0010 
0011 #include "config-kwin.h"
0012 #include "drm_buffer_gbm.h"
0013 #include "drm_dumb_buffer.h"
0014 #include "drm_dumb_swapchain.h"
0015 #include "drm_egl_backend.h"
0016 #include "drm_gbm_surface.h"
0017 #include "drm_gpu.h"
0018 #include "drm_logging.h"
0019 #include "drm_output.h"
0020 #include "drm_shadow_buffer.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 <cstring>
0028 #include <drm_fourcc.h>
0029 #include <errno.h>
0030 #include <gbm.h>
0031 #include <unistd.h>
0032 
0033 namespace KWin
0034 {
0035 
0036 static const QVector<uint64_t> linearModifier = {DRM_FORMAT_MOD_LINEAR};
0037 
0038 static gbm_format_name_desc formatName(uint32_t format)
0039 {
0040     gbm_format_name_desc ret;
0041     gbm_format_get_name(format, &ret);
0042     return ret;
0043 }
0044 
0045 EglGbmLayerSurface::EglGbmLayerSurface(DrmGpu *gpu, EglGbmBackend *eglBackend, BufferTarget target, FormatOption formatOption)
0046     : m_gpu(gpu)
0047     , m_eglBackend(eglBackend)
0048     , m_bufferTarget(target)
0049     , m_formatOption(formatOption)
0050 {
0051 }
0052 
0053 EglGbmLayerSurface::~EglGbmLayerSurface()
0054 {
0055     destroyResources();
0056 }
0057 
0058 void EglGbmLayerSurface::destroyResources()
0059 {
0060     if (m_surface.gbmSurface && (m_shadowBuffer || m_oldShadowBuffer)) {
0061         m_surface.gbmSurface->makeContextCurrent();
0062     }
0063     m_shadowBuffer.reset();
0064     m_oldShadowBuffer.reset();
0065     m_surface = {};
0066     m_oldSurface = {};
0067 }
0068 
0069 std::optional<OutputLayerBeginFrameInfo> EglGbmLayerSurface::startRendering(const QSize &bufferSize, DrmPlane::Transformations renderOrientation, DrmPlane::Transformations bufferOrientation, const QMap<uint32_t, QVector<uint64_t>> &formats)
0070 {
0071     if (!checkSurface(bufferSize, formats)) {
0072         return std::nullopt;
0073     }
0074     if (!m_surface.gbmSurface->makeContextCurrent()) {
0075         return std::nullopt;
0076     }
0077 
0078     // shadow buffer
0079     const QSize renderSize = (renderOrientation & (DrmPlane::Transformation::Rotate90 | DrmPlane::Transformation::Rotate270)) ? m_surface.gbmSurface->size().transposed() : m_surface.gbmSurface->size();
0080     if (doesShadowBufferFit(m_shadowBuffer.get(), renderSize, renderOrientation, bufferOrientation)) {
0081         m_oldShadowBuffer.reset();
0082     } else {
0083         if (doesShadowBufferFit(m_oldShadowBuffer.get(), renderSize, renderOrientation, bufferOrientation)) {
0084             m_shadowBuffer = m_oldShadowBuffer;
0085         } else {
0086             if (renderOrientation != bufferOrientation) {
0087                 const auto format = m_eglBackend->gbmFormatForDrmFormat(m_surface.gbmSurface->format());
0088                 if (!format.has_value()) {
0089                     return std::nullopt;
0090                 }
0091                 m_shadowBuffer = std::make_shared<ShadowBuffer>(renderSize, format.value());
0092                 if (!m_shadowBuffer->isComplete()) {
0093                     return std::nullopt;
0094                 }
0095             } else {
0096                 m_shadowBuffer.reset();
0097             }
0098         }
0099     }
0100 
0101     if (m_shadowBuffer) {
0102         // the blit after rendering will completely overwrite the back buffer anyways
0103         return OutputLayerBeginFrameInfo{
0104             .renderTarget = RenderTarget(m_shadowBuffer->fbo()),
0105             .repaint = {},
0106         };
0107     } else {
0108         return OutputLayerBeginFrameInfo{
0109             .renderTarget = RenderTarget(m_surface.gbmSurface->fbo()),
0110             .repaint = m_surface.gbmSurface->repaintRegion(),
0111         };
0112     }
0113 }
0114 
0115 void EglGbmLayerSurface::aboutToStartPainting(DrmOutput *output, const QRegion &damagedRegion)
0116 {
0117     if (m_shadowBuffer) {
0118         // with a shadow buffer, we always fully damage the surface
0119         return;
0120     }
0121     if (m_surface.gbmSurface && m_surface.gbmSurface->bufferAge() > 0 && !damagedRegion.isEmpty() && m_eglBackend->supportsPartialUpdate()) {
0122         QVector<EGLint> rects = output->regionToRects(damagedRegion);
0123         const bool correct = eglSetDamageRegionKHR(m_eglBackend->eglDisplay(), m_surface.gbmSurface->eglSurface(), rects.data(), rects.count() / 4);
0124         if (!correct) {
0125             qCWarning(KWIN_DRM) << "eglSetDamageRegionKHR failed:" << getEglErrorString();
0126         }
0127     }
0128 }
0129 
0130 bool EglGbmLayerSurface::endRendering(DrmPlane::Transformations renderOrientation, const QRegion &damagedRegion)
0131 {
0132     if (m_shadowBuffer) {
0133         GLFramebuffer::pushFramebuffer(m_surface.gbmSurface->fbo());
0134         // TODO handle bufferOrientation != Rotate0
0135         m_shadowBuffer->render(renderOrientation);
0136         GLFramebuffer::popFramebuffer();
0137     }
0138     const auto gbmBuffer = m_surface.gbmSurface->swapBuffers(damagedRegion);
0139     if (!gbmBuffer) {
0140         return false;
0141     }
0142     const auto buffer = importBuffer(m_surface, gbmBuffer);
0143     if (buffer) {
0144         m_surface.currentBuffer = gbmBuffer;
0145         m_surface.currentFramebuffer = buffer;
0146         return true;
0147     } else {
0148         return false;
0149     }
0150 }
0151 
0152 bool EglGbmLayerSurface::doesShadowBufferFit(ShadowBuffer *buffer, const QSize &size, DrmPlane::Transformations renderOrientation, DrmPlane::Transformations bufferOrientation) const
0153 {
0154     if (renderOrientation != bufferOrientation) {
0155         return buffer && buffer->texture()->size() == size && buffer->drmFormat() == m_surface.gbmSurface->format();
0156     } else {
0157         return buffer == nullptr;
0158     }
0159 }
0160 
0161 EglGbmBackend *EglGbmLayerSurface::eglBackend() const
0162 {
0163     return m_eglBackend;
0164 }
0165 
0166 std::shared_ptr<DrmFramebuffer> EglGbmLayerSurface::currentBuffer() const
0167 {
0168     return m_surface.currentFramebuffer;
0169 }
0170 
0171 bool EglGbmLayerSurface::doesSurfaceFit(const QSize &size, const QMap<uint32_t, QVector<uint64_t>> &formats) const
0172 {
0173     return doesSurfaceFit(m_surface, size, formats);
0174 }
0175 
0176 std::shared_ptr<GLTexture> EglGbmLayerSurface::texture() const
0177 {
0178     if (m_shadowBuffer) {
0179         return m_shadowBuffer->texture();
0180     }
0181     if (!m_surface.currentBuffer) {
0182         qCWarning(KWIN_DRM) << "Failed to record frame: No gbm buffer!";
0183         return nullptr;
0184     }
0185     return m_eglBackend->importBufferObjectAsTexture(m_surface.currentBuffer->bo());
0186 }
0187 
0188 std::shared_ptr<DrmFramebuffer> EglGbmLayerSurface::renderTestBuffer(const QSize &bufferSize, const QMap<uint32_t, QVector<uint64_t>> &formats)
0189 {
0190     if (checkSurface(bufferSize, formats)) {
0191         return m_surface.currentFramebuffer;
0192     } else {
0193         return nullptr;
0194     }
0195 }
0196 
0197 bool EglGbmLayerSurface::checkSurface(const QSize &size, const QMap<uint32_t, QVector<uint64_t>> &formats)
0198 {
0199     if (doesSurfaceFit(m_surface, size, formats)) {
0200         return true;
0201     }
0202     if (doesSurfaceFit(m_oldSurface, size, formats)) {
0203         m_surface = m_oldSurface;
0204         return true;
0205     }
0206     if (const auto newSurface = createSurface(size, formats)) {
0207         m_oldSurface = m_surface;
0208         m_surface = newSurface.value();
0209         return true;
0210     }
0211     return false;
0212 }
0213 
0214 bool EglGbmLayerSurface::doesSurfaceFit(const Surface &surface, const QSize &size, const QMap<uint32_t, QVector<uint64_t>> &formats) const
0215 {
0216     return surface.gbmSurface
0217         && surface.gbmSurface->size() == size
0218         && formats.contains(surface.gbmSurface->format())
0219         && (surface.forceLinear || surface.gbmSurface->modifiers().empty() || surface.gbmSurface->modifiers() == formats[surface.gbmSurface->format()]);
0220 }
0221 
0222 std::optional<EglGbmLayerSurface::Surface> EglGbmLayerSurface::createSurface(const QSize &size, const QMap<uint32_t, QVector<uint64_t>> &formats) const
0223 {
0224     QVector<GbmFormat> preferredFormats;
0225     QVector<GbmFormat> fallbackFormats;
0226     for (auto it = formats.begin(); it != formats.end(); it++) {
0227         const auto format = m_eglBackend->gbmFormatForDrmFormat(it.key());
0228         if (format.has_value() && format->bpp >= 24) {
0229             if (format->bpp <= 32) {
0230                 preferredFormats.push_back(format.value());
0231             } else {
0232                 fallbackFormats.push_back(format.value());
0233             }
0234         }
0235     }
0236     const auto sort = [this](const auto &lhs, const auto &rhs) {
0237         if (lhs.drmFormat == rhs.drmFormat) {
0238             // prefer having an alpha channel
0239             return lhs.alphaSize > rhs.alphaSize;
0240         } else if (m_eglBackend->prefer10bpc() && ((lhs.bpp == 30) != (rhs.bpp == 30))) {
0241             // prefer 10bpc / 30bpp formats
0242             return lhs.bpp == 30;
0243         } else {
0244             // fallback: prefer formats with lower bandwidth requirements
0245             return lhs.bpp < rhs.bpp;
0246         }
0247     };
0248     const auto testFormats = [this, &size, &formats](const QVector<GbmFormat> &gbmFormats, MultiGpuImportMode importMode) -> std::optional<Surface> {
0249         for (const auto &format : gbmFormats) {
0250             if (m_formatOption == FormatOption::RequireAlpha && format.alphaSize == 0) {
0251                 continue;
0252             }
0253             const auto surface = createSurface(size, format.drmFormat, formats[format.drmFormat], importMode);
0254             if (surface.has_value()) {
0255                 return surface;
0256             }
0257         }
0258         return std::nullopt;
0259     };
0260     std::sort(preferredFormats.begin(), preferredFormats.end(), sort);
0261     if (const auto surface = testFormats(preferredFormats, MultiGpuImportMode::Dmabuf)) {
0262         return surface;
0263     }
0264     if (m_gpu != m_eglBackend->gpu()) {
0265         if (const auto surface = testFormats(preferredFormats, MultiGpuImportMode::DumbBuffer)) {
0266             return surface;
0267         }
0268     }
0269     std::sort(fallbackFormats.begin(), fallbackFormats.end(), sort);
0270     if (const auto surface = testFormats(fallbackFormats, MultiGpuImportMode::Dmabuf)) {
0271         return surface;
0272     }
0273     if (m_gpu != m_eglBackend->gpu()) {
0274         if (const auto surface = testFormats(fallbackFormats, MultiGpuImportMode::DumbBuffer)) {
0275             return surface;
0276         }
0277     }
0278     return std::nullopt;
0279 }
0280 
0281 std::optional<EglGbmLayerSurface::Surface> EglGbmLayerSurface::createSurface(const QSize &size, uint32_t format, const QVector<uint64_t> &modifiers, MultiGpuImportMode importMode) const
0282 {
0283     Surface ret;
0284     ret.importMode = importMode;
0285     ret.forceLinear = importMode == MultiGpuImportMode::DumbBuffer || m_bufferTarget != BufferTarget::Normal;
0286     ret.gbmSurface = createGbmSurface(size, format, modifiers, ret.forceLinear);
0287     if (!ret.gbmSurface) {
0288         return std::nullopt;
0289     }
0290     if (importMode == MultiGpuImportMode::DumbBuffer || m_bufferTarget == BufferTarget::Dumb) {
0291         ret.importSwapchain = std::make_shared<DumbSwapchain>(m_gpu, size, format);
0292         if (ret.importSwapchain->isEmpty()) {
0293             return std::nullopt;
0294         }
0295     }
0296     if (!doRenderTestBuffer(ret)) {
0297         return std::nullopt;
0298     }
0299     return ret;
0300 }
0301 
0302 std::shared_ptr<GbmSurface> EglGbmLayerSurface::createGbmSurface(const QSize &size, uint32_t format, const QVector<uint64_t> &modifiers, bool forceLinear) const
0303 {
0304     static bool modifiersEnvSet = false;
0305     static const bool modifiersEnv = qEnvironmentVariableIntValue("KWIN_DRM_USE_MODIFIERS", &modifiersEnvSet) != 0;
0306     bool allowModifiers = m_gpu->addFB2ModifiersSupported() && (!modifiersEnvSet || (modifiersEnvSet && modifiersEnv)) && !modifiers.isEmpty();
0307 #if !HAVE_GBM_BO_GET_FD_FOR_PLANE
0308     allowModifiers &= m_gpu == m_eglBackend->gpu();
0309 #endif
0310     const auto config = m_eglBackend->config(format);
0311     if (!config) {
0312         return nullptr;
0313     }
0314 
0315     if (allowModifiers) {
0316         const auto ret = GbmSurface::createSurface(m_eglBackend, size, format, forceLinear ? linearModifier : modifiers, config);
0317         if (const auto surface = std::get_if<std::shared_ptr<GbmSurface>>(&ret)) {
0318             return *surface;
0319         } else if (std::get<GbmSurface::Error>(ret) != GbmSurface::Error::ModifiersUnsupported) {
0320             return nullptr;
0321         }
0322     }
0323     uint32_t gbmFlags = GBM_BO_USE_RENDERING;
0324     if (m_gpu == m_eglBackend->gpu()) {
0325         gbmFlags |= GBM_BO_USE_SCANOUT;
0326     }
0327     if (forceLinear || m_gpu != m_eglBackend->gpu()) {
0328         gbmFlags |= GBM_BO_USE_LINEAR;
0329     }
0330     const auto ret = GbmSurface::createSurface(m_eglBackend, size, format, gbmFlags, config);
0331     const auto surface = std::get_if<std::shared_ptr<GbmSurface>>(&ret);
0332     return surface ? *surface : nullptr;
0333 }
0334 
0335 std::shared_ptr<DrmFramebuffer> EglGbmLayerSurface::doRenderTestBuffer(Surface &surface) const
0336 {
0337     if (!surface.gbmSurface->makeContextCurrent()) {
0338         return nullptr;
0339     }
0340     glClear(GL_COLOR_BUFFER_BIT);
0341     const auto buffer = surface.gbmSurface->swapBuffers(infiniteRegion());
0342     if (!buffer) {
0343         return nullptr;
0344     }
0345     if (const auto ret = importBuffer(surface, buffer)) {
0346         surface.currentBuffer = buffer;
0347         surface.currentFramebuffer = ret;
0348         return ret;
0349     } else {
0350         return nullptr;
0351     }
0352 }
0353 
0354 std::shared_ptr<DrmFramebuffer> EglGbmLayerSurface::importBuffer(Surface &surface, const std::shared_ptr<GbmBuffer> &sourceBuffer) const
0355 {
0356     if (m_bufferTarget == BufferTarget::Dumb || surface.importMode == MultiGpuImportMode::DumbBuffer) {
0357         return importWithCpu(surface, sourceBuffer.get());
0358     } else if (m_gpu != m_eglBackend->gpu()) {
0359         return importDmabuf(sourceBuffer.get());
0360     } else {
0361         const auto ret = DrmFramebuffer::createFramebuffer(sourceBuffer);
0362         if (!ret) {
0363             qCWarning(KWIN_DRM, "Failed to create %s framebuffer: %s", formatName(sourceBuffer->format()).name, strerror(errno));
0364         }
0365         return ret;
0366     }
0367 }
0368 
0369 std::shared_ptr<DrmFramebuffer> EglGbmLayerSurface::importDmabuf(GbmBuffer *sourceBuffer) const
0370 {
0371     const auto imported = GbmBuffer::importBuffer(m_gpu, sourceBuffer, sourceBuffer->flags() | GBM_BO_USE_SCANOUT);
0372     if (!imported) {
0373         qCWarning(KWIN_DRM, "failed to import %s gbm_bo for multi-gpu usage: %s", formatName(sourceBuffer->format()).name, strerror(errno));
0374         return nullptr;
0375     }
0376     const auto ret = DrmFramebuffer::createFramebuffer(imported);
0377     if (!ret) {
0378         qCWarning(KWIN_DRM, "Failed to create %s framebuffer for multi-gpu: %s", formatName(imported->format()).name, strerror(errno));
0379     }
0380     return ret;
0381 }
0382 
0383 std::shared_ptr<DrmFramebuffer> EglGbmLayerSurface::importWithCpu(Surface &surface, GbmBuffer *sourceBuffer) const
0384 {
0385     Q_ASSERT(surface.importSwapchain && !surface.importSwapchain->isEmpty());
0386     const auto map = sourceBuffer->map(GBM_BO_TRANSFER_READ);
0387     if (!map.data) {
0388         qCWarning(KWIN_DRM, "mapping a %s gbm_bo failed: %s", formatName(sourceBuffer->format()).name, strerror(errno));
0389         return nullptr;
0390     }
0391     const auto importBuffer = surface.importSwapchain->acquireBuffer();
0392     if (map.stride == importBuffer->strides()[0]) {
0393         std::memcpy(importBuffer->data(), map.data, importBuffer->size().height() * importBuffer->strides()[0]);
0394     } else {
0395         const uint64_t usedLineWidth = std::min(map.stride, importBuffer->strides()[0]);
0396         for (int i = 0; i < importBuffer->size().height(); i++) {
0397             const char *srcAddress = reinterpret_cast<const char *>(map.data) + map.stride * i;
0398             char *dstAddress = reinterpret_cast<char *>(importBuffer->data()) + importBuffer->strides()[0] * i;
0399             std::memcpy(dstAddress, srcAddress, usedLineWidth);
0400         }
0401     }
0402     const auto ret = DrmFramebuffer::createFramebuffer(importBuffer);
0403     if (!ret) {
0404         qCWarning(KWIN_DRM, "Failed to create %s framebuffer for CPU import: %s", formatName(sourceBuffer->format()).name, strerror(errno));
0405     }
0406     return ret;
0407 }
0408 }