backends/drm: split commits for the cursor and primary plane

And reorder and merge atomic commits where possible to ensure the cursor always
stays smooth

BUG: 472663
This commit is contained in:
Xaver Hugl 2023-08-28 22:01:28 +02:00
parent 2e42d7193d
commit 4c397a9526
6 changed files with 184 additions and 52 deletions

View file

@ -57,6 +57,7 @@ void DrmAtomicCommit::addBuffer(DrmPlane *plane, const std::shared_ptr<DrmFrameb
{
addProperty(plane->fbId, buffer ? buffer->framebufferId() : 0);
m_buffers[plane] = buffer;
m_planes.emplace(plane);
}
void DrmAtomicCommit::setVrr(DrmCrtc *crtc, bool vrr)
@ -139,9 +140,35 @@ bool DrmAtomicCommit::areBuffersReadable() const
});
}
bool DrmAtomicCommit::isVrr() const
std::optional<bool> DrmAtomicCommit::isVrr() const
{
return m_vrr;
return m_vrr.value_or(false);
}
const std::unordered_set<DrmPlane *> &DrmAtomicCommit::modifiedPlanes() const
{
return m_planes;
}
void DrmAtomicCommit::merge(DrmAtomicCommit *onTop)
{
for (const auto &[obj, properties] : onTop->m_properties) {
auto &ownProperties = m_properties[obj];
for (const auto &[prop, value] : properties) {
ownProperties[prop] = value;
}
}
for (const auto &[plane, buffer] : onTop->m_buffers) {
m_buffers[plane] = buffer;
m_planes.emplace(plane);
}
for (const auto &[prop, blob] : onTop->m_blobs) {
m_blobs[prop] = blob;
}
if (onTop->m_vrr) {
m_vrr = onTop->m_vrr;
}
m_cursorOnly &= onTop->isCursorOnly();
}
void DrmAtomicCommit::setCursorOnly(bool cursor)

View file

@ -14,6 +14,7 @@
#include <QHash>
#include <chrono>
#include <unordered_map>
#include <unordered_set>
#include "drm_pointer.h"
#include "drm_property.h"
@ -49,6 +50,7 @@ class DrmAtomicCommit : public DrmCommit
{
public:
DrmAtomicCommit(const QVector<DrmPipeline *> &pipelines);
DrmAtomicCommit(const DrmAtomicCommit &copy) = default;
void addProperty(const DrmProperty &prop, uint64_t value);
template<typename T>
@ -68,7 +70,10 @@ public:
void pageFlipped(std::chrono::nanoseconds timestamp) const override;
bool areBuffersReadable() const;
bool isVrr() const;
std::optional<bool> isVrr() const;
const std::unordered_set<DrmPlane *> &modifiedPlanes() const;
void merge(DrmAtomicCommit *onTop);
void setCursorOnly(bool cursor);
bool isCursorOnly() const;
@ -77,9 +82,10 @@ private:
bool doCommit(uint32_t flags);
const QVector<DrmPipeline *> m_pipelines;
QHash<const DrmProperty *, std::shared_ptr<DrmBlob>> m_blobs;
std::unordered_map<const DrmProperty *, std::shared_ptr<DrmBlob>> m_blobs;
std::unordered_map<DrmPlane *, std::shared_ptr<DrmFramebuffer>> m_buffers;
bool m_vrr = false;
std::unordered_set<DrmPlane *> m_planes;
std::optional<bool> m_vrr;
std::unordered_map<uint32_t /* object */, std::unordered_map<uint32_t /* property */, uint64_t /* value */>> m_properties;
bool m_cursorOnly = false;
};

View file

