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 }