Introduce RenderLoop

At the moment, our frame scheduling infrastructure is still heavily
based on Xinerama-style rendering. Specifically, we assume that painting
is driven by a single timer, etc.

This change introduces a new type - RenderLoop. Its main purpose is to
drive compositing on a specific output, or in case of X11, on the
overlay window.

With RenderLoop, compositing is synchronized to vblank events. It
exposes the last and the next estimated presentation timestamp. The
expected presentation timestamp can be used by effects to ensure that
animations are synchronized with the upcoming vblank event.

On Wayland, every outputs has its own render loop. On X11, per screen
rendering is not possible, therefore the platform exposes the render
loop for the overlay window. Ideally, the Scene has to expose the
RenderLoop, but as the first step towards better compositing scheduling
it's good as is for the time being.

The RenderLoop tries to minimize the latency by delaying compositing as
close as possible to the next vblank event. One tricky thing about it is
that if compositing is too close to the next vblank event, animations
may become a little bit choppy. However, increasing the latency reduces
the choppiness.

Given that, there is no any "silver bullet" solution for the choppiness
issue, a new option has been added in the Compositing KCM to specify the
amount of latency. By default, it's "Medium," but if a user is not
satisfied with the upstream default, they can tweak it.
This commit is contained in:
Vlad Zahorodnii 2020-11-19 10:52:29 +02:00
parent a3ec0c9a57
commit b8a70e62d5
74 changed files with 1837 additions and 508 deletions

View file

@ -506,6 +506,7 @@ set(kwin_SRCS
pluginmanager.cpp
pointer_input.cpp
popup_input_filter.cpp
renderloop.cpp
rootinfo_filter.cpp
rulebooksettings.cpp
rules.cpp

View file

@ -118,4 +118,9 @@ QString AbstractOutput::serialNumber() const
return QString();
}
RenderLoop *AbstractOutput::renderLoop() const
{
return nullptr;
}
} // namespace KWin

View file

@ -24,6 +24,8 @@ class OutputChangeSet;
namespace KWin
{
class RenderLoop;
class KWIN_EXPORT GammaRamp
{
public:
@ -177,6 +179,12 @@ public:
*/
virtual QString serialNumber() const;
/**
* Returns the RenderLoop for this output. This function returns @c null if the
* underlying platform doesn't support per-screen rendering mode.
*/
virtual RenderLoop *renderLoop() const;
Q_SIGNALS:
/**
* This signal is emitted when the geometry of this output has changed.

View file

@ -10,6 +10,7 @@
#include "abstract_client.h"
#include "composite.h"
#include "scene.h"
#include "wayland_server.h"
#include <KWayland/Client/xdgshell.h>
@ -54,10 +55,10 @@ void BufferSizeChangeTest::testShmBufferSizeChange()
QVERIFY(client);
// add a first repaint
QSignalSpy swapSpy(Compositor::self(), &Compositor::bufferSwapCompleted);
QVERIFY(swapSpy.isValid());
QSignalSpy frameRenderedSpy(Compositor::self()->scene(), &Scene::frameRendered);
QVERIFY(frameRenderedSpy.isValid());
Compositor::self()->addRepaintFull();
QVERIFY(swapSpy.wait());
QVERIFY(frameRenderedSpy.wait());
// now change buffer size
Test::render(surface.data(), QSize(30, 10), Qt::red);
@ -66,7 +67,7 @@ void BufferSizeChangeTest::testShmBufferSizeChange()
QVERIFY(damagedSpy.isValid());
QVERIFY(damagedSpy.wait());
KWin::Compositor::self()->addRepaintFull();
QVERIFY(swapSpy.wait());
QVERIFY(frameRenderedSpy.wait());
}
void BufferSizeChangeTest::testShmBufferSizeChangeOnSubSurface()
@ -91,10 +92,10 @@ void BufferSizeChangeTest::testShmBufferSizeChangeOnSubSurface()
QVERIFY(parent);
// add a first repaint
QSignalSpy swapSpy(Compositor::self(), &Compositor::bufferSwapCompleted);
QVERIFY(swapSpy.isValid());
QSignalSpy frameRenderedSpy(Compositor::self()->scene(), &Scene::frameRendered);
QVERIFY(frameRenderedSpy.isValid());
Compositor::self()->addRepaintFull();
QVERIFY(swapSpy.wait());
QVERIFY(frameRenderedSpy.wait());
// change buffer size of sub surface
QSignalSpy damagedParentSpy(parent, &AbstractClient::damaged);
@ -106,7 +107,7 @@ void BufferSizeChangeTest::testShmBufferSizeChangeOnSubSurface()
// add a second repaint
KWin::Compositor::self()->addRepaintFull();
QVERIFY(swapSpy.wait());
QVERIFY(frameRenderedSpy.wait());
}
}

View file

@ -7,7 +7,7 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "composite.h"
#include "abstract_output.h"
#include "dbusinterface.h"
#include "x11client.h"
#include "decorations/decoratedclient.h"
@ -16,6 +16,7 @@
#include "internal_client.h"
#include "overlaywindow.h"
#include "platform.h"
#include "renderloop.h"
#include "scene.h"
#include "screens.h"
#include "shadow.h"
@ -60,7 +61,6 @@ namespace KWin
extern int screen_number;
extern bool is_multihead;
extern int currentRefreshRate();
Compositor *Compositor::s_compositor = nullptr;
Compositor *Compositor::self()
@ -104,25 +104,15 @@ private:
bool m_owning;
};
static inline qint64 milliToNano(int milli) { return qint64(milli) * 1000 * 1000; }
static inline qint64 nanoToMilli(int nano) { return nano / (1000*1000); }
Compositor::Compositor(QObject* workspace)
: QObject(workspace)
, m_state(State::Off)
, m_selectionOwner(nullptr)
, vBlankInterval(0)
, fpsInterval(0)
, m_timeSinceLastVBlank(0)
, m_scene(nullptr)
, m_bufferSwapPending(false)
, m_composeAtSwapCompletion(false)
{
connect(options, &Options::configChanged, this, &Compositor::configChanged);
connect(options, &Options::animationSpeedChanged, this, &Compositor::configChanged);
m_monotonicClock.start();
// 2 sec which should be enough to restart the compositor.
static const int compositorLostMessageDelay = 2000;
@ -336,16 +326,18 @@ void Compositor::startupWithWorkspace()
Workspace::self()->markXStackingOrderAsDirty();
Q_ASSERT(m_scene);
connect(workspace(), &Workspace::destroyed, this, [this] { compositeTimer.stop(); });
fpsInterval = options->maxFpsInterval();
if (m_scene->syncsToVBlank()) {
// If we do vsync, set the fps to the next multiple of the vblank rate.
vBlankInterval = milliToNano(1000) / currentRefreshRate();
fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval);
const Platform *platform = kwinApp()->platform();
if (platform->isPerScreenRenderingEnabled()) {
const QVector<AbstractOutput *> outputs = platform->enabledOutputs();
for (AbstractOutput *output : outputs) {
registerRenderLoop(output->renderLoop(), output);
}
connect(platform, &Platform::outputEnabled,
this, &Compositor::handleOutputEnabled);
connect(platform, &Platform::outputDisabled,
this, &Compositor::handleOutputDisabled);
} else {
// No vsync - DO NOT set "0", would cause div-by-zero segfaults.
vBlankInterval = milliToNano(1);
registerRenderLoop(platform->renderLoop(), nullptr);
}
// Sets also the 'effects' pointer.
@ -383,13 +375,47 @@ void Compositor::startupWithWorkspace()
// Render at least once.
addRepaintFull();
performCompositing();
}
void Compositor::registerRenderLoop(RenderLoop *renderLoop, AbstractOutput *output)
{
Q_ASSERT(!m_renderLoops.contains(renderLoop));
m_renderLoops.insert(renderLoop, output);
connect(renderLoop, &RenderLoop::frameRequested, this, &Compositor::handleFrameRequested);
}
void Compositor::unregisterRenderLoop(RenderLoop *renderLoop)
{
Q_ASSERT(m_renderLoops.contains(renderLoop));
m_renderLoops.remove(renderLoop);
disconnect(renderLoop, &RenderLoop::frameRequested, this, &Compositor::handleFrameRequested);
}
void Compositor::handleOutputEnabled(AbstractOutput *output)
{
registerRenderLoop(output->renderLoop(), output);
}
void Compositor::handleOutputDisabled(AbstractOutput *output)
{
unregisterRenderLoop(output->renderLoop());
}
int Compositor::screenForRenderLoop(RenderLoop *renderLoop) const
{
Q_ASSERT(m_renderLoops.contains(renderLoop));
AbstractOutput *output = m_renderLoops.value(renderLoop);
if (!output) {
return -1;
}
return kwinApp()->platform()->enabledOutputs().indexOf(output);
}
void Compositor::scheduleRepaint()
{
if (!compositeTimer.isActive())
setCompositeTimer();
for (auto it = m_renderLoops.constBegin(); it != m_renderLoops.constEnd(); ++it) {
it.key()->scheduleRepaint();
}
}
void Compositor::stop()
@ -445,9 +471,17 @@ void Compositor::stop()
}
}
while (!m_renderLoops.isEmpty()) {
unregisterRenderLoop(m_renderLoops.firstKey());
}
disconnect(kwinApp()->platform(), &Platform::outputEnabled,
this, &Compositor::handleOutputEnabled);
disconnect(kwinApp()->platform(), &Platform::outputDisabled,
this, &Compositor::handleOutputDisabled);
delete m_scene;
m_scene = nullptr;
compositeTimer.stop();
m_state = State::Off;
emit compositingToggled(false);
@ -550,49 +584,16 @@ void Compositor::addRepaintFull()
addRepaint(screens()->geometry());
}
void Compositor::timerEvent(QTimerEvent *te)
void Compositor::handleFrameRequested(RenderLoop *renderLoop)
{
if (te->timerId() == compositeTimer.timerId()) {
performCompositing();
} else
QObject::timerEvent(te);
}
void Compositor::aboutToSwapBuffers()
{
m_bufferSwapPending = true;
}
void Compositor::bufferSwapComplete()
{
Q_ASSERT(m_bufferSwapPending);
m_bufferSwapPending = false;
emit bufferSwapCompleted();
if (m_composeAtSwapCompletion) {
m_composeAtSwapCompletion = false;
performCompositing();
}
}
void Compositor::performCompositing()
{
// If a buffer swap is still pending, we return to the event loop and
// continue processing events until the swap has completed.
if (m_bufferSwapPending) {
m_composeAtSwapCompletion = true;
compositeTimer.stop();
return;
}
// If outputs are disabled, we return to the event loop and
// continue processing events until the outputs are enabled again
if (!kwinApp()->platform()->areOutputsEnabled()) {
compositeTimer.stop();
return;
}
const int screenId = screenForRenderLoop(renderLoop);
// Create a list of all windows in the stacking order
QList<Toplevel *> windows = Workspace::self()->xStackingOrder();
QList<Toplevel *> damaged;
@ -650,28 +651,20 @@ void Compositor::performCompositing()
}
}
const std::chrono::nanoseconds now = std::chrono::steady_clock::now().time_since_epoch();
const std::chrono::milliseconds presentTime =
std::chrono::duration_cast<std::chrono::milliseconds>(now);
if (m_framesToTestForSafety > 0 && (m_scene->compositingType() & OpenGLCompositing)) {
kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PreFrame);
}
m_renderTimer.start();
if (kwinApp()->platform()->isPerScreenRenderingEnabled()) {
for (int screenId = 0; screenId < screens()->count(); ++screenId) {
const QRegion repaints = m_scene->repaints(screenId);
m_scene->resetRepaints(screenId);
m_scene->paint(screenId, repaints, windows, presentTime);
}
} else {
const QRegion repaints = m_scene->repaints(-1);
m_scene->resetRepaints(-1);
const std::chrono::milliseconds presentTime =
std::chrono::duration_cast<std::chrono::milliseconds>(renderLoop->nextPresentationTimestamp());
const QRegion repaints = m_scene->repaints(screenId);
m_scene->resetRepaints(screenId);
renderLoop->beginFrame();
m_scene->paint(screenId, repaints, windows, presentTime);
renderLoop->endFrame();
m_scene->paint(-1, repaints, windows, presentTime);
}
m_timeSinceLastVBlank = m_renderTimer.elapsed();
if (m_framesToTestForSafety > 0) {
if (m_scene->compositingType() & OpenGLCompositing) {
kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostFrame);
@ -684,77 +677,28 @@ void Compositor::performCompositing()
}
if (waylandServer()) {
const auto currentTime = static_cast<quint32>(m_monotonicClock.elapsed());
for (Toplevel *win : qAsConst(windows)) {
if (auto surface = win->surface()) {
surface->frameRendered(currentTime);
const std::chrono::milliseconds frameTime =
std::chrono::duration_cast<std::chrono::milliseconds>(renderLoop->lastPresentationTimestamp());
for (Toplevel *window : qAsConst(windows)) {
if (!window->readyForPainting()) {
continue;
}
if (waylandServer()->isScreenLocked() &&
!(window->isLockScreen() || window->isInputMethod())) {
continue;
}
if (!window->isOnScreen(screenId)) {
continue;
}
if (auto surface = window->surface()) {
surface->frameRendered(frameTime.count());
}
}
if (!kwinApp()->platform()->isCursorHidden()) {
Cursors::self()->currentCursor()->markAsRendered();
}
}
// Stop here to ensure *we* cause the next repaint schedule - not some effect
// through m_scene->paint().
compositeTimer.stop();
// Trigger at least one more pass even if there would be nothing to paint, so that scene->idle()
// is called the next time. If there would be nothing pending, it will not restart the timer and
// scheduleRepaint() would restart it again somewhen later, called from functions that
// would again add something pending.
if (m_bufferSwapPending && m_scene->syncsToVBlank()) {
m_composeAtSwapCompletion = true;
} else {
scheduleRepaint();
}
}
void Compositor::setCompositeTimer()
{
if (m_state != State::On) {
return;
}
// Don't start the timer if we're waiting for a swap event
if (m_bufferSwapPending && m_composeAtSwapCompletion)
return;
// Don't start the timer if all outputs are disabled
if (!kwinApp()->platform()->areOutputsEnabled()) {
return;
}
uint waitTime = 1;
if (fpsInterval > m_timeSinceLastVBlank) {
waitTime = nanoToMilli(fpsInterval - m_timeSinceLastVBlank);
if (!waitTime) {
// Will ensure we don't block out the eventloop - the system's just not faster ...
waitTime = 1;
}
}
/* else if (m_scene->syncsToVBlank() && m_timeSinceLastVBlank - fpsInterval < (vBlankInterval<<1)) {
// NOTICE - "for later" ------------------------------------------------------------------
// It can happen that we push two frames within one refresh cycle.
// Swapping will then block even with triple buffering when the GPU does not discard but
// queues frames
// now here's the mean part: if we take that as "OMG, we're late - next frame ASAP",
// there'll immediately be 2 frames in the pipe, swapping will block, we think we're
// late ... ewww
// so instead we pad to the clock again and add 2ms safety to ensure the pipe is really
// free
// NOTICE: obviously m_timeSinceLastVBlank can be too big because we're too slow as well
// So if this code was enabled, we'd needlessly half the framerate once more (15 instead of 30)
waitTime = nanoToMilli(vBlankInterval - (m_timeSinceLastVBlank - fpsInterval)%vBlankInterval) + 2;
}*/
else {
// "0" would be sufficient here, but the compositor isn't the WMs only task.
waitTime = 1;
}
// Force 4fps minimum:
compositeTimer.start(qMin(waitTime, 250u), this);
}
bool Compositor::isActive()
@ -789,18 +733,9 @@ void WaylandCompositor::start()
}
}
int WaylandCompositor::refreshRate() const
{
// TODO: This makes no sense on Wayland. First step would be to atleast
// set the refresh rate to the highest available one. Second step
// would be to not use a uniform value at all but per screen.
return KWin::currentRefreshRate();
}
X11Compositor::X11Compositor(QObject *parent)
: Compositor(parent)
, m_suspended(options->isUseCompositing() ? NoReasonSuspend : UserSuspend)
, m_xrrRefreshRate(0)
{
}
@ -882,16 +817,15 @@ void X11Compositor::start()
// Internal setup failed, abort.
return;
}
m_xrrRefreshRate = KWin::currentRefreshRate();
startupWithWorkspace();
}
void X11Compositor::performCompositing()
void X11Compositor::handleFrameRequested(RenderLoop *renderLoop)
{
if (scene()->usesOverlayWindow() && !isOverlayWindowVisible()) {
// Return since nothing is visible.
return;
}
Compositor::performCompositing();
Compositor::handleFrameRequested(renderLoop);
}
bool X11Compositor::checkForOverlayWindow(WId w) const
@ -919,11 +853,6 @@ bool X11Compositor::isOverlayWindowVisible() const
return scene()->overlayWindow()->isVisible();
}
int X11Compositor::refreshRate() const
{
return m_xrrRefreshRate;
}
void X11Compositor::updateClientCompositeBlocking(X11Client *c)
{
if (c) {

View file

@ -12,14 +12,15 @@
#include <kwinglobals.h>
#include <QObject>
#include <QElapsedTimer>
#include <QTimer>
#include <QBasicTimer>
#include <QRegion>
namespace KWin
{
class AbstractOutput;
class CompositorSelectionOwner;
class RenderLoop;
class Scene;
class X11Client;
@ -49,18 +50,6 @@ public:
*/
void scheduleRepaint();
/**
* Notifies the compositor that SwapBuffers() is about to be called.
* Rendering of the next frame will be deferred until bufferSwapComplete()
* is called.
*/
void aboutToSwapBuffers();
/**
* Notifies the compositor that a pending buffer swap has completed.
*/
void bufferSwapComplete();
/**
* Toggles compositing, that is if the Compositor is suspended it will be resumed
* and if the Compositor is active it will be suspended.
@ -79,7 +68,6 @@ public:
* not shutting down itself.
*/
bool isActive();
virtual int refreshRate() const = 0;
Scene *scene() const {
return m_scene;
@ -103,11 +91,9 @@ Q_SIGNALS:
void aboutToDestroy();
void aboutToToggleCompositing();
void sceneCreated();
void bufferSwapCompleted();
protected:
explicit Compositor(QObject *parent = nullptr);
void timerEvent(QTimerEvent *te) override;
virtual void start() = 0;
void stop();
@ -121,7 +107,6 @@ protected:
* Continues the startup after Scene And Workspace are created
*/
void startupWithWorkspace();
virtual void performCompositing();
virtual void configChanged();
@ -129,34 +114,33 @@ protected:
static Compositor *s_compositor;
protected Q_SLOTS:
virtual void handleFrameRequested(RenderLoop *renderLoop);
private Q_SLOTS:
void handleOutputEnabled(AbstractOutput *output);
void handleOutputDisabled(AbstractOutput *output);
private:
void initializeX11();
void cleanupX11();
void setCompositeTimer();
void releaseCompositorSelection();
void deleteUnusedSupportProperties();
int screenForRenderLoop(RenderLoop *renderLoop) const;
void registerRenderLoop(RenderLoop *renderLoop, AbstractOutput *output);
void unregisterRenderLoop(RenderLoop *renderLoop);
State m_state;
QBasicTimer compositeTimer;
CompositorSelectionOwner *m_selectionOwner;
QTimer m_releaseSelectionTimer;
QList<xcb_atom_t> m_unusedSupportProperties;
QTimer m_unusedSupportPropertyTimer;
qint64 vBlankInterval, fpsInterval;
qint64 m_timeSinceLastVBlank;
Scene *m_scene;
bool m_bufferSwapPending;
bool m_composeAtSwapCompletion;
int m_framesToTestForSafety = 3;
QElapsedTimer m_renderTimer;
QElapsedTimer m_monotonicClock;
QMap<RenderLoop *, AbstractOutput *> m_renderLoops;
};
class KWIN_EXPORT WaylandCompositor : public Compositor
@ -165,8 +149,6 @@ class KWIN_EXPORT WaylandCompositor : public Compositor
public:
static WaylandCompositor *create(QObject *parent = nullptr);
int refreshRate() const override;
void toggleCompositing() override;
protected:
@ -239,15 +221,13 @@ public:
*/
bool isOverlayWindowVisible() const;
int refreshRate() const override;
void updateClientCompositeBlocking(X11Client *client = nullptr);
static X11Compositor *self();
protected:
void start() override;
void performCompositing() override;
void handleFrameRequested(RenderLoop *renderLoop) override;
private:
explicit X11Compositor(QObject *parent);
@ -255,8 +235,6 @@ private:
* Whether the Compositor is currently suspended, 8 bits encoding the reason
*/
SuspendReasons m_suspended;
int m_xrrRefreshRate;
};
}

View file

@ -218,14 +218,14 @@ Alternatively, you might want to use the XRender backend instead.</string>
</property>
</widget>
</item>
<item row="10" column="0">
<item row="12" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Tearing prevention (&quot;vsync&quot;):</string>
</property>
</widget>
</item>
<item row="10" column="1">
<item row="12" column="1">
<widget class="QComboBox" name="kcfg_glPreferBufferSwap">
<item>
<property name="text">
@ -254,14 +254,14 @@ Alternatively, you might want to use the XRender backend instead.</string>
</item>
</widget>
</item>
<item row="11" column="0">
<item row="13" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Keep window thumbnails:</string>
</property>
</widget>
</item>
<item row="11" column="1">
<item row="13" column="1">
<widget class="QComboBox" name="kcfg_HiddenPreviews">
<item>
<property name="text">
@ -280,7 +280,7 @@ Alternatively, you might want to use the XRender backend instead.</string>
</item>
</widget>
</item>
<item row="13" column="1">
<item row="15" column="1">
<widget class="QCheckBox" name="kcfg_WindowsBlockCompositing">
<property name="toolTip">
<string>Applications can set a hint to block compositing when the window is open.
@ -292,6 +292,42 @@ Alternatively, you might want to use the XRender backend instead.</string>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="latencyLabel">
<property name="text">
<string>Latency:</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QComboBox" name="kcfg_LatencyPolicy">
<item>
<property name="text">
<string>Force lowest latency (may cause dropped frames)</string>
</property>
</item>
<item>
<property name="text">
<string>Prefer lower latency</string>
</property>
</item>
<item>
<property name="text">
<string>Balance of latency and smoothness</string>
</property>
</item>
<item>
<property name="text">
<string>Prefer smoother animations</string>
</property>
</item>
<item>
<property name="text">
<string>Force smoothest animations</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
<customwidgets>

View file

@ -69,6 +69,17 @@
<default>true</default>
</entry>
<entry name="LatencyPolicy" type="Enum">
<choices name="LatencyPolicy">
<choice name="LatencyExtremelyLow" value="ExtremelyLow"/>
<choice name="LatencyLow" value="Low"/>
<choice name="LatencyMedium" value="Medium"/>
<choice name="LatencyHigh" value="High"/>
<choice name="LatencyExtremelyHigh" value="ExtremelyHigh"/>
</choices>
<default>LatencyMedium</default>
</entry>
</group>
</kcfg>

View file

@ -214,15 +214,6 @@
</entry>
</group>
<group name="Compositing">
<entry name="MaxFPS" type="UInt">
<default>60</default>
</entry>
<entry name="RefreshRate" type="UInt">
<default>0</default>
</entry>
<entry name="VBlankTime" type="UInt">
<default>6144</default>
</entry>
<entry name="Backend" type="String">
<default>OpenGL</default>
</entry>
@ -254,6 +245,16 @@
<entry name="WindowsBlockCompositing" type="Bool">
<default>true</default>
</entry>
<entry name="LatencyPolicy" type="Enum">
<choices name="KWin::LatencyPolicy">
<choice name="LatencyExtremelyLow" value="ExtremelyLow"/>
<choice name="LatencyLow" value="Low"/>
<choice name="LatencyMedium" value="Medium"/>
<choice name="LatencyHigh" value="High"/>
<choice name="LatencyExtremelyHigh" value="ExtremelyHigh"/>
</choices>
<default>LatencyMedium</default>
</entry>
</group>
<group name="TabBox">
<entry name="ShowDelay" type="Bool">

View file

@ -191,6 +191,8 @@ private:
} // namespace
Q_DECLARE_METATYPE(std::chrono::nanoseconds)
#define KWIN_SINGLETON_VARIABLE(ClassName, variableName) \
public: \
static ClassName *create(QObject *parent = nullptr);\

