Commit dece547a authored by Xaver Hugl's avatar Xaver Hugl Committed by Vlad Zahorodnii
Browse files

backends/drm: refactor surface handling

In order to support layered rendering and tiled outputs KWin needs to be
able to split rendering of outputs into multiple surfaces. This commit
prepares the drm backend for that, by moving most of the code in EglGbmBackend
out to a EglGbmSurface class, which will later be used for overlay surfaces
and rendering to multiple connectors side by side.

In doing that, this commit also cleans up the code a bit, removes a lot of
now unnecessary multi-gpu stuff and potentially makes modesets a little
bit more efficient by re-using resources more often.
parent 9dc4c730
Pipeline #137648 passed with stage
in 15 minutes and 6 seconds
......@@ -11,7 +11,6 @@ set(DRM_SOURCES
logging.cpp
scene_qpainter_drm_backend.cpp
drm_gpu.cpp
egl_multi_backend.cpp
dumb_swapchain.cpp
shadowbuffer.cpp
drm_pipeline.cpp
......@@ -20,6 +19,7 @@ set(DRM_SOURCES
drm_virtual_output.cpp
drm_lease_output.cpp
egl_gbm_backend.cpp
egl_gbm_layer.cpp
drm_buffer_gbm.cpp
gbm_surface.cpp
gbm_dmabuf.cpp
......
......@@ -23,7 +23,6 @@
#include "session.h"
#include "udev.h"
#include "drm_gpu.h"
#include "egl_multi_backend.h"
#include "drm_pipeline.h"
#include "drm_virtual_output.h"
#include "waylandoutputconfig.h"
......@@ -50,10 +49,6 @@
#include <xf86drm.h>
#include <libdrm/drm_mode.h>
#include "drm_gpu.h"
#include "egl_multi_backend.h"
#include "drm_pipeline.h"
namespace KWin
{
......@@ -543,18 +538,12 @@ InputBackend *DrmBackend::createInputBackend()
QPainterBackend *DrmBackend::createQPainterBackend()
{
return new DrmQPainterBackend(this, m_gpus.at(0));
return new DrmQPainterBackend(this);
}
OpenGLBackend *DrmBackend::createOpenGLBackend()
{
auto primaryBackend = new EglGbmBackend(this, m_gpus.at(0));
AbstractEglBackend::setPrimaryBackend(primaryBackend);
EglMultiBackend *backend = new EglMultiBackend(this, primaryBackend);
for (int i = 1; i < m_gpus.count(); i++) {
backend->addGpu(m_gpus[i]);
}
return backend;
return new EglGbmBackend(this);
}
void DrmBackend::sceneInitialized()
......
......@@ -49,9 +49,8 @@ bool DrmPipeline::present(const QSharedPointer<DrmBuffer> &buffer)
Q_ASSERT(pending.crtc);
Q_ASSERT(buffer);
m_primaryBuffer = buffer;
auto buf = dynamic_cast<DrmGbmBuffer*>(buffer.data());
// with direct scanout disallow modesets, calling presentFailed() and logging warnings
bool directScanout = buf && buf->clientBuffer();
const bool directScanout = isBufferForDirectScanout();
if (gpu()->needsModeset()) {
if (directScanout) {
return false;
......@@ -96,6 +95,12 @@ bool DrmPipeline::present(const QSharedPointer<DrmBuffer> &buffer)
return true;
}
bool DrmPipeline::isBufferForDirectScanout() const
{
const auto buf = dynamic_cast<DrmGbmBuffer*>(m_primaryBuffer.data());
return buf && buf->clientBuffer();
}
bool DrmPipeline::commitPipelines(const QVector<DrmPipeline*> &pipelines, CommitMode mode, const QVector<DrmObject*> &unusedObjects)
{
Q_ASSERT(!pipelines.isEmpty());
......@@ -293,42 +298,21 @@ void DrmPipeline::atomicCommitSuccessful(CommitMode mode)
bool DrmPipeline::checkTestBuffer()
{
if (!pending.crtc || (m_primaryBuffer && m_primaryBuffer->size() == bufferSize())) {
const auto backend = gpu()->eglBackend();
if (!pending.crtc || (!(backend && m_output) && m_primaryBuffer && m_primaryBuffer->size() == bufferSize()) || isBufferForDirectScanout()) {
return true;
}
auto backend = gpu()->eglBackend();
QSharedPointer<DrmBuffer> buffer;
// try to re-use buffers if possible.
const auto &checkBuffer = [this, backend, &buffer](const QSharedPointer<DrmBuffer> &buf){
const auto &mods = supportedModifiers(buf->format());
if (backend && buf->format() == backend->drmFormat(m_output)
&& (mods.isEmpty() || mods.contains(buf->modifier()))
&& buf->size() == bufferSize()) {
buffer = buf;
}
};
if (pending.crtc->primaryPlane() && pending.crtc->primaryPlane()->next()) {
checkBuffer(pending.crtc->primaryPlane()->next());
} else if (pending.crtc->primaryPlane() && pending.crtc->primaryPlane()->current()) {
checkBuffer(pending.crtc->primaryPlane()->current());
} else if (pending.crtc->next()) {
checkBuffer(pending.crtc->next());
} else if (pending.crtc->current()) {
checkBuffer(pending.crtc->current());
}
// if we don't have a fitting buffer already, get or create one
if (!buffer) {
if (backend && m_output) {
buffer = backend->renderTestFrame(m_output);
} else if (backend && gpu()->gbmDevice()) {
gbm_bo *bo = gbm_bo_create(gpu()->gbmDevice(), bufferSize().width(), bufferSize().height(), DRM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
if (!bo) {
return false;
}
buffer = QSharedPointer<DrmGbmBuffer>::create(gpu(), bo, nullptr);
} else {
buffer = QSharedPointer<DrmDumbBuffer>::create(gpu(), bufferSize(), DRM_FORMAT_XRGB8888);
if (backend && m_output) {
buffer = backend->testBuffer(m_output);
} else if (backend && gpu()->gbmDevice()) {
gbm_bo *bo = gbm_bo_create(gpu()->gbmDevice(), bufferSize().width(), bufferSize().height(), DRM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
if (!bo) {
return false;
}
buffer = QSharedPointer<DrmGbmBuffer>::create(gpu(), bo, nullptr);
} else {
buffer = QSharedPointer<DrmDumbBuffer>::create(gpu(), bufferSize(), DRM_FORMAT_XRGB8888);
}
if (buffer && buffer->bufferId()) {
m_oldTestBuffer = m_primaryBuffer;
......
......@@ -127,6 +127,7 @@ private:
bool checkTestBuffer();
bool activePending() const;
bool isCursorVisible() const;
bool isBufferForDirectScanout() const;
// legacy only
bool presentLegacy();
......
This diff is collapsed.
......@@ -38,13 +38,14 @@ class DumbSwapchain;
class ShadowBuffer;
class DrmBackend;
class DrmGpu;
class EglGbmLayer;
struct GbmFormat {
uint32_t drmFormat;
EGLint redSize;
EGLint greenSize;
EGLint blueSize;
EGLint alphaSize;
uint32_t drmFormat = 0;
EGLint redSize = -1;
EGLint greenSize = -1;
EGLint blueSize = -1;
EGLint alphaSize = -1;
};
bool operator==(const GbmFormat &lhs, const GbmFormat &rhs);
......@@ -55,7 +56,7 @@ class EglGbmBackend : public AbstractEglBackend
{
Q_OBJECT
public:
EglGbmBackend(DrmBackend *drmBackend, DrmGpu *gpu);
EglGbmBackend(DrmBackend *drmBackend);
~EglGbmBackend() override;
SurfaceTexture *createSurfaceTextureInternal(SurfacePixmapInternal *pixmap) override;
......@@ -69,16 +70,12 @@ public:
QSharedPointer<GLTexture> textureForOutput(AbstractOutput *requestedOutput) const override;
bool hasOutput(AbstractOutput *output) const;
bool swapBuffers(DrmAbstractOutput *output, const QRegion &dirty);
bool exportFramebuffer(DrmAbstractOutput *output, void *data, const QSize &size, uint32_t stride);
bool exportFramebufferAsDmabuf(DrmAbstractOutput *output, int *fds, int *strides, int *offsets, uint32_t *num_fds, uint32_t *format, uint64_t *modifier);
bool directScanoutAllowed(AbstractOutput *output) const override;
QSharedPointer<DrmBuffer> renderTestFrame(DrmAbstractOutput *output);
uint32_t drmFormat(DrmAbstractOutput *output) const;
DrmGpu *gpu() const;
QSharedPointer<DrmBuffer> testBuffer(DrmAbstractOutput *output);
EGLConfig config(uint32_t format) const;
GbmFormat gbmFormatForDrmFormat(uint32_t format) const;
std::optional<uint32_t> chooseFormat(DrmAbstractOutput *output) const;
protected:
void cleanupSurfaces() override;
......@@ -88,56 +85,14 @@ private:
bool initializeEgl();
bool initBufferConfigs();
bool initRenderingContext();
void addOutput(AbstractOutput *output);
void removeOutput(AbstractOutput *output);
enum class ImportMode {
Dmabuf,
DumbBuffer
};
struct Output {
DrmAbstractOutput *output = nullptr;
bool forceXrgb8888 = false;
struct RenderData {
QSharedPointer<ShadowBuffer> shadowBuffer;
QSharedPointer<GbmSurface> gbmSurface;
GbmFormat format;
// for secondary GPU import
ImportMode importMode = ImportMode::Dmabuf;
QSharedPointer<DumbSwapchain> importSwapchain;
} old, current;
KWaylandServer::SurfaceInterface *scanoutSurface = nullptr;
struct {
QPointer<KWaylandServer::SurfaceInterface> surface;
QMap<uint32_t, QVector<uint64_t>> attemptedFormats;
} scanoutCandidate;
QPointer<KWaylandServer::SurfaceInterface> oldScanoutCandidate;
};
bool doesRenderFit(const Output &output, const Output::RenderData &render);
bool resetOutput(Output &output);
bool addOutput(DrmAbstractOutput *output);
void removeOutput(DrmAbstractOutput *output);
void setViewport(const Output &output) const;
QRegion prepareRenderingForOutput(Output &output);
QSharedPointer<DrmBuffer> importFramebuffer(Output &output, const QRegion &dirty) const;
QSharedPointer<DrmBuffer> endFrameWithBuffer(AbstractOutput *output, const QRegion &dirty);
std::optional<GbmFormat> chooseFormat(Output &output) const;
void cleanupRenderData(Output::RenderData &output);
QMap<AbstractOutput *, Output> m_outputs;
QMap<AbstractOutput *, QSharedPointer<EglGbmLayer>> m_surfaces;
DrmBackend *m_backend;
DrmGpu *m_gpu;
QVector<GbmFormat> m_formats;
QMap<uint32_t, EGLConfig> m_configs;
static EglGbmBackend *renderingBackend();
void setForceXrgb8888(DrmAbstractOutput *output);
friend class EglGbmTexture;
};
......
<
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "egl_gbm_layer.h"
#include "gbm_surface.h"
#include "drm_abstract_output.h"
#include "drm_gpu.h"
#include "egl_gbm_backend.h"
#include "shadowbuffer.h"
#include "drm_output.h"
#include "drm_pipeline.h"
#include "dumb_swapchain.h"
#include "logging.h"
#include "egl_dmabuf.h"
#include "surfaceitem_wayland.h"
#include "kwineglimagetexture.h"
#include "KWaylandServer/surface_interface.h"
#include "KWaylandServer/linuxdmabufv1clientbuffer.h"
#include <QRegion>
#include <drm_fourcc.h>
#include <gbm.h>
#include <errno.h>
#include <unistd.h>
namespace KWin
{
EglGbmLayer::EglGbmLayer(DrmGpu *renderGpu, DrmAbstractOutput *output)
: m_output(output)
, m_renderGpu(renderGpu)
{
}
EglGbmLayer::~EglGbmLayer()
{
if (m_gbmSurface && m_shadowBuffer && m_gbmSurface->makeContextCurrent()) {
m_shadowBuffer.reset();
}
if (m_oldGbmSurface && m_oldShadowBuffer && m_oldGbmSurface->makeContextCurrent()) {
m_oldShadowBuffer.reset();
}
}
std::optional<QRegion> EglGbmLayer::startRendering()
{
// dmabuf feedback
if (!m_scanoutCandidate.attemptedThisFrame && m_scanoutCandidate.surface) {
if (const auto feedback = m_scanoutCandidate.surface->dmabufFeedbackV1()) {
feedback->setTranches({});
}
m_scanoutCandidate.surface = nullptr;
}
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 std::optional<QRegion>();
}
// dmabuf might work with the new surface
m_importMode = MultiGpuImportMode::Dmabuf;
}
}
if (!m_gbmSurface->makeContextCurrent()) {
return std::optional<QRegion>();
}
auto repaintRegion = m_gbmSurface->repaintRegion(m_output->geometry());
// shadow buffer
if (doesShadowBufferFit(m_shadowBuffer.data())) {
m_oldShadowBuffer.reset();
} else {
if (doesShadowBufferFit(m_oldShadowBuffer.data())) {
m_shadowBuffer = m_oldShadowBuffer;
} else {
if (m_output->needsSoftwareTransformation()) {
const auto format = m_renderGpu->eglBackend()->gbmFormatForDrmFormat(m_gbmSurface->format());
m_shadowBuffer = QSharedPointer<ShadowBuffer>::create(m_output->sourceSize(), format);
if (!m_shadowBuffer->isComplete()) {
return std::optional<QRegion>();
}
} else {
m_shadowBuffer.reset();
}
}
}
if (m_shadowBuffer) {
m_shadowBuffer->bind();
// the blit after rendering will completely overwrite the back buffer anyways
repaintRegion = QRegion();
}
glViewport(0, 0, m_output->sourceSize().width(), m_output->sourceSize().height());
return repaintRegion;
}
bool EglGbmLayer::endRendering(const QRegion &damagedRegion)
{
if (m_shadowBuffer) {
m_shadowBuffer->render(m_output);
}
const auto buffer = m_gbmSurface->swapBuffersForDrm(damagedRegion.intersected(m_output->geometry()));
if (buffer) {
m_currentBuffer = buffer;
}
return buffer;
}
QSharedPointer<DrmBuffer> EglGbmLayer::testBuffer()
{
if (!m_currentBuffer || !doesGbmSurfaceFit(m_gbmSurface.data())) {
if (!renderTestBuffer() && m_importMode == MultiGpuImportMode::DumbBufferXrgb8888) {
// try multi-gpu import again, this time with DRM_FORMAT_XRGB8888
renderTestBuffer();
}
}
return m_currentBuffer;
}
bool EglGbmLayer::renderTestBuffer()
{
if (!startRendering()) {
return false;
}
glClear(GL_COLOR_BUFFER_BIT);
if (!endRendering(m_output->geometry())) {
return false;
}
return true;
}
bool EglGbmLayer::createGbmSurface()
{
static bool modifiersEnvSet = false;
static const bool modifiersEnv = qEnvironmentVariableIntValue("KWIN_DRM_USE_MODIFIERS", &modifiersEnvSet) != 0;
auto format = m_renderGpu->eglBackend()->chooseFormat(m_output);
if (!format || m_importMode == MultiGpuImportMode::DumbBufferXrgb8888) {
format = DRM_FORMAT_XRGB8888;
}
const auto modifiers = m_output->supportedModifiers(format.value());
const auto size = m_output->bufferSize();
const auto config = m_renderGpu->eglBackend()->config(format.value());
const bool allowModifiers = m_renderGpu->addFB2ModifiersSupported() && m_output->gpu()->addFB2ModifiersSupported()
&& ((m_renderGpu->isNVidia() && !modifiersEnvSet) || (modifiersEnvSet && modifiersEnv));
QSharedPointer<GbmSurface> 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_output->gpu() != m_renderGpu) {
#endif
int flags = GBM_BO_USE_RENDERING;
if (m_output->gpu() == m_renderGpu) {
flags |= GBM_BO_USE_SCANOUT;
} else {
flags |= GBM_BO_USE_LINEAR;
}
gbmSurface = QSharedPointer<GbmSurface>::create(m_renderGpu, size, format.value(), flags, config);
} else {
gbmSurface = QSharedPointer<GbmSurface>::create(m_renderGpu, size, format.value(), 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<uint64_t> linear = {DRM_FORMAT_MOD_LINEAR};
gbmSurface = QSharedPointer<GbmSurface>::create(m_renderGpu, size, format.value(), linear, config);
}
}
if (!gbmSurface->isValid()) {
return false;
}
m_oldGbmSurface = m_gbmSurface;
m_gbmSurface = gbmSurface;
return true;
}
bool EglGbmLayer::doesGbmSurfaceFit(GbmSurface *surf) const
{
return surf && surf->size() == m_output->bufferSize()
&& m_output->isFormatSupported(surf->format())
&& (m_importMode != MultiGpuImportMode::DumbBufferXrgb8888 || surf->format() == DRM_FORMAT_XRGB8888)
&& (surf->modifiers().isEmpty() || m_output->supportedModifiers(surf->format()) == surf->modifiers());
}
bool EglGbmLayer::doesShadowBufferFit(ShadowBuffer *buffer) const
{
if (m_output->needsSoftwareTransformation()) {
return buffer && buffer->textureSize() == m_output->sourceSize() && buffer->drmFormat() == m_gbmSurface->format();
} else {
return buffer == nullptr;
}
}
bool EglGbmLayer::doesSwapchainFit(DumbSwapchain *swapchain) const
{
return swapchain && swapchain->size() == m_output->sourceSize() && swapchain->drmFormat() == m_gbmSurface->format();
}
QSharedPointer<GLTexture> EglGbmLayer::texture() const
{
if (m_shadowBuffer) {
const auto glTexture = QSharedPointer<KWin::GLTexture>::create(m_shadowBuffer->texture(), GL_RGBA8, m_shadowBuffer->textureSize());
glTexture->setYInverted(true);
return glTexture;
}
GbmBuffer *gbmBuffer = m_gbmSurface->currentBuffer().get();
if (!gbmBuffer) {
qCWarning(KWIN_DRM) << "Failed to record frame: No gbm buffer!";
return nullptr;
}
EGLImageKHR image = eglCreateImageKHR(m_renderGpu->eglDisplay(), nullptr, EGL_NATIVE_PIXMAP_KHR, gbmBuffer->getBo(), nullptr);
if (image == EGL_NO_IMAGE_KHR) {
qCWarning(KWIN_DRM) << "Failed to record frame: Error creating EGLImageKHR - " << glGetError();
return nullptr;
}
return QSharedPointer<EGLImageTexture>::create(m_renderGpu->eglDisplay(), image, GL_RGBA8, m_output->modeSize());
}
QSharedPointer<DrmBuffer> 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<DrmBuffer> 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<uint32_t>(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_output->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_output->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<DrmGbmBuffer>::create(m_output->gpu(), importedBuffer, nullptr);
return buffer->bufferId() ? buffer : nullptr;
}
QSharedPointer<DrmBuffer> 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<DumbSwapchain>::create(m_output->gpu(), m_output->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)
{