Wayland: variable refresh rate support

BUG: 405912
This commit is contained in:
Xaver Hugl 2021-02-20 16:04:18 +01:00
parent 2a6fe9748f
commit faddf0bf5f
17 changed files with 307 additions and 43 deletions

View file

@ -179,6 +179,10 @@ void AbstractWaylandOutput::applyChanges(const KWaylandServer::OutputChangeSet *
qCDebug(KWIN_CORE) << "Setting overscan:" << changeSet->overscan();
setOverscan(changeSet->overscan());
}
if (changeSet->vrrPolicyChanged()) {
qCDebug(KWIN_CORE) << "Setting VRR Policy:" << changeSet->vrrPolicy();
setVrrPolicy(static_cast<RenderLoop::VrrPolicy>(changeSet->vrrPolicy()));
}
overallSizeCheckNeeded |= emitModeChanged;
if (overallSizeCheckNeeded) {
@ -368,4 +372,17 @@ void AbstractWaylandOutput::setOverscan(uint32_t overscan)
Q_UNUSED(overscan);
}
void AbstractWaylandOutput::setVrrPolicy(RenderLoop::VrrPolicy policy)
{
if (renderLoop()->vrrPolicy() != policy) {
renderLoop()->setVrrPolicy(policy);
emit vrrPolicyChanged();
}
}
RenderLoop::VrrPolicy AbstractWaylandOutput::vrrPolicy() const
{
return renderLoop()->vrrPolicy();
}
}

View file

@ -11,6 +11,7 @@
#include "abstract_output.h"
#include "utils.h"
#include "renderloop.h"
#include <kwin_export.h>
#include <QObject>
@ -66,6 +67,7 @@ public:
enum class Capability : uint {
Dpms = 0x1,
Overscan = 0x2,
Vrr = 0x4,
};
Q_DECLARE_FLAGS(Capabilities, Capability)
@ -142,6 +144,9 @@ public:
bool isBeingRecorded();
void setVrrPolicy(RenderLoop::VrrPolicy policy);
RenderLoop::VrrPolicy vrrPolicy() const;
Q_SIGNALS:
void modeChanged();
void outputChange(const QRegion &damagedRegion);
@ -150,6 +155,7 @@ Q_SIGNALS:
void dpmsModeChanged();
void capabilitiesChanged();
void overscanChanged();
void vrrPolicyChanged();
protected:
void initialize(const QString &model, const QString &manufacturer,

View file

@ -39,6 +39,7 @@ bool DrmConnector::init()
PropertyDefinition(QByteArrayLiteral("DPMS")),
PropertyDefinition(QByteArrayLiteral("EDID")),
PropertyDefinition(QByteArrayLiteral("overscan")),
PropertyDefinition(QByteArrayLiteral("vrr_capable")),
}, DRM_MODE_OBJECT_CONNECTOR)) {
return false;
}
@ -158,4 +159,12 @@ void DrmConnector::setOverscan(uint32_t overscan)
setValue(PropertyIndex::Overscan, overscan);
}
bool DrmConnector::vrrCapable() const
{
if (const auto &prop = m_props[static_cast<int>(PropertyIndex::VrrCapable)]) {
return prop->value();
}
return false;
}
}

View file

@ -31,6 +31,7 @@ public:
Dpms = 2,
Edid = 3,
Overscan = 4,
VrrCapable = 5,
Count
};
@ -66,6 +67,8 @@ public:
uint32_t overscan() const;
void setOverscan(uint32_t overscan);
bool vrrCapable() const;
private:
DrmScopedPointer<drmModeConnector> m_conn;
QVector<uint32_t> m_encoders;

View file

