backends/drm: port DrmPipeline and DrmGpu to use layers

Instead of having the render backends manage layers, have DrmGpu and DrmPipeline
do it. This makes it possible to unify code paths for leased and normal
outputs, remove some redirection and have more freedom with assigning layers
to screens.
This commit is contained in:
Xaver Hugl 2022-02-13 17:49:13 +01:00
parent cdac2690d1
commit a04bdf2355
24 changed files with 295 additions and 206 deletions

View file

@ -9,6 +9,7 @@
#include "drm_abstract_output.h"
#include "drm_gpu.h"
#include "drm_backend.h"
#include "renderloop_p.h"
namespace KWin
{
@ -30,4 +31,14 @@ QRect DrmAbstractOutput::renderGeometry() const
return geometry();
}
void DrmAbstractOutput::frameFailed() const
{
RenderLoopPrivate::get(m_renderLoop)->notifyFrameFailed();
}
void DrmAbstractOutput::pageFlipped(std::chrono::nanoseconds timestamp) const
{
RenderLoopPrivate::get(m_renderLoop)->notifyFrameCompleted(timestamp);
}
}

View file

@ -24,6 +24,8 @@ public:
RenderLoop *renderLoop() const override;
QRect renderGeometry() const override;
void frameFailed() const override;
void pageFlipped(std::chrono::nanoseconds timestamp) const override;
protected:
friend class DrmGpu;

View file