View file

@ -103,6 +103,7 @@ Application::Application(Application::OperationMode mode, int &argc, char **argv
qRegisterMetaType<KWin::EffectWindow*>();
qRegisterMetaType<KWaylandServer::SurfaceInterface *>("KWaylandServer::SurfaceInterface *");
qRegisterMetaType<KSharedConfigPtr>();
qRegisterMetaType<std::chrono::nanoseconds>();
}
void Application::setConfigLock(bool lock)

View file

@ -30,50 +30,6 @@ namespace KWin
#ifndef KCMRULES
int currentRefreshRate()
{
return Options::currentRefreshRate();
}
int Options::currentRefreshRate()
{
int rate = -1;
QString syncScreenName(QLatin1String("primary screen"));
if (options->refreshRate() > 0) { // use manually configured refresh rate
rate = options->refreshRate();
} else if (Screens::self()->count() > 0) {
// prefer the refreshrate calculated from the screens mode information
// at least the nvidia driver reports 50Hz BS ... *again*!
int syncScreen = 0;
if (Screens::self()->count() > 1) {
const QByteArray syncDisplayDevice(qgetenv("__GL_SYNC_DISPLAY_DEVICE"));
// if __GL_SYNC_DISPLAY_DEVICE is exported, the GPU shall sync to that device
// so we try to use its refresh rate
if (!syncDisplayDevice.isEmpty()) {
for (int i = 0; i < Screens::self()->count(); ++i) {
if (Screens::self()->name(i) == syncDisplayDevice) {
syncScreenName = Screens::self()->name(i);
syncScreen = i;
break;
}
}
}
}
rate = qRound(Screens::self()->refreshRate(syncScreen)); // TODO forward float precision?
}
// 0Hz or less is invalid, so we fallback to a default rate
if (rate <= 0)
rate = 60; // and not shitty 50Hz for sure! *grrr*
// QTimer gives us 1msec (1000Hz) at best, so we ignore anything higher;
// however, additional throttling prevents very high rates from taking place anyway
else if (rate > 1000)
rate = 1000;
qCDebug(KWIN_CORE) << "Vertical Refresh rate " << rate << "Hz (" << syncScreenName << ")";
return rate;
}
Options::Options(QObject *parent)
: QObject(parent)
, m_settings(new Settings(kwinApp()->config()))
@ -97,14 +53,12 @@ Options::Options(QObject *parent)
, m_hideUtilityWindowsForInactive(false)
, m_xwaylandCrashPolicy(Options::defaultXwaylandCrashPolicy())
, m_xwaylandMaxCrashCount(Options::defaultXwaylandMaxCrashCount())
, m_latencyPolicy(Options::defaultLatencyPolicy())
, m_compositingMode(Options::defaultCompositingMode())
, m_useCompositing(Options::defaultUseCompositing())
, m_hiddenPreviews(Options::defaultHiddenPreviews())
, m_glSmoothScale(Options::defaultGlSmoothScale())
, m_xrenderSmoothScale(Options::defaultXrenderSmoothScale())
, m_maxFpsInterval(Options::defaultMaxFpsInterval())
, m_refreshRate(Options::defaultRefreshRate())
, m_vBlankTime(Options::defaultVBlankTime())
, m_glStrictBinding(Options::defaultGlStrictBinding())
, m_glStrictBindingFollowsDriver(Options::defaultGlStrictBindingFollowsDriver())
, m_glCoreProfile(Options::defaultGLCoreProfile())
@ -617,33 +571,6 @@ void Options::setXrenderSmoothScale(bool xrenderSmoothScale)
emit xrenderSmoothScaleChanged();
}
void Options::setMaxFpsInterval(qint64 maxFpsInterval)
{
if (m_maxFpsInterval == maxFpsInterval) {
return;
}
m_maxFpsInterval = maxFpsInterval;
emit maxFpsIntervalChanged();
}
void Options::setRefreshRate(uint refreshRate)
{
if (m_refreshRate == refreshRate) {
return;
}
m_refreshRate = refreshRate;
emit refreshRateChanged();
}
void Options::setVBlankTime(qint64 vBlankTime)
{
if (m_vBlankTime == vBlankTime) {
return;
}
m_vBlankTime = vBlankTime;
emit vBlankTimeChanged();
}
void Options::setGlStrictBinding(bool glStrictBinding)
{
if (m_glStrictBinding == glStrictBinding) {
@ -707,6 +634,20 @@ void Options::setGlPreferBufferSwap(char glPreferBufferSwap)
emit glPreferBufferSwapChanged();
}
LatencyPolicy Options::latencyPolicy() const
{
return m_latencyPolicy;
}
void Options::setLatencyPolicy(LatencyPolicy policy)
{
if (m_latencyPolicy == policy) {
return;
}
m_latencyPolicy = policy;
emit latencyPolicyChanged();
}
void Options::setGlPlatformInterface(OpenGLPlatformInterface interface)
{
// check environment variable
@ -800,12 +741,6 @@ void Options::loadConfig()
setCommandAll2(mouseCommand(config.readEntry("CommandAll2", "Toggle raise and lower"), false));
setCommandAll3(mouseCommand(config.readEntry("CommandAll3", "Resize"), false));
// TODO: should they be moved into reloadCompositingSettings?
config = KConfigGroup(m_settings->config(), "Compositing");
setMaxFpsInterval(1 * 1000 * 1000 * 1000 / config.readEntry("MaxFPS", Options::defaultMaxFps()));
setRefreshRate(config.readEntry("RefreshRate", Options::defaultRefreshRate()));
setVBlankTime(config.readEntry("VBlankTime", Options::defaultVBlankTime()) * 1000); // config in micro, value in nano resolution
// Modifier Only Shortcuts
config = KConfigGroup(m_settings->config(), "ModifierOnlyShortcuts");
m_modifierOnlyShortcuts.clear();
@ -860,7 +795,7 @@ void Options::syncFromKcfgc()
setElectricBorderCornerRatio(m_settings->electricBorderCornerRatio());
setWindowsBlockCompositing(m_settings->windowsBlockCompositing());
setMoveMinimizedWindowsToEndOfTabBoxFocusChain(m_settings->moveMinimizedWindowsToEndOfTabBoxFocusChain());
setLatencyPolicy(m_settings->latencyPolicy());
}
bool Options::loadCompositingConfig (bool force)

View file

@ -42,12 +42,24 @@ enum XwaylandCrashPolicy {
Restart,
};
/**
* This enum type specifies the latency level configured by the user.
*/
enum LatencyPolicy {
LatencyExteremelyLow,
LatencyLow,
LatencyMedium,
LatencyHigh,
LatencyExtremelyHigh,
};
class Settings;
class KWIN_EXPORT Options : public QObject
{
Q_OBJECT
Q_ENUMS(XwaylandCrashPolicy)
Q_ENUMS(LatencyPolicy)
Q_PROPERTY(FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy NOTIFY focusPolicyChanged)
Q_PROPERTY(XwaylandCrashPolicy xwaylandCrashPolicy READ xwaylandCrashPolicy WRITE setXwaylandCrashPolicy NOTIFY xwaylandCrashPolicyChanged)
Q_PROPERTY(int xwaylandMaxCrashCount READ xwaylandMaxCrashCount WRITE setXwaylandMaxCrashCount NOTIFY xwaylandMaxCrashCountChanged)
@ -164,9 +176,6 @@ class KWIN_EXPORT Options : public QObject
*/
Q_PROPERTY(int glSmoothScale READ glSmoothScale WRITE setGlSmoothScale NOTIFY glSmoothScaleChanged)
Q_PROPERTY(bool xrenderSmoothScale READ isXrenderSmoothScale WRITE setXrenderSmoothScale NOTIFY xrenderSmoothScaleChanged)
Q_PROPERTY(qint64 maxFpsInterval READ maxFpsInterval WRITE setMaxFpsInterval NOTIFY maxFpsIntervalChanged)
Q_PROPERTY(uint refreshRate READ refreshRate WRITE setRefreshRate NOTIFY refreshRateChanged)
Q_PROPERTY(qint64 vBlankTime READ vBlankTime WRITE setVBlankTime NOTIFY vBlankTimeChanged)
Q_PROPERTY(bool glStrictBinding READ isGlStrictBinding WRITE setGlStrictBinding NOTIFY glStrictBindingChanged)
/**
* Whether strict binding follows the driver or has been overwritten by a user defined config value.
@ -178,6 +187,7 @@ class KWIN_EXPORT Options : public QObject
Q_PROPERTY(GlSwapStrategy glPreferBufferSwap READ glPreferBufferSwap WRITE setGlPreferBufferSwap NOTIFY glPreferBufferSwapChanged)
Q_PROPERTY(KWin::OpenGLPlatformInterface glPlatformInterface READ glPlatformInterface WRITE setGlPlatformInterface NOTIFY glPlatformInterfaceChanged)
Q_PROPERTY(bool windowsBlockCompositing READ windowsBlockCompositing WRITE setWindowsBlockCompositing NOTIFY windowsBlockCompositingChanged)
Q_PROPERTY(LatencyPolicy latencyPolicy READ latencyPolicy WRITE setLatencyPolicy NOTIFY latencyPolicyChanged)
public:
explicit Options(QObject *parent = nullptr);
@ -555,16 +565,7 @@ public:
return m_xrenderSmoothScale;
}
qint64 maxFpsInterval() const {
return m_maxFpsInterval;
}
// Settings that should be auto-detected
uint refreshRate() const {
return m_refreshRate;
}
qint64 vBlankTime() const {
return m_vBlankTime;
}
bool isGlStrictBinding() const {
return m_glStrictBinding;
}
@ -594,6 +595,7 @@ public:
}
QStringList modifierOnlyDBusShortcut(Qt::KeyboardModifier mod) const;
LatencyPolicy latencyPolicy() const;
// setters
void setFocusPolicy(FocusPolicy focusPolicy);
@ -645,9 +647,6 @@ public:
void setHiddenPreviews(int hiddenPreviews);
void setGlSmoothScale(int glSmoothScale);
void setXrenderSmoothScale(bool xrenderSmoothScale);
void setMaxFpsInterval(qint64 maxFpsInterval);
void setRefreshRate(uint refreshRate);
void setVBlankTime(qint64 vBlankTime);
void setGlStrictBinding(bool glStrictBinding);
void setGlStrictBindingFollowsDriver(bool glStrictBindingFollowsDriver);
void setGLCoreProfile(bool glCoreProfile);
@ -655,6 +654,7 @@ public:
void setGlPlatformInterface(OpenGLPlatformInterface interface);
void setWindowsBlockCompositing(bool set);
void setMoveMinimizedWindowsToEndOfTabBoxFocusChain(bool set);
void setLatencyPolicy(LatencyPolicy policy);
// default values
static WindowOperation defaultOperationTitlebarDblClick() {
@ -732,18 +732,6 @@ public:
static bool defaultXrenderSmoothScale() {
return false;
}
static qint64 defaultMaxFpsInterval() {
return (1 * 1000 * 1000 * 1000) /60.0; // nanoseconds / Hz
}
static int defaultMaxFps() {
return 60;
}
static uint defaultRefreshRate() {
return 0;
}
static uint defaultVBlankTime() {
return 6000; // 6ms
}
static bool defaultGlStrictBinding() {
return true;
}
@ -765,6 +753,9 @@ public:
static int defaultXwaylandMaxCrashCount() {
return 3;
}
static LatencyPolicy defaultLatencyPolicy() {
return LatencyMedium;
}
/**
* Performs loading all settings except compositing related.
*/
@ -775,8 +766,6 @@ public:
bool loadCompositingConfig(bool force);
void reparseConfiguration();
static int currentRefreshRate();
//----------------------
Q_SIGNALS:
// for properties
@ -830,9 +819,6 @@ Q_SIGNALS:
void hiddenPreviewsChanged();
void glSmoothScaleChanged();
void xrenderSmoothScaleChanged();
void maxFpsIntervalChanged();
void refreshRateChanged();
void vBlankTimeChanged();
void glStrictBindingChanged();
void glStrictBindingFollowsDriverChanged();
void glCoreProfileChanged();
@ -840,7 +826,7 @@ Q_SIGNALS:
void glPlatformInterfaceChanged();
void windowsBlockCompositingChanged();
void animationSpeedChanged();
void latencyPolicyChanged();
void configChanged();
private:
@ -869,16 +855,14 @@ private:
bool m_hideUtilityWindowsForInactive;
XwaylandCrashPolicy m_xwaylandCrashPolicy;
int m_xwaylandMaxCrashCount;
LatencyPolicy m_latencyPolicy;
CompositingType m_compositingMode;
bool m_useCompositing;
HiddenPreviews m_hiddenPreviews;
int m_glSmoothScale;
bool m_xrenderSmoothScale;
qint64 m_maxFpsInterval;
// Settings that should be auto-detected
uint m_refreshRate;
qint64 m_vBlankTime;
bool m_glStrictBinding;
bool m_glStrictBindingFollowsDriver;
bool m_glCoreProfile;

View file

@ -445,6 +445,11 @@ void Platform::setPerScreenRenderingEnabled(bool enabled)
m_isPerScreenRenderingEnabled = enabled;
}
RenderLoop *Platform::renderLoop() const
{
return nullptr;
}
void Platform::warpPointer(const QPointF &globalPos)
{
Q_UNUSED(globalPos)

View file

@ -36,6 +36,7 @@ class OpenGLBackend;
class Outline;
class OutlineVisual;
class QPainterBackend;
class RenderLoop;
class Scene;
class ScreenEdges;
class Toplevel;
@ -461,6 +462,12 @@ public:
*/
bool isPerScreenRenderingEnabled() const;
/**
* If the Platform doesn't support per screen rendering, this function returns the
* RenderLoop that drives compositing.
*/
virtual RenderLoop *renderLoop() const;
public Q_SLOTS:
void pointerMotion(const QPointF &position, quint32 time);
void pointerButtonPressed(quint32 button, quint32 time);

View file

@ -1 +1,2 @@
add_subdirectory(scenes)
add_subdirectory(vsyncconvenience)

View file

@ -19,8 +19,7 @@ namespace KWin
{
OpenGLBackend::OpenGLBackend()
: m_syncsToVBlank(false)
, m_directRendering(false)
: m_directRendering(false)
, m_haveBufferAge(false)
, m_failed(false)
{

View file

@ -83,16 +83,6 @@ public:
bool isFailed() const {
return m_failed;
}
/**
* @brief Whether the Backend provides VSync.
*
* Currently only the GLX backend can provide VSync.
*
* @return bool @c true if VSync support is available, @c false otherwise
*/
bool syncsToVBlank() const {
return m_syncsToVBlank;
}
/**
* @brief Whether the backend uses direct rendering.
*
@ -170,16 +160,6 @@ protected:
* @param reason The reason why the initialization failed.
*/
void setFailed(const QString &reason);
/**
* @brief Sets whether the backend provides VSync.
*
* Should be called by the concrete subclass once it is determined whether VSync is supported.
* If the subclass does not call this method, the backend defaults to @c false.
* @param enabled @c true if VSync support available, @c false otherwise.
*/
void setSyncsToVBlank(bool enabled) {
m_syncsToVBlank = enabled;
}
/**
* @brief Sets whether the OpenGL context is direct.
*
@ -227,10 +207,6 @@ protected:
}
private:
/**
* @brief Whether VSync is available and used, defaults to @c false.
*/
bool m_syncsToVBlank;
/**
* @brief Whether direct rendering is used, defaults to @c false.
*/

View file

@ -0,0 +1,8 @@
set(vsyncconvenience_SOURCES
softwarevsyncmonitor.cpp
vsyncmonitor.cpp
)
add_library(VsyncSupport OBJECT ${vsyncconvenience_SOURCES})
target_link_libraries(VsyncSupport Qt5::Core Qt5::Gui)
target_include_directories(VsyncSupport PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View file

@ -0,0 +1,60 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "softwarevsyncmonitor.h"
namespace KWin
{
SoftwareVsyncMonitor *SoftwareVsyncMonitor::create(QObject *parent)
{
return new SoftwareVsyncMonitor(parent);
}
SoftwareVsyncMonitor::SoftwareVsyncMonitor(QObject *parent)
: VsyncMonitor(parent)
, m_softwareClock(new QTimer(this))
{
connect(m_softwareClock, &QTimer::timeout, this, &SoftwareVsyncMonitor::handleSyntheticVsync);
m_softwareClock->setSingleShot(true);
}
int SoftwareVsyncMonitor::refreshRate() const
{
return m_refreshRate;
}
void SoftwareVsyncMonitor::setRefreshRate(int refreshRate)
{
m_refreshRate = refreshRate;
}
void SoftwareVsyncMonitor::handleSyntheticVsync()
{
emit vblankOccurred(m_vblankTimestamp);
}
template <typename T>
T alignTimestamp(const T &timestamp, const T &alignment)
{
return timestamp + ((alignment - (timestamp % alignment)) % alignment);
}
void SoftwareVsyncMonitor::arm()
{
if (m_softwareClock->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 / m_refreshRate);
m_vblankTimestamp = alignTimestamp(currentTime, vblankInterval);
m_softwareClock->start(std::chrono::duration_cast<std::chrono::milliseconds>(m_vblankTimestamp - currentTime));
}
} // namespace KWin

View file

@ -0,0 +1,46 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "vsyncmonitor.h"
#include <QTimer>
namespace KWin
{
/**
* The SoftwareVsyncMonitor class provides synthetic vblank events with constant interval.
*
* The software vsync monitor can never fail and it is always available. It can be used as
* fallback if hardware based approaches to monitor vsync events are unavailable.
*
* The vblank interval can be changed by calling the setRefreshRate() function.
*/
class KWIN_EXPORT SoftwareVsyncMonitor : public VsyncMonitor
{
Q_OBJECT
public:
static SoftwareVsyncMonitor *create(QObject *parent);
int refreshRate() const;
void setRefreshRate(int refreshRate);
public Q_SLOTS:
void arm() override;
private:
explicit SoftwareVsyncMonitor(QObject *parent = nullptr);
void handleSyntheticVsync();
QTimer *m_softwareClock = nullptr;
int m_refreshRate = 60000;
std::chrono::nanoseconds m_vblankTimestamp = std::chrono::nanoseconds::zero();
};
} // namespace KWin

View file

@ -0,0 +1,17 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "vsyncmonitor.h"
namespace KWin
{
VsyncMonitor::VsyncMonitor(QObject *parent)
: QObject(parent)
{
}
} // namespace KWin

View file

@ -0,0 +1,36 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwinglobals.h"
#include <QObject>
#include <chrono>
namespace KWin
{
/**
* The VsyncMonitor class provides a convenient way to monitor vblank events.
*/
class KWIN_EXPORT VsyncMonitor : public QObject
{
Q_OBJECT
public:
explicit VsyncMonitor(QObject *parent = nullptr);
public Q_SLOTS:
virtual void arm() = 0;
signals:
void errorOccurred();
void vblankOccurred(std::chrono::nanoseconds timestamp);
};
} // namespace KWin

View file

@ -20,7 +20,6 @@ AbstractEglDrmBackend::AbstractEglDrmBackend(DrmBackend *drmBackend, DrmGpu *gpu
m_gpu->setEglBackend(this);
// Egl is always direct rendering.
setIsDirectRendering(true);
setSyncsToVBlank(true);
connect(m_gpu, &DrmGpu::outputEnabled, this, &AbstractEglDrmBackend::addOutput);
connect(m_gpu, &DrmGpu::outputDisabled, this, &AbstractEglDrmBackend::removeOutput);
}

View file

@ -16,6 +16,7 @@
#include "logging.h"
#include "logind.h"
#include "main.h"
#include "renderloop_p.h"
#include "scene_qpainter_drm_backend.h"
#include "udev.h"
#include "wayland_server.h"
@ -188,8 +189,11 @@ void DrmBackend::reactivate()
}
// restart compositor
m_pageFlipsPending = 0;
for (DrmOutput *output : qAsConst(m_outputs)) {
output->renderLoop()->uninhibit();
}
if (Compositor *compositor = Compositor::self()) {
compositor->bufferSwapComplete();
compositor->addRepaintFull();
}
}
@ -199,36 +203,61 @@ void DrmBackend::deactivate()
if (!m_active) {
return;
}
// block compositor
if (m_pageFlipsPending == 0 && Compositor::self()) {
Compositor::self()->aboutToSwapBuffers();
}
// hide cursor and disable
for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) {
DrmOutput *o = *it;
o->hideCursor();
for (DrmOutput *output : qAsConst(m_outputs)) {
output->hideCursor();
output->renderLoop()->inhibit();
}
m_active = false;
}
static std::chrono::nanoseconds convertTimestamp(const timespec &timestamp)
{
return std::chrono::seconds(timestamp.tv_sec) + std::chrono::nanoseconds(timestamp.tv_nsec);
}
static std::chrono::nanoseconds convertTimestamp(clockid_t sourceClock, clockid_t targetClock,
const timespec &timestamp)
{
if (sourceClock == targetClock) {
return convertTimestamp(timestamp);
}
timespec sourceCurrentTime = {};
timespec targetCurrentTime = {};
clock_gettime(sourceClock, &sourceCurrentTime);
clock_gettime(targetClock, &targetCurrentTime);
const auto delta = convertTimestamp(sourceCurrentTime) - convertTimestamp(timestamp);
return convertTimestamp(targetCurrentTime) - delta;
}
void DrmBackend::pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data)
{
Q_UNUSED(fd)
Q_UNUSED(frame)
Q_UNUSED(sec)
Q_UNUSED(usec)
auto output = reinterpret_cast<DrmOutput*>(data);
auto output = static_cast<DrmOutput *>(data);
DrmGpu *gpu = output->gpu();
DrmBackend *backend = output->m_backend;
std::chrono::nanoseconds timestamp = convertTimestamp(gpu->presentationClock(),
CLOCK_MONOTONIC,
{ sec, usec * 1000 });
if (timestamp == std::chrono::nanoseconds::zero()) {
qCDebug(KWIN_DRM, "Got invalid timestamp (sec: %u, usec: %u) on output %s",
sec, usec, qPrintable(output->name()));
timestamp = std::chrono::steady_clock::now().time_since_epoch();
}
output->pageFlipped();
output->m_backend->m_pageFlipsPending--;
if (output->m_backend->m_pageFlipsPending == 0) {
// TODO: improve, this currently means we wait for all page flips or all outputs.
// It would be better to driver the repaint per output
backend->m_pageFlipsPending--;
if (Compositor::self()) {
Compositor::self()->bufferSwapComplete();
}
}
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(output->renderLoop());
renderLoopPrivate->notifyFrameCompleted(timestamp);
}
void DrmBackend::openDrm()
@ -526,9 +555,6 @@ bool DrmBackend::present(DrmBuffer *buffer, DrmOutput *output)
if (output->present(buffer)) {
m_pageFlipsPending++;
if (m_pageFlipsPending == 1 && Compositor::self()) {
Compositor::self()->aboutToSwapBuffers();
}
return true;
} else if (output->gpu()->deleteBufferAfterPageFlip()) {
delete buffer;

View file

@ -48,6 +48,13 @@ DrmGpu::DrmGpu(DrmBackend *backend, QByteArray devNode, int fd, int drmId) : m_b
m_cursorSize.setHeight(64);
}
int ret = drmGetCap(fd, DRM_CAP_TIMESTAMP_MONOTONIC, &capability);
if (ret == 0 && capability == 1) {
m_presentationClock = CLOCK_MONOTONIC;
} else {
m_presentationClock = CLOCK_REALTIME;
}
// find out if this GPU is using the NVidia proprietary driver
DrmScopedPointer<drmVersion> version(drmGetVersion(fd));
m_useEglStreams = strstr(version->name, "nvidia-drm");
@ -69,6 +76,11 @@ DrmGpu::~DrmGpu()
close(m_fd);
}
clockid_t DrmGpu::presentationClock() const
{
return m_presentationClock;
}
void DrmGpu::tryAMS()
{
m_atomicModeSetting = false;

View file

@ -101,6 +101,12 @@ public:
m_eglBackend = eglBackend;
}
/**
* Returns the clock from which presentation timestamps are sourced. The returned value
* can be either CLOCK_MONOTONIC or CLOCK_REALTIME.
*/
clockid_t presentationClock() const;
Q_SIGNALS:
void outputAdded(DrmOutput *output);
void outputRemoved(DrmOutput *output);
@ -128,6 +134,7 @@ private:
bool m_deleteBufferAfterPageFlip;
gbm_device* m_gbmDevice;
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
clockid_t m_presentationClock;
// all available planes: primarys, cursors and overlays
QVector<DrmPlane*> m_planes;