@ -33,6 +33,7 @@ bool DrmCrtc::init()
return initProps({
PropertyDefinition(QByteArrayLiteral("MODE_ID")),
PropertyDefinition(QByteArrayLiteral("ACTIVE")),
PropertyDefinition(QByteArrayLiteral("VRR_ENABLED")),
}, DRM_MODE_OBJECT_CRTC);
}
@ -81,4 +82,30 @@ bool DrmCrtc::setGammaRamp(const GammaRamp &gamma)
return !isError;
}
bool DrmCrtc::setVrr(bool enable)
{
if (const auto &prop = m_props[static_cast<int>(PropertyIndex::VrrEnabled)]) {
if (prop->value() == enable) {
return false;
}
prop->setValue(enable);
if (!gpu()->atomicModeSetting() || gpu()->useEglStreams()) {
if (drmModeObjectSetProperty(gpu()->fd(), id(), DRM_MODE_OBJECT_CRTC, prop->propId(), enable) != 0) {
qCWarning(KWIN_DRM) << "drmModeObjectSetProperty(VRR_ENABLED) failed";
return false;
}
}
return true;
}
return false;
}
bool DrmCrtc::isVrrEnabled() const
{
if (const auto &prop = m_props[static_cast<int>(PropertyIndex::VrrEnabled)]) {
return prop->value();
}
return false;
}
}

View file

@ -32,6 +32,7 @@ public:
enum class PropertyIndex : uint32_t {
ModeId = 0,
Active,
VrrEnabled,
Count
};
@ -60,6 +61,9 @@ public:
}
bool setGammaRamp(const GammaRamp &gamma);
bool setVrr(bool enable);
bool isVrrEnabled() const;
private:
DrmScopedPointer<drmModeCrtc> m_crtc;
QSharedPointer<DrmBuffer> m_currentBuffer;

View file

