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:
parent
a3ec0c9a57
commit
b8a70e62d5
74 changed files with 1837 additions and 508 deletions
|
@ -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
|
||||
|
|
|
@ -118,4 +118,9 @@ QString AbstractOutput::serialNumber() const
|
|||
return QString();
|
||||
}
|
||||
|
||||
RenderLoop *AbstractOutput::renderLoop() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace KWin
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
249
composite.cpp
249
composite.cpp
|
@ -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) {
|
||||
|
|
54
composite.h
54
composite.h
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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 ("vsync"):</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>
|
||||
|
|
|
@ -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>
|
||||
|
|
19
kwin.kcfg
19
kwin.kcfg
|
@ -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">
|
||||
|
|
|
@ -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);\
|
||||
|
|
1
main.cpp
1
main.cpp
|
@ -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)
|
||||
|
|
97
options.cpp
97
options.cpp
|
@ -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)
|
||||
|
|
56
options.h
56
options.h
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
add_subdirectory(scenes)
|
||||
add_subdirectory(vsyncconvenience)
|
||||
|
|
|
@ -19,8 +19,7 @@ namespace KWin
|
|||
{
|
||||
|
||||
OpenGLBackend::OpenGLBackend()
|
||||
: m_syncsToVBlank(false)
|
||||
, m_directRendering(false)
|
||||
: m_directRendering(false)
|
||||
, m_haveBufferAge(false)
|
||||
, m_failed(false)
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
8
platformsupport/vsyncconvenience/CMakeLists.txt
Normal file
8
platformsupport/vsyncconvenience/CMakeLists.txt
Normal 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})
|
60
platformsupport/vsyncconvenience/softwarevsyncmonitor.cpp
Normal file
60
platformsupport/vsyncconvenience/softwarevsyncmonitor.cpp
Normal 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 ×tamp, 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
|
46
platformsupport/vsyncconvenience/softwarevsyncmonitor.h
Normal file
46
platformsupport/vsyncconvenience/softwarevsyncmonitor.h
Normal 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
|
17
platformsupport/vsyncconvenience/vsyncmonitor.cpp
Normal file
17
platformsupport/vsyncconvenience/vsyncmonitor.cpp
Normal 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
|
36
platformsupport/vsyncconvenience/vsyncmonitor.h
Normal file
36
platformsupport/vsyncconvenience/vsyncmonitor.h
Normal 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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 ×tamp)
|
||||
{
|
||||
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 ×tamp)
|
||||
{
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -17,7 +17,6 @@ EglMultiBackend::EglMultiBackend(AbstractEglDrmBackend *backend0) : OpenGLBacken
|
|||
{
|
||||
m_backends.append(backend0);
|
||||
setIsDirectRendering(true);
|
||||
setSyncsToVBlank(true);
|
||||
}
|
||||
|
||||
EglMultiBackend::~EglMultiBackend()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
77
plugins/platforms/fbdev/fbvsyncmonitor.cpp
Normal file
77
plugins/platforms/fbdev/fbvsyncmonitor.cpp
Normal 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
|
63
plugins/platforms/fbdev/fbvsyncmonitor.h
Normal file
63
plugins/platforms/fbdev/fbvsyncmonitor.h
Normal 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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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++)));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
************************************************/
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
132
plugins/platforms/x11/standalone/omlsynccontrolvsyncmonitor.cpp
Normal file
132
plugins/platforms/x11/standalone/omlsynccontrolvsyncmonitor.cpp
Normal 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
|
|
@ -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
|
134
plugins/platforms/x11/standalone/sgivideosyncvsyncmonitor.cpp
Normal file
134
plugins/platforms/x11/standalone/sgivideosyncvsyncmonitor.cpp
Normal 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
|
75
plugins/platforms/x11/standalone/sgivideosyncvsyncmonitor.h
Normal file
75
plugins/platforms/x11/standalone/sgivideosyncvsyncmonitor.h
Normal 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
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
207
renderloop.cpp
Normal 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 ×tamp, 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
111
renderloop.h
Normal 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
42
renderloop_p.h
Normal 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
|
10
scene.cpp
10
scene.cpp
|
@ -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 ®ion, ScreenPaintData& data)
|
||||
{
|
||||
|
@ -663,11 +658,6 @@ void Scene::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen)
|
|||
Q_UNUSED(opaqueFullscreen);
|
||||
}
|
||||
|
||||
bool Scene::syncsToVBlank() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void Scene::screenGeometryChanged(const QSize &size)
|
||||
{
|
||||
if (!overlayWindow()) {
|
||||
|
|
3
scene.h
3
scene.h
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue