backends/drm: handle failing commits better
It can happen that the drm backend temporarily lacks permission to do atomic commits, or that the cached drm property values become out of sync with the real values held by the kernel. Instead of failing with both, attempt to update property values and try the commits again at a later time.
This commit is contained in:
parent
6ff6dcac26
commit
42c5e6bcf6
8 changed files with 158 additions and 93 deletions
|
@ -706,7 +706,7 @@ bool DrmBackend::applyOutputChanges(const OutputConfiguration &config)
|
||||||
toBeDisabled << output;
|
toBeDisabled << output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!gpu->testPendingConfiguration()) {
|
if (gpu->testPendingConfiguration() != DrmPipeline::Error::None) {
|
||||||
for (const auto &output : qAsConst(toBeEnabled)) {
|
for (const auto &output : qAsConst(toBeEnabled)) {
|
||||||
output->revertQueuedChanges();
|
output->revertQueuedChanges();
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ public:
|
||||||
DrmRenderBackend *renderBackend() const;
|
DrmRenderBackend *renderBackend() const;
|
||||||
|
|
||||||
void releaseBuffers();
|
void releaseBuffers();
|
||||||
|
void updateOutputs();
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void turnOutputsOn();
|
void turnOutputsOn();
|
||||||
|
@ -96,7 +97,6 @@ private:
|
||||||
void activate(bool active);
|
void activate(bool active);
|
||||||
void reactivate();
|
void reactivate();
|
||||||
void deactivate();
|
void deactivate();
|
||||||
void updateOutputs();
|
|
||||||
bool readOutputsConfiguration(const QVector<DrmAbstractOutput *> &outputs);
|
bool readOutputsConfiguration(const QVector<DrmAbstractOutput *> &outputs);
|
||||||
void handleUdevEvent();
|
void handleUdevEvent();
|
||||||
DrmGpu *addGpu(const QString &fileName);
|
DrmGpu *addGpu(const QString &fileName);
|
||||||
|
|
|
@ -316,8 +316,8 @@ bool DrmGpu::updateOutputs()
|
||||||
for (const auto &plane : qAsConst(m_planes)) {
|
for (const auto &plane : qAsConst(m_planes)) {
|
||||||
plane->updateProperties();
|
plane->updateProperties();
|
||||||
}
|
}
|
||||||
|
DrmPipeline::Error err = testPendingConfiguration();
|
||||||
if (testPendingConfiguration()) {
|
if (err == DrmPipeline::Error::None) {
|
||||||
for (const auto &pipeline : qAsConst(m_pipelines)) {
|
for (const auto &pipeline : qAsConst(m_pipelines)) {
|
||||||
pipeline->applyPendingChanges();
|
pipeline->applyPendingChanges();
|
||||||
if (pipeline->output() && !pipeline->crtc()) {
|
if (pipeline->output() && !pipeline->crtc()) {
|
||||||
|
@ -325,6 +325,15 @@ bool DrmGpu::updateOutputs()
|
||||||
pipeline->output()->setEnabled(false);
|
pipeline->output()->setEnabled(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (err == DrmPipeline::Error::NoPermission) {
|
||||||
|
for (const auto &pipeline : qAsConst(m_pipelines)) {
|
||||||
|
pipeline->revertPendingChanges();
|
||||||
|
}
|
||||||
|
for (const auto &output : qAsConst(addedOutputs)) {
|
||||||
|
m_connectors.removeOne(output->connector());
|
||||||
|
removeOutput(output);
|
||||||
|
}
|
||||||
|
QTimer::singleShot(50, m_platform, &DrmBackend::updateOutputs);
|
||||||
} else {
|
} else {
|
||||||
qCWarning(KWIN_DRM, "Failed to find a working setup for new outputs!");
|
qCWarning(KWIN_DRM, "Failed to find a working setup for new outputs!");
|
||||||
for (const auto &pipeline : qAsConst(m_pipelines)) {
|
for (const auto &pipeline : qAsConst(m_pipelines)) {
|
||||||
|
@ -340,12 +349,12 @@ bool DrmGpu::updateOutputs()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrmGpu::checkCrtcAssignment(QVector<DrmConnector *> connectors, const QVector<DrmCrtc *> &crtcs)
|
DrmPipeline::Error DrmGpu::checkCrtcAssignment(QVector<DrmConnector *> connectors, const QVector<DrmCrtc *> &crtcs)
|
||||||
{
|
{
|
||||||
if (connectors.isEmpty() || crtcs.isEmpty()) {
|
if (connectors.isEmpty() || crtcs.isEmpty()) {
|
||||||
if (m_pipelines.isEmpty()) {
|
if (m_pipelines.isEmpty()) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return true;
|
return DrmPipeline::Error::None;
|
||||||
}
|
}
|
||||||
// remaining connectors can't be powered
|
// remaining connectors can't be powered
|
||||||
for (const auto &conn : qAsConst(connectors)) {
|
for (const auto &conn : qAsConst(connectors)) {
|
||||||
|
@ -374,8 +383,9 @@ bool DrmGpu::checkCrtcAssignment(QVector<DrmConnector *> connectors, const QVect
|
||||||
crtcsLeft.removeOne(currentCrtc);
|
crtcsLeft.removeOne(currentCrtc);
|
||||||
pipeline->setCrtc(currentCrtc);
|
pipeline->setCrtc(currentCrtc);
|
||||||
do {
|
do {
|
||||||
if (checkCrtcAssignment(connectors, crtcsLeft)) {
|
DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcsLeft);
|
||||||
return true;
|
if (err == DrmPipeline::Error::None || err == DrmPipeline::Error::NoPermission || err == DrmPipeline::Error::FramePending) {
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
} while (pipeline->pruneModifier());
|
} while (pipeline->pruneModifier());
|
||||||
}
|
}
|
||||||
|
@ -386,16 +396,17 @@ bool DrmGpu::checkCrtcAssignment(QVector<DrmConnector *> connectors, const QVect
|
||||||
crtcsLeft.removeOne(crtc);
|
crtcsLeft.removeOne(crtc);
|
||||||
pipeline->setCrtc(crtc);
|
pipeline->setCrtc(crtc);
|
||||||
do {
|
do {
|
||||||
if (checkCrtcAssignment(connectors, crtcsLeft)) {
|
DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcsLeft);
|
||||||
return true;
|
if (err == DrmPipeline::Error::None || err == DrmPipeline::Error::NoPermission || err == DrmPipeline::Error::FramePending) {
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
} while (pipeline->pruneModifier());
|
} while (pipeline->pruneModifier());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return DrmPipeline::Error::InvalidArguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrmGpu::testPendingConfiguration()
|
DrmPipeline::Error DrmGpu::testPendingConfiguration()
|
||||||
{
|
{
|
||||||
QVector<DrmConnector *> connectors;
|
QVector<DrmConnector *> connectors;
|
||||||
for (const auto &conn : qAsConst(m_connectors)) {
|
for (const auto &conn : qAsConst(m_connectors)) {
|
||||||
|
@ -417,8 +428,9 @@ bool DrmGpu::testPendingConfiguration()
|
||||||
return c1->getProp(DrmConnector::PropertyIndex::CrtcId)->current() > c2->getProp(DrmConnector::PropertyIndex::CrtcId)->current();
|
return c1->getProp(DrmConnector::PropertyIndex::CrtcId)->current() > c2->getProp(DrmConnector::PropertyIndex::CrtcId)->current();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (checkCrtcAssignment(connectors, crtcs)) {
|
DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcs);
|
||||||
return true;
|
if (err == DrmPipeline::Error::None || err == DrmPipeline::Error::NoPermission || err == DrmPipeline::Error::FramePending) {
|
||||||
|
return err;
|
||||||
} else {
|
} else {
|
||||||
// try again without hw rotation
|
// try again without hw rotation
|
||||||
bool hwRotationUsed = false;
|
bool hwRotationUsed = false;
|
||||||
|
@ -426,25 +438,27 @@ bool DrmGpu::testPendingConfiguration()
|
||||||
hwRotationUsed |= (pipeline->bufferOrientation() != DrmPlane::Transformations(DrmPlane::Transformation::Rotate0));
|
hwRotationUsed |= (pipeline->bufferOrientation() != DrmPlane::Transformations(DrmPlane::Transformation::Rotate0));
|
||||||
pipeline->setBufferOrientation(DrmPlane::Transformation::Rotate0);
|
pipeline->setBufferOrientation(DrmPlane::Transformation::Rotate0);
|
||||||
}
|
}
|
||||||
return hwRotationUsed ? checkCrtcAssignment(connectors, crtcs) : false;
|
if (hwRotationUsed) {
|
||||||
|
err = checkCrtcAssignment(connectors, crtcs);
|
||||||
|
}
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrmGpu::testPipelines()
|
DrmPipeline::Error DrmGpu::testPipelines()
|
||||||
{
|
{
|
||||||
QVector<DrmPipeline *> inactivePipelines;
|
QVector<DrmPipeline *> inactivePipelines;
|
||||||
std::copy_if(m_pipelines.constBegin(), m_pipelines.constEnd(), std::back_inserter(inactivePipelines), [](const auto pipeline) {
|
std::copy_if(m_pipelines.constBegin(), m_pipelines.constEnd(), std::back_inserter(inactivePipelines), [](const auto pipeline) {
|
||||||
return pipeline->enabled() && !pipeline->active();
|
return pipeline->enabled() && !pipeline->active();
|
||||||
});
|
});
|
||||||
const auto unused = unusedObjects();
|
DrmPipeline::Error test = DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::Test, unusedObjects());
|
||||||
bool test = DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::Test, unused);
|
if (!inactivePipelines.isEmpty() && test == DrmPipeline::Error::None) {
|
||||||
if (!inactivePipelines.isEmpty() && test) {
|
|
||||||
// ensure that pipelines that are set as enabled but currently inactive
|
// ensure that pipelines that are set as enabled but currently inactive
|
||||||
// still work when they need to be set active again
|
// still work when they need to be set active again
|
||||||
for (const auto pipeline : qAsConst(inactivePipelines)) {
|
for (const auto pipeline : qAsConst(inactivePipelines)) {
|
||||||
pipeline->setActive(true);
|
pipeline->setActive(true);
|
||||||
}
|
}
|
||||||
test = DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::Test, unused);
|
test = DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::Test, unusedObjects());
|
||||||
for (const auto pipeline : qAsConst(inactivePipelines)) {
|
for (const auto pipeline : qAsConst(inactivePipelines)) {
|
||||||
pipeline->setActive(false);
|
pipeline->setActive(false);
|
||||||
}
|
}
|
||||||
|
@ -750,16 +764,23 @@ bool DrmGpu::maybeModeset()
|
||||||
}
|
}
|
||||||
// make sure there's no pending pageflips
|
// make sure there's no pending pageflips
|
||||||
waitIdle();
|
waitIdle();
|
||||||
const bool ok = DrmPipeline::commitPipelines(pipelines, DrmPipeline::CommitMode::CommitModeset, unusedObjects());
|
const DrmPipeline::Error err = DrmPipeline::commitPipelines(pipelines, DrmPipeline::CommitMode::CommitModeset, unusedObjects());
|
||||||
for (DrmPipeline *pipeline : qAsConst(pipelines)) {
|
for (DrmPipeline *pipeline : qAsConst(pipelines)) {
|
||||||
if (pipeline->modesetPresentPending()) {
|
if (pipeline->modesetPresentPending()) {
|
||||||
pipeline->resetModesetPresentPending();
|
pipeline->resetModesetPresentPending();
|
||||||
if (!ok) {
|
if (err != DrmPipeline::Error::None) {
|
||||||
pipeline->output()->frameFailed();
|
pipeline->output()->frameFailed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ok;
|
if (err == DrmPipeline::Error::None) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (err != DrmPipeline::Error::FramePending) {
|
||||||
|
QTimer::singleShot(0, m_platform, &DrmBackend::updateOutputs);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<DrmObject *> DrmGpu::unusedObjects() const
|
QVector<DrmObject *> DrmGpu::unusedObjects() const
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#ifndef DRM_GPU_H
|
#ifndef DRM_GPU_H
|
||||||
#define DRM_GPU_H
|
#define DRM_GPU_H
|
||||||
|
|
||||||
|
#include "drm_pipeline.h"
|
||||||
#include "drm_virtual_output.h"
|
#include "drm_virtual_output.h"
|
||||||
|
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
@ -39,7 +40,6 @@ class DrmConnector;
|
||||||
class DrmPlane;
|
class DrmPlane;
|
||||||
class DrmBackend;
|
class DrmBackend;
|
||||||
class EglGbmBackend;
|
class EglGbmBackend;
|
||||||
class DrmPipeline;
|
|
||||||
class DrmAbstractOutput;
|
class DrmAbstractOutput;
|
||||||
class DrmLeaseOutput;
|
class DrmLeaseOutput;
|
||||||
class DrmRenderBackend;
|
class DrmRenderBackend;
|
||||||
|
@ -78,7 +78,7 @@ public:
|
||||||
DrmVirtualOutput *createVirtualOutput(const QString &name, const QSize &size, double scale, DrmVirtualOutput::Type type);
|
DrmVirtualOutput *createVirtualOutput(const QString &name, const QSize &size, double scale, DrmVirtualOutput::Type type);
|
||||||
void removeVirtualOutput(DrmVirtualOutput *output);
|
void removeVirtualOutput(DrmVirtualOutput *output);
|
||||||
|
|
||||||
bool testPendingConfiguration();
|
DrmPipeline::Error testPendingConfiguration();
|
||||||
bool needsModeset() const;
|
bool needsModeset() const;
|
||||||
bool maybeModeset();
|
bool maybeModeset();
|
||||||
|
|
||||||
|
@ -100,8 +100,8 @@ private:
|
||||||
void initDrmResources();
|
void initDrmResources();
|
||||||
void waitIdle();
|
void waitIdle();
|
||||||
|
|
||||||
bool checkCrtcAssignment(QVector<DrmConnector *> connectors, const QVector<DrmCrtc *> &crtcs);
|
DrmPipeline::Error checkCrtcAssignment(QVector<DrmConnector *> connectors, const QVector<DrmCrtc *> &crtcs);
|
||||||
bool testPipelines();
|
DrmPipeline::Error testPipelines();
|
||||||
QVector<DrmObject *> unusedObjects() const;
|
QVector<DrmObject *> unusedObjects() const;
|
||||||
|
|
||||||
void handleLeaseRequest(KWaylandServer::DrmLeaseV1Interface *leaseRequest);
|
void handleLeaseRequest(KWaylandServer::DrmLeaseV1Interface *leaseRequest);
|
||||||
|
|
|
@ -231,7 +231,7 @@ bool DrmOutput::setDrmDpmsMode(DpmsMode mode)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
m_pipeline->setActive(active);
|
m_pipeline->setActive(active);
|
||||||
if (DrmPipeline::commitPipelines({m_pipeline}, active ? DrmPipeline::CommitMode::Test : DrmPipeline::CommitMode::CommitModeset)) {
|
if (DrmPipeline::commitPipelines({m_pipeline}, active ? DrmPipeline::CommitMode::Test : DrmPipeline::CommitMode::CommitModeset) == DrmPipeline::Error::None) {
|
||||||
m_pipeline->applyPendingChanges();
|
m_pipeline->applyPendingChanges();
|
||||||
setDpmsModeInternal(mode);
|
setDpmsModeInternal(mode);
|
||||||
if (active) {
|
if (active) {
|
||||||
|
@ -289,7 +289,7 @@ void DrmOutput::updateModes()
|
||||||
if (currentMode != m_pipeline->mode()) {
|
if (currentMode != m_pipeline->mode()) {
|
||||||
// DrmConnector::findCurrentMode might fail
|
// DrmConnector::findCurrentMode might fail
|
||||||
m_pipeline->setMode(currentMode ? currentMode : m_pipeline->connector()->modes().constFirst());
|
m_pipeline->setMode(currentMode ? currentMode : m_pipeline->connector()->modes().constFirst());
|
||||||
if (m_gpu->testPendingConfiguration()) {
|
if (m_gpu->testPendingConfiguration() == DrmPipeline::Error::None) {
|
||||||
m_pipeline->applyPendingChanges();
|
m_pipeline->applyPendingChanges();
|
||||||
m_renderLoop->setRefreshRate(m_pipeline->mode()->refreshRate());
|
m_renderLoop->setRefreshRate(m_pipeline->mode()->refreshRate());
|
||||||
} else {
|
} else {
|
||||||
|
@ -312,17 +312,27 @@ bool DrmOutput::present()
|
||||||
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_renderLoop.get());
|
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_renderLoop.get());
|
||||||
if (m_pipeline->syncMode() != renderLoopPrivate->presentMode) {
|
if (m_pipeline->syncMode() != renderLoopPrivate->presentMode) {
|
||||||
m_pipeline->setSyncMode(renderLoopPrivate->presentMode);
|
m_pipeline->setSyncMode(renderLoopPrivate->presentMode);
|
||||||
if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test)) {
|
if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) {
|
||||||
m_pipeline->applyPendingChanges();
|
m_pipeline->applyPendingChanges();
|
||||||
} else {
|
} else {
|
||||||
m_pipeline->revertPendingChanges();
|
m_pipeline->revertPendingChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool modeset = gpu()->needsModeset();
|
const bool needsModeset = gpu()->needsModeset();
|
||||||
if (modeset ? m_pipeline->maybeModeset() : m_pipeline->present()) {
|
bool success;
|
||||||
|
if (needsModeset) {
|
||||||
|
success = m_pipeline->maybeModeset();
|
||||||
|
} else {
|
||||||
|
DrmPipeline::Error err = m_pipeline->present();
|
||||||
|
success = err == DrmPipeline::Error::None;
|
||||||
|
if (err == DrmPipeline::Error::InvalidArguments) {
|
||||||
|
QTimer::singleShot(0, m_gpu->platform(), &DrmBackend::updateOutputs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
Q_EMIT outputChange(m_pipeline->primaryLayer()->currentDamage());
|
Q_EMIT outputChange(m_pipeline->primaryLayer()->currentDamage());
|
||||||
return true;
|
return true;
|
||||||
} else if (!modeset) {
|
} else if (!needsModeset) {
|
||||||
qCWarning(KWIN_DRM) << "Presentation failed!" << strerror(errno);
|
qCWarning(KWIN_DRM) << "Presentation failed!" << strerror(errno);
|
||||||
frameFailed();
|
frameFailed();
|
||||||
}
|
}
|
||||||
|
@ -413,7 +423,7 @@ DrmOutputLayer *DrmOutput::outputLayer() const
|
||||||
void DrmOutput::setColorTransformation(const std::shared_ptr<ColorTransformation> &transformation)
|
void DrmOutput::setColorTransformation(const std::shared_ptr<ColorTransformation> &transformation)
|
||||||
{
|
{
|
||||||
m_pipeline->setColorTransformation(transformation);
|
m_pipeline->setColorTransformation(transformation);
|
||||||
if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test)) {
|
if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) {
|
||||||
m_pipeline->applyPendingChanges();
|
m_pipeline->applyPendingChanges();
|
||||||
m_renderLoop->scheduleRepaint();
|
m_renderLoop->scheduleRepaint();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -53,15 +53,15 @@ bool DrmPipeline::testScanout()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (gpu()->atomicModeSetting()) {
|
if (gpu()->atomicModeSetting()) {
|
||||||
return commitPipelines({this}, CommitMode::Test);
|
return commitPipelines({this}, CommitMode::Test) == Error::None;
|
||||||
} else {
|
} else {
|
||||||
// no other way to test than to do it.
|
// no other way to test than to do it.
|
||||||
// As we only have a maximum of one test per scanout cycle, this is fine
|
// As we only have a maximum of one test per scanout cycle, this is fine
|
||||||
return presentLegacy();
|
return presentLegacy() == Error::None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrmPipeline::present()
|
DrmPipeline::Error DrmPipeline::present()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_pending.crtc);
|
Q_ASSERT(m_pending.crtc);
|
||||||
if (gpu()->atomicModeSetting()) {
|
if (gpu()->atomicModeSetting()) {
|
||||||
|
@ -69,13 +69,10 @@ bool DrmPipeline::present()
|
||||||
} else {
|
} else {
|
||||||
if (m_pending.layer->hasDirectScanoutBuffer()) {
|
if (m_pending.layer->hasDirectScanoutBuffer()) {
|
||||||
// already presented
|
// already presented
|
||||||
return true;
|
return Error::None;
|
||||||
}
|
|
||||||
if (!presentLegacy()) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return presentLegacy();
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrmPipeline::maybeModeset()
|
bool DrmPipeline::maybeModeset()
|
||||||
|
@ -84,7 +81,7 @@ bool DrmPipeline::maybeModeset()
|
||||||
return gpu()->maybeModeset();
|
return gpu()->maybeModeset();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrmPipeline::commitPipelines(const QVector<DrmPipeline *> &pipelines, CommitMode mode, const QVector<DrmObject *> &unusedObjects)
|
DrmPipeline::Error DrmPipeline::commitPipelines(const QVector<DrmPipeline *> &pipelines, CommitMode mode, const QVector<DrmObject *> &unusedObjects)
|
||||||
{
|
{
|
||||||
Q_ASSERT(!pipelines.isEmpty());
|
Q_ASSERT(!pipelines.isEmpty());
|
||||||
if (pipelines[0]->gpu()->atomicModeSetting()) {
|
if (pipelines[0]->gpu()->atomicModeSetting()) {
|
||||||
|
@ -94,12 +91,12 @@ bool DrmPipeline::commitPipelines(const QVector<DrmPipeline *> &pipelines, Commi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrmPipeline::commitPipelinesAtomic(const QVector<DrmPipeline *> &pipelines, CommitMode mode, const QVector<DrmObject *> &unusedObjects)
|
DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QVector<DrmPipeline *> &pipelines, CommitMode mode, const QVector<DrmObject *> &unusedObjects)
|
||||||
{
|
{
|
||||||
drmModeAtomicReq *req = drmModeAtomicAlloc();
|
drmModeAtomicReq *req = drmModeAtomicAlloc();
|
||||||
if (!req) {
|
if (!req) {
|
||||||
qCCritical(KWIN_DRM) << "Failed to allocate drmModeAtomicReq!" << strerror(errno);
|
qCCritical(KWIN_DRM) << "Failed to allocate drmModeAtomicReq!" << strerror(errno);
|
||||||
return false;
|
return Error::OutofMemory;
|
||||||
}
|
}
|
||||||
uint32_t flags = 0;
|
uint32_t flags = 0;
|
||||||
const auto &failed = [pipelines, req, &flags, unusedObjects]() {
|
const auto &failed = [pipelines, req, &flags, unusedObjects]() {
|
||||||
|
@ -113,16 +110,17 @@ bool DrmPipeline::commitPipelinesAtomic(const QVector<DrmPipeline *> &pipelines,
|
||||||
printProps(obj, PrintMode::OnlyChanged);
|
printProps(obj, PrintMode::OnlyChanged);
|
||||||
obj->rollbackPending();
|
obj->rollbackPending();
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
for (const auto &pipeline : pipelines) {
|
for (const auto &pipeline : pipelines) {
|
||||||
if (pipeline->activePending() && !pipeline->m_pending.layer->checkTestBuffer()) {
|
if (pipeline->activePending() && !pipeline->m_pending.layer->checkTestBuffer()) {
|
||||||
qCWarning(KWIN_DRM) << "Checking test buffer failed for" << mode;
|
qCWarning(KWIN_DRM) << "Checking test buffer failed for" << mode;
|
||||||
return failed();
|
failed();
|
||||||
|
return Error::TestBufferFailed;
|
||||||
}
|
}
|
||||||
if (!pipeline->populateAtomicValues(req, flags)) {
|
if (Error err = pipeline->populateAtomicValues(req, flags); err != Error::None) {
|
||||||
qCWarning(KWIN_DRM) << "Populating atomic values failed for" << mode;
|
qCWarning(KWIN_DRM) << "Populating atomic values failed for" << mode;
|
||||||
return failed();
|
failed();
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto &unused : unusedObjects) {
|
for (const auto &unused : unusedObjects) {
|
||||||
|
@ -132,7 +130,8 @@ bool DrmPipeline::commitPipelinesAtomic(const QVector<DrmPipeline *> &pipelines,
|
||||||
}
|
}
|
||||||
if (!unused->atomicPopulate(req)) {
|
if (!unused->atomicPopulate(req)) {
|
||||||
qCWarning(KWIN_DRM) << "Populating atomic values failed for unused resource" << unused;
|
qCWarning(KWIN_DRM) << "Populating atomic values failed for unused resource" << unused;
|
||||||
return failed();
|
failed();
|
||||||
|
return errnoToError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool modeset = flags & DRM_MODE_ATOMIC_ALLOW_MODESET;
|
bool modeset = flags & DRM_MODE_ATOMIC_ALLOW_MODESET;
|
||||||
|
@ -148,11 +147,13 @@ bool DrmPipeline::commitPipelinesAtomic(const QVector<DrmPipeline *> &pipelines,
|
||||||
}
|
}
|
||||||
if (drmModeAtomicCommit(pipelines[0]->gpu()->fd(), req, (flags & (~DRM_MODE_PAGE_FLIP_EVENT)) | DRM_MODE_ATOMIC_TEST_ONLY, nullptr) != 0) {
|
if (drmModeAtomicCommit(pipelines[0]->gpu()->fd(), req, (flags & (~DRM_MODE_PAGE_FLIP_EVENT)) | DRM_MODE_ATOMIC_TEST_ONLY, nullptr) != 0) {
|
||||||
qCDebug(KWIN_DRM) << "Atomic test for" << mode << "failed!" << strerror(errno);
|
qCDebug(KWIN_DRM) << "Atomic test for" << mode << "failed!" << strerror(errno);
|
||||||
return failed();
|
failed();
|
||||||
|
return errnoToError();
|
||||||
}
|
}
|
||||||
if (mode != CommitMode::Test && drmModeAtomicCommit(pipelines[0]->gpu()->fd(), req, flags, nullptr) != 0) {
|
if (mode != CommitMode::Test && drmModeAtomicCommit(pipelines[0]->gpu()->fd(), req, flags, nullptr) != 0) {
|
||||||
qCCritical(KWIN_DRM) << "Atomic commit failed! This should never happen!" << strerror(errno);
|
qCCritical(KWIN_DRM) << "Atomic commit failed! This should never happen!" << strerror(errno);
|
||||||
return failed();
|
failed();
|
||||||
|
return errnoToError();
|
||||||
}
|
}
|
||||||
for (const auto &pipeline : pipelines) {
|
for (const auto &pipeline : pipelines) {
|
||||||
pipeline->atomicCommitSuccessful(mode);
|
pipeline->atomicCommitSuccessful(mode);
|
||||||
|
@ -164,10 +165,10 @@ bool DrmPipeline::commitPipelinesAtomic(const QVector<DrmPipeline *> &pipelines,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drmModeAtomicFree(req);
|
drmModeAtomicFree(req);
|
||||||
return true;
|
return Error::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrmPipeline::populateAtomicValues(drmModeAtomicReq *req, uint32_t &flags)
|
DrmPipeline::Error DrmPipeline::populateAtomicValues(drmModeAtomicReq *req, uint32_t &flags)
|
||||||
{
|
{
|
||||||
if (needsModeset()) {
|
if (needsModeset()) {
|
||||||
prepareAtomicModeset();
|
prepareAtomicModeset();
|
||||||
|
@ -193,20 +194,20 @@ bool DrmPipeline::populateAtomicValues(drmModeAtomicReq *req, uint32_t &flags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!m_connector->atomicPopulate(req)) {
|
if (!m_connector->atomicPopulate(req)) {
|
||||||
return false;
|
return errnoToError();
|
||||||
}
|
}
|
||||||
if (m_pending.crtc) {
|
if (m_pending.crtc) {
|
||||||
if (!m_pending.crtc->atomicPopulate(req)) {
|
if (!m_pending.crtc->atomicPopulate(req)) {
|
||||||
return false;
|
return errnoToError();
|
||||||
}
|
}
|
||||||
if (!m_pending.crtc->primaryPlane()->atomicPopulate(req)) {
|
if (!m_pending.crtc->primaryPlane()->atomicPopulate(req)) {
|
||||||
return false;
|
return errnoToError();
|
||||||
}
|
}
|
||||||
if (m_pending.crtc->cursorPlane() && !m_pending.crtc->cursorPlane()->atomicPopulate(req)) {
|
if (m_pending.crtc->cursorPlane() && !m_pending.crtc->cursorPlane()->atomicPopulate(req)) {
|
||||||
return false;
|
return errnoToError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return Error::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrmPipeline::prepareAtomicModeset()
|
void DrmPipeline::prepareAtomicModeset()
|
||||||
|
@ -262,6 +263,22 @@ uint32_t DrmPipeline::calculateUnderscan()
|
||||||
return hborder;
|
return hborder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DrmPipeline::Error DrmPipeline::errnoToError()
|
||||||
|
{
|
||||||
|
switch (errno) {
|
||||||
|
case EINVAL:
|
||||||
|
return Error::InvalidArguments;
|
||||||
|
case EBUSY:
|
||||||
|
return Error::FramePending;
|
||||||
|
case ENOMEM:
|
||||||
|
return Error::OutofMemory;
|
||||||
|
case EACCES:
|
||||||
|
return Error::NoPermission;
|
||||||
|
default:
|
||||||
|
return Error::Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DrmPipeline::atomicCommitFailed()
|
void DrmPipeline::atomicCommitFailed()
|
||||||
{
|
{
|
||||||
m_connector->rollbackPending();
|
m_connector->rollbackPending();
|
||||||
|
@ -311,7 +328,7 @@ bool DrmPipeline::setCursor(const QPoint &hotspot)
|
||||||
m_pending.cursorHotspot = hotspot;
|
m_pending.cursorHotspot = hotspot;
|
||||||
// explicitly check for the cursor plane and not for AMS, as we might not always have one
|
// explicitly check for the cursor plane and not for AMS, as we might not always have one
|
||||||
if (m_pending.crtc->cursorPlane()) {
|
if (m_pending.crtc->cursorPlane()) {
|
||||||
result = commitPipelines({this}, CommitMode::Test);
|
result = commitPipelines({this}, CommitMode::Test) == Error::None;
|
||||||
if (result && m_output) {
|
if (result && m_output) {
|
||||||
m_output->renderLoop()->scheduleRepaint();
|
m_output->renderLoop()->scheduleRepaint();
|
||||||
}
|
}
|
||||||
|
@ -331,7 +348,7 @@ bool DrmPipeline::moveCursor()
|
||||||
bool result;
|
bool result;
|
||||||
// explicitly check for the cursor plane and not for AMS, as we might not always have one
|
// explicitly check for the cursor plane and not for AMS, as we might not always have one
|
||||||
if (m_pending.crtc->cursorPlane()) {
|
if (m_pending.crtc->cursorPlane()) {
|
||||||
result = commitPipelines({this}, CommitMode::Test);
|
result = commitPipelines({this}, CommitMode::Test) == Error::None;
|
||||||
} else {
|
} else {
|
||||||
result = moveCursorLegacy();
|
result = moveCursorLegacy();
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,11 +53,22 @@ public:
|
||||||
DrmPipeline(DrmConnector *conn);
|
DrmPipeline(DrmConnector *conn);
|
||||||
~DrmPipeline();
|
~DrmPipeline();
|
||||||
|
|
||||||
|
enum class Error {
|
||||||
|
None,
|
||||||
|
OutofMemory,
|
||||||
|
InvalidArguments,
|
||||||
|
NoPermission,
|
||||||
|
FramePending,
|
||||||
|
TestBufferFailed,
|
||||||
|
Unknown,
|
||||||
|
};
|
||||||
|
Q_ENUM(Error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tests the pending commit first and commits it if the test passes
|
* tests the pending commit first and commits it if the test passes
|
||||||
* if the test fails, there is a guarantee for no lasting changes
|
* if the test fails, there is a guarantee for no lasting changes
|
||||||
*/
|
*/
|
||||||
bool present();
|
Error present();
|
||||||
bool testScanout();
|
bool testScanout();
|
||||||
bool maybeModeset();
|
bool maybeModeset();
|
||||||
|
|
||||||
|
@ -118,28 +129,29 @@ public:
|
||||||
Commit,
|
Commit,
|
||||||
CommitModeset
|
CommitModeset
|
||||||
};
|
};
|
||||||
Q_ENUM(CommitMode)
|
Q_ENUM(CommitMode);
|
||||||
static bool commitPipelines(const QVector<DrmPipeline *> &pipelines, CommitMode mode, const QVector<DrmObject *> &unusedObjects = {});
|
static Error commitPipelines(const QVector<DrmPipeline *> &pipelines, CommitMode mode, const QVector<DrmObject *> &unusedObjects = {});
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool activePending() const;
|
bool activePending() const;
|
||||||
bool isBufferForDirectScanout() const;
|
bool isBufferForDirectScanout() const;
|
||||||
uint32_t calculateUnderscan();
|
uint32_t calculateUnderscan();
|
||||||
|
static Error errnoToError();
|
||||||
|
|
||||||
// legacy only
|
// legacy only
|
||||||
bool presentLegacy();
|
Error presentLegacy();
|
||||||
bool legacyModeset();
|
Error legacyModeset();
|
||||||
bool applyPendingChangesLegacy();
|
Error applyPendingChangesLegacy();
|
||||||
bool setCursorLegacy();
|
bool setCursorLegacy();
|
||||||
bool moveCursorLegacy();
|
bool moveCursorLegacy();
|
||||||
static bool commitPipelinesLegacy(const QVector<DrmPipeline *> &pipelines, CommitMode mode);
|
static Error commitPipelinesLegacy(const QVector<DrmPipeline *> &pipelines, CommitMode mode);
|
||||||
|
|
||||||
// atomic modesetting only
|
// atomic modesetting only
|
||||||
bool populateAtomicValues(drmModeAtomicReq *req, uint32_t &flags);
|
Error populateAtomicValues(drmModeAtomicReq *req, uint32_t &flags);
|
||||||
void atomicCommitFailed();
|
void atomicCommitFailed();
|
||||||
void atomicCommitSuccessful(CommitMode mode);
|
void atomicCommitSuccessful(CommitMode mode);
|
||||||
void prepareAtomicModeset();
|
void prepareAtomicModeset();
|
||||||
static bool commitPipelinesAtomic(const QVector<DrmPipeline *> &pipelines, CommitMode mode, const QVector<DrmObject *> &unusedObjects);
|
static Error commitPipelinesAtomic(const QVector<DrmPipeline *> &pipelines, CommitMode mode, const QVector<DrmObject *> &unusedObjects);
|
||||||
|
|
||||||
// logging helpers
|
// logging helpers
|
||||||
enum class PrintMode {
|
enum class PrintMode {
|
||||||
|
|
|
@ -21,31 +21,34 @@
|
||||||
namespace KWin
|
namespace KWin
|
||||||
{
|
{
|
||||||
|
|
||||||
bool DrmPipeline::presentLegacy()
|
DrmPipeline::Error DrmPipeline::presentLegacy()
|
||||||
{
|
{
|
||||||
if (!m_pending.crtc->current() && !legacyModeset()) {
|
if (!m_pending.crtc->current()) {
|
||||||
return false;
|
Error err = legacyModeset();
|
||||||
|
if (err != Error::None) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const auto buffer = m_pending.layer->currentBuffer();
|
const auto buffer = m_pending.layer->currentBuffer();
|
||||||
if (drmModePageFlip(gpu()->fd(), m_pending.crtc->id(), buffer->framebufferId(), DRM_MODE_PAGE_FLIP_EVENT, nullptr) != 0) {
|
if (drmModePageFlip(gpu()->fd(), m_pending.crtc->id(), buffer->framebufferId(), DRM_MODE_PAGE_FLIP_EVENT, nullptr) != 0) {
|
||||||
qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno);
|
qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno);
|
||||||
return false;
|
return errnoToError();
|
||||||
}
|
}
|
||||||
m_pageflipPending = true;
|
m_pageflipPending = true;
|
||||||
m_pending.crtc->setNext(buffer);
|
m_pending.crtc->setNext(buffer);
|
||||||
return true;
|
return Error::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrmPipeline::legacyModeset()
|
DrmPipeline::Error DrmPipeline::legacyModeset()
|
||||||
{
|
{
|
||||||
uint32_t connId = m_connector->id();
|
uint32_t connId = m_connector->id();
|
||||||
if (!m_pending.layer->checkTestBuffer()) {
|
if (!m_pending.layer->checkTestBuffer()) {
|
||||||
return false;
|
return Error::TestBufferFailed;
|
||||||
}
|
}
|
||||||
const auto buffer = m_pending.layer->currentBuffer();
|
const auto buffer = m_pending.layer->currentBuffer();
|
||||||
if (drmModeSetCrtc(gpu()->fd(), m_pending.crtc->id(), buffer->framebufferId(), 0, 0, &connId, 1, m_pending.mode->nativeMode()) != 0) {
|
if (drmModeSetCrtc(gpu()->fd(), m_pending.crtc->id(), buffer->framebufferId(), 0, 0, &connId, 1, m_pending.mode->nativeMode()) != 0) {
|
||||||
qCWarning(KWIN_DRM) << "Modeset failed!" << strerror(errno);
|
qCWarning(KWIN_DRM) << "Modeset failed!" << strerror(errno);
|
||||||
return false;
|
return errnoToError();
|
||||||
}
|
}
|
||||||
// make sure the buffer gets kept alive, or the modeset gets reverted by the kernel
|
// make sure the buffer gets kept alive, or the modeset gets reverted by the kernel
|
||||||
if (m_pending.crtc->current()) {
|
if (m_pending.crtc->current()) {
|
||||||
|
@ -53,25 +56,24 @@ bool DrmPipeline::legacyModeset()
|
||||||
} else {
|
} else {
|
||||||
m_pending.crtc->setCurrent(buffer);
|
m_pending.crtc->setCurrent(buffer);
|
||||||
}
|
}
|
||||||
return true;
|
return Error::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrmPipeline::commitPipelinesLegacy(const QVector<DrmPipeline *> &pipelines, CommitMode mode)
|
DrmPipeline::Error DrmPipeline::commitPipelinesLegacy(const QVector<DrmPipeline *> &pipelines, CommitMode mode)
|
||||||
{
|
{
|
||||||
bool failure = false;
|
Error err = Error::None;
|
||||||
for (const auto &pipeline : pipelines) {
|
for (const auto &pipeline : pipelines) {
|
||||||
if (!pipeline->applyPendingChangesLegacy()) {
|
err = pipeline->applyPendingChangesLegacy();
|
||||||
failure = true;
|
if (err != Error::None) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (failure) {
|
if (err != Error::None) {
|
||||||
// at least try to revert the config
|
// at least try to revert the config
|
||||||
for (const auto &pipeline : pipelines) {
|
for (const auto &pipeline : pipelines) {
|
||||||
pipeline->revertPendingChanges();
|
pipeline->revertPendingChanges();
|
||||||
pipeline->applyPendingChangesLegacy();
|
pipeline->applyPendingChangesLegacy();
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
} else {
|
} else {
|
||||||
for (const auto &pipeline : pipelines) {
|
for (const auto &pipeline : pipelines) {
|
||||||
pipeline->applyPendingChanges();
|
pipeline->applyPendingChanges();
|
||||||
|
@ -80,11 +82,11 @@ bool DrmPipeline::commitPipelinesLegacy(const QVector<DrmPipeline *> &pipelines,
|
||||||
pipeline->pageFlipped(std::chrono::steady_clock::now().time_since_epoch());
|
pipeline->pageFlipped(std::chrono::steady_clock::now().time_since_epoch());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrmPipeline::applyPendingChangesLegacy()
|
DrmPipeline::Error DrmPipeline::applyPendingChangesLegacy()
|
||||||
{
|
{
|
||||||
if (!m_pending.active && m_pending.crtc) {
|
if (!m_pending.active && m_pending.crtc) {
|
||||||
drmModeSetCursor(gpu()->fd(), m_pending.crtc->id(), 0, 0, 0);
|
drmModeSetCursor(gpu()->fd(), m_pending.crtc->id(), 0, 0, 0);
|
||||||
|
@ -93,7 +95,7 @@ bool DrmPipeline::applyPendingChangesLegacy()
|
||||||
auto vrr = m_pending.crtc->getProp(DrmCrtc::PropertyIndex::VrrEnabled);
|
auto vrr = m_pending.crtc->getProp(DrmCrtc::PropertyIndex::VrrEnabled);
|
||||||
if (vrr && !vrr->setPropertyLegacy(m_pending.syncMode == RenderLoopPrivate::SyncMode::Adaptive)) {
|
if (vrr && !vrr->setPropertyLegacy(m_pending.syncMode == RenderLoopPrivate::SyncMode::Adaptive)) {
|
||||||
qCWarning(KWIN_DRM) << "Setting vrr failed!" << strerror(errno);
|
qCWarning(KWIN_DRM) << "Setting vrr failed!" << strerror(errno);
|
||||||
return false;
|
return errnoToError();
|
||||||
}
|
}
|
||||||
if (const auto &rgbRange = m_connector->getProp(DrmConnector::PropertyIndex::Broadcast_RGB)) {
|
if (const auto &rgbRange = m_connector->getProp(DrmConnector::PropertyIndex::Broadcast_RGB)) {
|
||||||
rgbRange->setEnumLegacy(m_pending.rgbRange);
|
rgbRange->setEnumLegacy(m_pending.rgbRange);
|
||||||
|
@ -106,21 +108,24 @@ bool DrmPipeline::applyPendingChangesLegacy()
|
||||||
m_connector->getProp(DrmConnector::PropertyIndex::Underscan_vborder)->setPropertyLegacy(m_pending.overscan);
|
m_connector->getProp(DrmConnector::PropertyIndex::Underscan_vborder)->setPropertyLegacy(m_pending.overscan);
|
||||||
m_connector->getProp(DrmConnector::PropertyIndex::Underscan_hborder)->setPropertyLegacy(hborder);
|
m_connector->getProp(DrmConnector::PropertyIndex::Underscan_hborder)->setPropertyLegacy(hborder);
|
||||||
}
|
}
|
||||||
if (needsModeset() && !legacyModeset()) {
|
if (needsModeset()) {
|
||||||
return false;
|
Error err = legacyModeset();
|
||||||
|
if (err != Error::None) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (m_pending.gamma && drmModeCrtcSetGamma(gpu()->fd(), m_pending.crtc->id(), m_pending.gamma->lut().size(), m_pending.gamma->lut().red(), m_pending.gamma->lut().green(), m_pending.gamma->lut().blue()) != 0) {
|
if (m_pending.gamma && drmModeCrtcSetGamma(gpu()->fd(), m_pending.crtc->id(), m_pending.gamma->lut().size(), m_pending.gamma->lut().red(), m_pending.gamma->lut().green(), m_pending.gamma->lut().blue()) != 0) {
|
||||||
qCWarning(KWIN_DRM) << "Setting gamma failed!" << strerror(errno);
|
qCWarning(KWIN_DRM) << "Setting gamma failed!" << strerror(errno);
|
||||||
return false;
|
return errnoToError();
|
||||||
}
|
}
|
||||||
setCursorLegacy();
|
setCursorLegacy();
|
||||||
moveCursorLegacy();
|
moveCursorLegacy();
|
||||||
}
|
}
|
||||||
if (!m_connector->getProp(DrmConnector::PropertyIndex::Dpms)->setPropertyLegacy(activePending() ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF)) {
|
if (!m_connector->getProp(DrmConnector::PropertyIndex::Dpms)->setPropertyLegacy(activePending() ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF)) {
|
||||||
qCWarning(KWIN_DRM) << "Setting legacy dpms failed!" << strerror(errno);
|
qCWarning(KWIN_DRM) << "Setting legacy dpms failed!" << strerror(errno);
|
||||||
return false;
|
return errnoToError();
|
||||||
}
|
}
|
||||||
return true;
|
return Error::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrmPipeline::setCursorLegacy()
|
bool DrmPipeline::setCursorLegacy()
|
||||||
|
|
Loading…
Reference in a new issue