Compare commits
4 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
192232e8d1 | |
![]() |
71628fa9c1 | |
![]() |
2ab11fcb29 | |
![]() |
c3b43f26fb |
autotests/drm
src
backends
drm
virtual
x11
|
@ -384,7 +384,7 @@ void DrmTest::testModeset()
|
|||
layer->beginFrame();
|
||||
output->renderLoop()->prepareNewFrame();
|
||||
output->renderLoop()->beginPaint();
|
||||
const auto frame = std::make_shared<OutputFrame>(output->renderLoop());
|
||||
const auto frame = std::make_shared<OutputFrame>(output->renderLoop(), std::chrono::nanoseconds(1'000'000'000'000 / output->refreshRate()));
|
||||
layer->endFrame(infiniteRegion(), infiniteRegion(), frame.get());
|
||||
QVERIFY(output->present(frame));
|
||||
|
||||
|
|
|
@ -27,17 +27,6 @@ RenderLoop *DrmAbstractOutput::renderLoop() const
|
|||
return m_renderLoop.get();
|
||||
}
|
||||
|
||||
void DrmAbstractOutput::frameFailed() const
|
||||
{
|
||||
m_frame->failed();
|
||||
}
|
||||
|
||||
void DrmAbstractOutput::pageFlipped(std::chrono::nanoseconds timestamp, PresentationMode mode)
|
||||
{
|
||||
m_frame->presented(std::chrono::nanoseconds(1'000'000'000'000 / refreshRate()), timestamp, mode);
|
||||
m_frame.reset();
|
||||
}
|
||||
|
||||
DrmGpu *DrmAbstractOutput::gpu() const
|
||||
{
|
||||
return m_gpu;
|
||||
|
|
|
@ -25,8 +25,6 @@ public:
|
|||
DrmAbstractOutput(DrmGpu *gpu);
|
||||
|
||||
RenderLoop *renderLoop() const override;
|
||||
void frameFailed() const;
|
||||
void pageFlipped(std::chrono::nanoseconds timestamp, PresentationMode mode);
|
||||
DrmGpu *gpu() const;
|
||||
|
||||
virtual bool present(const std::shared_ptr<OutputFrame> &frame) = 0;
|
||||
|
@ -39,7 +37,6 @@ protected:
|
|||
friend class DrmGpu;
|
||||
|
||||
std::unique_ptr<RenderLoop> m_renderLoop;
|
||||
std::shared_ptr<OutputFrame> m_frame;
|
||||
DrmGpu *const m_gpu;
|
||||
};
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#include "drm_commit.h"
|
||||
#include "core/renderbackend.h"
|
||||
#include "drm_blob.h"
|
||||
#include "drm_buffer.h"
|
||||
#include "drm_connector.h"
|
||||
|
@ -54,10 +55,11 @@ void DrmAtomicCommit::addBlob(const DrmProperty &prop, const std::shared_ptr<Drm
|
|||
m_blobs[&prop] = blob;
|
||||
}
|
||||
|
||||
void DrmAtomicCommit::addBuffer(DrmPlane *plane, const std::shared_ptr<DrmFramebuffer> &buffer)
|
||||
void DrmAtomicCommit::addBuffer(DrmPlane *plane, const std::shared_ptr<DrmFramebuffer> &buffer, const std::shared_ptr<OutputFrame> &frame)
|
||||
{
|
||||
addProperty(plane->fbId, buffer ? buffer->framebufferId() : 0);
|
||||
m_buffers[plane] = buffer;
|
||||
m_frames[plane] = frame;
|
||||
// atomic commits with IN_FENCE_FD fail with NVidia
|
||||
if (plane->inFenceFd.isValid() && !plane->gpu()->isNVidia()) {
|
||||
addProperty(plane->inFenceFd, buffer ? buffer->syncFd().get() : -1);
|
||||
|
@ -132,20 +134,20 @@ bool DrmAtomicCommit::doCommit(uint32_t flags)
|
|||
return drmIoctl(m_gpu->fd(), DRM_IOCTL_MODE_ATOMIC, &commitData) == 0;
|
||||
}
|
||||
|
||||
void DrmAtomicCommit::pageFlipped(std::chrono::nanoseconds timestamp) const
|
||||
void DrmAtomicCommit::pageFlipped(std::chrono::nanoseconds timestamp)
|
||||
{
|
||||
Q_ASSERT(QThread::currentThread() == QApplication::instance()->thread());
|
||||
for (const auto &[plane, buffer] : m_buffers) {
|
||||
plane->setCurrentBuffer(buffer);
|
||||
}
|
||||
DrmPipeline::PageflipType type = DrmPipeline::PageflipType::Normal;
|
||||
if (m_modeset) {
|
||||
type = DrmPipeline::PageflipType::Modeset;
|
||||
} else if (m_cursorOnly) {
|
||||
type = DrmPipeline::PageflipType::CursorOnly;
|
||||
for (const auto &[plane, frame] : m_frames) {
|
||||
if (frame) {
|
||||
frame->presented(timestamp, m_mode);
|
||||
}
|
||||
}
|
||||
m_frames.clear();
|
||||
for (const auto pipeline : std::as_const(m_pipelines)) {
|
||||
pipeline->pageFlipped(timestamp, type, m_mode);
|
||||
pipeline->pageFlipped(timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,6 +188,7 @@ void DrmAtomicCommit::merge(DrmAtomicCommit *onTop)
|
|||
}
|
||||
for (const auto &[plane, buffer] : onTop->m_buffers) {
|
||||
m_buffers[plane] = buffer;
|
||||
m_frames[plane] = onTop->m_frames[plane];
|
||||
m_planes.emplace(plane);
|
||||
}
|
||||
for (const auto &[prop, blob] : onTop->m_blobs) {
|
||||
|
@ -207,16 +210,16 @@ bool DrmAtomicCommit::isCursorOnly() const
|
|||
return m_cursorOnly;
|
||||
}
|
||||
|
||||
DrmLegacyCommit::DrmLegacyCommit(DrmPipeline *pipeline, const std::shared_ptr<DrmFramebuffer> &buffer)
|
||||
DrmLegacyCommit::DrmLegacyCommit(DrmPipeline *pipeline, const std::shared_ptr<DrmFramebuffer> &buffer, const std::shared_ptr<OutputFrame> &frame)
|
||||
: DrmCommit(pipeline->gpu())
|
||||
, m_pipeline(pipeline)
|
||||
, m_buffer(buffer)
|
||||
, m_frame(frame)
|
||||
{
|
||||
}
|
||||
|
||||
bool DrmLegacyCommit::doModeset(DrmConnector *connector, DrmConnectorMode *mode)
|
||||
{
|
||||
m_modeset = true;
|
||||
uint32_t connectorId = connector->id();
|
||||
if (drmModeSetCrtc(gpu()->fd(), m_pipeline->crtc()->id(), m_buffer->framebufferId(), 0, 0, &connectorId, 1, mode->nativeMode()) == 0) {
|
||||
m_pipeline->crtc()->setCurrent(m_buffer);
|
||||
|
@ -236,10 +239,14 @@ bool DrmLegacyCommit::doPageflip(PresentationMode mode)
|
|||
return drmModePageFlip(gpu()->fd(), m_pipeline->crtc()->id(), m_buffer->framebufferId(), flags, this) == 0;
|
||||
}
|
||||
|
||||
void DrmLegacyCommit::pageFlipped(std::chrono::nanoseconds timestamp) const
|
||||
void DrmLegacyCommit::pageFlipped(std::chrono::nanoseconds timestamp)
|
||||
{
|
||||
Q_ASSERT(QThread::currentThread() == QApplication::instance()->thread());
|
||||
m_pipeline->crtc()->setCurrent(m_buffer);
|
||||
m_pipeline->pageFlipped(timestamp, m_modeset ? DrmPipeline::PageflipType::Modeset : DrmPipeline::PageflipType::Normal, m_mode);
|
||||
if (m_frame) {
|
||||
m_frame->presented(timestamp, m_mode);
|
||||
m_frame.reset();
|
||||
}
|
||||
m_pipeline->pageFlipped(timestamp);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ class DrmGpu;
|
|||
class DrmPlane;
|
||||
class DrmProperty;
|
||||
class DrmPipeline;
|
||||
class OutputFrame;
|
||||
|
||||
class DrmCommit
|
||||
{
|
||||
|
@ -39,7 +40,7 @@ public:
|
|||
virtual ~DrmCommit();
|
||||
|
||||
DrmGpu *gpu() const;
|
||||
virtual void pageFlipped(std::chrono::nanoseconds timestamp) const = 0;
|
||||
virtual void pageFlipped(std::chrono::nanoseconds timestamp) = 0;
|
||||
|
||||
protected:
|
||||
DrmCommit(DrmGpu *gpu);
|
||||
|
@ -60,7 +61,7 @@ public:
|
|||
addProperty(prop, prop.valueForEnum(enumValue));
|
||||
}
|
||||
void addBlob(const DrmProperty &prop, const std::shared_ptr<DrmBlob> &blob);
|
||||
void addBuffer(DrmPlane *plane, const std::shared_ptr<DrmFramebuffer> &buffer);
|
||||
void addBuffer(DrmPlane *plane, const std::shared_ptr<DrmFramebuffer> &buffer, const std::shared_ptr<OutputFrame> &frame);
|
||||
void setVrr(DrmCrtc *crtc, bool vrr);
|
||||
void setPresentationMode(PresentationMode mode);
|
||||
|
||||
|
@ -69,7 +70,7 @@ public:
|
|||
bool commit();
|
||||
bool commitModeset();
|
||||
|
||||
void pageFlipped(std::chrono::nanoseconds timestamp) const override;
|
||||
void pageFlipped(std::chrono::nanoseconds timestamp) override;
|
||||
|
||||
bool areBuffersReadable() const;
|
||||
void setDeadline(std::chrono::steady_clock::time_point deadline);
|
||||
|
@ -87,6 +88,7 @@ private:
|
|||
const QList<DrmPipeline *> m_pipelines;
|
||||
std::unordered_map<const DrmProperty *, std::shared_ptr<DrmBlob>> m_blobs;
|
||||
std::unordered_map<DrmPlane *, std::shared_ptr<DrmFramebuffer>> m_buffers;
|
||||
std::unordered_map<DrmPlane *, std::shared_ptr<OutputFrame>> m_frames;
|
||||
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;
|
||||
|
@ -98,16 +100,16 @@ private:
|
|||
class DrmLegacyCommit : public DrmCommit
|
||||
{
|
||||
public:
|
||||
DrmLegacyCommit(DrmPipeline *pipeline, const std::shared_ptr<DrmFramebuffer> &buffer);
|
||||
DrmLegacyCommit(DrmPipeline *pipeline, const std::shared_ptr<DrmFramebuffer> &buffer, const std::shared_ptr<OutputFrame> &frame);
|
||||
|
||||
bool doModeset(DrmConnector *connector, DrmConnectorMode *mode);
|
||||
bool doPageflip(PresentationMode mode);
|
||||
void pageFlipped(std::chrono::nanoseconds timestamp) const override;
|
||||
void pageFlipped(std::chrono::nanoseconds timestamp) override;
|
||||
|
||||
private:
|
||||
DrmPipeline *const m_pipeline;
|
||||
const std::shared_ptr<DrmFramebuffer> m_buffer;
|
||||
bool m_modeset = false;
|
||||
std::shared_ptr<OutputFrame> m_frame;
|
||||
PresentationMode m_mode = PresentationMode::VSync;
|
||||
};
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "drm_logging.h"
|
||||
#include "utils/realtime.h"
|
||||
|
||||
#include <span>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace KWin
|
||||
|
@ -116,7 +118,7 @@ void DrmCommitThread::submit()
|
|||
auto toMerge = std::move(m_commits[1]);
|
||||
m_commits.erase(m_commits.begin() + 1);
|
||||
m_commits.front()->merge(toMerge.get());
|
||||
m_droppedCommits.push_back(std::move(toMerge));
|
||||
m_commitsToDelete.push_back(std::move(toMerge));
|
||||
}
|
||||
if (commit->test()) {
|
||||
// presentation didn't fail after all, try again
|
||||
|
@ -124,21 +126,24 @@ void DrmCommitThread::submit()
|
|||
return;
|
||||
}
|
||||
}
|
||||
const bool cursorOnly = std::ranges::all_of(m_commits, [](const auto &commit) {
|
||||
return commit->isCursorOnly();
|
||||
});
|
||||
for (auto &commit : m_commits) {
|
||||
m_droppedCommits.push_back(std::move(commit));
|
||||
m_commitsToDelete.push_back(std::move(commit));
|
||||
}
|
||||
m_commits.clear();
|
||||
qCWarning(KWIN_DRM) << "atomic commit failed:" << strerror(errno);
|
||||
if (!cursorOnly) {
|
||||
QMetaObject::invokeMethod(this, &DrmCommitThread::commitFailed, Qt::ConnectionType::QueuedConnection);
|
||||
}
|
||||
}
|
||||
QMetaObject::invokeMethod(this, &DrmCommitThread::clearDroppedCommits, Qt::ConnectionType::QueuedConnection);
|
||||
}
|
||||
|
||||
static std::unique_ptr<DrmAtomicCommit> mergeCommits(std::span<const std::unique_ptr<DrmAtomicCommit>> commits)
|
||||
{
|
||||
auto ret = std::make_unique<DrmAtomicCommit>(*commits.front());
|
||||
for (const auto &onTop : commits.subspan(1)) {
|
||||
ret->merge(onTop.get());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void DrmCommitThread::optimizeCommits()
|
||||
{
|
||||
if (m_commits.size() <= 1) {
|
||||
|
@ -146,22 +151,31 @@ void DrmCommitThread::optimizeCommits()
|
|||
}
|
||||
// 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);
|
||||
const auto firstNotReadable = std::find_if(m_commits.begin() + 1, m_commits.end(), [](const auto &commit) {
|
||||
return !commit->areBuffersReadable();
|
||||
});
|
||||
if (firstNotReadable != m_commits.begin() + 1) {
|
||||
auto merged = mergeCommits(std::span(m_commits.begin(), firstNotReadable));
|
||||
std::move(m_commits.begin(), firstNotReadable, std::back_inserter(m_commitsToDelete));
|
||||
m_commits.erase(m_commits.begin() + 1, firstNotReadable);
|
||||
m_commits.front() = std::move(merged);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
const auto startIt = it;
|
||||
auto &startCommit = *startIt;
|
||||
const auto firstNotSamePlaneReadable = std::find_if(startIt + 1, m_commits.end(), [&startCommit](const auto &commit) {
|
||||
return startCommit->modifiedPlanes() != commit->modifiedPlanes() || !commit->areBuffersReadable();
|
||||
});
|
||||
if (firstNotSamePlaneReadable == startIt + 1) {
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
auto merged = mergeCommits(std::span(startIt, firstNotSamePlaneReadable));
|
||||
std::move(startIt, firstNotSamePlaneReadable, std::back_inserter(m_commitsToDelete));
|
||||
startCommit = std::move(merged);
|
||||
it = m_commits.erase(startIt + 1, firstNotSamePlaneReadable);
|
||||
}
|
||||
if (m_commits.size() == 1) {
|
||||
// already done
|
||||
|
@ -169,7 +183,10 @@ void DrmCommitThread::optimizeCommits()
|
|||
}
|
||||
std::unique_ptr<DrmAtomicCommit> front;
|
||||
if (m_commits.front()->areBuffersReadable()) {
|
||||
front = std::move(m_commits.front());
|
||||
// can't just move the commit, or merging might drop the last reference
|
||||
// to an OutputFrame, which should only happen in the main thread
|
||||
front = std::make_unique<DrmAtomicCommit>(*m_commits.front());
|
||||
m_commitsToDelete.push_back(std::move(m_commits.front()));
|
||||
m_commits.erase(m_commits.begin());
|
||||
}
|
||||
// try to move commits that are ready to the front
|
||||
|
@ -192,7 +209,7 @@ void DrmCommitThread::optimizeCommits()
|
|||
duplicate = std::make_unique<DrmAtomicCommit>(*front);
|
||||
duplicate->merge(commit.get());
|
||||
if (!duplicate->test()) {
|
||||
m_droppedCommits.push_back(std::move(duplicate));
|
||||
m_commitsToDelete.push_back(std::move(duplicate));
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
|
@ -213,13 +230,14 @@ void DrmCommitThread::optimizeCommits()
|
|||
}
|
||||
}
|
||||
}
|
||||
m_droppedCommits.push_back(std::move(duplicate));
|
||||
m_commitsToDelete.push_back(std::move(duplicate));
|
||||
if (success) {
|
||||
if (front) {
|
||||
front->merge(commit.get());
|
||||
m_droppedCommits.push_back(std::move(commit));
|
||||
m_commitsToDelete.push_back(std::move(commit));
|
||||
} else {
|
||||
front = std::move(commit);
|
||||
front = std::make_unique<DrmAtomicCommit>(*commit);
|
||||
m_commitsToDelete.push_back(std::move(commit));
|
||||
}
|
||||
it = m_commits.erase(it);
|
||||
} else {
|
||||
|
@ -262,7 +280,7 @@ void DrmCommitThread::setPendingCommit(std::unique_ptr<DrmLegacyCommit> &&commit
|
|||
void DrmCommitThread::clearDroppedCommits()
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_droppedCommits.clear();
|
||||
m_commitsToDelete.clear();
|
||||
}
|
||||
|
||||
void DrmCommitThread::setModeInfo(uint32_t maximum, std::chrono::nanoseconds vblankTime)
|
||||
|
|
|
@ -43,9 +43,6 @@ public:
|
|||
*/
|
||||
std::chrono::nanoseconds safetyMargin() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void commitFailed();
|
||||
|
||||
private:
|
||||
void clearDroppedCommits();
|
||||
TimePoint estimateNextVblank(TimePoint now) const;
|
||||
|
@ -60,7 +57,7 @@ private:
|
|||
TimePoint m_lastPageflip;
|
||||
TimePoint m_targetPageflipTime;
|
||||
std::chrono::nanoseconds m_minVblankInterval;
|
||||
std::vector<std::unique_ptr<DrmAtomicCommit>> m_droppedCommits;
|
||||
std::vector<std::unique_ptr<DrmAtomicCommit>> m_commitsToDelete;
|
||||
bool m_vrr = false;
|
||||
std::chrono::nanoseconds m_safetyMargin{0};
|
||||
};
|
||||
|
|
|
@ -83,7 +83,7 @@ ColorDescription EglGbmLayer::colorDescription() const
|
|||
return m_surface.colorDescription();
|
||||
}
|
||||
|
||||
bool EglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color)
|
||||
bool EglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame)
|
||||
{
|
||||
static bool valid;
|
||||
static const bool directScanoutDisabled = qEnvironmentVariableIntValue("KWIN_DRM_NO_DIRECT_SCANOUT", &valid) == 1 && valid;
|
||||
|
@ -113,7 +113,7 @@ bool EglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescriptio
|
|||
return false;
|
||||
}
|
||||
m_scanoutBuffer = m_pipeline->gpu()->importBuffer(buffer, FileDescriptor{});
|
||||
if (m_scanoutBuffer && m_pipeline->testScanout()) {
|
||||
if (m_scanoutBuffer && m_pipeline->testScanout(frame)) {
|
||||
m_surface.forgetDamage(); // TODO: Use absolute frame sequence numbers for indexing the DamageJournal. It's more flexible and less error-prone
|
||||
return true;
|
||||
} else {
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
std::optional<QSize> fixedSize() const override;
|
||||
|
||||
private:
|
||||
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color) override;
|
||||
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame) override;
|
||||
|
||||
std::shared_ptr<DrmFramebuffer> m_scanoutBuffer;
|
||||
|
||||
|
|
|
@ -740,13 +740,16 @@ bool DrmGpu::isActive() const
|
|||
|
||||
bool DrmGpu::needsModeset() const
|
||||
{
|
||||
return m_forceModeset || std::ranges::any_of(m_pipelines, [](const auto &pipeline) {
|
||||
return pipeline->needsModeset();
|
||||
});
|
||||
return std::any_of(m_pipelines.constBegin(), m_pipelines.constEnd(), [](const auto &pipeline) {
|
||||
return pipeline->needsModeset();
|
||||
}) || m_forceModeset || !m_pendingModesetFrames.empty();
|
||||
}
|
||||
|
||||
bool DrmGpu::maybeModeset()
|
||||
bool DrmGpu::maybeModeset(const std::shared_ptr<OutputFrame> &frame)
|
||||
{
|
||||
if (frame) {
|
||||
m_pendingModesetFrames.push_back(frame);
|
||||
}
|
||||
auto pipelines = m_pipelines;
|
||||
for (const auto &output : std::as_const(m_drmOutputs)) {
|
||||
if (output->lease()) {
|
||||
|
@ -766,18 +769,20 @@ bool DrmGpu::maybeModeset()
|
|||
for (DrmPipeline *pipeline : std::as_const(pipelines)) {
|
||||
if (pipeline->modesetPresentPending()) {
|
||||
pipeline->resetModesetPresentPending();
|
||||
if (err != DrmPipeline::Error::None) {
|
||||
pipeline->output()->frameFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
m_forceModeset = false;
|
||||
if (err == DrmPipeline::Error::None) {
|
||||
for (const auto &frame : m_pendingModesetFrames) {
|
||||
frame->presented(std::chrono::steady_clock::now().time_since_epoch(), PresentationMode::VSync);
|
||||
}
|
||||
m_pendingModesetFrames.clear();
|
||||
return true;
|
||||
} else {
|
||||
if (err != DrmPipeline::Error::FramePending) {
|
||||
QTimer::singleShot(0, m_platform, &DrmBackend::updateOutputs);
|
||||
}
|
||||
m_pendingModesetFrames.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ class DrmVirtualOutput;
|
|||
class EglDisplay;
|
||||
class GraphicsBuffer;
|
||||
class GraphicsBufferAllocator;
|
||||
class OutputFrame;
|
||||
|
||||
class DrmLease : public QObject
|
||||
{
|
||||
|
@ -103,7 +104,7 @@ public:
|
|||
|
||||
DrmPipeline::Error testPendingConfiguration();
|
||||
bool needsModeset() const;
|
||||
bool maybeModeset();
|
||||
bool maybeModeset(const std::shared_ptr<OutputFrame> &frame);
|
||||
|
||||
std::shared_ptr<DrmFramebuffer> importBuffer(GraphicsBuffer *buffer, FileDescriptor &&explicitFence);
|
||||
void releaseBuffers();
|
||||
|
@ -159,6 +160,7 @@ private:
|
|||
|
||||
std::unique_ptr<QSocketNotifier> m_socketNotifier;
|
||||
QSize m_cursorSize;
|
||||
std::vector<std::shared_ptr<OutputFrame>> m_pendingModesetFrames;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ namespace KWin
|
|||
{
|
||||
|
||||
static const bool s_allowColorspaceIntel = qEnvironmentVariableIntValue("KWIN_DRM_ALLOW_INTEL_COLORSPACE") == 1;
|
||||
static const bool s_disableTripleBuffering = qEnvironmentVariableIntValue("KWIN_DRM_DISABLE_TRIPLE_BUFFERING") == 1;
|
||||
|
||||
DrmOutput::DrmOutput(const std::shared_ptr<DrmConnector> &conn)
|
||||
: DrmAbstractOutput(conn->gpu())
|
||||
|
@ -44,6 +45,9 @@ DrmOutput::DrmOutput(const std::shared_ptr<DrmConnector> &conn)
|
|||
{
|
||||
m_pipeline->setOutput(this);
|
||||
m_renderLoop->setRefreshRate(m_pipeline->mode()->refreshRate());
|
||||
if (m_gpu->atomicModeSetting() && !s_disableTripleBuffering) {
|
||||
m_renderLoop->setMaxPendingFrameCount(2);
|
||||
}
|
||||
|
||||
Capabilities capabilities = Capability::Dpms | Capability::IccProfile;
|
||||
State initialState;
|
||||
|
@ -277,20 +281,19 @@ void DrmOutput::updateDpmsMode(DpmsMode dpmsMode)
|
|||
|
||||
bool DrmOutput::present(const std::shared_ptr<OutputFrame> &frame)
|
||||
{
|
||||
m_frame = frame;
|
||||
const bool needsModeset = gpu()->needsModeset();
|
||||
bool success;
|
||||
if (needsModeset) {
|
||||
m_pipeline->setPresentationMode(PresentationMode::VSync);
|
||||
m_pipeline->setContentType(DrmConnector::DrmContentType::Graphics);
|
||||
success = m_pipeline->maybeModeset();
|
||||
success = m_pipeline->maybeModeset(frame);
|
||||
} else {
|
||||
m_pipeline->setPresentationMode(frame->presentationMode());
|
||||
DrmPipeline::Error err = m_pipeline->present();
|
||||
DrmPipeline::Error err = m_pipeline->present(frame);
|
||||
if (err != DrmPipeline::Error::None && frame->presentationMode() != PresentationMode::VSync) {
|
||||
// retry with a more basic presentation mode
|
||||
m_pipeline->setPresentationMode(PresentationMode::VSync);
|
||||
err = m_pipeline->present();
|
||||
err = m_pipeline->present(frame);
|
||||
}
|
||||
success = err == DrmPipeline::Error::None;
|
||||
if (err == DrmPipeline::Error::InvalidArguments) {
|
||||
|
@ -303,7 +306,6 @@ bool DrmOutput::present(const std::shared_ptr<OutputFrame> &frame)
|
|||
return true;
|
||||
} else if (!needsModeset) {
|
||||
qCWarning(KWIN_DRM) << "Presentation failed!" << strerror(errno);
|
||||
m_frame->failed();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -406,7 +408,7 @@ void DrmOutput::applyQueuedChanges(const std::shared_ptr<OutputChangeSet> &props
|
|||
setState(next);
|
||||
|
||||
if (!isEnabled() && m_pipeline->needsModeset()) {
|
||||
m_gpu->maybeModeset();
|
||||
m_gpu->maybeModeset(nullptr);
|
||||
}
|
||||
|
||||
m_renderLoop->setRefreshRate(refreshRate());
|
||||
|
|
|
@ -42,11 +42,6 @@ DrmPipeline::DrmPipeline(DrmConnector *conn)
|
|||
: m_connector(conn)
|
||||
, m_commitThread(std::make_unique<DrmCommitThread>(conn->gpu(), conn->connectorName()))
|
||||
{
|
||||
QObject::connect(m_commitThread.get(), &DrmCommitThread::commitFailed, [this]() {
|
||||
if (m_output) {
|
||||
m_output->frameFailed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
DrmPipeline::~DrmPipeline()
|
||||
|
@ -56,13 +51,13 @@ DrmPipeline::~DrmPipeline()
|
|||
}
|
||||
}
|
||||
|
||||
bool DrmPipeline::testScanout()
|
||||
bool DrmPipeline::testScanout(const std::shared_ptr<OutputFrame> &frame)
|
||||
{
|
||||
if (gpu()->needsModeset()) {
|
||||
return false;
|
||||
}
|
||||
if (gpu()->atomicModeSetting()) {
|
||||
return commitPipelines({this}, CommitMode::Test) == Error::None;
|
||||
return DrmPipeline::commitPipelinesAtomic({this}, CommitMode::Test, frame, {}) == Error::None;
|
||||
} else {
|
||||
if (m_primaryLayer->currentBuffer()->buffer()->size() != m_pending.mode->size()) {
|
||||
// scaling isn't supported with the legacy API
|
||||
|
@ -70,7 +65,7 @@ bool DrmPipeline::testScanout()
|
|||
}
|
||||
// no other way to test than to do it.
|
||||
// As we only have a maximum of one test per scanout cycle, this is fine
|
||||
const bool ret = presentLegacy() == Error::None;
|
||||
const bool ret = presentLegacy(frame) == Error::None;
|
||||
if (ret) {
|
||||
m_didLegacyScanoutHack = true;
|
||||
}
|
||||
|
@ -78,21 +73,17 @@ bool DrmPipeline::testScanout()
|
|||
}
|
||||
}
|
||||
|
||||
DrmPipeline::Error DrmPipeline::present()
|
||||
DrmPipeline::Error DrmPipeline::present(const std::shared_ptr<OutputFrame> &frame)
|
||||
{
|
||||
Q_ASSERT(m_pending.crtc);
|
||||
if (gpu()->atomicModeSetting()) {
|
||||
// test the full state, to take pending commits into account
|
||||
auto fullState = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
|
||||
if (Error err = prepareAtomicCommit(fullState.get(), CommitMode::Test); err != Error::None) {
|
||||
if (auto err = DrmPipeline::commitPipelinesAtomic({this}, CommitMode::Test, frame, {}); err != Error::None) {
|
||||
return err;
|
||||
}
|
||||
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>(QList<DrmPipeline *>{this});
|
||||
if (Error err = prepareAtomicPresentation(primaryPlaneUpdate.get()); err != Error::None) {
|
||||
if (Error err = prepareAtomicPresentation(primaryPlaneUpdate.get(), frame); err != Error::None) {
|
||||
return err;
|
||||
}
|
||||
if (m_pending.needsModesetProperties && !prepareAtomicModeset(primaryPlaneUpdate.get())) {
|
||||
|
@ -107,27 +98,27 @@ DrmPipeline::Error DrmPipeline::present()
|
|||
m_didLegacyScanoutHack = false;
|
||||
return Error::None;
|
||||
}
|
||||
return presentLegacy();
|
||||
return presentLegacy(frame);
|
||||
}
|
||||
}
|
||||
|
||||
bool DrmPipeline::maybeModeset()
|
||||
bool DrmPipeline::maybeModeset(const std::shared_ptr<OutputFrame> &frame)
|
||||
{
|
||||
m_modesetPresentPending = true;
|
||||
return gpu()->maybeModeset();
|
||||
return gpu()->maybeModeset(frame);
|
||||
}
|
||||
|
||||
DrmPipeline::Error DrmPipeline::commitPipelines(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects)
|
||||
{
|
||||
Q_ASSERT(!pipelines.isEmpty());
|
||||
if (pipelines[0]->gpu()->atomicModeSetting()) {
|
||||
return commitPipelinesAtomic(pipelines, mode, unusedObjects);
|
||||
return commitPipelinesAtomic(pipelines, mode, nullptr, unusedObjects);
|
||||
} else {
|
||||
return commitPipelinesLegacy(pipelines, mode, unusedObjects);
|
||||
}
|
||||
}
|
||||
|
||||
DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects)
|
||||
DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *> &pipelines, CommitMode mode, const std::shared_ptr<OutputFrame> &frame, const QList<DrmObject *> &unusedObjects)
|
||||
{
|
||||
auto commit = std::make_unique<DrmAtomicCommit>(pipelines);
|
||||
if (mode == CommitMode::Test) {
|
||||
|
@ -141,7 +132,7 @@ DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *>
|
|||
}
|
||||
}
|
||||
for (const auto &pipeline : pipelines) {
|
||||
if (Error err = pipeline->prepareAtomicCommit(commit.get(), mode); err != Error::None) {
|
||||
if (Error err = pipeline->prepareAtomicCommit(commit.get(), mode, frame); err != Error::None) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
@ -154,9 +145,9 @@ DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *>
|
|||
qCDebug(KWIN_DRM) << "Atomic modeset test failed!" << strerror(errno);
|
||||
return errnoToError();
|
||||
}
|
||||
const bool withoutModeset = std::ranges::all_of(pipelines, [](DrmPipeline *pipeline) {
|
||||
const bool withoutModeset = std::ranges::all_of(pipelines, [&frame](DrmPipeline *pipeline) {
|
||||
auto commit = std::make_unique<DrmAtomicCommit>(QVector<DrmPipeline *>{pipeline});
|
||||
return pipeline->prepareAtomicCommit(commit.get(), CommitMode::TestAllowModeset) == Error::None && commit->test();
|
||||
return pipeline->prepareAtomicCommit(commit.get(), CommitMode::TestAllowModeset, frame) == Error::None && commit->test();
|
||||
});
|
||||
for (const auto &pipeline : pipelines) {
|
||||
pipeline->m_pending.needsModeset = !withoutModeset;
|
||||
|
@ -191,10 +182,10 @@ DrmPipeline::Error DrmPipeline::commitPipelinesAtomic(const QList<DrmPipeline *>
|
|||
}
|
||||
}
|
||||
|
||||
DrmPipeline::Error DrmPipeline::prepareAtomicCommit(DrmAtomicCommit *commit, CommitMode mode)
|
||||
DrmPipeline::Error DrmPipeline::prepareAtomicCommit(DrmAtomicCommit *commit, CommitMode mode, const std::shared_ptr<OutputFrame> &frame)
|
||||
{
|
||||
if (activePending()) {
|
||||
if (Error err = prepareAtomicPresentation(commit); err != Error::None) {
|
||||
if (Error err = prepareAtomicPresentation(commit, frame); err != Error::None) {
|
||||
return err;
|
||||
}
|
||||
if (m_pending.crtc->cursorPlane()) {
|
||||
|
@ -211,7 +202,7 @@ DrmPipeline::Error DrmPipeline::prepareAtomicCommit(DrmAtomicCommit *commit, Com
|
|||
return Error::None;
|
||||
}
|
||||
|
||||
DrmPipeline::Error DrmPipeline::prepareAtomicPresentation(DrmAtomicCommit *commit)
|
||||
DrmPipeline::Error DrmPipeline::prepareAtomicPresentation(DrmAtomicCommit *commit, const std::shared_ptr<OutputFrame> &frame)
|
||||
{
|
||||
commit->setPresentationMode(m_pending.presentationMode);
|
||||
if (m_connector->contentType.isValid()) {
|
||||
|
@ -252,7 +243,7 @@ DrmPipeline::Error DrmPipeline::prepareAtomicPresentation(DrmAtomicCommit *commi
|
|||
return Error::InvalidArguments;
|
||||
}
|
||||
primary->set(commit, m_primaryLayer->sourceRect().toRect(), m_primaryLayer->targetRect());
|
||||
commit->addBuffer(m_pending.crtc->primaryPlane(), fb);
|
||||
commit->addBuffer(m_pending.crtc->primaryPlane(), fb, frame);
|
||||
if (fb->buffer()->dmabufAttributes()->format == DRM_FORMAT_NV12) {
|
||||
if (!primary->colorEncoding.isValid() || !primary->colorRange.isValid()) {
|
||||
// don't allow NV12 direct scanout if we don't know what the driver will do
|
||||
|
@ -271,14 +262,14 @@ void DrmPipeline::prepareAtomicCursor(DrmAtomicCommit *commit)
|
|||
if (layer->isEnabled()) {
|
||||
plane->set(commit, layer->sourceRect().toRect(), layer->targetRect());
|
||||
commit->addProperty(plane->crtcId, m_pending.crtc->id());
|
||||
commit->addBuffer(plane, layer->currentBuffer());
|
||||
commit->addBuffer(plane, layer->currentBuffer(), nullptr);
|
||||
if (plane->vmHotspotX.isValid() && plane->vmHotspotY.isValid()) {
|
||||
commit->addProperty(plane->vmHotspotX, std::round(layer->hotspot().x()));
|
||||
commit->addProperty(plane->vmHotspotY, std::round(layer->hotspot().y()));
|
||||
}
|
||||
} else {
|
||||
commit->addProperty(plane->crtcId, 0);
|
||||
commit->addBuffer(plane, nullptr);
|
||||
commit->addBuffer(plane, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -412,12 +403,7 @@ bool DrmPipeline::updateCursor()
|
|||
// explicitly check for the cursor plane and not for AMS, as we might not always have one
|
||||
if (m_pending.crtc->cursorPlane()) {
|
||||
// test the full state, to take pending commits into account
|
||||
auto fullState = std::make_unique<DrmAtomicCommit>(QList<DrmPipeline *>{this});
|
||||
if (prepareAtomicPresentation(fullState.get()) != Error::None) {
|
||||
return false;
|
||||
}
|
||||
prepareAtomicCursor(fullState.get());
|
||||
if (!fullState->test()) {
|
||||
if (DrmPipeline::commitPipelinesAtomic({this}, CommitMode::Test, nullptr, {}) != Error::None) {
|
||||
return false;
|
||||
}
|
||||
// only give the actual state update to the commit thread, so that it can potentially reorder the commits
|
||||
|
@ -456,19 +442,10 @@ DrmGpu *DrmPipeline::gpu() const
|
|||
return m_connector->gpu();
|
||||
}
|
||||
|
||||
void DrmPipeline::pageFlipped(std::chrono::nanoseconds timestamp, PageflipType type, PresentationMode mode)
|
||||
void DrmPipeline::pageFlipped(std::chrono::nanoseconds timestamp)
|
||||
{
|
||||
RenderLoopPrivate::get(m_output->renderLoop())->notifyVblank(timestamp);
|
||||
m_commitThread->pageFlipped(timestamp);
|
||||
if (type == PageflipType::Modeset && !activePending()) {
|
||||
return;
|
||||
}
|
||||
if (m_output) {
|
||||
if (type == PageflipType::Normal || type == PageflipType::Modeset) {
|
||||
m_output->pageFlipped(timestamp, mode);
|
||||
} else {
|
||||
RenderLoopPrivate::get(m_output->renderLoop())->notifyVblank(timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrmPipeline::setOutput(DrmOutput *output)
|
||||
|
|
|
@ -34,6 +34,7 @@ class GammaRamp;
|
|||
class DrmConnectorMode;
|
||||
class DrmPipelineLayer;
|
||||
class DrmCommitThread;
|
||||
class OutputFrame;
|
||||
|
||||
class DrmGammaRamp
|
||||
{
|
||||
|
@ -69,9 +70,9 @@ 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
|
||||
*/
|
||||
Error present();
|
||||
bool testScanout();
|
||||
bool maybeModeset();
|
||||
Error present(const std::shared_ptr<OutputFrame> &frame);
|
||||
bool testScanout(const std::shared_ptr<OutputFrame> &frame);
|
||||
bool maybeModeset(const std::shared_ptr<OutputFrame> &frame);
|
||||
void forceLegacyModeset();
|
||||
|
||||
bool needsModeset() const;
|
||||
|
@ -83,12 +84,7 @@ public:
|
|||
DrmConnector *connector() const;
|
||||
DrmGpu *gpu() const;
|
||||
|
||||
enum class PageflipType {
|
||||
Normal,
|
||||
CursorOnly,
|
||||
Modeset,
|
||||
};
|
||||
void pageFlipped(std::chrono::nanoseconds timestamp, PageflipType type, PresentationMode mode);
|
||||
void pageFlipped(std::chrono::nanoseconds timestamp);
|
||||
bool pageflipsPending() const;
|
||||
bool modesetPresentPending() const;
|
||||
void resetModesetPresentPending();
|
||||
|
@ -151,7 +147,7 @@ private:
|
|||
std::shared_ptr<DrmBlob> createHdrMetadata(NamedTransferFunction transferFunction) const;
|
||||
|
||||
// legacy only
|
||||
Error presentLegacy();
|
||||
Error presentLegacy(const std::shared_ptr<OutputFrame> &frame);
|
||||
Error legacyModeset();
|
||||
Error setLegacyGamma();
|
||||
Error applyPendingChangesLegacy();
|
||||
|
@ -159,12 +155,12 @@ private:
|
|||
static Error commitPipelinesLegacy(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects);
|
||||
|
||||
// atomic modesetting only
|
||||
Error prepareAtomicCommit(DrmAtomicCommit *commit, CommitMode mode);
|
||||
Error prepareAtomicCommit(DrmAtomicCommit *commit, CommitMode mode, const std::shared_ptr<OutputFrame> &frame);
|
||||
bool prepareAtomicModeset(DrmAtomicCommit *commit);
|
||||
Error prepareAtomicPresentation(DrmAtomicCommit *commit);
|
||||
Error prepareAtomicPresentation(DrmAtomicCommit *commit, const std::shared_ptr<OutputFrame> &frame);
|
||||
void prepareAtomicCursor(DrmAtomicCommit *commit);
|
||||
void prepareAtomicDisable(DrmAtomicCommit *commit);
|
||||
static Error commitPipelinesAtomic(const QList<DrmPipeline *> &pipelines, CommitMode mode, const QList<DrmObject *> &unusedObjects);
|
||||
static Error commitPipelinesAtomic(const QList<DrmPipeline *> &pipelines, CommitMode mode, const std::shared_ptr<OutputFrame> &frame, const QList<DrmObject *> &unusedObjects);
|
||||
|
||||
DrmOutput *m_output = nullptr;
|
||||
DrmConnector *m_connector = nullptr;
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
namespace KWin
|
||||
{
|
||||
|
||||
DrmPipeline::Error DrmPipeline::presentLegacy()
|
||||
DrmPipeline::Error DrmPipeline::presentLegacy(const std::shared_ptr<OutputFrame> &frame)
|
||||
{
|
||||
if (Error err = applyPendingChangesLegacy(); err != Error::None) {
|
||||
return err;
|
||||
|
@ -33,7 +33,7 @@ DrmPipeline::Error DrmPipeline::presentLegacy()
|
|||
if (m_primaryLayer->sourceRect() != m_primaryLayer->targetRect() || m_primaryLayer->targetRect() != QRect(QPoint(0, 0), buffer->buffer()->size())) {
|
||||
return Error::InvalidArguments;
|
||||
}
|
||||
auto commit = std::make_unique<DrmLegacyCommit>(this, buffer);
|
||||
auto commit = std::make_unique<DrmLegacyCommit>(this, buffer, frame);
|
||||
if (!commit->doPageflip(m_pending.presentationMode)) {
|
||||
qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno);
|
||||
return errnoToError();
|
||||
|
@ -57,7 +57,7 @@ DrmPipeline::Error DrmPipeline::legacyModeset()
|
|||
if (m_primaryLayer->sourceRect() != QRect(QPoint(0, 0), buffer->buffer()->size())) {
|
||||
return Error::InvalidArguments;
|
||||
}
|
||||
auto commit = std::make_unique<DrmLegacyCommit>(this, buffer);
|
||||
auto commit = std::make_unique<DrmLegacyCommit>(this, buffer, nullptr);
|
||||
if (!commit->doModeset(m_connector, m_pending.mode.get())) {
|
||||
qCWarning(KWIN_DRM) << "Modeset failed!" << strerror(errno);
|
||||
return errnoToError();
|
||||
|
@ -84,7 +84,7 @@ DrmPipeline::Error DrmPipeline::commitPipelinesLegacy(const QList<DrmPipeline *>
|
|||
for (const auto &pipeline : pipelines) {
|
||||
pipeline->applyPendingChanges();
|
||||
if (mode == CommitMode::CommitModeset && pipeline->activePending()) {
|
||||
pipeline->pageFlipped(std::chrono::steady_clock::now().time_since_epoch(), PageflipType::Normal, PresentationMode::VSync);
|
||||
pipeline->pageFlipped(std::chrono::steady_clock::now().time_since_epoch());
|
||||
}
|
||||
}
|
||||
for (const auto &obj : unusedObjects) {
|
||||
|
|
|
@ -162,7 +162,7 @@ void DrmPlane::setCurrentBuffer(const std::shared_ptr<DrmFramebuffer> &b)
|
|||
void DrmPlane::disable(DrmAtomicCommit *commit)
|
||||
{
|
||||
commit->addProperty(crtcId, 0);
|
||||
commit->addBuffer(this, nullptr);
|
||||
commit->addBuffer(this, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void DrmPlane::releaseCurrentBuffer()
|
||||
|
|
|
@ -133,7 +133,7 @@ std::shared_ptr<GLTexture> VirtualEglGbmLayer::texture() const
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bool VirtualEglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color)
|
||||
bool VirtualEglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame)
|
||||
{
|
||||
static bool valid;
|
||||
static const bool directScanoutDisabled = qEnvironmentVariableIntValue("KWIN_DRM_NO_DIRECT_SCANOUT", &valid) == 1 && valid;
|
||||
|
|
|
@ -44,7 +44,7 @@ public:
|
|||
const ColorDescription &colorDescription() const;
|
||||
|
||||
private:
|
||||
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color) override;
|
||||
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame) override;
|
||||
std::shared_ptr<EglSwapchain> createGbmSwapchain() const;
|
||||
bool doesGbmSwapchainFit(EglSwapchain *swapchain) const;
|
||||
|
||||
|
|
|
@ -50,15 +50,15 @@ bool DrmVirtualOutput::present(const std::shared_ptr<OutputFrame> &frame)
|
|||
{
|
||||
m_frame = frame;
|
||||
m_vsyncMonitor->arm();
|
||||
m_pageFlipPending = true;
|
||||
Q_EMIT outputChange(frame->damage());
|
||||
return true;
|
||||
}
|
||||
|
||||
void DrmVirtualOutput::vblank(std::chrono::nanoseconds timestamp)
|
||||
{
|
||||
if (m_pageFlipPending) {
|
||||
DrmAbstractOutput::pageFlipped(timestamp, PresentationMode::VSync);
|
||||
if (m_frame) {
|
||||
m_frame->presented(timestamp, PresentationMode::VSync);
|
||||
m_frame.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ private:
|
|||
void setDpmsMode(DpmsMode mode) override;
|
||||
|
||||
std::shared_ptr<DrmOutputLayer> m_layer;
|
||||
bool m_pageFlipPending = true;
|
||||
std::shared_ptr<OutputFrame> m_frame;
|
||||
|
||||
std::unique_ptr<SoftwareVsyncMonitor> m_vsyncMonitor;
|
||||
};
|
||||
|
|
|
@ -75,8 +75,10 @@ void VirtualOutput::updateEnabled(bool enabled)
|
|||
|
||||
void VirtualOutput::vblank(std::chrono::nanoseconds timestamp)
|
||||
{
|
||||
m_frame->presented(std::chrono::nanoseconds(1'000'000'000'000 / refreshRate()), timestamp, PresentationMode::VSync);
|
||||
m_frame.reset();
|
||||
if (m_frame) {
|
||||
m_frame->presented(timestamp, PresentationMode::VSync);
|
||||
m_frame.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ bool WaylandEglPrimaryLayer::doEndFrame(const QRegion &renderedRegion, const QRe
|
|||
return true;
|
||||
}
|
||||
|
||||
bool WaylandEglPrimaryLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color)
|
||||
bool WaylandEglPrimaryLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame)
|
||||
{
|
||||
Q_ASSERT(!m_presentationBuffer);
|
||||
// TODO use viewporter to relax this check
|
||||
|
|
|
@ -44,7 +44,7 @@ public:
|
|||
|
||||
std::optional<OutputLayerBeginFrameInfo> doBeginFrame() override;
|
||||
bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) override;
|
||||
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color) override;
|
||||
bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame) override;
|
||||
DrmDevice *scanoutDevice() const override;
|
||||
QHash<uint32_t, QList<uint64_t>> supportedDrmFormats() const override;
|
||||
|
||||
|
|
|
@ -125,8 +125,7 @@ WaylandOutput::WaylandOutput(const QString &name, WaylandBackend *backend)
|
|||
|
||||
connect(m_surface.get(), &KWayland::Client::Surface::frameRendered, this, [this]() {
|
||||
Q_ASSERT(m_frame);
|
||||
const auto primary = Compositor::self()->backend()->primaryLayer(this);
|
||||
m_frame->presented(std::chrono::nanoseconds(1'000'000'000'000 / refreshRate()), std::chrono::steady_clock::now().time_since_epoch(), PresentationMode::VSync);
|
||||
m_frame->presented(std::chrono::steady_clock::now().time_since_epoch(), PresentationMode::VSync);
|
||||
m_frame.reset();
|
||||
});
|
||||
|
||||
|
|
|
@ -441,7 +441,7 @@ OutputLayer *EglBackend::primaryLayer(Output *output)
|
|||
|
||||
void EglBackend::vblank(std::chrono::nanoseconds timestamp)
|
||||
{
|
||||
m_frame->presented(std::chrono::nanoseconds::zero(), timestamp, PresentationMode::VSync);
|
||||
m_frame->presented(timestamp, PresentationMode::VSync);
|
||||
m_frame.reset();
|
||||
}
|
||||
|
||||
|
|
|
@ -714,7 +714,7 @@ void GlxBackend::present(Output *output, const std::shared_ptr<OutputFrame> &fra
|
|||
|
||||
void GlxBackend::vblank(std::chrono::nanoseconds timestamp)
|
||||
{
|
||||
m_frame->presented(std::chrono::nanoseconds::zero(), timestamp, PresentationMode::VSync);
|
||||
m_frame->presented(timestamp, PresentationMode::VSync);
|
||||
m_frame.reset();
|
||||
}
|
||||
|
||||
|
|
|
@ -308,7 +308,7 @@ void X11WindowedOutput::resize(const QSize &pixelSize)
|
|||
void X11WindowedOutput::handlePresentCompleteNotify(xcb_present_complete_notify_event_t *event)
|
||||
{
|
||||
std::chrono::microseconds timestamp(event->ust);
|
||||
m_frame->presented(std::chrono::nanoseconds(1'000'000'000'000 / refreshRate()), timestamp, PresentationMode::VSync);
|
||||
m_frame->presented(timestamp, PresentationMode::VSync);
|
||||
m_frame.reset();
|
||||
}
|
||||
|
||||
|
|
|
@ -290,7 +290,7 @@ void WaylandCompositor::composite(RenderLoop *renderLoop)
|
|||
superLayer->setOutputLayer(primaryLayer);
|
||||
|
||||
renderLoop->prepareNewFrame();
|
||||
auto frame = std::make_shared<OutputFrame>(renderLoop);
|
||||
auto frame = std::make_shared<OutputFrame>(renderLoop, std::chrono::nanoseconds(1'000'000'000'000 / output->refreshRate()));
|
||||
|
||||
if (primaryLayer->needsRepaint() || superLayer->needsRepaint()) {
|
||||
renderLoop->beginPaint();
|
||||
|
@ -321,7 +321,7 @@ void WaylandCompositor::composite(RenderLoop *renderLoop)
|
|||
});
|
||||
if (scanoutPossible) {
|
||||
primaryLayer->setTargetRect(centerBuffer(output->transform().map(scanoutCandidate->size()), output->modeSize()));
|
||||
directScanout = primaryLayer->attemptScanout(scanoutCandidate);
|
||||
directScanout = primaryLayer->attemptScanout(scanoutCandidate, frame);
|
||||
}
|
||||
} else {
|
||||
primaryLayer->notifyNoScanoutCandidate();
|
||||
|
|
|
@ -457,7 +457,7 @@ void X11Compositor::composite(RenderLoop *renderLoop)
|
|||
superLayer->setOutputLayer(primaryLayer);
|
||||
|
||||
renderLoop->prepareNewFrame();
|
||||
auto frame = std::make_shared<OutputFrame>(renderLoop);
|
||||
auto frame = std::make_shared<OutputFrame>(renderLoop, std::chrono::nanoseconds(1'000'000'000'000) / renderLoop->refreshRate());
|
||||
|
||||
if (primaryLayer->needsRepaint() || superLayer->needsRepaint()) {
|
||||
renderLoop->beginPaint();
|
||||
|
|
|
@ -62,12 +62,12 @@ bool OutputLayer::needsRepaint() const
|
|||
return !m_repaints.isEmpty();
|
||||
}
|
||||
|
||||
bool OutputLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color)
|
||||
bool OutputLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OutputLayer::attemptScanout(SurfaceItem *surfaceItem)
|
||||
bool OutputLayer::attemptScanout(SurfaceItem *surfaceItem, const std::shared_ptr<OutputFrame> &frame)
|
||||
{
|
||||
SurfaceItemWayland *wayland = qobject_cast<SurfaceItemWayland *>(surfaceItem);
|
||||
if (!wayland || !wayland->surface()) {
|
||||
|
@ -91,7 +91,7 @@ bool OutputLayer::attemptScanout(SurfaceItem *surfaceItem)
|
|||
m_bufferTransform = surfaceItem->bufferTransform();
|
||||
const auto desiredTransform = m_output ? m_output->transform() : OutputTransform::Kind::Normal;
|
||||
m_offloadTransform = m_bufferTransform.combine(desiredTransform.inverted());
|
||||
const bool ret = doAttemptScanout(buffer, surfaceItem->colorDescription());
|
||||
const bool ret = doAttemptScanout(buffer, surfaceItem->colorDescription(), frame);
|
||||
if (ret) {
|
||||
surfaceItem->resetDamage();
|
||||
// ensure the pixmap is updated when direct scanout ends
|
||||
|
|
|
@ -65,7 +65,7 @@ public:
|
|||
* Tries to import the newest buffer of the surface for direct scanout
|
||||
* Returns @c true if scanout succeeds, @c false if rendering is necessary
|
||||
*/
|
||||
bool attemptScanout(SurfaceItem *item);
|
||||
bool attemptScanout(SurfaceItem *item, const std::shared_ptr<OutputFrame> &frame);
|
||||
|
||||
/**
|
||||
* Notify that there's no scanout candidate this frame
|
||||
|
@ -95,7 +95,7 @@ public:
|
|||
OutputTransform bufferTransform() const;
|
||||
|
||||
protected:
|
||||
virtual bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color);
|
||||
virtual bool doAttemptScanout(GraphicsBuffer *buffer, const ColorDescription &color, const std::shared_ptr<OutputFrame> &frame);
|
||||
virtual std::optional<OutputLayerBeginFrameInfo> doBeginFrame() = 0;
|
||||
virtual bool doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame) = 0;
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include "scene/surfaceitem.h"
|
||||
#include "syncobjtimeline.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QThread>
|
||||
#include <drm_fourcc.h>
|
||||
#include <ranges>
|
||||
|
||||
|
@ -42,12 +44,19 @@ std::optional<RenderTimeSpan> CpuRenderTimeQuery::query()
|
|||
};
|
||||
}
|
||||
|
||||
OutputFrame::OutputFrame(RenderLoop *loop)
|
||||
OutputFrame::OutputFrame(RenderLoop *loop, std::chrono::nanoseconds refreshDuration)
|
||||
: m_loop(loop)
|
||||
, m_refreshDuration(refreshDuration)
|
||||
{
|
||||
}
|
||||
|
||||
OutputFrame::~OutputFrame() = default;
|
||||
OutputFrame::~OutputFrame()
|
||||
{
|
||||
Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread());
|
||||
if (!m_presented) {
|
||||
RenderLoopPrivate::get(m_loop)->notifyFrameDropped();
|
||||
}
|
||||
}
|
||||
|
||||
void OutputFrame::addFeedback(std::unique_ptr<PresentationFeedback> &&feedback)
|
||||
{
|
||||
|
@ -74,20 +83,16 @@ std::optional<std::chrono::nanoseconds> OutputFrame::queryRenderTime() const
|
|||
return ret.end - ret.start;
|
||||
}
|
||||
|
||||
void OutputFrame::presented(std::chrono::nanoseconds refreshDuration, std::chrono::nanoseconds timestamp, PresentationMode mode)
|
||||
void OutputFrame::presented(std::chrono::nanoseconds timestamp, PresentationMode mode)
|
||||
{
|
||||
std::optional<std::chrono::nanoseconds> renderTime = queryRenderTime();
|
||||
RenderLoopPrivate::get(m_loop)->notifyFrameCompleted(timestamp, renderTime, mode);
|
||||
m_presented = true;
|
||||
for (const auto &feedback : m_feedbacks) {
|
||||
feedback->presented(refreshDuration, timestamp, mode);
|
||||
feedback->presented(m_refreshDuration, timestamp, mode);
|
||||
}
|
||||
}
|
||||
|
||||
void OutputFrame::failed()
|
||||
{
|
||||
RenderLoopPrivate::get(m_loop)->notifyFrameFailed();
|
||||
}
|
||||
|
||||
void OutputFrame::setContentType(ContentType type)
|
||||
{
|
||||
m_contentType = type;
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include "utils/filedescriptor.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <QPointer>
|
||||
#include <memory>
|
||||
|
||||
namespace KWin
|
||||
|
@ -75,11 +75,10 @@ private:
|
|||
class KWIN_EXPORT OutputFrame
|
||||
{
|
||||
public:
|
||||
explicit OutputFrame(RenderLoop *loop);
|
||||
explicit OutputFrame(RenderLoop *loop, std::chrono::nanoseconds refreshDuration);
|
||||
~OutputFrame();
|
||||
|
||||
void presented(std::chrono::nanoseconds refreshDuration, std::chrono::nanoseconds timestamp, PresentationMode mode);
|
||||
void failed();
|
||||
void presented(std::chrono::nanoseconds timestamp, PresentationMode mode);
|
||||
|
||||
void addFeedback(std::unique_ptr<PresentationFeedback> &&feedback);
|
||||
|
||||
|
@ -97,11 +96,13 @@ private:
|
|||
std::optional<std::chrono::nanoseconds> queryRenderTime() const;
|
||||
|
||||
RenderLoop *const m_loop;
|
||||
const std::chrono::nanoseconds m_refreshDuration;
|
||||
std::vector<std::unique_ptr<PresentationFeedback>> m_feedbacks;
|
||||
std::optional<ContentType> m_contentType;
|
||||
PresentationMode m_presentationMode = PresentationMode::VSync;
|
||||
QRegion m_damage;
|
||||
std::vector<std::unique_ptr<RenderTimeQuery>> m_renderTimeQueries;
|
||||
bool m_presented = false;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,15 +12,11 @@
|
|||
#include "window.h"
|
||||
#include "workspace.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
T alignTimestamp(const T ×tamp, const T &alignment)
|
||||
{
|
||||
return timestamp + ((alignment - (timestamp % alignment)) % alignment);
|
||||
}
|
||||
|
||||
RenderLoopPrivate *RenderLoopPrivate::get(RenderLoop *loop)
|
||||
{
|
||||
return loop->d.get();
|
||||
|
@ -36,36 +32,51 @@ RenderLoopPrivate::RenderLoopPrivate(RenderLoop *q, Output *output)
|
|||
});
|
||||
}
|
||||
|
||||
void RenderLoopPrivate::scheduleRepaint()
|
||||
static std::chrono::nanoseconds estimateNextPageflip(std::chrono::nanoseconds earliestSubmitTime, std::chrono::nanoseconds lastPageflip, std::chrono::nanoseconds vblankInterval)
|
||||
{
|
||||
// the last pageflip may be in the future
|
||||
const uint64_t pageflipsSince = earliestSubmitTime > lastPageflip ? (earliestSubmitTime - lastPageflip) / vblankInterval : 0;
|
||||
return lastPageflip + vblankInterval * (pageflipsSince + 1);
|
||||
}
|
||||
|
||||
void RenderLoopPrivate::scheduleNextRepaint()
|
||||
{
|
||||
if (kwinApp()->isTerminating() || compositeTimer.isActive()) {
|
||||
return;
|
||||
}
|
||||
scheduleRepaint(nextPresentationTimestamp);
|
||||
}
|
||||
|
||||
void RenderLoopPrivate::scheduleRepaint(std::chrono::nanoseconds lastTargetTimestamp)
|
||||
{
|
||||
pendingReschedule = false;
|
||||
const std::chrono::nanoseconds vblankInterval(1'000'000'000'000ull / refreshRate);
|
||||
const std::chrono::nanoseconds currentTime(std::chrono::steady_clock::now().time_since_epoch());
|
||||
|
||||
// Estimate when the next presentation will occur. Note that this is a prediction.
|
||||
nextPresentationTimestamp = lastPresentationTimestamp + vblankInterval;
|
||||
if (nextPresentationTimestamp < currentTime && presentationMode == PresentationMode::VSync) {
|
||||
nextPresentationTimestamp = lastPresentationTimestamp
|
||||
+ alignTimestamp(currentTime - lastPresentationTimestamp, vblankInterval);
|
||||
}
|
||||
|
||||
// Estimate when it's a good time to perform the next compositing cycle.
|
||||
// the 1ms on top of the safety margin is required for timer and scheduler inaccuracies
|
||||
std::chrono::nanoseconds nextRenderTimestamp = nextPresentationTimestamp - renderJournal.result() - safetyMargin - std::chrono::milliseconds(1);
|
||||
const std::chrono::nanoseconds expectedCompositingTime = std::min(renderJournal.result() + safetyMargin + 1ms, 2 * vblankInterval);
|
||||
|
||||
// If we can't render the frame before the deadline, start compositing immediately.
|
||||
if (nextRenderTimestamp < currentTime) {
|
||||
nextRenderTimestamp = currentTime;
|
||||
}
|
||||
|
||||
if (presentationMode == PresentationMode::Async || presentationMode == PresentationMode::AdaptiveAsync) {
|
||||
compositeTimer.start(0);
|
||||
if (presentationMode == PresentationMode::VSync) {
|
||||
// normal presentation: pageflips only happen at vblank
|
||||
if (maxPendingFrameCount == 1) {
|
||||
// keep the old behavior for backends not supporting triple buffering
|
||||
nextPresentationTimestamp = estimateNextPageflip(currentTime, lastPresentationTimestamp, vblankInterval);
|
||||
} else {
|
||||
// estimate the next pageflip that can realistically be hit
|
||||
nextPresentationTimestamp = estimateNextPageflip(std::max(lastTargetTimestamp, currentTime + expectedCompositingTime), lastPresentationTimestamp, vblankInterval);
|
||||
}
|
||||
} else if (presentationMode == PresentationMode::Async || presentationMode == PresentationMode::AdaptiveAsync) {
|
||||
// tearing: pageflips happen ASAP
|
||||
nextPresentationTimestamp = currentTime;
|
||||
} else {
|
||||
const std::chrono::nanoseconds waitInterval = nextRenderTimestamp - currentTime;
|
||||
compositeTimer.start(std::chrono::duration_cast<std::chrono::milliseconds>(waitInterval));
|
||||
// adaptive sync: pageflips happen after one vblank interval
|
||||
// TODO read minimum refresh rate from the EDID and take it into account here
|
||||
nextPresentationTimestamp = lastPresentationTimestamp + vblankInterval;
|
||||
}
|
||||
|
||||
const std::chrono::nanoseconds nextRenderTimestamp = nextPresentationTimestamp - expectedCompositingTime;
|
||||
compositeTimer.start(std::max(0ms, std::chrono::duration_cast<std::chrono::milliseconds>(nextRenderTimestamp - currentTime)));
|
||||
}
|
||||
|
||||
void RenderLoopPrivate::delayScheduleRepaint()
|
||||
|
@ -73,21 +84,13 @@ void RenderLoopPrivate::delayScheduleRepaint()
|
|||
pendingReschedule = true;
|
||||
}
|
||||
|
||||
void RenderLoopPrivate::maybeScheduleRepaint()
|
||||
{
|
||||
if (pendingReschedule) {
|
||||
scheduleRepaint();
|
||||
pendingReschedule = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderLoopPrivate::notifyFrameFailed()
|
||||
void RenderLoopPrivate::notifyFrameDropped()
|
||||
{
|
||||
Q_ASSERT(pendingFrameCount > 0);
|
||||
pendingFrameCount--;
|
||||
|
||||
if (!inhibitCount) {
|
||||
maybeScheduleRepaint();
|
||||
if (!inhibitCount && pendingReschedule) {
|
||||
scheduleNextRepaint();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,8 +104,12 @@ void RenderLoopPrivate::notifyFrameCompleted(std::chrono::nanoseconds timestamp,
|
|||
if (renderTime) {
|
||||
renderJournal.add(*renderTime, timestamp);
|
||||
}
|
||||
if (!inhibitCount) {
|
||||
maybeScheduleRepaint();
|
||||
if (compositeTimer.isActive()) {
|
||||
// reschedule to match the new timestamp and render time
|
||||
scheduleRepaint(lastPresentationTimestamp);
|
||||
}
|
||||
if (!inhibitCount && pendingReschedule) {
|
||||
scheduleNextRepaint();
|
||||
}
|
||||
|
||||
Q_EMIT q->framePresented(q, timestamp, mode);
|
||||
|
@ -165,7 +172,7 @@ void RenderLoop::uninhibit()
|
|||
d->inhibitCount--;
|
||||
|
||||
if (d->inhibitCount == 0) {
|
||||
d->maybeScheduleRepaint();
|
||||
d->scheduleNextRepaint();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,8 +217,8 @@ void RenderLoop::scheduleRepaint(Item *item)
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (!d->pendingFrameCount && !d->inhibitCount) {
|
||||
d->scheduleRepaint();
|
||||
if (d->pendingFrameCount < d->maxPendingFrameCount && !d->inhibitCount) {
|
||||
d->scheduleNextRepaint();
|
||||
} else {
|
||||
d->delayScheduleRepaint();
|
||||
}
|
||||
|
@ -232,6 +239,11 @@ void RenderLoop::setPresentationMode(PresentationMode mode)
|
|||
d->presentationMode = mode;
|
||||
}
|
||||
|
||||
void RenderLoop::setMaxPendingFrameCount(uint32_t maxCount)
|
||||
{
|
||||
d->maxPendingFrameCount = maxCount;
|
||||
}
|
||||
|
||||
} // namespace KWin
|
||||
|
||||
#include "moc_renderloop.cpp"
|
||||
|
|
|
@ -92,6 +92,8 @@ public:
|
|||
|
||||
void setPresentationMode(PresentationMode mode);
|
||||
|
||||
void setMaxPendingFrameCount(uint32_t maxCount);
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* This signal is emitted when the refresh rate of this RenderLoop has changed.
|
||||
|
|
|
@ -28,10 +28,10 @@ public:
|
|||
void invalidate();
|
||||
|
||||
void delayScheduleRepaint();
|
||||
void scheduleRepaint();
|
||||
void maybeScheduleRepaint();
|
||||
void scheduleNextRepaint();
|
||||
void scheduleRepaint(std::chrono::nanoseconds lastTargetTimestamp);
|
||||
|
||||
void notifyFrameFailed();
|
||||
void notifyFrameDropped();
|
||||
void notifyFrameCompleted(std::chrono::nanoseconds timestamp, std::optional<std::chrono::nanoseconds> renderTime, PresentationMode mode = PresentationMode::VSync);
|
||||
void notifyVblank(std::chrono::nanoseconds timestamp);
|
||||
|
||||
|
@ -49,6 +49,7 @@ public:
|
|||
std::chrono::nanoseconds safetyMargin{0};
|
||||
|
||||
PresentationMode presentationMode = PresentationMode::VSync;
|
||||
int maxPendingFrameCount = 1;
|
||||
};
|
||||
|
||||
} // namespace KWin
|
||||
|
|
Loading…
Reference in New Issue