@ -44,11 +44,10 @@ DrmCommitThread::DrmCommitThread()
std::this_thread::sleep_until(m_targetPageflipTime - s_safetyMargin);
lock.lock();
}
const auto it = std::find_if(m_commits.rbegin(), m_commits.rend(), [](const auto &commit) {
return commit->areBuffersReadable();
});
if (it == m_commits.rend()) {
// reschedule, this commit would not hit the pageflip deadline anyways
optimizeCommits();
auto &commit = m_commits.front();
if (!commit->areBuffersReadable()) {
// no commit is ready yet, reschedule
if (m_vrr) {
m_targetPageflipTime += 50us;
} else {
@ -56,21 +55,14 @@ DrmCommitThread::DrmCommitThread()
}
continue;
}
auto &commit = *it;
commit->setCursorOnly(std::all_of(m_commits.begin(), it.base(), [](const auto &commit) {
return commit->isCursorOnly();
}));
const bool vrr = commit->isVrr();
const auto vrr = commit->isVrr();
const bool success = commit->commit();
if (success) {
m_pageflipPending = true;
m_vrr = vrr;
m_vrr = vrr.value_or(m_vrr);
// the atomic commit takes ownership of the object
commit.release();
// commits that were pushed earlier are outdated and can be dropped
std::move(m_commits.begin(), it.base(), std::back_inserter(m_droppedCommits));
m_commits.erase(m_commits.begin(), it.base());
// commits that came after will be rescheduled once the pageflip is done
m_commits.erase(m_commits.begin());
} else {
for (auto &commit : m_commits) {
m_droppedCommits.push_back(std::move(commit));
@ -86,6 +78,98 @@ DrmCommitThread::DrmCommitThread()
m_thread->start();
}
void DrmCommitThread::optimizeCommits()
{
if (m_commits.size() <= 1) {
return;
}
// merge commits in the front that are already ready (regardless of which planes they modify)
if (m_commits.front()->areBuffersReadable()) {
auto it = m_commits.begin() + 1;
while (it != m_commits.end() && (*it)->areBuffersReadable()) {
m_commits.front()->merge(it->get());
m_droppedCommits.push_back(std::move(*it));
it = m_commits.erase(it);
}
}
// merge commits that are ready and modify the same drm planes
for (auto it = m_commits.begin(); it != m_commits.end();) {
DrmAtomicCommit *const commit = it->get();
it++;
while (it != m_commits.end() && commit->modifiedPlanes() == (*it)->modifiedPlanes() && (*it)->areBuffersReadable()) {
commit->merge(it->get());
m_droppedCommits.push_back(std::move(*it));
it = m_commits.erase(it);
}
}
if (m_commits.size() == 1) {
// already done
return;
}
std::unique_ptr<DrmAtomicCommit> front;
if (m_commits.front()->areBuffersReadable()) {
front = std::move(m_commits.front());
m_commits.erase(m_commits.begin());
}
// try to move commits that are ready to the front
for (auto it = m_commits.begin() + 1; it != m_commits.end();) {
auto &commit = *it;
// commits that target the same plane(s) need to stay in the same order
const auto &planes = commit->modifiedPlanes();
const bool skipping = std::any_of(m_commits.begin(), it, [&planes](const auto &other) {
return std::any_of(planes.begin(), planes.end(), [&other](DrmPlane *plane) {
return other->modifiedPlanes().contains(plane);
});
});
if (skipping || !commit->areBuffersReadable()) {
it++;
continue;
}
// find out if the modified commit order will actually work
std::unique_ptr<DrmAtomicCommit> duplicate;
if (front) {
duplicate = std::make_unique<DrmAtomicCommit>(*front);
duplicate->merge(commit.get());
if (!duplicate->test()) {
m_droppedCommits.push_back(std::move(duplicate));
it++;
continue;
}
} else {
if (!commit->test()) {
it++;
continue;
}
duplicate = std::make_unique<DrmAtomicCommit>(*commit);
}
bool success = true;
for (const auto &otherCommit : m_commits) {
if (otherCommit != commit) {
duplicate->merge(otherCommit.get());
if (!duplicate->test()) {
success = false;
break;
}
}
}
m_droppedCommits.push_back(std::move(duplicate));
if (success) {
if (front) {
front->merge(commit.get());
m_droppedCommits.push_back(std::move(commit));
} else {
front = std::move(commit);
}
it = m_commits.erase(it);
} else {
it++;
}
}
if (front) {
m_commits.insert(m_commits.begin(), std::move(front));
}
}
DrmCommitThread::~DrmCommitThread()
{
m_thread->requestInterruption();

View file

@ -41,6 +41,7 @@ Q_SIGNALS:
private:
void clearDroppedCommits();
TimePoint estimateNextVblank(TimePoint now) const;
void optimizeCommits();
std::vector<std::unique_ptr<DrmAtomicCommit>> m_commits;
std::unique_ptr<QThread> m_thread;

View file

@ -78,7 +78,24 @@ DrmPipeline::Error DrmPipeline::present()
{
Q_ASSERT(m_pending.crtc);
if (gpu()->atomicModeSetting()) {
return commitPipelines({this}, CommitMode::Commit);
// test the full state, to take pending commits into account
auto fullState = std::make_unique<DrmAtomicCommit>(QVector<DrmPipeline *>{this});
if (!prepareAtomicPresentation(fullState.get())) {
return Error::InvalidArguments;
}
if (m_pending.crtc->cursorPlane()) {
prepareAtomicCursor(fullState.get());
}
if (!fullState->test()) {
return errnoToError();
}
// only give the actual state update to the commit thread, so that it can potentially reorder the commits
auto primaryPlaneUpdate = std::make_unique<DrmAtomicCommit>(QVector<DrmPipeline *>{this});
if (!prepareAtomicPresentation(primaryPlaneUpdate.get())) {
return Error::InvalidArguments;
}
m_commitThread->addCommit(std::move(primaryPlaneUpdate));
return Error::None;
} else {
if (m_primaryLayer->hasDirectScanoutBuffer()) {
// already presented
@ -126,6 +143,9 @@ DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QVector<DrmPipeline
if (!pipeline->prepareAtomicPresentation(commit.get())) {
return Error::InvalidArguments;
}
if (pipeline->m_pending.crtc->cursorPlane()) {
pipeline->prepareAtomicCursor(commit.get());
}
if (mode == CommitMode::TestAllowModeset || mode == CommitMode::CommitModeset) {
if (!pipeline->prepareAtomicModeset(commit.get())) {
return Error::InvalidArguments;
@ -170,24 +190,6 @@ DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QVector<DrmPipeline
}
return Error::None;
}
case CommitMode::Commit: {
Q_ASSERT(pipelines.size() == 1);
Q_ASSERT(unusedObjects.isEmpty());
const auto pipeline = pipelines.front();
pipeline->m_commitThread->addCommit(std::move(commit));
pipeline->atomicCommitSuccessful();
return Error::None;
}
case CommitMode::CommitCursor: {
commit->setCursorOnly(true);
if (!commit->test()) {
return errnoToError();
}
const auto pipeline = pipelines.front();
pipeline->m_commitThread->addCommit(std::move(commit));
pipeline->atomicCommitSuccessful();
return Error::None;
};
default:
Q_UNREACHABLE();
}
@ -231,16 +233,18 @@ bool DrmPipeline::prepareAtomicPresentation(DrmAtomicCommit *commit)
const auto fb = m_primaryLayer->currentBuffer();
m_pending.crtc->primaryPlane()->set(commit, QPoint(0, 0), fb->buffer()->size(), centerBuffer(fb->buffer()->size(), m_pending.mode->size()));
commit->addBuffer(m_pending.crtc->primaryPlane(), fb);
if (auto plane = m_pending.crtc->cursorPlane()) {
const auto layer = cursorLayer();
plane->set(commit, QPoint(0, 0), gpu()->cursorSize(), QRect(layer->position().toPoint(), gpu()->cursorSize()));
commit->addProperty(plane->crtcId, layer->isEnabled() ? m_pending.crtc->id() : 0);
commit->addBuffer(plane, layer->isEnabled() ? layer->currentBuffer() : nullptr);
}
return true;
}
void DrmPipeline::prepareAtomicCursor(DrmAtomicCommit *commit)
{
auto plane = m_pending.crtc->cursorPlane();
const auto layer = cursorLayer();
plane->set(commit, QPoint(0, 0), gpu()->cursorSize(), QRect(layer->position().toPoint(), gpu()->cursorSize()));
commit->addProperty(plane->crtcId, layer->isEnabled() ? m_pending.crtc->id() : 0);
commit->addBuffer(plane, layer->isEnabled() ? layer->currentBuffer() : nullptr);
}
void DrmPipeline::prepareAtomicDisable(DrmAtomicCommit *commit)
{
m_connector->disable(commit);
@ -318,6 +322,7 @@ bool DrmPipeline::prepareAtomicModeset(DrmAtomicCommit *commit)
if (cursor->pixelBlendMode.isValid()) {
commit->addEnum(cursor->pixelBlendMode, DrmPlane::PixelBlendMode::PreMultiplied);
}
prepareAtomicCursor(commit);
}
return true;
}
@ -362,17 +367,27 @@ bool DrmPipeline::updateCursor()
if (!m_pending.crtc) {
return false;
}
bool result;
// explicitly check for the cursor plane and not for AMS, as we might not always have one
if (m_pending.crtc->cursorPlane()) {
if (needsModeset() || !m_current.active) {
return false;
}
result = commitPipelines({this}, CommitMode::CommitCursor) == Error::None;
// test the full state, to take pending commits into account
auto fullState = std::make_unique<DrmAtomicCommit>(QVector<DrmPipeline *>{this});
prepareAtomicPresentation(fullState.get());
prepareAtomicCursor(fullState.get());
if (!fullState->test()) {
return false;
}
// only give the actual state update to the commit thread, so that it can potentially reorder the commits
auto cursorOnly = std::make_unique<DrmAtomicCommit>(QVector<DrmPipeline *>{this});
prepareAtomicCursor(cursorOnly.get());
cursorOnly->setCursorOnly(true);
m_commitThread->addCommit(std::move(cursorOnly));
return true;
} else {
result = setCursorLegacy();
return setCursorLegacy();
}
return result;
}
void DrmPipeline::applyPendingChanges()

View file

@ -136,8 +136,6 @@ public:
enum class CommitMode {
Test,
TestAllowModeset,
Commit,
CommitCursor,
CommitModeset
};
Q_ENUM(CommitMode)
@ -161,6 +159,7 @@ private:
void atomicCommitSuccessful();
bool prepareAtomicModeset(DrmAtomicCommit *commit);
bool prepareAtomicPresentation(DrmAtomicCommit *commit);
void prepareAtomicCursor(DrmAtomicCommit *commit);
void prepareAtomicDisable(DrmAtomicCommit *commit);
static Error commitPipelinesAtomic(const QVector<DrmPipeline *> &pipelines, CommitMode mode, const QVector<DrmObject *> &unusedObjects);