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 }