Commit a04bdf23 authored by Xaver Hugl's avatar Xaver Hugl
Browse files

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.
parent cdac2690
......@@ -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);
}
}
......@@ -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;
......
......@@ -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;
......
......@@ -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);
}
DrmRenderBackend *DrmGpu::renderBackend() const
{
return m_renderBackend;
}
void DrmGpu::setEglBackend(EglGbmBackend *eglBackend)
void DrmGpu::setRenderBackend(DrmRenderBackend *backend)
{
m_eglBackend = eglBackend;
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();
}
}
}
......
......@@ -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;
......
......@@ -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;
};
......
......@@ -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)
}
}
......@@ -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;
......
......@@ -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();
}
}
......@@ -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:
......
......@@ -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)
......
......@@ -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();