@ -17,6 +17,7 @@ namespace KWin
class DrmBuffer;
class DrmGpu;
class DrmLayer;
class DrmDisplayDevice
{
@ -26,7 +27,11 @@ public:
DrmGpu *gpu() const;
virtual bool present(const QSharedPointer<DrmBuffer> &buffer, QRegion damagedRegion) = 0;
virtual bool present() = 0;
virtual bool testScanout() = 0;
virtual void frameFailed() const = 0;
virtual void pageFlipped(std::chrono::nanoseconds timestamp) const = 0;
virtual DrmPlane::Transformations softwareTransforms() const = 0;
virtual QSize bufferSize() const = 0;
virtual QSize sourceSize() const = 0;
@ -34,6 +39,7 @@ public:
virtual QVector<uint64_t> supportedModifiers(uint32_t drmFormat) const = 0;
virtual int maxBpc() const = 0;
virtual QRect renderGeometry() const = 0;
virtual DrmLayer *outputLayer() const = 0;
protected:
DrmGpu *const m_gpu;

View file

@ -309,9 +309,9 @@ bool DrmGpu::updateOutputs()
if (testPendingConfiguration()) {
for (const auto &pipeline : qAsConst(m_pipelines)) {
pipeline->applyPendingChanges();
if (!pipeline->pending.crtc && pipeline->output()) {
if (const auto drmOutput = dynamic_cast<DrmAbstractOutput*>(pipeline->displayDevice()); drmOutput && !pipeline->pending.crtc) {
pipeline->pending.enabled = false;
pipeline->output()->setEnabled(false);
drmOutput->setEnabled(false);
}
}
} else {
@ -421,6 +421,9 @@ bool DrmGpu::testPipelines()
// pipelines that are enabled but not active need to be activated for the test
QVector<DrmPipeline*> inactivePipelines;
for (const auto &pipeline : qAsConst(m_pipelines)) {
if (!pipeline->pending.layer) {
pipeline->pending.layer = m_renderBackend->createLayer(pipeline->displayDevice());
}
if (!pipeline->pending.active) {
pipeline->pending.active = true;
inactivePipelines << pipeline;
@ -553,6 +556,7 @@ void DrmGpu::removeOutput(DrmOutput *output)
qCDebug(KWIN_DRM) << "Removing output" << output;
m_drmOutputs.removeOne(output);
m_pipelines.removeOne(output->pipeline());
output->pipeline()->pending.layer.reset();
m_outputs.removeOne(output);
Q_EMIT outputRemoved(output);
delete output;
@ -560,12 +564,17 @@ void DrmGpu::removeOutput(DrmOutput *output)
EglGbmBackend *DrmGpu::eglBackend() const
{
return m_eglBackend;
return dynamic_cast<EglGbmBackend*>(m_renderBackend);
}
void DrmGpu::setEglBackend(EglGbmBackend *eglBackend)
DrmRenderBackend *DrmGpu::renderBackend() const
{
m_eglBackend = eglBackend;
return m_renderBackend;
}
void DrmGpu::setRenderBackend(DrmRenderBackend *backend)
{
m_renderBackend = backend;
}
DrmBackend *DrmGpu::platform() const {
@ -661,6 +670,7 @@ void DrmGpu::removeLeaseOutput(DrmLeaseOutput *output)
qCDebug(KWIN_DRM) << "Removing leased output" << output;
m_leaseOutputs.removeOne(output);
m_pipelines.removeOne(output->pipeline());
output->pipeline()->pending.layer.reset();
delete output;
}
@ -740,10 +750,10 @@ bool DrmGpu::maybeModeset()
}
const bool ok = DrmPipeline::commitPipelines(pipelines, DrmPipeline::CommitMode::CommitModeset, unusedObjects());
for (DrmPipeline *pipeline : qAsConst(pipelines)) {
if (pipeline->modesetPresentPending() && pipeline->output()) {
if (pipeline->modesetPresentPending()) {
pipeline->resetModesetPresentPending();
if (!ok) {
pipeline->output()->presentFailed();
pipeline->displayDevice()->frameFailed();
}
}
}

View file

@ -41,6 +41,7 @@ class DrmPipeline;
class DrmAbstractOutput;
class DrmVirtualOutput;
class DrmLeaseOutput;
class DrmRenderBackend;
class DrmGpu : public QObject
{
@ -59,6 +60,7 @@ public:
gbm_device *gbmDevice() const;
EGLDisplay eglDisplay() const;
EglGbmBackend *eglBackend() const;
DrmRenderBackend *renderBackend() const;
DrmBackend *platform() const;
/**
* Returns the clock from which presentation timestamps are sourced. The returned value
@ -71,7 +73,7 @@ public:
const QVector<DrmPipeline*> pipelines() const;
void setEglDisplay(EGLDisplay display);
void setEglBackend(EglGbmBackend *eglBackend);
void setRenderBackend(DrmRenderBackend *backend);
bool updateOutputs();
@ -120,7 +122,7 @@ private:
clockid_t m_presentationClock;
gbm_device* m_gbmDevice;
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
QPointer<EglGbmBackend> m_eglBackend;
DrmRenderBackend *m_renderBackend;
DrmBackend* const m_platform;
QVector<DrmPlane*> m_planes;

View file

@ -41,6 +41,9 @@ public:
virtual QSharedPointer<DrmBuffer> testBuffer() = 0;
virtual QSharedPointer<DrmBuffer> currentBuffer() const = 0;
virtual QRegion currentDamage() const = 0;
virtual bool hasDirectScanoutBuffer() const = 0;
virtual DrmDisplayDevice *displayDevice() const = 0;
};

View file

@ -32,11 +32,13 @@ DrmLeaseOutput::DrmLeaseOutput(DrmPipeline *pipeline, KWaylandServer::DrmLeaseDe
, DrmDisplayDevice(pipeline->gpu())
, m_pipeline(pipeline)
{
m_pipeline->setDisplayDevice(this);
qCDebug(KWIN_DRM) << "offering connector" << m_pipeline->connector()->id() << "for lease";
}
DrmLeaseOutput::~DrmLeaseOutput()
{
m_pipeline->setDisplayDevice(nullptr);
qCDebug(KWIN_DRM) << "revoking lease offer for connector" << m_pipeline->connector()->id();
}
@ -76,10 +78,13 @@ DrmPipeline *DrmLeaseOutput::pipeline() const
return m_pipeline;
}
bool DrmLeaseOutput::present(const QSharedPointer<DrmBuffer> &buffer, QRegion damagedRegion)
bool DrmLeaseOutput::present()
{
return false;
}
bool DrmLeaseOutput::testScanout()
{
Q_UNUSED(buffer)
Q_UNUSED(damagedRegion)
return false;
}
@ -122,4 +127,18 @@ QRect DrmLeaseOutput::renderGeometry() const
return QRect(QPoint(), m_pipeline->sourceSize());
}
DrmLayer *DrmLeaseOutput::outputLayer() const
{
return m_pipeline->pending.layer.data();
}
void DrmLeaseOutput::frameFailed() const
{
}
void DrmLeaseOutput::pageFlipped(std::chrono::nanoseconds timestamp) const
{
Q_UNUSED(timestamp)
}
}

View file

@ -40,7 +40,7 @@ public:
KWaylandServer::DrmLeaseV1Interface *lease() const;
DrmPipeline *pipeline() const;
bool present(const QSharedPointer<DrmBuffer> &buffer, QRegion damagedRegion) override;
bool present() override;
DrmPlane::Transformations softwareTransforms() const override;
QSize bufferSize() const override;
QSize sourceSize() const override;
@ -48,6 +48,10 @@ public:
QVector<uint64_t> supportedModifiers(uint32_t drmFormat) const override;
int maxBpc() const override;
QRect renderGeometry() const override;
DrmLayer *outputLayer() const override;
bool testScanout() override;
void frameFailed() const override;
void pageFlipped(std::chrono::nanoseconds timestamp) const override;
private:
DrmPipeline *m_pipeline;

View file

@ -26,6 +26,7 @@
#include "waylandoutputconfig.h"
#include "dumb_swapchain.h"
#include "cursor.h"
#include "drm_layer.h"
// Qt
#include <QMatrix4x4>
#include <QCryptographicHash>
@ -45,7 +46,7 @@ DrmOutput::DrmOutput(DrmPipeline *pipeline)
, m_pipeline(pipeline)
, m_connector(pipeline->connector())
{
m_pipeline->setOutput(this);
m_pipeline->setDisplayDevice(this);
const auto conn = m_pipeline->connector();
m_renderLoop->setRefreshRate(m_pipeline->pending.mode->refreshRate());
setSubPixelInternal(conn->subpixel());
@ -78,7 +79,7 @@ DrmOutput::DrmOutput(DrmPipeline *pipeline)
DrmOutput::~DrmOutput()
{
m_pipeline->setOutput(nullptr);
m_pipeline->setDisplayDevice(nullptr);
}
static bool isCursorSpriteCompatible(const QImage *buffer, const QImage *sprite)
@ -318,12 +319,8 @@ void DrmOutput::updateModes()
}
}
bool DrmOutput::present(const QSharedPointer<DrmBuffer> &buffer, QRegion damagedRegion)
bool DrmOutput::present()
{
if (!buffer || buffer->bufferId() == 0) {
presentFailed();
return false;
}
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_renderLoop);
if (m_pipeline->pending.syncMode != renderLoopPrivate->presentMode) {
m_pipeline->pending.syncMode = renderLoopPrivate->presentMode;
@ -334,14 +331,21 @@ bool DrmOutput::present(const QSharedPointer<DrmBuffer> &buffer, QRegion damaged
setVrrPolicy(RenderLoop::VrrPolicy::Never);
}
}
if (m_pipeline->present(buffer)) {
Q_EMIT outputChange(damagedRegion);
if (m_pipeline->present()) {
Q_EMIT outputChange(m_pipeline->pending.layer->currentDamage());
return true;
} else {
qCWarning(KWIN_DRM) << "Presentation failed!" << strerror(errno);
frameFailed();
return false;
}
}
bool DrmOutput::testScanout()
{
return m_pipeline->testScanout();
}
int DrmOutput::gammaRampSize() const
{
return m_pipeline->pending.crtc ? m_pipeline->pending.crtc->gammaRampSize() : 256;
@ -446,16 +450,6 @@ void DrmOutput::revertQueuedChanges()
m_pipeline->revertPendingChanges();
}
void DrmOutput::pageFlipped(std::chrono::nanoseconds timestamp)
{
RenderLoopPrivate::get(m_renderLoop)->notifyFrameCompleted(timestamp);
}
void DrmOutput::presentFailed()
{
RenderLoopPrivate::get(m_renderLoop)->notifyFrameFailed();
}
int DrmOutput::maxBpc() const
{
auto prop = m_connector->getProp(DrmConnector::PropertyIndex::MaxBpc);
@ -477,4 +471,9 @@ DrmPlane::Transformations DrmOutput::softwareTransforms() const
}
}
DrmLayer *DrmOutput::outputLayer() const
{
return m_pipeline->pending.layer.data();
}
}

View file

@ -43,25 +43,24 @@ public:
DrmOutput(DrmPipeline *pipeline);
~DrmOutput() override;
bool present(const QSharedPointer<DrmBuffer> &buffer, QRegion damagedRegion) override;
DrmConnector *connector() const;
DrmPipeline *pipeline() const;
bool present() override;
bool testScanout() override;
QSize bufferSize() const override;
QSize sourceSize() const override;
bool isFormatSupported(uint32_t drmFormat) const override;
QVector<uint64_t> supportedModifiers(uint32_t drmFormat) const override;
DrmPlane::Transformations softwareTransforms() const override;
int maxBpc() const override;
DrmLayer *outputLayer() const override;
bool queueChanges(const WaylandOutputConfig &config);
void applyQueuedChanges(const WaylandOutputConfig &config);
void revertQueuedChanges();
void updateModes();
void pageFlipped(std::chrono::nanoseconds timestamp);
void presentFailed();
bool usesSoftwareCursor() const override;
private:

View file

@ -23,6 +23,7 @@
#include "drm_backend.h"
#include "egl_gbm_backend.h"
#include "drm_buffer_gbm.h"
#include "drm_layer.h"
#include <gbm.h>
#include <drm_fourcc.h>
@ -31,30 +32,37 @@ namespace KWin
{
DrmPipeline::DrmPipeline(DrmConnector *conn)
: m_output(nullptr)
: m_displayDevice(nullptr)
, m_connector(conn)
{
}
DrmPipeline::~DrmPipeline()
{
m_output = nullptr;
if (m_pageflipPending && m_current.crtc) {
pageFlipped({});
}
}
bool DrmPipeline::present(const QSharedPointer<DrmBuffer> &buffer)
bool DrmPipeline::testScanout()
{
// TODO make the modeset check only be tested at most once per scanout cycle
if (gpu()->needsModeset()) {
return false;
}
if (gpu()->atomicModeSetting()) {
return commitPipelines({this}, CommitMode::Test);
} else {
// no other way to test than to do it.
// As we only have a maximum of one test per scanout cycle, this is fine
return presentLegacy();
}
}
bool DrmPipeline::present()
{
Q_ASSERT(pending.crtc);
Q_ASSERT(buffer);
m_primaryBuffer = buffer;
// with direct scanout disallow modesets, calling presentFailed() and logging warnings
const bool directScanout = isBufferForDirectScanout();
if (gpu()->needsModeset()) {
if (directScanout) {
return false;
}
m_modesetPresentPending = true;
return gpu()->maybeModeset();
}
@ -72,35 +80,22 @@ bool DrmPipeline::present(const QSharedPointer<DrmBuffer> &buffer)
}
}
if (!commitPipelines({this}, CommitMode::Commit)) {
if (directScanout) {
return false;
}
qCWarning(KWIN_DRM) << "Atomic present failed!" << strerror(errno);
printDebugInfo();
if (m_output) {
m_output->presentFailed();
}
return false;
}
}
} else {
if (pending.layer->hasDirectScanoutBuffer()) {
// already presented
return true;
}
if (!presentLegacy()) {
qCWarning(KWIN_DRM) << "Present failed!" << strerror(errno);
if (m_output) {
m_output->presentFailed();
}
return false;
}
}
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());
@ -133,7 +128,7 @@ bool DrmPipeline::commitPipelinesAtomic(const QVector<DrmPipeline*> &pipelines,
return false;
};
for (const auto &pipeline : pipelines) {
if (!pipeline->checkTestBuffer()) {
if (!pipeline->pending.layer->testBuffer()) {
qCWarning(KWIN_DRM) << "Checking test buffer failed for" << mode;
return failed();
}
@ -197,8 +192,9 @@ bool DrmPipeline::populateAtomicValues(drmModeAtomicReq *req, uint32_t &flags)
pending.crtc->setPending(DrmCrtc::PropertyIndex::VrrEnabled, pending.syncMode == RenderLoopPrivate::SyncMode::Adaptive);
pending.crtc->setPending(DrmCrtc::PropertyIndex::Gamma_LUT, pending.gamma ? pending.gamma->blobId() : 0);
const auto modeSize = pending.mode->size();
pending.crtc->primaryPlane()->set(QPoint(0, 0), m_primaryBuffer ? m_primaryBuffer->size() : bufferSize(), QPoint(0, 0), modeSize);
pending.crtc->primaryPlane()->setBuffer(activePending() ? m_primaryBuffer.get() : nullptr);
const auto buffer = pending.layer->currentBuffer().data();
pending.crtc->primaryPlane()->set(QPoint(0, 0), buffer ? buffer->size() : bufferSize(), QPoint(0, 0), modeSize);
pending.crtc->primaryPlane()->setBuffer(activePending() ? buffer : nullptr);
if (pending.crtc->cursorPlane()) {
pending.crtc->cursorPlane()->set(QPoint(0, 0), gpu()->cursorSize(), pending.cursorPos, gpu()->cursorSize());
@ -271,10 +267,6 @@ uint32_t DrmPipeline::calculateUnderscan()
void DrmPipeline::atomicCommitFailed()
{
if (m_oldTestBuffer) {
m_primaryBuffer = m_oldTestBuffer;
m_oldTestBuffer = nullptr;
}
m_connector->rollbackPending();
if (pending.crtc) {
pending.crtc->rollbackPending();
@ -287,7 +279,6 @@ void DrmPipeline::atomicCommitFailed()
void DrmPipeline::atomicCommitSuccessful(CommitMode mode)
{
m_oldTestBuffer = nullptr;
m_connector->commitPending();
if (pending.crtc) {
pending.crtc->commitPending();
@ -303,7 +294,7 @@ void DrmPipeline::atomicCommitSuccessful(CommitMode mode)
m_connector->commit();
if (pending.crtc) {
pending.crtc->commit();
pending.crtc->primaryPlane()->setNext(m_primaryBuffer);
pending.crtc->primaryPlane()->setNext(pending.layer->currentBuffer());
pending.crtc->primaryPlane()->commit();
if (pending.crtc->cursorPlane()) {
pending.crtc->cursorPlane()->setNext(pending.cursorBo);
@ -317,32 +308,6 @@ void DrmPipeline::atomicCommitSuccessful(CommitMode mode)
}
}
bool DrmPipeline::checkTestBuffer()
{
const auto backend = gpu()->eglBackend();
if (!pending.crtc || (!(backend && m_output) && m_primaryBuffer && m_primaryBuffer->size() == bufferSize()) || isBufferForDirectScanout()) {
return true;
}
QSharedPointer<DrmBuffer> buffer;
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;
m_primaryBuffer = buffer;
return true;
}
return false;
}
bool DrmPipeline::setCursor(const QSharedPointer<DrmDumbBuffer> &buffer, const QPoint &hotspot)
{
if (pending.cursorBo == buffer && pending.cursorHotspot == hotspot) {
@ -360,8 +325,8 @@ bool DrmPipeline::setCursor(const QSharedPointer<DrmDumbBuffer> &buffer, const Q
}
if (result) {
m_next = pending;
if (m_output && (visibleBefore || isCursorVisible())) {
m_output->renderLoop()->scheduleRepaint();
if (const auto drmOutput = dynamic_cast<DrmOutput*>(m_displayDevice); drmOutput && (visibleBefore || isCursorVisible())) {
drmOutput->renderLoop()->scheduleRepaint();
}
} else {
pending = m_next;
@ -385,8 +350,8 @@ bool DrmPipeline::moveCursor(QPoint pos)
}
if (result) {
m_next = pending;
if (m_output && (visibleBefore || isCursorVisible())) {
m_output->renderLoop()->scheduleRepaint();
if (const auto drmOutput = dynamic_cast<DrmOutput*>(m_displayDevice); drmOutput && (visibleBefore || isCursorVisible())) {
drmOutput->renderLoop()->scheduleRepaint();
}
} else {
pending = m_next;
@ -446,19 +411,12 @@ void DrmPipeline::pageFlipped(std::chrono::nanoseconds timestamp)
m_current.crtc->cursorPlane()->flipBuffer();
}
m_pageflipPending = false;
if (m_output) {
m_output->pageFlipped(timestamp);
}
m_displayDevice->pageFlipped(timestamp);
}
void DrmPipeline::setOutput(DrmOutput *output)
void DrmPipeline::setDisplayDevice(DrmDisplayDevice *device)
{
m_output = output;
}
DrmOutput *DrmPipeline::output() const
{
return m_output;
m_displayDevice = device;
}
static const QMap<uint32_t, QVector<uint64_t>> legacyFormats = {
@ -541,6 +499,11 @@ DrmCrtc *DrmPipeline::currentCrtc() const
return m_current.crtc;
}
DrmDisplayDevice *DrmPipeline::displayDevice() const
{
return m_displayDevice;
}
DrmGammaRamp::DrmGammaRamp(DrmGpu *gpu, const GammaRamp &lut)
: m_gpu(gpu)
, m_lut(lut)

View file

@ -31,6 +31,8 @@ class DrmBuffer;
class DrmDumbBuffer;
class GammaRamp;
class DrmConnectorMode;
class DrmLayer;
class DrmDisplayDevice;
class DrmGammaRamp
{
@ -60,7 +62,8 @@ public:
* tests the pending commit first and commits it if the test passes
* if the test fails, there is a guarantee for no lasting changes
*/
bool present(const QSharedPointer<DrmBuffer> &buffer);
bool present();
bool testScanout();
bool needsModeset() const;
void applyPendingChanges();
@ -91,8 +94,8 @@ public:
QVector<uint64_t> supportedModifiers(uint32_t drmFormat) const;
QMap<uint32_t, QVector<uint64_t>> supportedFormats() const;
void setOutput(DrmOutput *output);
DrmOutput *output() const;
void setDisplayDevice(DrmDisplayDevice *device);
DrmDisplayDevice *displayDevice() const;
struct State {
DrmCrtc *crtc = nullptr;
@ -104,6 +107,8 @@ public:
RenderLoopPrivate::SyncMode syncMode = RenderLoopPrivate::SyncMode::Fixed;
QSharedPointer<DrmGammaRamp> gamma;
QSharedPointer<DrmLayer> layer;
QPoint cursorPos;
QPoint cursorHotspot;
QSharedPointer<DrmDumbBuffer> cursorBo;
@ -124,7 +129,6 @@ public:
static bool commitPipelines(const QVector<DrmPipeline*> &pipelines, CommitMode mode, const QVector<DrmObject*> &unusedObjects = {});
private:
bool checkTestBuffer();
bool activePending() const;
bool isCursorVisible() const;
bool isBufferForDirectScanout() const;
@ -150,11 +154,9 @@ private:
static void printFlags(uint32_t flags);
static void printProps(DrmObject *object, PrintMode mode);
DrmOutput *m_output = nullptr;
DrmDisplayDevice *m_displayDevice = nullptr;
DrmConnector *m_connector = nullptr;
QSharedPointer<DrmBuffer> m_primaryBuffer;
QSharedPointer<DrmBuffer> m_oldTestBuffer;
bool m_pageflipPending = false;
bool m_modesetPresentPending = false;

View file

@ -13,6 +13,7 @@
#include "drm_object_crtc.h"
#include "drm_object_connector.h"
#include "logging.h"
#include "drm_layer.h"
#include <errno.h>
@ -21,33 +22,32 @@ namespace KWin
bool DrmPipeline::presentLegacy()
{
if ((!pending.crtc->current() || pending.crtc->current()->needsModeChange(m_primaryBuffer.get())) && !legacyModeset()) {
const auto buffer = pending.layer->currentBuffer();
if ((!pending.crtc->current() || pending.crtc->current()->needsModeChange(buffer.get())) && !legacyModeset()) {
return false;
}
if (drmModePageFlip(gpu()->fd(), pending.crtc->id(), m_primaryBuffer ? m_primaryBuffer->bufferId() : 0, DRM_MODE_PAGE_FLIP_EVENT, nullptr) != 0) {
qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno) << m_primaryBuffer;
if (drmModePageFlip(gpu()->fd(), pending.crtc->id(), buffer ? buffer->bufferId() : 0, DRM_MODE_PAGE_FLIP_EVENT, nullptr) != 0) {
qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno) << buffer;
return false;
}
m_pageflipPending = true;
pending.crtc->setNext(m_primaryBuffer);
pending.crtc->setNext(buffer);
return true;
}
bool DrmPipeline::legacyModeset()
{
uint32_t connId = m_connector->id();
if (!checkTestBuffer() || drmModeSetCrtc(gpu()->fd(), pending.crtc->id(), m_primaryBuffer->bufferId(), 0, 0, &connId, 1, pending.mode->nativeMode()) != 0) {
if (!pending.layer->testBuffer() || drmModeSetCrtc(gpu()->fd(), pending.crtc->id(), pending.layer->currentBuffer()->bufferId(), 0, 0, &connId, 1, pending.mode->nativeMode()) != 0) {
qCWarning(KWIN_DRM) << "Modeset failed!" << strerror(errno);
pending = m_next;
m_primaryBuffer = m_oldTestBuffer;
return false;
}
m_oldTestBuffer = nullptr;
// make sure the buffer gets kept alive, or the modeset gets reverted by the kernel
if (pending.crtc->current()) {
pending.crtc->setNext(m_primaryBuffer);
pending.crtc->setNext(pending.layer->currentBuffer());
} else {
pending.crtc->setCurrent(m_primaryBuffer);
pending.crtc->setCurrent(pending.layer->currentBuffer());
}
return true;
}

View file

@ -35,6 +35,7 @@ std::optional<QRegion> DrmQPainterLayer::startRendering()
bool DrmQPainterLayer::endRendering(const QRegion &damagedRegion)
{
m_currentDamage = damagedRegion;
m_swapchain->releaseBuffer(m_swapchain->currentBuffer(), damagedRegion);
return true;
}
@ -63,9 +64,19 @@ QSharedPointer<DrmBuffer> DrmQPainterLayer::currentBuffer() const
return m_swapchain ? m_swapchain->currentBuffer() : nullptr;
}
QRegion DrmQPainterLayer::currentDamage() const
{
return m_currentDamage;
}
DrmDisplayDevice *DrmQPainterLayer::displayDevice() const
{
return m_displayDevice;
}
bool DrmQPainterLayer::hasDirectScanoutBuffer() const
{
return false;
}
}

View file

@ -23,12 +23,15 @@ public:
bool scanout(SurfaceItem *surfaceItem) override;
QSharedPointer<DrmBuffer> testBuffer() override;
QSharedPointer<DrmBuffer> currentBuffer() const override;
QRegion currentDamage() const override;
bool hasDirectScanoutBuffer() const override;
DrmDisplayDevice *displayDevice() const override;
private:
bool doesSwapchainFit() const;
QSharedPointer<DumbSwapchain> m_swapchain;
QRegion m_currentDamage;
DrmDisplayDevice *const m_displayDevice;
};

View file

@ -0,0 +1,28 @@
/*
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
*/
#pragma once
#include <QSharedPointer>
namespace KWin
{
class DrmLayer;
class DrmDisplayDevice;
class DrmRenderBackend
{
public:
virtual ~DrmRenderBackend() = default;
virtual QSharedPointer<DrmLayer> createLayer(DrmDisplayDevice *displayDevice) const = 0;
};
}

View file

@ -14,9 +14,12 @@
#include "drm_gpu.h"
#include "drm_backend.h"
#include "logging.h"
#include "drm_layer.h"
#include "drm_render_backend.h"
namespace KWin
{
static int s_serial = 0;
DrmVirtualOutput::DrmVirtualOutput(DrmGpu *gpu, const QSize &size)
: DrmVirtualOutput(QString::number(s_serial++), gpu, size)
@ -40,20 +43,19 @@ DrmVirtualOutput::DrmVirtualOutput(const QString &name, DrmGpu *gpu, const QSize
modes,
QByteArray("EDID_") + name.toUtf8());
m_renderLoop->setRefreshRate(modes[m_modeIndex].refreshRate);
m_layer = gpu->renderBackend()->createLayer(this);
}
DrmVirtualOutput::~DrmVirtualOutput()
{
}
bool DrmVirtualOutput::present(const QSharedPointer<DrmBuffer> &buffer, QRegion damagedRegion)
bool DrmVirtualOutput::present()
{
Q_UNUSED(damagedRegion)
m_currentBuffer = buffer;
m_vsyncMonitor->arm();
m_pageFlipPending = true;
Q_EMIT outputChange(damagedRegion);
Q_EMIT outputChange(m_layer->currentDamage());
return true;
}
@ -119,4 +121,14 @@ DrmPlane::Transformations DrmVirtualOutput::softwareTransforms() const
return DrmPlane::Transformation::Rotate0;
}
DrmLayer *DrmVirtualOutput::outputLayer() const
{
return m_layer.data();
}
bool DrmVirtualOutput::testScanout()
{
return true;
}
}

View file

@ -19,6 +19,7 @@ namespace KWin
class SoftwareVsyncMonitor;
class VirtualBackend;
class DrmLayer;
class DrmVirtualOutput : public DrmAbstractOutput
{
@ -28,7 +29,7 @@ public:
DrmVirtualOutput(DrmGpu *gpu, const QSize &size);
~DrmVirtualOutput() override;
bool present(const QSharedPointer<DrmBuffer> &buffer, QRegion damagedRegion) override;
bool present() override;
QSize bufferSize() const override;
QSize sourceSize() const override;
@ -39,13 +40,15 @@ public:
int gammaRampSize() const override;
bool setGammaRamp(const GammaRamp &gamma) override;
DrmPlane::Transformations softwareTransforms() const override;
DrmLayer *outputLayer() const override;
bool testScanout() override;
private:
void vblank(std::chrono::nanoseconds timestamp);
void setDpmsMode(DpmsMode mode) override;
void updateEnablement(bool enable) override;
QSharedPointer<DrmBuffer> m_currentBuffer;
QSharedPointer<DrmLayer> m_layer;
bool m_pageFlipPending = true;
int m_modeIndex = 0;

View file

@ -49,20 +49,14 @@ EglGbmBackend::EglGbmBackend(DrmBackend *drmBackend)
: AbstractEglBackend(drmBackend->primaryGpu()->deviceId())
, m_backend(drmBackend)
{
drmBackend->primaryGpu()->setEglBackend(this);
connect(m_backend, &DrmBackend::outputAdded, this, &EglGbmBackend::addOutput);
connect(m_backend, &DrmBackend::outputRemoved, this, &EglGbmBackend::removeOutput);
drmBackend->primaryGpu()->setRenderBackend(this);
setIsDirectRendering(true);
}
EglGbmBackend::~EglGbmBackend()
{
cleanup();
}
void EglGbmBackend::cleanupSurfaces()
{
m_surfaces.clear();
m_backend->primaryGpu()->setRenderBackend(nullptr);
}
bool EglGbmBackend::initializeEgl()
@ -124,23 +118,9 @@ bool EglGbmBackend::initRenderingContext()
if (!createContext() || !makeCurrent()) {
return false;
}
const auto outputs = m_backend->outputs();
for (const auto &output : outputs) {
addOutput(output);
}
return true;
}
void EglGbmBackend::addOutput(AbstractOutput *output) {
auto drmOutput = static_cast<DrmAbstractOutput*>(output);
m_surfaces.insert(drmOutput, QSharedPointer<EglGbmLayer>::create(m_backend->primaryGpu(), drmOutput));
}
void EglGbmBackend::removeOutput(AbstractOutput *output)
{
m_surfaces.remove(output);
}
bool EglGbmBackend::initBufferConfigs()
{
const EGLint config_attribs[] = {
@ -250,9 +230,8 @@ static QVector<EGLint> regionToRects(const QRegion &region, DrmAbstractOutput *o
void EglGbmBackend::aboutToStartPainting(AbstractOutput *output, const QRegion &damagedRegion)
{
Q_ASSERT_X(output, "aboutToStartPainting", "not using per screen rendering");
Q_ASSERT(m_surfaces.contains(output));
const auto &surface = m_surfaces[output];
const auto drmOutput = static_cast<DrmAbstractOutput*>(output);
const auto &surface = static_cast<EglGbmLayer*>(drmOutput->outputLayer());
if (surface->bufferAge() > 0 && !damagedRegion.isEmpty() && supportsPartialUpdate()) {
QVector<EGLint> rects = regionToRects(damagedRegion, static_cast<DrmAbstractOutput*>(output));
const bool correct = eglSetDamageRegionKHR(eglDisplay(), surface->eglSurface(), rects.data(), rects.count()/4);
@ -274,34 +253,34 @@ SurfaceTexture *EglGbmBackend::createSurfaceTextureWayland(SurfacePixmapWayland
QRegion EglGbmBackend::beginFrame(AbstractOutput *output)
{
Q_ASSERT(m_surfaces.contains(output));
return m_surfaces[output]->startRendering().value_or(QRegion());
return static_cast<DrmAbstractOutput*>(output)->outputLayer()->startRendering().value_or(QRegion());
}
void EglGbmBackend::endFrame(AbstractOutput *output, const QRegion &renderedRegion,
const QRegion &damagedRegion)
{
Q_ASSERT(m_surfaces.contains(output));
Q_UNUSED(renderedRegion)
m_surfaces[output]->endRendering(damagedRegion);
static_cast<DrmAbstractOutput*>(output)->present(m_surfaces[output]->currentBuffer(), damagedRegion);
const auto drmOutput = static_cast<DrmAbstractOutput*>(output);
drmOutput->outputLayer()->endRendering(damagedRegion);
drmOutput->present();
}
bool EglGbmBackend::scanout(AbstractOutput *output, SurfaceItem *surfaceItem)
{
return m_surfaces[output]->scanout(surfaceItem);
}
QSharedPointer<DrmBuffer> EglGbmBackend::testBuffer(DrmAbstractOutput *output)
{
return m_surfaces[output]->testBuffer();
const auto drmOutput = static_cast<DrmAbstractOutput*>(output);
if (drmOutput->outputLayer()->scanout(surfaceItem)) {
drmOutput->present();
return true;
} else {
return false;
}
}
QSharedPointer<GLTexture> EglGbmBackend::textureForOutput(AbstractOutput *output) const
{
Q_ASSERT(m_surfaces.contains(output));
return m_surfaces[output]->texture();
const auto drmOutput = static_cast<DrmAbstractOutput*>(output);
return static_cast<EglGbmLayer*>(drmOutput->outputLayer())->texture();
}
GbmFormat EglGbmBackend::gbmFormatForDrmFormat(uint32_t format) const
@ -352,6 +331,11 @@ EGLConfig EglGbmBackend::config(uint32_t format) const
return m_configs[format];
}
QSharedPointer<DrmLayer> EglGbmBackend::createLayer(DrmDisplayDevice *displayDevice) const
{
return QSharedPointer<EglGbmLayer>::create(m_backend->primaryGpu(), displayDevice);
}
bool operator==(const GbmFormat &lhs, const GbmFormat &rhs)
{
return lhs.drmFormat == rhs.drmFormat;

View file

@ -6,9 +6,9 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef KWIN_EGL_GBM_BACKEND_H
#define KWIN_EGL_GBM_BACKEND_H
#pragma once
#include "abstract_egl_backend.h"
#include "drm_render_backend.h"
#include <kwinglutils.h>
@ -52,7 +52,7 @@ bool operator==(const GbmFormat &lhs, const GbmFormat &rhs);
/**
* @brief OpenGL Backend using Egl on a GBM surface.
*/
class EglGbmBackend : public AbstractEglBackend
class EglGbmBackend : public AbstractEglBackend, public DrmRenderBackend
{
Q_OBJECT
public:
@ -67,6 +67,7 @@ public:
void init() override;
bool scanout(AbstractOutput *output, SurfaceItem *surfaceItem) override;
bool prefer10bpc() const override;
QSharedPointer<DrmLayer> createLayer(DrmDisplayDevice *displayDevice) const override;
QSharedPointer<GLTexture> textureForOutput(AbstractOutput *requestedOutput) const override;
@ -76,17 +77,13 @@ public:
std::optional<uint32_t> chooseFormat(DrmDisplayDevice *displyDevice) const;
protected:
void cleanupSurfaces() override;
void aboutToStartPainting(AbstractOutput *output, const QRegion &damage) override;
private:
bool initializeEgl();
bool initBufferConfigs();
bool initRenderingContext();
void addOutput(AbstractOutput *output);
void removeOutput(AbstractOutput *output);
QMap<AbstractOutput *, QSharedPointer<EglGbmLayer>> m_surfaces;
DrmBackend *m_backend;
QVector<GbmFormat> m_formats;
QMap<uint32_t, EGLConfig> m_configs;
@ -95,5 +92,3 @@ private:
};
} // namespace
#endif

View file

@ -50,6 +50,7 @@ EglGbmLayer::~EglGbmLayer()
std::optional<QRegion> EglGbmLayer::startRendering()
{
m_scanoutBuffer.reset();
// dmabuf feedback
if (!m_scanoutCandidate.attemptedThisFrame && m_scanoutCandidate.surface) {
if (const auto feedback = m_scanoutCandidate.surface->dmabufFeedbackV1()) {
@ -117,13 +118,27 @@ bool EglGbmLayer::endRendering(const QRegion &damagedRegion)
const auto buffer = m_gbmSurface->swapBuffersForDrm(damagedRegion);
if (buffer) {
m_currentBuffer = buffer;
m_currentDamage = damagedRegion;
}
return buffer;
}
QRegion EglGbmLayer::currentDamage() const
{
return m_currentDamage;
}
QSharedPointer<DrmBuffer> EglGbmLayer::testBuffer()
{
if (!m_currentBuffer || !doesGbmSurfaceFit(m_gbmSurface.data())) {
if (doesGbmSurfaceFit(m_oldGbmSurface.data())) {
// re-use old surface and buffer without rendering
m_gbmSurface = m_oldGbmSurface;
if (m_gbmSurface->currentBuffer()) {
m_currentBuffer = m_gbmSurface->currentDrmBuffer();
return m_currentBuffer;
}
}
if (!renderTestBuffer() && m_importMode == MultiGpuImportMode::DumbBufferXrgb8888) {
// try multi-gpu import again, this time with DRM_FORMAT_XRGB8888
renderTestBuffer();
@ -401,10 +416,11 @@ bool EglGbmLayer::scanout(SurfaceItem *surfaceItem)
}
return false;
}
const auto bo = QSharedPointer<DrmGbmBuffer>::create(m_displayDevice->gpu(), importedBuffer, buffer);
if (!bo->bufferId()) {
m_scanoutBuffer = QSharedPointer<DrmGbmBuffer>::create(m_displayDevice->gpu(), importedBuffer, buffer);
if (!m_scanoutBuffer->bufferId()) {
// buffer can't actually be scanned out. Mesa is supposed to prevent this from happening
// in gbm_bo_import but apparently that doesn't always work
m_scanoutBuffer.reset();
sendDmabufFeedback(buffer);
return false;
}
@ -421,10 +437,12 @@ bool EglGbmLayer::scanout(SurfaceItem *surfaceItem)
} else {
damage = m_displayDevice->renderGeometry();
}
if (m_displayDevice->present(bo, damage)) {
m_currentBuffer = bo;
if (m_displayDevice->testScanout()) {
m_currentBuffer = m_scanoutBuffer;
m_currentDamage = damage;
return true;
} else {
m_scanoutBuffer.reset();
return false;
}
}
@ -462,7 +480,7 @@ void EglGbmLayer::sendDmabufFeedback(KWaylandServer::LinuxDmaBufV1ClientBuffer *
QSharedPointer<DrmBuffer> EglGbmLayer::currentBuffer() const
{
return m_currentBuffer;
return m_scanoutBuffer ? m_scanoutBuffer : m_currentBuffer;
}
DrmDisplayDevice *EglGbmLayer::displayDevice() const
@ -480,4 +498,9 @@ EGLSurface EglGbmLayer::eglSurface() const
return m_gbmSurface ? m_gbmSurface->eglSurface() : EGL_NO_SURFACE;
}
bool EglGbmLayer::hasDirectScanoutBuffer() const
{
return m_scanoutBuffer != nullptr;
}
}

View file

@ -44,6 +44,8 @@ public:
bool scanout(SurfaceItem *surfaceItem) override;
QSharedPointer<DrmBuffer> testBuffer() override;
QSharedPointer<DrmBuffer> currentBuffer() const override;
bool hasDirectScanoutBuffer() const override;
QRegion currentDamage() const override;
QSharedPointer<GLTexture> texture() const;
DrmDisplayDevice *displayDevice() const override;
@ -76,7 +78,9 @@ private:
bool attemptedThisFrame = false;
} m_scanoutCandidate;
QSharedPointer<DrmBuffer> m_scanoutBuffer;
QSharedPointer<DrmBuffer> m_currentBuffer;
QRegion m_currentDamage;
QSharedPointer<GbmSurface> m_gbmSurface;
QSharedPointer<GbmSurface> m_oldGbmSurface;
QSharedPointer<ShadowBuffer> m_shadowBuffer;

View file

@ -23,29 +23,37 @@ DrmQPainterBackend::DrmQPainterBackend(DrmBackend *backend)
: QPainterBackend()
, m_backend(backend)
{
connect(m_backend, &DrmBackend::outputEnabled, this, [this] (const auto output) {
m_swapchains[output] = QSharedPointer<DrmQPainterLayer>::create(static_cast<DrmAbstractOutput*>(output));
});
connect(m_backend, &DrmBackend::outputDisabled, this, [this] (const auto output) {
m_swapchains.remove(output);
});
m_backend->primaryGpu()->setRenderBackend(this);
}
DrmQPainterBackend::~DrmQPainterBackend()
{
m_backend->primaryGpu()->setRenderBackend(nullptr);
}
QImage *DrmQPainterBackend::bufferForScreen(AbstractOutput *output)
{
return static_cast<DrmDumbBuffer*>(m_swapchains[output]->currentBuffer().data())->image();
const auto drmOutput = static_cast<DrmAbstractOutput*>(output);
return static_cast<DrmDumbBuffer*>(drmOutput->outputLayer()->currentBuffer().data())->image();
}
QRegion DrmQPainterBackend::beginFrame(AbstractOutput *output)
{
return m_swapchains[output]->startRendering().value_or(QRegion());
const auto drmOutput = static_cast<DrmAbstractOutput*>(output);
return drmOutput->outputLayer()->startRendering().value_or(QRegion());
}
void DrmQPainterBackend::endFrame(AbstractOutput *output, const QRegion &renderedRegion, const QRegion &damage)
{
Q_UNUSED(renderedRegion)
m_swapchains[output]->endRendering(damage);
static_cast<DrmAbstractOutput*>(output)->present(m_swapchains[output]->currentBuffer(), output->geometry());
const auto drmOutput = static_cast<DrmAbstractOutput*>(output);
drmOutput->outputLayer()->endRendering(damage);
static_cast<DrmAbstractOutput*>(output)->present();
}
QSharedPointer<DrmLayer> DrmQPainterBackend::createLayer(DrmDisplayDevice *displayDevice) const
{
return QSharedPointer<DrmQPainterLayer>::create(displayDevice);
}
}

View file

@ -6,16 +6,15 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef KWIN_SCENE_QPAINTER_DRM_BACKEND_H
#define KWIN_SCENE_QPAINTER_DRM_BACKEND_H
#pragma once
#include "qpainterbackend.h"
#include "drm_render_backend.h"
#include "dumb_swapchain.h"
#include <QObject>
#include <QVector>
#include <QSharedPointer>
#include "dumb_swapchain.h"
namespace KWin
{
@ -23,20 +22,19 @@ class DrmBackend;
class DrmAbstractOutput;
class DrmQPainterLayer;
class DrmQPainterBackend : public QPainterBackend
class DrmQPainterBackend : public QPainterBackend, public DrmRenderBackend
{
Q_OBJECT
public:
DrmQPainterBackend(DrmBackend *backend);
~DrmQPainterBackend();
QImage *bufferForScreen(AbstractOutput *output) override;
QRegion beginFrame(AbstractOutput *output) override;
void endFrame(AbstractOutput *output, const QRegion &renderedRegion, const QRegion &damagedRegion) override;
QSharedPointer<DrmLayer> createLayer(DrmDisplayDevice *displayDevice) const override;
private:
QMap<AbstractOutput *, QSharedPointer<DrmQPainterLayer>> m_swapchains;
DrmBackend *m_backend;
};
}
#endif