View file

@ -17,6 +17,7 @@
#include "logging.h"
#include "main.h"
#include "screens.h"
#include "renderloop.h"
#include "wayland_server.h"
// KWayland
#include <KWaylandServer/output_interface.h>
@ -43,6 +44,7 @@ DrmOutput::DrmOutput(DrmBackend *backend, DrmGpu *gpu)
: AbstractWaylandOutput(backend)
, m_backend(backend)
, m_gpu(gpu)
, m_renderLoop(new RenderLoop(this))
{
}
@ -52,6 +54,11 @@ DrmOutput::~DrmOutput()
teardown();
}
RenderLoop *DrmOutput::renderLoop() const
{
return m_renderLoop;
}
void DrmOutput::teardown()
{
if (m_deleted) {
@ -869,6 +876,7 @@ bool DrmOutput::presentAtomically(DrmBuffer *buffer)
m_lastWorkingState.planeTransformations = m_primaryPlane->transformation();
}
m_lastWorkingState.valid = true;
m_renderLoop->setRefreshRate(refreshRateForMode(&m_mode));
}
m_pageFlipPending = true;
return true;

View file

@ -39,6 +39,9 @@ class KWIN_EXPORT DrmOutput : public AbstractWaylandOutput
public:
///deletes the output, calling this whilst a page flip is pending will result in an error
~DrmOutput() override;
RenderLoop *renderLoop() const override;
///queues deleting the output after a page flip has completed.
void teardown();
void releaseGbm();
@ -153,6 +156,7 @@ private:
DpmsMode m_dpmsMode = DpmsMode::On;
DpmsMode m_dpmsModePending = DpmsMode::On;
QByteArray m_uuid;
RenderLoop *m_renderLoop;
uint32_t m_blobId = 0;
DrmPlane *m_primaryPlane = nullptr;