@ -16,6 +16,7 @@
#include "logging.h"
#include "main.h"
#include "renderloop.h"
#include "renderloop_p.h"
#include "screens.h"
#include "session.h"
// Qt
@ -76,13 +77,25 @@ void DrmOutput::teardown()
bool DrmOutput::hideCursor()
{
if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
&& isCursorVisible()) {
m_renderLoop->scheduleRepaint();
}
return drmModeSetCursor(m_gpu->fd(), m_crtc->id(), 0, 0, 0) == 0;
}
bool DrmOutput::showCursor(DrmDumbBuffer *c)
{
const QSize &s = c->size();
return drmModeSetCursor(m_gpu->fd(), m_crtc->id(), c->handle(), s.width(), s.height()) == 0;
if (drmModeSetCursor(m_gpu->fd(), m_crtc->id(), c->handle(), s.width(), s.height()) == 0) {
if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
&& isCursorVisible()) {
m_renderLoop->scheduleRepaint();
}
return true;
} else {
return false;
}
}
bool DrmOutput::showCursor()
@ -97,10 +110,16 @@ bool DrmOutput::showCursor()
return ret;
}
bool visibleBefore = isCursorVisible();
if (m_hasNewCursor) {
m_cursorIndex = (m_cursorIndex + 1) % 2;
m_hasNewCursor = false;
}
if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
&& !visibleBefore
&& isCursorVisible()) {
m_renderLoop->scheduleRepaint();
}
return ret;
}
@ -142,7 +161,10 @@ bool DrmOutput::updateCursor()
p.setWorldTransform(logicalToNativeMatrix(cursor->rect(), 1, transform()).toTransform());
p.drawImage(QPoint(0, 0), cursorImage);
p.end();
if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
&& isCursorVisible()) {
m_renderLoop->scheduleRepaint();
}
return true;
}
@ -155,7 +177,15 @@ void DrmOutput::moveCursor()
QPoint pos = monitorMatrix.map(cursor->pos());
pos -= hotspotMatrix.map(cursor->hotspot());
drmModeMoveCursor(m_gpu->fd(), m_crtc->id(), pos.x(), pos.y());
if (pos != m_cursorPos) {
bool visible = isCursorVisible();
drmModeMoveCursor(m_gpu->fd(), m_crtc->id(), pos.x(), pos.y());
m_cursorPos = pos;
if (RenderLoopPrivate::get(m_renderLoop)->presentMode == RenderLoopPrivate::SyncMode::Adaptive
&& (visible || visible != isCursorVisible())) {
m_renderLoop->scheduleRepaint();
}
}
}
namespace {
@ -210,6 +240,10 @@ bool DrmOutput::init(drmModeConnector *connector)
setCapabilityInternal(Capability::Overscan);
setOverscanInternal(m_conn->overscan());
}
if (m_conn->vrrCapable()) {
setCapabilityInternal(Capability::Vrr);
setVrrPolicy(RenderLoop::VrrPolicy::Automatic);
}
initOutputDevice(connector);
if (!m_gpu->atomicModeSetting() && !m_crtc->blank(this)) {
@ -564,6 +598,8 @@ bool DrmOutput::present(const QSharedPointer<DrmBuffer> &buffer)
if (m_dpmsModePending != DpmsMode::On) {
return false;
}
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_renderLoop);
setVrr(renderLoopPrivate->presentMode == RenderLoopPrivate::SyncMode::Adaptive);
return m_gpu->atomicModeSetting() ? presentAtomically(buffer) : presentLegacy(buffer);
}
@ -747,6 +783,12 @@ bool DrmOutput::doAtomicCommit(AtomicCommitMode mode)
flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
}
if (!m_crtc->atomicPopulate(req)) {
qCWarning(KWIN_DRM) << "Failed to populate crtc. Abort atomic commit!";
errorHandler();
return false;
}
if (mode == AtomicCommitMode::Real) {
if (m_dpmsModePending == DpmsMode::On) {
if (!(flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) {
@ -834,11 +876,7 @@ bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable)
m_crtc->setValue(DrmCrtc::PropertyIndex::ModeId, enable ? m_blobId : 0);
m_crtc->setValue(DrmCrtc::PropertyIndex::Active, enable);
bool ret = true;
ret &= m_conn->atomicPopulate(req);
ret &= m_crtc->atomicPopulate(req);
return ret;
return m_conn->atomicPopulate(req);
}
int DrmOutput::gammaRampSize() const
@ -859,4 +897,21 @@ void DrmOutput::setOverscan(uint32_t overscan)
}
}
void DrmOutput::setVrr(bool enable)
{
if (!m_conn->vrrCapable() || m_crtc->isVrrEnabled() == enable) {
return;
}
if (!m_crtc->setVrr(enable) || (m_gpu->atomicModeSetting() && !doAtomicCommit(AtomicCommitMode::Test))) {
qCWarning(KWIN_DRM) << "Failed to set vrr on" << this;
setVrrPolicy(RenderLoop::VrrPolicy::Never);
m_crtc->setVrr(false);
}
}
bool DrmOutput::isCursorVisible()
{
return m_cursor[m_cursorIndex] && QRect(m_cursorPos, m_cursor[m_cursorIndex]->size()).intersects(QRect(0, 0, m_mode.vdisplay, m_mode.hdisplay));
}
}

View file

@ -129,6 +129,9 @@ private:
bool setGammaRamp(const GammaRamp &gamma) override;
void setOverscan(uint32_t overscan) override;
void setVrr(bool enable);
bool isCursorVisible();
DrmBackend *m_backend;
DrmGpu *m_gpu;
DrmConnector *m_conn = nullptr;
@ -141,6 +144,8 @@ private:
uint32_t m_blobId = 0;
DrmPlane *m_primaryPlane = nullptr;
QVector<DrmPlane*> m_nextPlanesFlipList;
QPoint m_cursorPos;
bool m_pageFlipPending = false;
bool m_atomicOffPending = false;
bool m_modesetRequested = true;

View file