View file

@ -17,7 +17,6 @@ EglMultiBackend::EglMultiBackend(AbstractEglDrmBackend *backend0) : OpenGLBacken
{
m_backends.append(backend0);
setIsDirectRendering(true);
setSyncsToVBlank(true);
}
EglMultiBackend::~EglMultiBackend()

View file

@ -1,12 +1,13 @@
set(FBDEV_SOURCES
fb_backend.cpp
fbvsyncmonitor.cpp
logging.cpp
scene_qpainter_fb_backend.cpp
)
add_library(KWinWaylandFbdevBackend MODULE ${FBDEV_SOURCES})
set_target_properties(KWinWaylandFbdevBackend PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/org.kde.kwin.waylandbackends/")
target_link_libraries(KWinWaylandFbdevBackend kwin SceneQPainterBackend)
target_link_libraries(KWinWaylandFbdevBackend kwin SceneQPainterBackend VsyncSupport)
install(
TARGETS

View file

@ -7,11 +7,14 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "fb_backend.h"
#include "fbvsyncmonitor.h"
#include "composite.h"
#include "logging.h"
#include "logind.h"
#include "renderloop_p.h"
#include "scene_qpainter_fb_backend.h"
#include "softwarevsyncmonitor.h"
#include "virtual_terminal.h"
#include "udev.h"
// system
@ -25,22 +28,56 @@
namespace KWin
{
FramebufferOutput::FramebufferOutput(QObject *parent):
AbstractWaylandOutput(parent)
FramebufferOutput::FramebufferOutput(FramebufferBackend *backend, QObject *parent)
: AbstractWaylandOutput(parent)
, m_renderLoop(new RenderLoop(this))
{
setName("FB-0");
if (!qEnvironmentVariableIsSet("KWIN_FB_NO_HW_VSYNC")) {
m_vsyncMonitor = FramebufferVsyncMonitor::create(backend->fileDescriptor(), this);
}
if (!m_vsyncMonitor) {
SoftwareVsyncMonitor *monitor = SoftwareVsyncMonitor::create(this);
monitor->setRefreshRate(m_renderLoop->refreshRate());
connect(m_renderLoop, &RenderLoop::refreshRateChanged, this, [this, monitor]() {
monitor->setRefreshRate(m_renderLoop->refreshRate());
});
m_vsyncMonitor = monitor;
}
connect(m_vsyncMonitor, &VsyncMonitor::vblankOccurred, this, &FramebufferOutput::vblank);
}
RenderLoop *FramebufferOutput::renderLoop() const
{
return m_renderLoop;
}
VsyncMonitor *FramebufferOutput::vsyncMonitor() const
{
return m_vsyncMonitor;
}
void FramebufferOutput::init(const QSize &pixelSize, const QSize &physicalSize)
{
const int refreshRate = 60000; // TODO: get actual refresh rate of fb device?
m_renderLoop->setRefreshRate(refreshRate);
KWaylandServer::OutputDeviceInterface::Mode mode;
mode.id = 0;
mode.size = pixelSize;
mode.flags = KWaylandServer::OutputDeviceInterface::ModeFlag::Current;
mode.refreshRate = 60000; // TODO: get actual refresh rate of fb device?
mode.refreshRate = refreshRate;
initInterfaces("model_TODO", "manufacturer_TODO", "UUID_TODO", physicalSize, { mode }, {});
}
void FramebufferOutput::vblank(std::chrono::nanoseconds timestamp)
{
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_renderLoop);
renderLoopPrivate->notifyFrameCompleted(timestamp);
}
FramebufferBackend::FramebufferBackend(QObject *parent)
: Platform(parent)
{
@ -80,6 +117,11 @@ void FramebufferBackend::init()
VirtualTerminal::create(this);
}
int FramebufferBackend::fileDescriptor() const
{
return m_fd;
}
void FramebufferBackend::openFrameBuffer()
{
VirtualTerminal::self()->init();

View file

@ -18,15 +18,27 @@
namespace KWin
{
class FramebufferBackend;
class VsyncMonitor;
class FramebufferOutput : public AbstractWaylandOutput
{
Q_OBJECT
public:
FramebufferOutput(QObject *parent = nullptr);
explicit FramebufferOutput(FramebufferBackend *backend, QObject *parent = nullptr);
~FramebufferOutput() override = default;
RenderLoop *renderLoop() const override;
VsyncMonitor *vsyncMonitor() const;
void init(const QSize &pixelSize, const QSize &physicalSize);
private:
void vblank(std::chrono::nanoseconds timestamp);
RenderLoop *m_renderLoop = nullptr;
VsyncMonitor *m_vsyncMonitor = nullptr;
};
class KWIN_EXPORT FramebufferBackend : public Platform
@ -44,6 +56,7 @@ public:
void init() override;
int fileDescriptor() const;
bool isValid() const {
return m_fd >= 0;
}

View file

@ -0,0 +1,77 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "fbvsyncmonitor.h"
#include <QThread>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <unistd.h>
namespace KWin
{
FramebufferVsyncMonitorHelper::FramebufferVsyncMonitorHelper(int fileDescriptor, QObject *parent)
: QObject(parent)
, m_fileDescriptor(fileDescriptor)
{
}
FramebufferVsyncMonitorHelper::~FramebufferVsyncMonitorHelper()
{
close(m_fileDescriptor);
}
void FramebufferVsyncMonitorHelper::poll()
{
if (ioctl(m_fileDescriptor, FBIO_WAITFORVSYNC)) {
emit errorOccurred();
} else {
emit vblankOccurred(std::chrono::steady_clock::now().time_since_epoch());
}
}
FramebufferVsyncMonitor::FramebufferVsyncMonitor(int fileDescriptor, QObject *parent)
: VsyncMonitor(parent)
, m_thread(new QThread)
, m_helper(new FramebufferVsyncMonitorHelper(fileDescriptor))
{
m_helper->moveToThread(m_thread);
connect(m_helper, &FramebufferVsyncMonitorHelper::errorOccurred,
this, &FramebufferVsyncMonitor::errorOccurred);
connect(m_helper, &FramebufferVsyncMonitorHelper::vblankOccurred,
this, &FramebufferVsyncMonitor::vblankOccurred);
m_thread->setObjectName(QStringLiteral("vsync event monitor"));
m_thread->start();
}
FramebufferVsyncMonitor::~FramebufferVsyncMonitor()
{
m_thread->quit();
m_thread->wait();
delete m_helper;
delete m_thread;
}
void FramebufferVsyncMonitor::arm()
{
QMetaObject::invokeMethod(m_helper, &FramebufferVsyncMonitorHelper::poll);
}
FramebufferVsyncMonitor *FramebufferVsyncMonitor::create(int fileDescriptor, QObject *parent)
{
const int threadFileDescriptor = dup(fileDescriptor);
if (threadFileDescriptor == -1) {
return nullptr;
}
return new FramebufferVsyncMonitor(threadFileDescriptor, parent);
}
} // namespace KWin

View file

@ -0,0 +1,63 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "vsyncmonitor.h"
namespace KWin
{
/**
* The FramebufferVsyncMonitorHelper class is responsible for waiting vsync events using the
* FBIO_WAITFORVSYNC ioctl. Note that the helper runs on a separate thread.
*/
class FramebufferVsyncMonitorHelper : public QObject
{
Q_OBJECT
public:
explicit FramebufferVsyncMonitorHelper(int fileDescriptor, QObject *parent = nullptr);
~FramebufferVsyncMonitorHelper() override;
public Q_SLOTS:
void poll();
Q_SIGNALS:
void errorOccurred();
void vblankOccurred(std::chrono::nanoseconds timestamp);
private:
int m_fileDescriptor;
};
/**
* The FramebufferVsyncMonitor class monitors vblank events using the FBIO_WAITFORVSYNC ioctl.
*/
class FramebufferVsyncMonitor : public VsyncMonitor
{
Q_OBJECT
public:
/**
* Creates a fbdev vsync monitor for the device with the specified file descriptor @p fd.
* This function returns @c null if an error has occurred.
*/
static FramebufferVsyncMonitor *create(int fd, QObject *parent);
~FramebufferVsyncMonitor() override;
public Q_SLOTS:
void arm() override;
private:
explicit FramebufferVsyncMonitor(int fileDescriptor, QObject *parent = nullptr);
QThread *m_thread;
FramebufferVsyncMonitorHelper *m_helper;
};
} // namespace KWin

View file

@ -11,7 +11,9 @@
#include "composite.h"
#include "logind.h"
#include "cursor.h"
#include "renderloop.h"
#include "virtual_terminal.h"
#include "vsyncmonitor.h"
// Qt
#include <QPainter>
@ -33,16 +35,30 @@ FramebufferQPainterBackend::FramebufferQPainterBackend(FramebufferBackend *backe
m_backend->bytesPerLine(), m_backend->imageFormat());
m_backBuffer.fill(Qt::black);
connect(VirtualTerminal::self(), &VirtualTerminal::activeChanged, this,
[] (bool active) {
if (active) {
Compositor::self()->bufferSwapComplete();
Compositor::self()->addRepaintFull();
} else {
Compositor::self()->aboutToSwapBuffers();
}
connect(VirtualTerminal::self(), &VirtualTerminal::activeChanged, this, [this](bool active) {
if (active) {
reactivate();
} else {
deactivate();
}
);
});
}
void FramebufferQPainterBackend::reactivate()
{
const QVector<AbstractOutput *> outputs = m_backend->outputs();
for (AbstractOutput *output : outputs) {
output->renderLoop()->uninhibit();
}
Compositor::self()->addRepaintFull();
}
void FramebufferQPainterBackend::deactivate()
{
const QVector<AbstractOutput *> outputs = m_backend->outputs();
for (AbstractOutput *output : outputs) {
output->renderLoop()->inhibit();
}
}
FramebufferQPainterBackend::~FramebufferQPainterBackend() = default;
@ -76,6 +92,9 @@ void FramebufferQPainterBackend::endFrame(int screenId, int mask, const QRegion
}
m_needsFullRepaint = false;
FramebufferOutput *output = static_cast<FramebufferOutput *>(m_backend->findOutput(screenId));
output->vsyncMonitor()->arm();
QPainter p(&m_backBuffer);
p.drawImage(QPoint(0, 0), m_backend->isBGR() ? m_renderBuffer.rgbSwapped() : m_renderBuffer);
}

View file

@ -30,6 +30,9 @@ public:
void endFrame(int screenId, int mask, const QRegion &damage) override;
private:
void reactivate();
void deactivate();
/**
* @brief mapped memory buffer on fb device
*/

View file

@ -6,12 +6,13 @@ set(VIRTUAL_SOURCES
)
include_directories(${CMAKE_SOURCE_DIR}/platformsupport/scenes/opengl)
include(ECMQtDeclareLoggingCategory)
ecm_qt_declare_logging_category(VIRTUAL_SOURCES HEADER logging.h IDENTIFIER KWIN_VIRTUAL CATEGORY_NAME kwin_platform_virtual DEFAULT_SEVERITY Critical)
add_library(KWinWaylandVirtualBackend MODULE ${VIRTUAL_SOURCES})
set_target_properties(KWinWaylandVirtualBackend PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/org.kde.kwin.waylandbackends/")
target_link_libraries(KWinWaylandVirtualBackend kwin SceneQPainterBackend SceneOpenGLBackend)
target_link_libraries(KWinWaylandVirtualBackend kwin SceneQPainterBackend SceneOpenGLBackend VsyncSupport)
install(
TARGETS

View file

@ -12,6 +12,8 @@
#include "virtual_backend.h"
#include "options.h"
#include "screens.h"
#include "softwarevsyncmonitor.h"
#include "virtual_output.h"
#include <logging.h>
// kwin libs
#include <kwinglplatform.h>
@ -194,10 +196,13 @@ static void convertFromGLImage(QImage &img, int w, int h)
void EglGbmBackend::endFrame(int screenId, const QRegion &renderedRegion, const QRegion &damagedRegion)
{
Q_UNUSED(screenId)
Q_UNUSED(renderedRegion)
Q_UNUSED(damagedRegion)
glFlush();
VirtualOutput *output = static_cast<VirtualOutput *>(m_backend->findOutput(screenId));
output->vsyncMonitor()->arm();
if (m_backend->saveFrames()) {
QImage img = QImage(QSize(m_backBuffer->width(), m_backBuffer->height()), QImage::Format_ARGB32);
glReadnPixels(0, 0, m_backBuffer->width(), m_backBuffer->height(), GL_RGBA, GL_UNSIGNED_BYTE, img.sizeInBytes(), (GLvoid*)img.bits());
@ -206,9 +211,7 @@ void EglGbmBackend::endFrame(int screenId, const QRegion &renderedRegion, const
}
GLRenderTarget::popRenderTarget();
Compositor::self()->aboutToSwapBuffers();
eglSwapBuffers(eglDisplay(), surface());
Compositor::self()->bufferSwapComplete();
}
bool EglGbmBackend::usesOverlayWindow() const

View file

@ -7,9 +7,11 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "scene_qpainter_virtual_backend.h"
#include "virtual_backend.h"
#include "cursor.h"
#include "screens.h"
#include "softwarevsyncmonitor.h"
#include "virtual_backend.h"
#include "virtual_output.h"
#include <QPainter>
@ -55,6 +57,10 @@ void VirtualQPainterBackend::endFrame(int screenId, int mask, const QRegion &dam
{
Q_UNUSED(mask)
Q_UNUSED(damage)
VirtualOutput *output = static_cast<VirtualOutput *>(m_backend->findOutput(screenId));
output->vsyncMonitor()->arm();
if (m_backend->saveFrames()) {
m_backBuffers[screenId].save(QStringLiteral("%1/screen%2-%3.png").arg(m_backend->screenshotDirPath(), QString::number(screenId), QString::number(m_frameCounter++)));
}

View file

@ -7,14 +7,21 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "virtual_output.h"
#include "renderloop_p.h"
#include "softwarevsyncmonitor.h"
namespace KWin
{
VirtualOutput::VirtualOutput(QObject *parent)
: AbstractWaylandOutput()
, m_renderLoop(new RenderLoop(this))
, m_vsyncMonitor(SoftwareVsyncMonitor::create(this))
{
Q_UNUSED(parent);
connect(m_vsyncMonitor, &VsyncMonitor::vblankOccurred, this, &VirtualOutput::vblank);
static int identifier = -1;
identifier++;
setName("Virtual-" + QString::number(identifier));
@ -24,13 +31,27 @@ VirtualOutput::~VirtualOutput()
{
}
RenderLoop *VirtualOutput::renderLoop() const
{
return m_renderLoop;
}
SoftwareVsyncMonitor *VirtualOutput::vsyncMonitor() const
{
return m_vsyncMonitor;
}
void VirtualOutput::init(const QPoint &logicalPosition, const QSize &pixelSize)
{
const int refreshRate = 60000; // TODO: Make the refresh rate configurable.
m_renderLoop->setRefreshRate(refreshRate);
m_vsyncMonitor->setRefreshRate(refreshRate);
KWaylandServer::OutputDeviceInterface::Mode mode;
mode.id = 0;
mode.size = pixelSize;
mode.flags = KWaylandServer::OutputDeviceInterface::ModeFlag::Current;
mode.refreshRate = 60000; // TODO
mode.refreshRate = refreshRate;
initInterfaces("model_TODO", "manufacturer_TODO", "UUID_TODO", pixelSize, { mode }, {});
setGeometry(QRect(logicalPosition, pixelSize));
}
@ -41,4 +62,10 @@ void VirtualOutput::setGeometry(const QRect &geo)
setGlobalPos(geo.topLeft());
}
void VirtualOutput::vblank(std::chrono::nanoseconds timestamp)
{
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_renderLoop);
renderLoopPrivate->notifyFrameCompleted(timestamp);
}
}

View file

@ -16,6 +16,8 @@
namespace KWin
{
class SoftwareVsyncMonitor;
class VirtualBackend;
class VirtualOutput : public AbstractWaylandOutput
@ -26,6 +28,9 @@ public:
VirtualOutput(QObject *parent = nullptr);
~VirtualOutput() override;
RenderLoop *renderLoop() const override;
SoftwareVsyncMonitor *vsyncMonitor() const;
void init(const QPoint &logicalPosition, const QSize &pixelSize);
void setGeometry(const QRect &geo);
@ -39,9 +44,13 @@ public:
}
private:
void vblank(std::chrono::nanoseconds timestamp);
Q_DISABLE_COPY(VirtualOutput);
friend class VirtualBackend;
RenderLoop *m_renderLoop;
SoftwareVsyncMonitor *m_vsyncMonitor;
int m_gammaSize = 200;
bool m_gammaResult = true;
};

View file

@ -314,7 +314,6 @@ void EglWaylandBackend::presentOnSurface(EglWaylandOutput *output, const QRegion
waylandOutput->surface()->setupFrameCallback();
waylandOutput->surface()->setScale(waylandOutput->scale());
Compositor::self()->aboutToSwapBuffers();
Q_EMIT waylandOutput->outputChange(damage);
if (supportsSwapBuffersWithDamage() && !output->m_damageHistory.isEmpty()) {

View file

@ -171,8 +171,6 @@ void WaylandQPainterBackend::endFrame(int screenId, int mask, const QRegion &dam
WaylandQPainterOutput *rendererOutput = m_outputs.value(screenId);
Q_ASSERT(rendererOutput);
Compositor::self()->aboutToSwapBuffers();
rendererOutput->setNeedsFullRepaint(false);
rendererOutput->present(rendererOutput->mapToLocal(damage));
}

View file

@ -18,6 +18,7 @@
#endif
#endif
#include "logging.h"
#include "renderloop_p.h"
#include "scene_qpainter_wayland_backend.h"
#include "wayland_output.h"
@ -717,7 +718,15 @@ void WaylandBackend::createOutputs()
updateScreenSize(waylandOutput);
Compositor::self()->addRepaintFull();
});
connect(waylandOutput, &WaylandOutput::frameRendered, this, &WaylandBackend::checkBufferSwap);
connect(waylandOutput, &WaylandOutput::frameRendered, this, [waylandOutput]() {
waylandOutput->resetRendered();
// The current time of the monotonic clock is a pretty good estimate when the frame
// has been presented, however it will be much better if we check whether the host
// compositor supports the wp_presentation protocol.
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(waylandOutput->renderLoop());
renderLoopPrivate->notifyFrameCompleted(std::chrono::steady_clock::now().time_since_epoch());
});
logicalWidthSum += logicalWidth;
m_outputs << waylandOutput;
@ -740,23 +749,6 @@ QPainterBackend *WaylandBackend::createQPainterBackend()
return new WaylandQPainterBackend(this);
}
void WaylandBackend::checkBufferSwap()
{
const bool allRendered = std::all_of(m_outputs.constBegin(), m_outputs.constEnd(), [](WaylandOutput *o) {
return o->rendered();
});
if (!allRendered) {
// need to wait more
// TODO: what if one does not need to be rendered (no damage)?
return;
}
Compositor::self()->bufferSwapComplete();
for (auto *output : qAsConst(m_outputs)) {
output->resetRendered();
}
}
void WaylandBackend::flush()
{
if (m_connectionThreadObject) {

View file

@ -192,8 +192,6 @@ public:
QVector<CompositingType> supportedCompositors() const override;
void checkBufferSwap();
WaylandOutput* getOutputAt(const QPointF &globalPosition);
Outputs outputs() const override;
Outputs enabledOutputs() const override;

View file

@ -7,8 +7,8 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "wayland_output.h"
#include "renderloop.h"
#include "wayland_backend.h"
#include "wayland_server.h"
#include <KWayland/Client/pointerconstraints.h>
@ -27,6 +27,7 @@ using namespace KWayland::Client;
WaylandOutput::WaylandOutput(Surface *surface, WaylandBackend *backend)
: AbstractWaylandOutput(backend)
, m_renderLoop(new RenderLoop(this))
, m_surface(surface)
, m_backend(backend)
{
@ -46,13 +47,21 @@ WaylandOutput::~WaylandOutput()
delete m_surface;
}
RenderLoop *WaylandOutput::renderLoop() const
{
return m_renderLoop;
}
void WaylandOutput::init(const QPoint &logicalPosition, const QSize &pixelSize)
{
const int refreshRate = 60000; // TODO: can we get refresh rate data from Wayland host?
m_renderLoop->setRefreshRate(refreshRate);
KWaylandServer::OutputDeviceInterface::Mode mode;
mode.id = 0;
mode.size = pixelSize;
mode.flags = KWaylandServer::OutputDeviceInterface::ModeFlag::Current;
mode.refreshRate = 60000; // TODO: can we get refresh rate data from Wayland host?
mode.refreshRate = refreshRate;
initInterfaces("model_TODO", "manufacturer_TODO", "UUID_TODO", pixelSize, { mode }, {});
setGeometry(logicalPosition, pixelSize);
setScale(backend()->initialOutputScale());

View file

@ -42,6 +42,8 @@ public:
WaylandOutput(KWayland::Client::Surface *surface, WaylandBackend *backend);
~WaylandOutput() override;
RenderLoop *renderLoop() const override;
void init(const QPoint &logicalPosition, const QSize &pixelSize);
virtual void lockPointer(KWayland::Client::Pointer *pointer, bool lock) {
@ -79,6 +81,7 @@ protected:
}
private:
RenderLoop *m_renderLoop;
KWayland::Client::Surface *m_surface;
WaylandBackend *m_backend;

View file

@ -99,7 +99,6 @@ void EglOnXBackend::init()
}
}
setSyncsToVBlank(false);
if (surfaceHasSubPost) {
qCDebug(KWIN_CORE) << "EGL implementation and surface support eglPostSubBufferNV, let's use it";
@ -110,7 +109,6 @@ void EglOnXBackend::init()
if (val >= 1) {
if (eglSwapInterval(eglDisplay(), 1)) {
qCDebug(KWIN_CORE) << "Enabled v-sync";
setSyncsToVBlank(true);
}
} else {
qCWarning(KWIN_CORE) << "Cannot enable v-sync as max. swap interval is" << val;

View file

@ -5,8 +5,10 @@ set(X11PLATFORM_SOURCES
eglbackend.cpp
logging.cpp
non_composited_outline.cpp
omlsynccontrolvsyncmonitor.cpp
overlaywindow_x11.cpp
screenedges_filter.cpp
sgivideosyncvsyncmonitor.cpp
windowselector.cpp
x11_decoration_renderer.cpp
x11_output.cpp
@ -29,7 +31,7 @@ include_directories(${CMAKE_SOURCE_DIR}/platformsupport/scenes/xrender)
add_library(KWinX11Platform MODULE ${X11PLATFORM_SOURCES})
set_target_properties(KWinX11Platform PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/org.kde.kwin.platforms/")
target_link_libraries(KWinX11Platform eglx11common kwin kwinxrenderutils SceneOpenGLBackend Qt5::X11Extras XCB::CURSOR KF5::Crash )
target_link_libraries(KWinX11Platform eglx11common kwin kwinxrenderutils SceneOpenGLBackend VsyncSupport Qt5::X11Extras XCB::CURSOR KF5::Crash )
if (X11_Xinput_FOUND)
target_link_libraries(KWinX11Platform ${X11_Xinput_LIB})
endif()

View file

@ -8,15 +8,38 @@
#include "eglbackend.h"
#include "options.h"
#include "overlaywindow.h"
#include "platform.h"
#include "renderloop_p.h"
#include "scene.h"
#include "screens.h"
#include "softwarevsyncmonitor.h"
#include "x11_platform.h"
namespace KWin
{
EglBackend::EglBackend(Display *display)
EglBackend::EglBackend(Display *display, X11StandalonePlatform *backend)
: EglOnXBackend(display)
, m_backend(backend)
{
// There is no any way to determine when a buffer swap completes with EGL. Fallback
// to software vblank events. Could we use the Present extension to get notified when
// the overlay window is actually presented on the screen?
m_vsyncMonitor = SoftwareVsyncMonitor::create(this);
connect(backend->renderLoop(), &RenderLoop::refreshRateChanged, this, [this, backend]() {
m_vsyncMonitor->setRefreshRate(backend->renderLoop()->refreshRate());
});
m_vsyncMonitor->setRefreshRate(backend->renderLoop()->refreshRate());
connect(m_vsyncMonitor, &VsyncMonitor::vblankOccurred, this, &EglBackend::vblank);
}
EglBackend::~EglBackend()
{
// No completion events will be received for in-flight frames, this may lock the
// render loop. We need to ensure that the render loop is back to its initial state
// if the render backend is about to be destroyed.
RenderLoopPrivate::get(kwinApp()->platform()->renderLoop())->invalidate();
}
SceneOpenGLTexturePrivate *EglBackend::createBackendTexture(SceneOpenGLTexture *texture)
@ -37,6 +60,7 @@ void EglBackend::screenGeometryChanged(const QSize &size)
QRegion EglBackend::beginFrame(int screenId)
{
Q_UNUSED(screenId)
QRegion repaint;
if (supportsBufferAge())
repaint = accumulatedDamageHistory(m_bufferAge);
@ -50,6 +74,10 @@ void EglBackend::endFrame(int screenId, const QRegion &renderedRegion, const QRe
{
Q_UNUSED(screenId)
// Start the software vsync monitor. There is no any reliable way to determine when
// eglSwapBuffers() or eglSwapBuffersWithDamageEXT() completes.
m_vsyncMonitor->arm();
presentSurface(surface(), renderedRegion, screens()->geometry());
if (overlayWindow() && overlayWindow()->window()) { // show the window only after the first pass,
@ -80,6 +108,12 @@ void EglBackend::presentSurface(EGLSurface surface, const QRegion &damage, const
}
}
void EglBackend::vblank(std::chrono::nanoseconds timestamp)
{
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_backend->renderLoop());
renderLoopPrivate->notifyFrameCompleted(timestamp);
}
/************************************************
* EglTexture
************************************************/

View file

@ -11,12 +11,16 @@
namespace KWin
{
class SoftwareVsyncMonitor;
class X11StandalonePlatform;
class EglBackend : public EglOnXBackend
{
Q_OBJECT
public:
explicit EglBackend(Display *display);
EglBackend(Display *display, X11StandalonePlatform *platform);
~EglBackend() override;
SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override;
QRegion beginFrame(int screenId) override;
@ -25,7 +29,10 @@ public:
private:
void presentSurface(EGLSurface surface, const QRegion &damage, const QRect &screenGeometry);
void vblank(std::chrono::nanoseconds timestamp);
X11StandalonePlatform *m_backend;
SoftwareVsyncMonitor *m_vsyncMonitor;
int m_bufferAge = 0;
};

View file

@ -15,11 +15,16 @@
#include "glxbackend.h"
#include "logging.h"
#include "glx_context_attribute_builder.h"
#include "omlsynccontrolvsyncmonitor.h"
#include "sgivideosyncvsyncmonitor.h"
#include "softwarevsyncmonitor.h"
#include "x11_platform.h"
// kwin
#include "options.h"
#include "overlaywindow.h"
#include "composite.h"
#include "platform.h"
#include "renderloop_p.h"
#include "scene.h"
#include "screens.h"
#include "xcbutils.h"
@ -73,26 +78,23 @@ SwapEventFilter::SwapEventFilter(xcb_drawable_t drawable, xcb_glx_drawable_t glx
bool SwapEventFilter::event(xcb_generic_event_t *event)
{
xcb_glx_buffer_swap_complete_event_t *ev =
const xcb_glx_buffer_swap_complete_event_t *swapEvent =
reinterpret_cast<xcb_glx_buffer_swap_complete_event_t *>(event);
// The drawable field is the X drawable when the event was synthesized
// by a WireToEvent handler, and the GLX drawable when the event was
// received over the wire
if (ev->drawable == m_drawable || ev->drawable == m_glxDrawable) {
Compositor::self()->bufferSwapComplete();
return true;
if (swapEvent->drawable != m_drawable && swapEvent->drawable != m_glxDrawable) {
return false;
}
return false;
// The clock for the UST timestamp is left unspecified in the spec, however, usually,
// it's CLOCK_MONOTONIC, so no special conversions are needed.
const std::chrono::microseconds timestamp((uint64_t(swapEvent->ust_hi) << 32) | swapEvent->ust_lo);
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(kwinApp()->platform()->renderLoop());
renderLoopPrivate->notifyFrameCompleted(timestamp);
return true;
}
// -----------------------------------------------------------------------
GlxBackend::GlxBackend(Display *display)
GlxBackend::GlxBackend(Display *display, X11StandalonePlatform *backend)
: OpenGLBackend()
, m_overlayWindow(kwinApp()->platform()->createOverlayWindow())
, window(None)
@ -101,6 +103,7 @@ GlxBackend::GlxBackend(Display *display)
, ctx(nullptr)
, m_bufferAge(0)
, m_x11Display(display)
, m_backend(backend)
{
// Force initialization of GLX integration in the Qt's xcb backend
// to make it call XESetWireToEvent callbacks, which is required
@ -110,6 +113,13 @@ GlxBackend::GlxBackend(Display *display)
GlxBackend::~GlxBackend()
{
delete m_vsyncMonitor;
// No completion events will be received for in-flight frames, this may lock the
// render loop. We need to ensure that the render loop is back to its initial state
// if the render backend is about to be destroyed.
RenderLoopPrivate::get(kwinApp()->platform()->renderLoop())->invalidate();
if (isFailed()) {
m_overlayWindow->destroy();
}
@ -194,14 +204,8 @@ void GlxBackend::init()
m_haveMESASwapControl = hasExtension(QByteArrayLiteral("GLX_MESA_swap_control"));
m_haveEXTSwapControl = hasExtension(QByteArrayLiteral("GLX_EXT_swap_control"));
m_haveSGISwapControl = hasExtension(QByteArrayLiteral("GLX_SGI_swap_control"));
// only enable Intel swap event if env variable is set, see BUG 342582
m_haveINTELSwapEvent = hasExtension(QByteArrayLiteral("GLX_INTEL_swap_event"))
&& qgetenv("KWIN_USE_INTEL_SWAP_EVENT") == QByteArrayLiteral("1");
if (m_haveINTELSwapEvent) {
m_swapEventFilter = std::make_unique<SwapEventFilter>(window, glxWindow);
glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK);
}
&& qgetenv("KWIN_USE_INTEL_SWAP_EVENT") != QByteArrayLiteral("0");
bool haveSwapInterval = m_haveMESASwapControl || m_haveEXTSwapControl || m_haveSGISwapControl;
@ -214,12 +218,16 @@ void GlxBackend::init()
setSupportsBufferAge(true);
}
setSyncsToVBlank(false);
// If the buffer age extension is unsupported, glXSwapBuffers() is not guaranteed to
// be called. Therefore, there is no point for creating the swap event filter.
if (!supportsBufferAge()) {
m_haveINTELSwapEvent = false;
}
const bool wantSync = options->glPreferBufferSwap() != Options::NoSwapEncourage;
if (wantSync && glXIsDirect(display(), ctx)) {
if (haveSwapInterval) { // glXSwapInterval is preferred being more reliable
setSwapInterval(1);
setSyncsToVBlank(true);
} else {
qCWarning(KWIN_X11STANDALONE) << "glSwapInterval is unsupported";
}
@ -234,6 +242,36 @@ void GlxBackend::init()
glXQueryDrawable = nullptr;
}
if (m_haveINTELSwapEvent) {
// Nice, the GLX_INTEL_swap_event extension is available. We are going to receive
// the presentation timestamp (UST) after glXSwapBuffers() via the X command stream.
m_swapEventFilter = std::make_unique<SwapEventFilter>(window, glxWindow);
glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK);
} else {
// If the GLX_INTEL_swap_event extension is unavailble, we are going to wait for
// the next vblank event after swapping buffers. This is a bit racy solution, e.g.
// the vblank may occur right in between querying video sync counter and the act
// of swapping buffers, but on the other hand, there is no any better alternative
// option. NVIDIA doesn't provide any extension such as GLX_INTEL_swap_event.
if (!m_vsyncMonitor) {
m_vsyncMonitor = SGIVideoSyncVsyncMonitor::create(this);
}
if (!m_vsyncMonitor) {
m_vsyncMonitor = OMLSyncControlVsyncMonitor::create(this);
}
if (!m_vsyncMonitor) {
SoftwareVsyncMonitor *monitor = SoftwareVsyncMonitor::create(this);
RenderLoop *renderLoop = m_backend->renderLoop();
monitor->setRefreshRate(renderLoop->refreshRate());
connect(renderLoop, &RenderLoop::refreshRateChanged, this, [this, monitor]() {
monitor->setRefreshRate(m_backend->renderLoop()->refreshRate());
});
m_vsyncMonitor = monitor;
}
connect(m_vsyncMonitor, &VsyncMonitor::vblankOccurred, this, &GlxBackend::vblank);
}
setIsDirectRendering(bool(glXIsDirect(display(), ctx)));
qCDebug(KWIN_X11STANDALONE) << "Direct rendering:" << isDirectRendering();
@ -670,9 +708,6 @@ void GlxBackend::present(const QRegion &damage)
const bool fullRepaint = supportsBufferAge() || (damage == displayRegion);
if (fullRepaint) {
if (m_haveINTELSwapEvent)
Compositor::self()->aboutToSwapBuffers();
glXSwapBuffers(display(), glxWindow);
if (supportsBufferAge()) {
glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge);
@ -718,6 +753,7 @@ SceneOpenGLTexturePrivate *GlxBackend::createBackendTexture(SceneOpenGLTexture *
QRegion GlxBackend::beginFrame(int screenId)
{
Q_UNUSED(screenId)
QRegion repaint;
makeCurrent();
@ -733,6 +769,12 @@ void GlxBackend::endFrame(int screenId, const QRegion &renderedRegion, const QRe
{
Q_UNUSED(screenId)
// If the GLX_INTEL_swap_event extension is not used for getting presentation feedback,
// assume that the frame will be presented at the next vblank event, this is racy.
if (m_vsyncMonitor) {
m_vsyncMonitor->arm();
}
present(renderedRegion);
if (overlayWindow()->window()) // show the window only after the first pass,
@ -743,6 +785,12 @@ void GlxBackend::endFrame(int screenId, const QRegion &renderedRegion, const QRe
addToDamageHistory(damagedRegion);
}
void GlxBackend::vblank(std::chrono::nanoseconds timestamp)
{
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_backend->renderLoop());
renderLoopPrivate->notifyFrameCompleted(timestamp);
}
bool GlxBackend::makeCurrent()
{
if (QOpenGLContext *context = QOpenGLContext::currentContext()) {

View file

@ -15,11 +15,13 @@
#include <xcb/glx.h>
#include <epoxy/glx.h>
#include <fixx11h.h>
#include <memory>
namespace KWin
{
class VsyncMonitor;
class X11StandalonePlatform;
// GLX_MESA_swap_interval
using glXSwapIntervalMESA_func = int (*)(unsigned int interval);
extern glXSwapIntervalMESA_func glXSwapIntervalMESA;
@ -53,10 +55,12 @@ private:
/**
* @brief OpenGL Backend using GLX over an X overlay window.
*/
class GlxBackend : public OpenGLBackend
class GlxBackend : public QObject, public OpenGLBackend
{
Q_OBJECT
public:
GlxBackend(Display *display);
GlxBackend(Display *display, X11StandalonePlatform *backend);
~GlxBackend() override;
void screenGeometryChanged(const QSize &size) override;
SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override;
@ -69,6 +73,7 @@ public:
void init() override;
private:
void vblank(std::chrono::nanoseconds timestamp);
void present(const QRegion &damage);
bool initBuffer();
bool checkVersion();
@ -102,6 +107,8 @@ private:
bool m_haveSGISwapControl = false;
bool m_haveINTELSwapEvent = false;
Display *m_x11Display;
X11StandalonePlatform *m_backend;
VsyncMonitor *m_vsyncMonitor = nullptr;
friend class GlxTexture;
};

View file

@ -0,0 +1,132 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "omlsynccontrolvsyncmonitor.h"
#include "logging.h"
#include <QX11Info>
namespace KWin
{
OMLSyncControlVsyncMonitor *OMLSyncControlVsyncMonitor::create(QObject *parent)
{
const char *extensions = glXQueryExtensionsString(QX11Info::display(),
QX11Info::appScreen());
if (!strstr(extensions, "GLX_OML_sync_control")) {
return nullptr; // GLX_OML_sync_control is unsupported.
}
OMLSyncControlVsyncMonitor *monitor = new OMLSyncControlVsyncMonitor(parent);
if (monitor->isValid()) {
return monitor;
}
delete monitor;
return nullptr;
}
OMLSyncControlVsyncMonitorHelper::OMLSyncControlVsyncMonitorHelper(QObject *parent)
: QObject(parent)
{
// Establish a new X11 connection to avoid locking up the main X11 connection.
m_display = XOpenDisplay(DisplayString(QX11Info::display()));
if (!m_display) {
qCDebug(KWIN_X11STANDALONE) << "Failed to establish vsync monitor X11 connection";
return;
}
const int attribs[] = {
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
0
};
int count;
GLXFBConfig *configs = glXChooseFBConfig(m_display, DefaultScreen(m_display),
attribs, &count);
if (!count) {
qCDebug(KWIN_X11STANDALONE) << "Couldn't find any suitable FBConfig for vsync monitor";
return;
}
GLXFBConfig config = configs[0];
XFree(configs);
m_localContext = glXCreateNewContext(m_display, config, GLX_RGBA_TYPE, 0, true);
if (!m_localContext) {
qCDebug(KWIN_X11STANDALONE) << "Failed to create opengl context for vsync monitor";
return;
}
m_drawable = DefaultRootWindow(m_display);
}
OMLSyncControlVsyncMonitorHelper::~OMLSyncControlVsyncMonitorHelper()
{
if (m_localContext) {
glXDestroyContext(m_display, m_localContext);
}
if (m_display) {
XCloseDisplay(m_display);
}
}
bool OMLSyncControlVsyncMonitorHelper::isValid() const
{
return m_display && m_localContext && m_drawable;
}
void OMLSyncControlVsyncMonitorHelper::poll()
{
if (!glXMakeCurrent(m_display, m_drawable, m_localContext)) {
qCDebug(KWIN_X11STANDALONE) << "Failed to make vsync monitor OpenGL context current";
return;
}
int64_t ust, msc, sbc;
glXGetSyncValuesOML(m_display, m_drawable, &ust, &msc, &sbc);
glXWaitForMscOML(m_display, m_drawable, 0, 2, (msc + 1) % 2, &ust, &msc, &sbc);
emit vblankOccurred(std::chrono::microseconds(ust));
}
OMLSyncControlVsyncMonitor::OMLSyncControlVsyncMonitor(QObject *parent)
: VsyncMonitor(parent)
, m_thread(new QThread)
, m_helper(new OMLSyncControlVsyncMonitorHelper)
{
m_helper->moveToThread(m_thread);
connect(m_helper, &OMLSyncControlVsyncMonitorHelper::errorOccurred,
this, &OMLSyncControlVsyncMonitor::errorOccurred);
connect(m_helper, &OMLSyncControlVsyncMonitorHelper::vblankOccurred,
this, &OMLSyncControlVsyncMonitor::vblankOccurred);
m_thread->setObjectName(QStringLiteral("vsync event monitor"));
m_thread->start();
}
OMLSyncControlVsyncMonitor::~OMLSyncControlVsyncMonitor()
{
m_thread->quit();
m_thread->wait();
delete m_helper;
delete m_thread;
}
bool OMLSyncControlVsyncMonitor::isValid() const
{
return m_helper->isValid();
}
void OMLSyncControlVsyncMonitor::arm()
{
QMetaObject::invokeMethod(m_helper, &OMLSyncControlVsyncMonitorHelper::poll);
}
} // namespace KWin

View file

@ -0,0 +1,75 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "vsyncmonitor.h"
#include <epoxy/glx.h>
#include <GL/glxext.h>
#include "fixx11h.h"
#include <QThread>
namespace KWin
{
/**
* The OMLSyncControlVsyncMonitorHelper class is responsible for waiting vsync events on the
* root window. Note that the helper runs on a separate thread.
*/
class OMLSyncControlVsyncMonitorHelper : public QObject
{
Q_OBJECT
public:
explicit OMLSyncControlVsyncMonitorHelper(QObject *parent = nullptr);
~OMLSyncControlVsyncMonitorHelper() override;
bool isValid() const;
public Q_SLOTS:
void poll();
Q_SIGNALS:
void errorOccurred();
void vblankOccurred(std::chrono::nanoseconds timestamp);
private:
Display *m_display = nullptr;
GLXContext m_localContext = 0;
GLXDrawable m_drawable = 0;
};
/**
* The OMLSyncControlVsyncMonitor class monitors vblank events using the GLX_OML_sync_control
* extension.
*
* Vblank events are monitored in a separated thread to avoid blocking the main thread. In
* order to avoid locking up the main X11 connection, the worker thread establishes its own
* X11 connection.
*/
class OMLSyncControlVsyncMonitor : public VsyncMonitor
{
Q_OBJECT
public:
static OMLSyncControlVsyncMonitor *create(QObject *parent);
~OMLSyncControlVsyncMonitor() override;
bool isValid() const;
public Q_SLOTS:
void arm() override;
private:
explicit OMLSyncControlVsyncMonitor(QObject *parent = nullptr);
QThread *m_thread = nullptr;
OMLSyncControlVsyncMonitorHelper *m_helper = nullptr;
};
} // namespace KWin

View file

@ -0,0 +1,134 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "sgivideosyncvsyncmonitor.h"
#include "logging.h"
#include <QX11Info>
namespace KWin
{
SGIVideoSyncVsyncMonitor *SGIVideoSyncVsyncMonitor::create(QObject *parent)
{
const char *extensions = glXQueryExtensionsString(QX11Info::display(),
QX11Info::appScreen());
if (!strstr(extensions, "GLX_SGI_video_sync")) {
return nullptr; // GLX_SGI_video_sync is unsupported.
}
SGIVideoSyncVsyncMonitor *monitor = new SGIVideoSyncVsyncMonitor(parent);
if (monitor->isValid()) {
return monitor;
}
delete monitor;
return nullptr;
}
SGIVideoSyncVsyncMonitorHelper::SGIVideoSyncVsyncMonitorHelper(QObject *parent)
: QObject(parent)
{
// Establish a new X11 connection to avoid locking up the main X11 connection.
m_display = XOpenDisplay(DisplayString(QX11Info::display()));
if (!m_display) {
qCDebug(KWIN_X11STANDALONE) << "Failed to establish vsync monitor X11 connection";
return;
}
const int attribs[] = {
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
0
};
int count;
GLXFBConfig *configs = glXChooseFBConfig(m_display, DefaultScreen(m_display),
attribs, &count);
if (!count) {
qCDebug(KWIN_X11STANDALONE) << "Couldn't find any suitable FBConfig for vsync monitor";
return;
}
GLXFBConfig config = configs[0];
XFree(configs);
m_localContext = glXCreateNewContext(m_display, config, GLX_RGBA_TYPE, 0, true);
if (!m_localContext) {
qCDebug(KWIN_X11STANDALONE) << "Failed to create opengl context for vsync monitor";
return;
}
m_drawable = DefaultRootWindow(m_display);
}
SGIVideoSyncVsyncMonitorHelper::~SGIVideoSyncVsyncMonitorHelper()
{
if (m_localContext) {
glXDestroyContext(m_display, m_localContext);
}
if (m_display) {
XCloseDisplay(m_display);
}
}
bool SGIVideoSyncVsyncMonitorHelper::isValid() const
{
return m_display && m_localContext && m_drawable;
}
void SGIVideoSyncVsyncMonitorHelper::poll()
{
if (!glXMakeCurrent(m_display, m_drawable, m_localContext)) {
qCDebug(KWIN_X11STANDALONE) << "Failed to make vsync monitor OpenGL context current";
emit errorOccurred();
return;
}
uint count;
glXGetVideoSyncSGI(&count);
glXWaitVideoSyncSGI(2, (count + 1) % 2, &count);
// Using monotonic clock is inaccurate, but it's still a pretty good estimate.
emit vblankOccurred(std::chrono::steady_clock::now().time_since_epoch());
}
SGIVideoSyncVsyncMonitor::SGIVideoSyncVsyncMonitor(QObject *parent)
: VsyncMonitor(parent)
, m_thread(new QThread)
, m_helper(new SGIVideoSyncVsyncMonitorHelper)
{
m_helper->moveToThread(m_thread);
connect(m_helper, &SGIVideoSyncVsyncMonitorHelper::errorOccurred,
this, &SGIVideoSyncVsyncMonitor::errorOccurred);
connect(m_helper, &SGIVideoSyncVsyncMonitorHelper::vblankOccurred,
this, &SGIVideoSyncVsyncMonitor::vblankOccurred);
m_thread->setObjectName(QStringLiteral("vsync event monitor"));
m_thread->start();
}
SGIVideoSyncVsyncMonitor::~SGIVideoSyncVsyncMonitor()
{
m_thread->quit();
m_thread->wait();
delete m_helper;
delete m_thread;
}
bool SGIVideoSyncVsyncMonitor::isValid() const
{
return m_helper->isValid();
}
void SGIVideoSyncVsyncMonitor::arm()
{
QMetaObject::invokeMethod(m_helper, &SGIVideoSyncVsyncMonitorHelper::poll);
}
} // namespace KWin

View file

@ -0,0 +1,75 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "vsyncmonitor.h"
#include <epoxy/glx.h>
#include <GL/glxext.h>
#include "fixx11h.h"
#include <QThread>
namespace KWin
{
/**
* The SGIVideoSyncVsyncMonitorHelper class is responsible for waiting for vsync events on the
* root window. Note that the helper runs on a separate thread.
*/
class SGIVideoSyncVsyncMonitorHelper : public QObject
{
Q_OBJECT
public:
explicit SGIVideoSyncVsyncMonitorHelper(QObject *parent = nullptr);
~SGIVideoSyncVsyncMonitorHelper() override;
bool isValid() const;
public Q_SLOTS:
void poll();
Q_SIGNALS:
void errorOccurred();
void vblankOccurred(std::chrono::nanoseconds timestamp);
private:
Display *m_display = nullptr;
GLXContext m_localContext = 0;
GLXDrawable m_drawable = 0;
};
/**
* The SGIVideoSyncVsyncMonitor class monitors vblank events using the GLX_SGI_video_sync
* extension.
*
* Vblank events are monitored in a separated thread to avoid blocking the main thread. In
* order to avoid locking up the main X11 connection, the worker thread establishes its own
* X11 connection.
*/
class SGIVideoSyncVsyncMonitor : public VsyncMonitor
{
Q_OBJECT
public:
static SGIVideoSyncVsyncMonitor *create(QObject *parent);
~SGIVideoSyncVsyncMonitor() override;
bool isValid() const;
public Q_SLOTS:
void arm() override;
private:
explicit SGIVideoSyncVsyncMonitor(QObject *parent = nullptr);
QThread *m_thread = nullptr;
SGIVideoSyncVsyncMonitorHelper *m_helper = nullptr;
};
} // namespace KWin

View file

@ -23,7 +23,6 @@
#include "xinputintegration.h"
#endif
#include "abstract_client.h"
#include "composite.h"
#include "effects_x11.h"
#include "eglbackend.h"
#include "keyboard_input.h"
@ -36,6 +35,7 @@
#include "x11_decoration_renderer.h"
#include "x11_output.h"
#include "xcbutils.h"
#include "renderloop.h"
#include <kwinxrenderutils.h>
@ -95,6 +95,7 @@ X11StandalonePlatform::X11StandalonePlatform(QObject *parent)
: Platform(parent)
, m_updateOutputsTimer(new QTimer(this))
, m_x11Display(QX11Info::display())
, m_renderLoop(new RenderLoop(this))
{
#if HAVE_X11_XINPUT
if (!qEnvironmentVariableIsSet("KWIN_NO_XI2")) {
@ -152,7 +153,7 @@ OpenGLBackend *X11StandalonePlatform::createOpenGLBackend()
#if HAVE_EPOXY_GLX
case GlxPlatformInterface:
if (hasGlx()) {
return new GlxBackend(m_x11Display);
return new GlxBackend(m_x11Display, this);
} else {
qCWarning(KWIN_X11STANDALONE) << "Glx not available, trying EGL instead.";
// no break, needs fall-through
@ -160,7 +161,7 @@ OpenGLBackend *X11StandalonePlatform::createOpenGLBackend()
}
#endif
case EglPlatformInterface:
return new EglBackend(m_x11Display);
return new EglBackend(m_x11Display, this);
default:
// no backend available
return nullptr;
@ -612,18 +613,50 @@ Outputs X11StandalonePlatform::enabledOutputs() const
return m_outputs;
}
RenderLoop *X11StandalonePlatform::renderLoop() const
{
return m_renderLoop;
}
static bool refreshRate_compare(const AbstractOutput *first, const AbstractOutput *smallest)
{
return first->refreshRate() < smallest->refreshRate();
}
static int currentRefreshRate()
{
const int refreshRate = qEnvironmentVariableIntValue("KWIN_X11_REFRESH_RATE");
if (refreshRate) {
return refreshRate;
}
const QVector<AbstractOutput *> outputs = kwinApp()->platform()->enabledOutputs();
if (outputs.isEmpty()) {
return 60000;
}
const QString syncDisplayDevice = qEnvironmentVariable("__GL_SYNC_DISPLAY_DEVICE");
if (!syncDisplayDevice.isEmpty()) {
for (const AbstractOutput *output : outputs) {
if (output->name() == syncDisplayDevice) {
return output->refreshRate();
}
}
}
auto syncIt = std::min_element(outputs.begin(), outputs.end(), refreshRate_compare);
return (*syncIt)->refreshRate();
}
void X11StandalonePlatform::updateRefreshRate()
{
if (!workspace() || !workspace()->compositing()) {
return;
int refreshRate = currentRefreshRate();
if (refreshRate <= 0) {
qCWarning(KWIN_X11STANDALONE) << "Bogus refresh rate" << refreshRate;
refreshRate = 60000;
}
if (Compositor::self()->refreshRate() == Options::currentRefreshRate()) {
return;
}
// desktopResized() should take care of when the size or
// shape of the desktop has changed, but we also want to
// catch refresh rate changes
Compositor::self()->reinitialize();
m_renderLoop->setRefreshRate(refreshRate);
}
}

View file

@ -67,6 +67,7 @@ public:
void scheduleUpdateOutputs();
void updateOutputs();
RenderLoop *renderLoop() const override;
Outputs outputs() const override;
Outputs enabledOutputs() const override;
@ -98,6 +99,7 @@ private:
QScopedPointer<WindowSelector> m_windowSelector;
QScopedPointer<X11EventFilter> m_screenEdgesFilter;
QScopedPointer<X11EventFilter> m_randrEventFilter;
RenderLoop *m_renderLoop;
QVector<AbstractOutput *> m_outputs;
};

View file

@ -13,8 +13,10 @@
#include "main.h"
#include "platform.h"
#include "overlaywindow.h"
#include "renderloop_p.h"
#include "scene.h"
#include "screens.h"
#include "softwarevsyncmonitor.h"
#include "utils.h"
#include "x11_platform.h"
@ -30,11 +32,26 @@ X11XRenderBackend::X11XRenderBackend(X11StandalonePlatform *backend)
, m_front(XCB_RENDER_PICTURE_NONE)
, m_format(0)
{
// Fallback to software vblank events for now. Maybe use the Present extension or
// something to get notified when the overlay window is actually presented?
m_vsyncMonitor = SoftwareVsyncMonitor::create(this);
connect(backend->renderLoop(), &RenderLoop::refreshRateChanged, this, [this, backend]() {
m_vsyncMonitor->setRefreshRate(backend->renderLoop()->refreshRate());
});
m_vsyncMonitor->setRefreshRate(backend->renderLoop()->refreshRate());
connect(m_vsyncMonitor, &VsyncMonitor::vblankOccurred, this, &X11XRenderBackend::vblank);
init(true);
}
X11XRenderBackend::~X11XRenderBackend()
{
// No completion events will be received for in-flight frames, this may lock the
// render loop. We need to ensure that the render loop is back to its initial state
// if the render backend is about to be destroyed.
RenderLoopPrivate::get(kwinApp()->platform()->renderLoop())->invalidate();
if (m_front) {
xcb_render_free_picture(connection(), m_front);
}
@ -100,6 +117,8 @@ void X11XRenderBackend::createBuffer()
void X11XRenderBackend::present(int mask, const QRegion &damage)
{
m_vsyncMonitor->arm();
const auto displaySize = screens()->displaySize();
if (mask & Scene::PAINT_SCREEN_REGION) {
// Use the damage region as the clip region for the root window
@ -119,6 +138,12 @@ void X11XRenderBackend::present(int mask, const QRegion &damage)
xcb_flush(connection());
}
void X11XRenderBackend::vblank(std::chrono::nanoseconds timestamp)
{
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_backend->renderLoop());
renderLoopPrivate->notifyFrameCompleted(timestamp);
}
void X11XRenderBackend::screenGeometryChanged(const QSize &size)
{
Q_UNUSED(size)

View file

@ -14,13 +14,16 @@
namespace KWin
{
class SoftwareVsyncMonitor;
class X11StandalonePlatform;
/**
* @brief XRenderBackend using an X11 Overlay Window as compositing target.
*/
class X11XRenderBackend : public XRenderBackend
class X11XRenderBackend : public QObject, public XRenderBackend
{
Q_OBJECT
public:
explicit X11XRenderBackend(X11StandalonePlatform *backend);
~X11XRenderBackend() override;
@ -34,7 +37,10 @@ public:
private:
void init(bool createOverlay);
void createBuffer();
void vblank(std::chrono::nanoseconds timestamp);
X11StandalonePlatform *m_backend;
SoftwareVsyncMonitor *m_vsyncMonitor;
QScopedPointer<OverlayWindow> m_overlayWindow;
xcb_render_picture_t m_front;
xcb_render_pictformat_t m_format;

View file

@ -7,9 +7,11 @@ set(X11BACKEND_SOURCES
)
include_directories(${CMAKE_SOURCE_DIR}/platformsupport/scenes/opengl)
include_directories(${CMAKE_SOURCE_DIR}/platformsupport/vsyncconvenience)
add_library(KWinWaylandX11Backend MODULE ${X11BACKEND_SOURCES})
set_target_properties(KWinWaylandX11Backend PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/org.kde.kwin.waylandbackends/")
target_link_libraries(KWinWaylandX11Backend eglx11common kwin kwinxrenderutils X11::XCB SceneQPainterBackend SceneOpenGLBackend)
target_link_libraries(KWinWaylandX11Backend eglx11common kwin kwinxrenderutils X11::XCB SceneQPainterBackend SceneOpenGLBackend VsyncSupport)
if (X11_Xinput_FOUND)
target_link_libraries(KWinWaylandX11Backend ${X11_Xinput_LIB})
endif()

View file

@ -8,8 +8,11 @@
*/
#include "egl_x11_backend.h"
// kwin
#include "main.h"
#include "screens.h"
#include "softwarevsyncmonitor.h"
#include "x11windowed_backend.h"
#include "x11windowed_output.h"
// kwin libs
#include <kwinglplatform.h>
@ -82,6 +85,10 @@ void EglX11Backend::setupViewport(int screenId)
void EglX11Backend::endFrame(int screenId, const QRegion &renderedRegion, const QRegion &damagedRegion)
{
Q_UNUSED(damagedRegion)
X11WindowedOutput *output = static_cast<X11WindowedOutput *>(kwinApp()->platform()->findOutput(screenId));
output->vsyncMonitor()->arm();
const QRect &outputGeometry = screens()->geometry(screenId);
presentSurface(m_surfaces.at(screenId), renderedRegion, outputGeometry);
}

View file

@ -7,8 +7,11 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "scene_qpainter_x11_backend.h"
#include "x11windowed_backend.h"
#include "main.h"
#include "screens.h"
#include "softwarevsyncmonitor.h"
#include "x11windowed_backend.h"
#include "x11windowed_output.h"
namespace KWin
{
@ -62,6 +65,10 @@ void X11WindowedQPainterBackend::endFrame(int screenId, int mask, const QRegion
{
Q_UNUSED(mask)
Q_UNUSED(damage)
X11WindowedOutput *output = static_cast<X11WindowedOutput *>(kwinApp()->platform()->findOutput(screenId));
output->vsyncMonitor()->arm();
xcb_connection_t *c = m_backend->connection();
const xcb_window_t window = m_backend->window();
if (m_gc == XCB_NONE) {

View file

@ -7,7 +7,8 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "x11windowed_output.h"
#include "renderloop_p.h"
#include "softwarevsyncmonitor.h"
#include "x11windowed_backend.h"
#include <NETWM>
@ -23,6 +24,8 @@ namespace KWin
X11WindowedOutput::X11WindowedOutput(X11WindowedBackend *backend)
: AbstractWaylandOutput(backend)
, m_renderLoop(new RenderLoop(this))
, m_vsyncMonitor(SoftwareVsyncMonitor::create(this))
, m_backend(backend)
{
m_window = xcb_generate_id(m_backend->connection());
@ -30,6 +33,8 @@ X11WindowedOutput::X11WindowedOutput(X11WindowedBackend *backend)
static int identifier = -1;
identifier++;
setName("X11-" + QString::number(identifier));
connect(m_vsyncMonitor, &VsyncMonitor::vblankOccurred, this, &X11WindowedOutput::vblank);
}
X11WindowedOutput::~X11WindowedOutput()
@ -40,13 +45,27 @@ X11WindowedOutput::~X11WindowedOutput()
xcb_flush(m_backend->connection());
}
RenderLoop *X11WindowedOutput::renderLoop() const
{
return m_renderLoop;
}
SoftwareVsyncMonitor *X11WindowedOutput::vsyncMonitor() const
{
return m_vsyncMonitor;
}
void X11WindowedOutput::init(const QPoint &logicalPosition, const QSize &pixelSize)
{
const int refreshRate = 60000; // TODO: get refresh rate via randr
m_renderLoop->setRefreshRate(refreshRate);
m_vsyncMonitor->setRefreshRate(refreshRate);
KWaylandServer::OutputDeviceInterface::Mode mode;
mode.id = 0;
mode.size = pixelSize;
mode.flags = KWaylandServer::OutputDeviceInterface::ModeFlag::Current;
mode.refreshRate = 60000; // TODO: get refresh rate via randr
mode.refreshRate = refreshRate;
// Physicial size must be adjusted, such that QPA calculates correct sizes of
// internal elements.
@ -154,4 +173,10 @@ QPointF X11WindowedOutput::mapFromGlobal(const QPointF &pos) const
return (pos - hostPosition() + internalPosition()) / scale();
}
void X11WindowedOutput::vblank(std::chrono::nanoseconds timestamp)
{
RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(m_renderLoop);
renderLoopPrivate->notifyFrameCompleted(timestamp);
}
}

View file

@ -22,6 +22,8 @@ class NETWinInfo;
namespace KWin
{
class SoftwareVsyncMonitor;
class X11WindowedBackend;
/**
@ -34,6 +36,9 @@ public:
explicit X11WindowedOutput(X11WindowedBackend *backend);
~X11WindowedOutput() override;
RenderLoop *renderLoop() const override;
SoftwareVsyncMonitor *vsyncMonitor() const;
void init(const QPoint &logicalPosition, const QSize &pixelSize);
xcb_window_t window() const {
@ -62,10 +67,12 @@ public:
private:
void initXInputForWindow();
void vblank(std::chrono::nanoseconds timestamp);
xcb_window_t m_window = XCB_WINDOW_NONE;
NETWinInfo *m_winInfo = nullptr;
RenderLoop *m_renderLoop;
SoftwareVsyncMonitor *m_vsyncMonitor;
QPoint m_hostPosition;
X11WindowedBackend *m_backend;

View file

@ -75,9 +75,6 @@
namespace KWin
{
extern int currentRefreshRate();
/**
* SyncObject represents a fence used to synchronize operations in
* the kwin command stream with operations in the X command stream.
@ -482,11 +479,6 @@ OverlayWindow *SceneOpenGL::overlayWindow() const
return m_backend->overlayWindow();
}
bool SceneOpenGL::syncsToVBlank() const
{
return m_backend->syncsToVBlank();
}
bool SceneOpenGL::initFailed() const
{
return !init_ok;

View file

@ -41,7 +41,6 @@ public:
void screenGeometryChanged(const QSize &size) override;
OverlayWindow *overlayWindow() const override;
bool usesOverlayWindow() const override;
bool syncsToVBlank() const override;
bool makeOpenGLContextCurrent() override;
void doneOpenGLContextCurrent() override;
bool supportsSurfacelessContext() const override;

207
renderloop.cpp Normal file
View file

@ -0,0 +1,207 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "renderloop.h"
#include "options.h"
#include "renderloop_p.h"
#include "utils.h"
namespace KWin
{
template <typename T>
T alignTimestamp(const T &timestamp, const T &alignment)
{
return timestamp + ((alignment - (timestamp % alignment)) % alignment);
}
RenderLoopPrivate *RenderLoopPrivate::get(RenderLoop *loop)
{
return loop->d.data();
}
RenderLoopPrivate::RenderLoopPrivate(RenderLoop *q)
: q(q)
{
compositeTimer.setSingleShot(true);
QObject::connect(&compositeTimer, &QTimer::timeout, q, [this]() { dispatch(); });
}
void RenderLoopPrivate::scheduleRepaint()
{
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
+ alignTimestamp(currentTime - lastPresentationTimestamp, vblankInterval);
// Estimate when it's a good time to perform the next compositing cycle.
const std::chrono::nanoseconds safetyMargin = std::chrono::milliseconds(3);
std::chrono::nanoseconds renderTime;
switch (options->latencyPolicy()) {
case LatencyExteremelyLow:
renderTime = std::chrono::nanoseconds(long(vblankInterval.count() * 0.1));
break;
case LatencyLow:
renderTime = std::chrono::nanoseconds(long(vblankInterval.count() * 0.25));
break;
case LatencyMedium:
renderTime = std::chrono::nanoseconds(long(vblankInterval.count() * 0.5));
break;
case LatencyHigh:
renderTime = std::chrono::nanoseconds(long(vblankInterval.count() * 0.75));
break;
case LatencyExtremelyHigh:
renderTime = std::chrono::nanoseconds(long(vblankInterval.count() * 0.9));
break;
}
std::chrono::nanoseconds nextRenderTimestamp = nextPresentationTimestamp - renderTime - safetyMargin;
// If we can't render the frame before the deadline, start compositing immediately.
if (nextRenderTimestamp < currentTime) {
nextRenderTimestamp = currentTime;
}
const std::chrono::nanoseconds waitInterval = nextRenderTimestamp - currentTime;
compositeTimer.start(std::chrono::duration_cast<std::chrono::milliseconds>(waitInterval));
}
void RenderLoopPrivate::delayScheduleRepaint()
{
pendingReschedule = true;
}
void RenderLoopPrivate::maybeScheduleRepaint()
{
if (pendingReschedule) {
scheduleRepaint();
pendingReschedule = false;
}
}
void RenderLoopPrivate::notifyFrameCompleted(std::chrono::nanoseconds timestamp)
{
Q_ASSERT(pendingFrameCount > 0);
pendingFrameCount--;
if (lastPresentationTimestamp <= timestamp) {
lastPresentationTimestamp = timestamp;
} else {
qCWarning(KWIN_CORE, "Got invalid presentation timestamp: %ld (current %ld)",
timestamp.count(), lastPresentationTimestamp.count());
lastPresentationTimestamp = std::chrono::steady_clock::now().time_since_epoch();
}
if (!inhibitCount) {
maybeScheduleRepaint();
}
emit q->framePresented(q, timestamp);
}
void RenderLoopPrivate::dispatch()
{
// On X11, we want to ignore repaints that are scheduled by windows right before
// the Compositor starts repainting.
pendingRepaint = true;
emit q->frameRequested(q);
// The Compositor may decide to not repaint when the frameRequested() signal is
// emitted, in which case the pending repaint flag has to be reset manually.
pendingRepaint = false;
}
void RenderLoopPrivate::invalidate()
{
pendingReschedule = false;
pendingFrameCount = 0;
compositeTimer.stop();
}
RenderLoop::RenderLoop(QObject *parent)
: QObject(parent)
, d(new RenderLoopPrivate(this))
{
}
RenderLoop::~RenderLoop()
{
}
void RenderLoop::inhibit()
{
d->inhibitCount++;
if (d->inhibitCount == 1) {
d->compositeTimer.stop();
}
}
void RenderLoop::uninhibit()
{
Q_ASSERT(d->inhibitCount > 0);
d->inhibitCount--;
if (d->inhibitCount == 0) {
d->maybeScheduleRepaint();
}
}
void RenderLoop::beginFrame()
{
d->pendingRepaint = false;
d->pendingFrameCount++;
}
void RenderLoop::endFrame()
{
}
int RenderLoop::refreshRate() const
{
return d->refreshRate;
}
void RenderLoop::setRefreshRate(int refreshRate)
{
if (d->refreshRate == refreshRate) {
return;
}
d->refreshRate = refreshRate;
emit refreshRateChanged();
}
void RenderLoop::scheduleRepaint()
{
if (d->pendingRepaint) {
return;
}
if (!d->pendingFrameCount && !d->inhibitCount) {
d->scheduleRepaint();
} else {
d->delayScheduleRepaint();
}
}
std::chrono::nanoseconds RenderLoop::lastPresentationTimestamp() const
{
return d->lastPresentationTimestamp;
}
std::chrono::nanoseconds RenderLoop::nextPresentationTimestamp() const
{
return d->nextPresentationTimestamp;
}
} // namespace KWin

111
renderloop.h Normal file
View file

@ -0,0 +1,111 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwinglobals.h"
#include <QObject>
namespace KWin
{
class RenderLoopPrivate;
/**
* The RenderLoop class represents the compositing scheduler on a particular output.
*
* The RenderLoop class drives the compositing. The frameRequested() signal is emitted
* when the loop wants a new frame to be rendered. The frameCompleted() signal is
* emitted when a previously rendered frame has been presented on the screen. In case
* you want the compositor to repaint the scene, call the scheduleRepaint() function.
*/
class KWIN_EXPORT RenderLoop : public QObject
{
Q_OBJECT
public:
explicit RenderLoop(QObject *parent = nullptr);
~RenderLoop() override;
/**
* Pauses the render loop. While the render loop is inhibited, scheduleRepaint()
* requests are queued.
*
* Once the render loop is uninhibited, the pending schedule requests are going to
* be re-applied.
*/
void inhibit();
/**
* Uninhibits the render loop.
*/
void uninhibit();
/**
* This function must be called before the Compositor starts rendering the next
* frame.
*/
void beginFrame();
/**
* This function must be called after the Compositor has finished rendering the
* next frame.
*/
void endFrame();
/**
* Returns the refresh rate at which the output is being updated, in millihertz.
*/
int refreshRate() const;
/**
* Sets the refresh rate of this RenderLoop to @a refreshRate, in millihertz.
*/
void setRefreshRate(int refreshRate);
/**
* Schedules a compositing cycle at the next available moment.
*/
void scheduleRepaint();
/**
* Returns the timestamp of the last frame that has been presented on the screen.
* The returned timestamp is sourced from the monotonic clock.
*/
std::chrono::nanoseconds lastPresentationTimestamp() const;
/**
* If a repaint has been scheduled, this function returns the expected time when
* the next frame will be presented on the screen. The returned timestamp is sourced
* from the monotonic clock.
*/
std::chrono::nanoseconds nextPresentationTimestamp() const;
Q_SIGNALS:
/**
* This signal is emitted when the refresh rate of this RenderLoop has changed.
*/
void refreshRateChanged();
/**
* This signal is emitted when a frame has been actually presented on the screen.
* @a timestamp indicates the time when it took place.
*/
void framePresented(RenderLoop *loop, std::chrono::nanoseconds timestamp);
/**
* This signal is emitted when the render loop wants a new frame to be composited.
*
* The Compositor should make a connection to this signal using Qt::DirectConnection.
*/
void frameRequested(RenderLoop *loop);
private:
QScopedPointer<RenderLoopPrivate> d;
friend class RenderLoopPrivate;
};
} // namespace KWin

42
renderloop_p.h Normal file
View file

@ -0,0 +1,42 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "renderloop.h"
#include <QTimer>
namespace KWin
{
class KWIN_EXPORT RenderLoopPrivate
{
public:
static RenderLoopPrivate *get(RenderLoop *loop);
explicit RenderLoopPrivate(RenderLoop *q);
void dispatch();
void invalidate();
void delayScheduleRepaint();
void scheduleRepaint();
void maybeScheduleRepaint();
void notifyFrameCompleted(std::chrono::nanoseconds timestamp);
RenderLoop *q;
std::chrono::nanoseconds lastPresentationTimestamp = std::chrono::nanoseconds::zero();
std::chrono::nanoseconds nextPresentationTimestamp = std::chrono::nanoseconds::zero();
QTimer compositeTimer;
int refreshRate = 60000;
int pendingFrameCount = 0;
int inhibitCount = 0;
bool pendingReschedule = false;
bool pendingRepaint = false;
};
} // namespace KWin

View file

@ -210,11 +210,6 @@ void Scene::paintScreen(int* mask, const QRegion &damage, const QRegion &repaint
Q_ASSERT(!PaintClipper::clip());
}
// Painting pass is optimized away.
void Scene::idle()
{
}
// the function that'll be eventually called by paintScreen() above
void Scene::finalPaintScreen(int mask, const QRegion &region, ScreenPaintData& data)
{
@ -663,11 +658,6 @@ void Scene::extendPaintRegion(QRegion &region, bool opaqueFullscreen)
Q_UNUSED(opaqueFullscreen);
}
bool Scene::syncsToVBlank() const
{
return false;
}
void Scene::screenGeometryChanged(const QSize &size)
{
if (!overlayWindow()) {

View file

@ -142,9 +142,6 @@ public:
};
// types of filtering available
enum ImageFilterType { ImageFilterFast, ImageFilterGood };
// there's nothing to paint (adjust time_diff later)
void idle();
virtual bool syncsToVBlank() const;
virtual OverlayWindow* overlayWindow() const = 0;
virtual bool makeOpenGLContextCurrent();