@ -644,41 +644,42 @@ void SceneOpenGL::paint(int screenId, const QRegion &damage, const QList<Topleve
} else {
renderLoop->beginFrame();
bool directScanout = false;
if (m_backend->directScanoutAllowed(screenId)) {
EffectsHandlerImpl *implEffects = static_cast<EffectsHandlerImpl*>(effects);
if (!implEffects->blocksDirectScanout()) {
for (int i = stacking_order.count() - 1; i >= 0; i--) {
Window *window = stacking_order[i];
Toplevel *toplevel = window->window();
if (toplevel->isOnScreen(screenId) && window->isVisible() && toplevel->opacity() > 0) {
AbstractClient *c = dynamic_cast<AbstractClient*>(toplevel);
if (!c || !c->isFullScreen()) {
break;
}
if (!window->surfaceItem()) {
continue;
}
SurfaceItem *topMost = findTopMostSurface(window->surfaceItem());
auto pixmap = topMost->windowPixmap();
if (!pixmap) {
break;
}
pixmap->update();
// the subsurface has to be able to cover the whole window
if (topMost->position() != QPoint(0, 0)) {
break;
}
// and it has to be completely opaque
if (!window->isOpaque() && !topMost->opaque().contains(QRect(0, 0, window->width(), window->height()))) {
break;
}
directScanout = m_backend->scanout(screenId, topMost);
break;
}
SurfaceItem *fullscreenSurface = nullptr;
for (int i = stacking_order.count() - 1; i >=0; i--) {
Window *window = stacking_order[i];
Toplevel *toplevel = window->window();
if (toplevel->isOnScreen(screenId) && window->isVisible() && toplevel->opacity() > 0) {
AbstractClient *c = dynamic_cast<AbstractClient*>(toplevel);
if (!c || !c->isFullScreen()) {
break;
}
if (!window->surfaceItem()) {
break;
}
SurfaceItem *topMost = findTopMostSurface(window->surfaceItem());
auto pixmap = topMost->windowPixmap();
if (!pixmap) {
break;
}
pixmap->update();
// the subsurface has to be able to cover the whole window
if (topMost->position() != QPoint(0, 0)) {
break;
}
// and it has to be completely opaque
if (!window->isOpaque() && !topMost->opaque().contains(QRect(0, 0, window->width(), window->height()))) {
break;
}
fullscreenSurface = topMost;
break;
}
}
renderLoop->setFullscreenSurface(fullscreenSurface);
bool directScanout = false;
if (m_backend->directScanoutAllowed(screenId) && !static_cast<EffectsHandlerImpl*>(effects)->blocksDirectScanout()) {
directScanout = m_backend->scanout(screenId, fullscreenSurface);
}
if (directScanout) {
renderLoop->endFrame();
} else {

View file

@ -8,6 +8,7 @@
#include "options.h"
#include "renderloop_p.h"
#include "utils.h"
#include "surfaceitem.h"
namespace KWin
{
@ -32,12 +33,30 @@ RenderLoopPrivate::RenderLoopPrivate(RenderLoop *q)
void RenderLoopPrivate::scheduleRepaint()
{
if (compositeTimer.isActive() || kwinApp()->isTerminating()) {
if (kwinApp()->isTerminating()) {
return;
}
if (vrrPolicy == RenderLoop::VrrPolicy::Always || (vrrPolicy == RenderLoop::VrrPolicy::Automatic && hasFullscreenSurface)) {
presentMode = SyncMode::Adaptive;
} else {
presentMode = SyncMode::Fixed;
}
const std::chrono::nanoseconds vblankInterval(1'000'000'000'000ull / refreshRate);
if (presentMode == SyncMode::Adaptive) {
std::chrono::nanoseconds timeSincePresent = std::chrono::steady_clock::now().time_since_epoch() - lastPresentationTimestamp;
if (timeSincePresent > vblankInterval) {
// client renders slower than refresh rate -> immediately present
compositeTimer.start(0);
return;
}
// client renders faster than refresh rate -> normal frame scheduling
}
if (compositeTimer.isActive()) {
return;
}
const std::chrono::nanoseconds currentTime(std::chrono::steady_clock::now().time_since_epoch());
const std::chrono::nanoseconds vblankInterval(1'000'000'000'000ull / refreshRate);
// Estimate when the next presentation will occur. Note that this is a prediction.
nextPresentationTimestamp = lastPresentationTimestamp + vblankInterval;
@ -231,4 +250,19 @@ std::chrono::nanoseconds RenderLoop::nextPresentationTimestamp() const
return d->nextPresentationTimestamp;
}
void RenderLoop::setFullscreenSurface(SurfaceItem *surfaceItem)
{
d->hasFullscreenSurface = surfaceItem != nullptr;
}
RenderLoop::VrrPolicy RenderLoop::vrrPolicy() const
{
return d->vrrPolicy;
}
void RenderLoop::setVrrPolicy(VrrPolicy policy)
{
d->vrrPolicy = policy;
}
} // namespace KWin

View file

@ -14,6 +14,7 @@ namespace KWin
{
class RenderLoopPrivate;
class SurfaceItem;
/**
* The RenderLoop class represents the compositing scheduler on a particular output.
@ -85,6 +86,29 @@ public:
*/
std::chrono::nanoseconds nextPresentationTimestamp() const;
/**
* Sets the surface that currently gets scanned out,
* so that this RenderLoop can adjust its timing behavior to that surface
*/
void setFullscreenSurface(SurfaceItem *surface);
enum class VrrPolicy : uint32_t {
Never = 0,
Always = 1,
Automatic = 2,
};
Q_ENUM(VrrPolicy);
/**
* the current policy regarding the use of variable refresh rate
*/
VrrPolicy vrrPolicy() const;
/**
* Set the policy regarding the use of variable refresh rate with RenderLoop
*/
void setVrrPolicy(VrrPolicy vrrPolicy);
Q_SIGNALS:
/**
* This signal is emitted when the refresh rate of this RenderLoop has changed.

View file

@ -40,6 +40,14 @@ public:
int inhibitCount = 0;
bool pendingReschedule = false;
bool pendingRepaint = false;
RenderLoop::VrrPolicy vrrPolicy = RenderLoop::VrrPolicy::Never;
bool hasFullscreenSurface = false;
enum class SyncMode {
Fixed,
Adaptive,
};
SyncMode presentMode = SyncMode::Fixed;
};
} // namespace KWin

View file

@ -17,6 +17,7 @@
#include <config-kwin.h>
#include "platform.h"
#include "wayland_server.h"
#include "abstract_wayland_output.h"
#ifdef KWIN_UNIT_TEST
#include <mock_screens.h>
#endif
@ -256,6 +257,34 @@ int Screens::physicalDpiY(int screen) const
return size(screen).height() / physicalSize(screen).height() * qreal(25.4);
}
bool Screens::isVrrCapable(int screen) const
{
#ifdef KWIN_UNIT_TEST
Q_UNUSED(screen);
return false;
#else
if (auto output = findOutput(screen)) {
if (auto waylandoutput = dynamic_cast<AbstractWaylandOutput*>(output)) {
return waylandoutput->capabilities() & AbstractWaylandOutput::Capability::Vrr;
}
}
#endif
return true;
}
RenderLoop::VrrPolicy Screens::vrrPolicy(int screen) const
{
#ifdef KWIN_UNIT_TEST
Q_UNUSED(screen);
return RenderLoop::VrrPolicy::Never;
#else
if (auto output = findOutput(screen)) {
return output->renderLoop()->vrrPolicy();
}
return RenderLoop::VrrPolicy::Never;
#endif
}
int Screens::number(const QPoint &pos) const
{
// TODO: Do something about testScreens and other tests that use MockScreens.

View file

@ -11,6 +11,7 @@
// KWin includes
#include <kwinglobals.h>
#include <renderloop.h>
// KDE includes
#include <KConfig>
#include <KSharedConfig>
@ -131,6 +132,15 @@ public:
int physicalDpiX(int screen) const;
int physicalDpiY(int screen) const;
/**
* @returns @c true if the @p screen is capable of variable refresh rate and if the platform can use it
*/
bool isVrrCapable(int screen) const;
/**
* @returns the vrr policy of the @p screen
*/
RenderLoop::VrrPolicy vrrPolicy(int screen) const;
public Q_SLOTS:
void reconfigure();

View file

@ -26,9 +26,17 @@ static KWaylandServer::OutputDeviceInterface::Capabilities kwinCapabilitiesToOut
if (caps & AbstractWaylandOutput::Capability::Overscan) {
ret |= KWaylandServer::OutputDeviceInterface::Capability::Overscan;
}
if (caps & AbstractWaylandOutput::Capability::Vrr) {
ret |= KWaylandServer::OutputDeviceInterface::Capability::Vrr;
}
return ret;
}
static KWaylandServer::OutputDeviceInterface::VrrPolicy kwinVrrPolicyToOutputDeviceVrrPolicy(RenderLoop::VrrPolicy policy)
{
return static_cast<KWaylandServer::OutputDeviceInterface::VrrPolicy>(policy);
}
WaylandOutputDevice::WaylandOutputDevice(AbstractWaylandOutput *output, QObject *parent)
: QObject(parent)
, m_platformOutput(output)
@ -47,6 +55,7 @@ WaylandOutputDevice::WaylandOutputDevice(AbstractWaylandOutput *output, QObject
m_outputDevice->setSubPixel(kwinSubPixelToOutputDeviceSubPixel(output->subPixel()));
m_outputDevice->setOverscan(output->overscan());
m_outputDevice->setCapabilities(kwinCapabilitiesToOutputDeviceCapabilities(output->capabilities()));
m_outputDevice->setVrrPolicy(kwinVrrPolicyToOutputDeviceVrrPolicy(output->vrrPolicy()));
const auto modes = output->modes();
for (const AbstractWaylandOutput::Mode &mode : modes) {
@ -79,6 +88,8 @@ WaylandOutputDevice::WaylandOutputDevice(AbstractWaylandOutput *output, QObject
this, &WaylandOutputDevice::handleCapabilitiesChanged);
connect(output, &AbstractWaylandOutput::overscanChanged,
this, &WaylandOutputDevice::handleOverscanChanged);
connect(output, &AbstractWaylandOutput::vrrPolicyChanged,
this, &WaylandOutputDevice::handleVrrPolicyChanged);
}
void WaylandOutputDevice::handleGeometryChanged()
@ -120,4 +131,9 @@ void WaylandOutputDevice::handleOverscanChanged()
m_outputDevice->setOverscan(m_platformOutput->overscan());
}
void WaylandOutputDevice::handleVrrPolicyChanged()
{
m_outputDevice->setVrrPolicy(kwinVrrPolicyToOutputDeviceVrrPolicy(m_platformOutput->vrrPolicy()));
}
} // namespace KWin

View file

@ -28,6 +28,7 @@ private Q_SLOTS:
void handleModeChanged();
void handleCapabilitiesChanged();
void handleOverscanChanged();
void handleVrrPolicyChanged();
private:
AbstractWaylandOutput *m_platformOutput;

View file

@ -1537,7 +1537,22 @@ QString Workspace::supportInformation() const
.arg(geo.width())
.arg(geo.height()));
support.append(QStringLiteral("Scale: %1\n").arg(screens()->scale(i)));
support.append(QStringLiteral("Refresh Rate: %1\n\n").arg(screens()->refreshRate(i)));
support.append(QStringLiteral("Refresh Rate: %1\n").arg(screens()->refreshRate(i)));
QString vrr = QStringLiteral("incapable");
if (screens()->isVrrCapable(i)) {
switch(screens()->vrrPolicy(i)) {
case RenderLoop::VrrPolicy::Never:
vrr = QStringLiteral("never");
break;
case RenderLoop::VrrPolicy::Always:
vrr = QStringLiteral("always");
break;
case RenderLoop::VrrPolicy::Automatic:
vrr = QStringLiteral("automatic");
break;
}
}
support.append(QStringLiteral("Adaptive Sync: %1\n").arg(vrr));
}
support.append(QStringLiteral("\nCompositing\n"));
support.append(QStringLiteral( "===========\n"));