backends/x11: Use PRESENT extension to get notified about frame completion

Currently, we use a timer to get notified when a frame is presented, but
there's a better way using PRESENT extension's PresentCompleteNotify events.

Note that we implicitly rely on the fact that EGL implementation uses
the PRESENT extension to present pixmaps, which is usually the case.
QPainter backend needs some adjustments.
This commit is contained in:
Vlad Zahorodnii 2022-12-09 23:25:03 +02:00
parent e88a9e511a
commit ae4c3c1a65
8 changed files with 100 additions and 60 deletions

View file

@ -278,6 +278,7 @@ find_package(XCB 1.10 REQUIRED COMPONENTS
ICCCM
IMAGE
KEYSYMS
PRESENT
RANDR
RENDER
SHAPE

View file

@ -6,7 +6,11 @@ target_sources(kwin PRIVATE
x11_windowed_qpainter_backend.cpp
)
target_link_libraries(kwin X11::XCB X11::X11)
target_link_libraries(kwin
X11::XCB
X11::X11
XCB::PRESENT
)
if (X11_Xi_FOUND)
target_link_libraries(kwin X11::Xi)
endif()

View file

@ -25,11 +25,11 @@
#include <QSocketNotifier>
// xcb
#include <xcb/xcb_keysyms.h>
#include <xcb/present.h>
// X11
#include <X11/Xlib-xcb.h>
#include <fixx11h.h>
#if HAVE_X11_XINPUT
#include "../common/ge_event_mem_mover.h"
#include <X11/extensions/XI2proto.h>
#include <X11/extensions/XInput2.h>
#endif
@ -193,6 +193,24 @@ bool X11WindowedBackend::initialize()
m_screen = it.data;
}
}
const xcb_query_extension_reply_t *presentExtension = xcb_get_extension_data(m_connection, &xcb_present_id);
if (presentExtension && presentExtension->present) {
m_presentOpcode = presentExtension->major_opcode;
xcb_present_query_version_cookie_t cookie = xcb_present_query_version(m_connection, 1, 2);
xcb_present_query_version_reply_t *reply = xcb_present_query_version_reply(m_connection, cookie, nullptr);
if (!reply) {
qCWarning(KWIN_X11WINDOWED) << "Requested Present extension version is unsupported";
return false;
}
m_presentMajorVersion = reply->major_version;
m_presentMinorVersion = reply->minor_version;
free(reply);
} else {
qCWarning(KWIN_X11WINDOWED) << "Present X11 extension is unavailable";
return false;
}
initXInput();
XRenderUtils::init(m_connection, m_screen->root);
createOutputs();
@ -365,43 +383,15 @@ void X11WindowedBackend::handleEvent(xcb_generic_event_t *e)
xcb_refresh_keyboard_mapping(m_keySymbols, reinterpret_cast<xcb_mapping_notify_event_t *>(e));
}
break;
#if HAVE_X11_XINPUT
case XCB_GE_GENERIC: {
GeEventMemMover ge(e);
auto te = reinterpret_cast<xXIDeviceEvent *>(e);
const X11WindowedOutput *output = findOutput(te->event);
if (!output) {
break;
}
const QPointF position = output->mapFromGlobal(QPointF(fixed1616ToReal(te->root_x), fixed1616ToReal(te->root_y)));
switch (ge->event_type) {
case XI_TouchBegin: {
Q_EMIT m_touchDevice->touchDown(te->detail, position, te->time, m_touchDevice.get());
Q_EMIT m_touchDevice->touchFrame(m_touchDevice.get());
break;
}
case XI_TouchUpdate: {
Q_EMIT m_touchDevice->touchMotion(te->detail, position, te->time, m_touchDevice.get());
Q_EMIT m_touchDevice->touchFrame(m_touchDevice.get());
break;
}
case XI_TouchEnd: {
Q_EMIT m_touchDevice->touchUp(te->detail, te->time, m_touchDevice.get());
Q_EMIT m_touchDevice->touchFrame(m_touchDevice.get());
break;
}
case XI_TouchOwnership: {
auto te = reinterpret_cast<xXITouchOwnershipEvent *>(e);
XIAllowTouchEvents(m_display, te->deviceid, te->sourceid, te->touchid, XIAcceptTouch);
break;
}
xcb_ge_generic_event_t *ev = reinterpret_cast<xcb_ge_generic_event_t *>(e);
if (ev->extension == m_presentOpcode) {
handlePresentEvent(ev);
} else if (ev->extension == m_xiOpcode) {
handleXinputEvent(ev);
}
break;
}
#endif
default:
break;
}
@ -558,6 +548,55 @@ void X11WindowedBackend::updateSize(xcb_configure_notify_event_t *event)
}
}
void X11WindowedBackend::handleXinputEvent(xcb_ge_generic_event_t *ge)
{
#if HAVE_X11_XINPUT
auto te = reinterpret_cast<xXIDeviceEvent *>(ge);
const X11WindowedOutput *output = findOutput(te->event);
if (!output) {
return;
}
const QPointF position = output->mapFromGlobal(QPointF(fixed1616ToReal(te->root_x), fixed1616ToReal(te->root_y)));
switch (ge->event_type) {
case XI_TouchBegin: {
Q_EMIT m_touchDevice->touchDown(te->detail, position, te->time, m_touchDevice.get());
Q_EMIT m_touchDevice->touchFrame(m_touchDevice.get());
break;
}
case XI_TouchUpdate: {
Q_EMIT m_touchDevice->touchMotion(te->detail, position, te->time, m_touchDevice.get());
Q_EMIT m_touchDevice->touchFrame(m_touchDevice.get());
break;
}
case XI_TouchEnd: {
Q_EMIT m_touchDevice->touchUp(te->detail, te->time, m_touchDevice.get());
Q_EMIT m_touchDevice->touchFrame(m_touchDevice.get());
break;
}
case XI_TouchOwnership: {
auto te = reinterpret_cast<xXITouchOwnershipEvent *>(ge);
XIAllowTouchEvents(m_display, te->deviceid, te->sourceid, te->touchid, XIAcceptTouch);
break;
}
}
#endif
}
void X11WindowedBackend::handlePresentEvent(xcb_ge_generic_event_t *ge)
{
switch (ge->event_type) {
case XCB_PRESENT_EVENT_COMPLETE_NOTIFY: {
xcb_present_complete_notify_event_t *completeNotify = reinterpret_cast<xcb_present_complete_notify_event_t *>(ge);
if (X11WindowedOutput *output = findOutput(completeNotify->window)) {
output->handlePresentCompleteNotify(completeNotify);
}
break;
}
}
}
xcb_window_t X11WindowedBackend::rootWindow() const
{
if (!m_screen) {

View file

@ -124,6 +124,8 @@ private:
void handleClientMessage(xcb_client_message_event_t *event);
void handleButtonPress(xcb_button_press_event_t *event);
void handleExpose(xcb_expose_event_t *event);
void handleXinputEvent(xcb_ge_generic_event_t *event);
void handlePresentEvent(xcb_ge_generic_event_t *event);
void updateSize(xcb_configure_notify_event_t *event);
void initXInput();
X11WindowedOutput *findOutput(xcb_window_t window) const;
@ -149,6 +151,10 @@ private:
int m_majorVersion = 0;
int m_minorVersion = 0;
int m_presentOpcode = 0;
int m_presentMajorVersion = 0;
int m_presentMinorVersion = 0;
QVector<X11WindowedOutput *> m_outputs;
};

View file

@ -10,7 +10,6 @@
// kwin
#include "basiceglsurfacetexture_internal.h"
#include "basiceglsurfacetexture_wayland.h"
#include "softwarevsyncmonitor.h"
#include "x11_windowed_backend.h"
#include "x11_windowed_output.h"
// kwin libs
@ -177,8 +176,6 @@ bool X11WindowedEglBackend::createSurfaces()
void X11WindowedEglBackend::present(Output *output)
{
static_cast<X11WindowedOutput *>(output)->vsyncMonitor()->arm();
const auto &renderOutput = m_outputs[output];
presentSurface(renderOutput.primaryLayer->surface(), renderOutput.primaryLayer->lastDamage(), output->geometry());
}

View file

@ -15,7 +15,6 @@
#include "core/renderloop_p.h"
#include "composite.h"
#include "softwarevsyncmonitor.h"
#include "x11_windowed_backend.h"
#include <NETWM>
@ -90,7 +89,6 @@ void X11WindowedCursor::update(const QImage &image, const QPoint &hotspot)
X11WindowedOutput::X11WindowedOutput(X11WindowedBackend *backend)
: Output(backend)
, m_renderLoop(std::make_unique<RenderLoop>())
, m_vsyncMonitor(SoftwareVsyncMonitor::create())
, m_backend(backend)
{
m_window = xcb_generate_id(m_backend->connection());
@ -100,12 +98,11 @@ X11WindowedOutput::X11WindowedOutput(X11WindowedBackend *backend)
setInformation(Information{
.name = QStringLiteral("X11-%1").arg(identifier),
});
connect(m_vsyncMonitor.get(), &VsyncMonitor::vblankOccurred, this, &X11WindowedOutput::vblank);
}
X11WindowedOutput::~X11WindowedOutput()
{
xcb_present_select_input(m_backend->connection(), m_presentEvent, m_window, 0);
xcb_unmap_window(m_backend->connection(), m_window);
xcb_destroy_window(m_backend->connection(), m_window);
xcb_flush(m_backend->connection());
@ -131,11 +128,6 @@ RenderLoop *X11WindowedOutput::renderLoop() const
return m_renderLoop.get();
}
SoftwareVsyncMonitor *X11WindowedOutput::vsyncMonitor() const
{
return m_vsyncMonitor.get();
}
X11WindowedBackend *X11WindowedOutput::backend() const
{
return m_backend;
@ -160,7 +152,6 @@ void X11WindowedOutput::init(const QSize &pixelSize, qreal scale)
{
const int refreshRate = 60000; // TODO: get refresh rate via randr
m_renderLoop->setRefreshRate(refreshRate);
m_vsyncMonitor->setRefreshRate(refreshRate);
auto mode = std::make_shared<OutputMode>(pixelSize, m_renderLoop->refreshRate());
@ -199,6 +190,10 @@ void X11WindowedOutput::init(const QSize &pixelSize, qreal scale)
// select xinput 2 events
initXInputForWindow();
const uint32_t presentEventMask = XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY;
m_presentEvent = xcb_generate_id(m_backend->connection());
xcb_present_select_input(m_backend->connection(), m_presentEvent, m_window, presentEventMask);
m_winInfo = std::make_unique<NETWinInfo>(m_backend->connection(), m_window, m_backend->screen()->root,
NET::WMWindowType, NET::Properties2());
@ -256,6 +251,12 @@ void X11WindowedOutput::resize(const QSize &pixelSize)
setState(next);
}
void X11WindowedOutput::handlePresentCompleteNotify(xcb_present_complete_notify_event_t *event)
{
std::chrono::microseconds timestamp(event->ust);
RenderLoopPrivate::get(m_renderLoop.get())->notifyFrameCompleted(timestamp);
}
void X11WindowedOutput::setWindowTitle(const QString &title)
{
m_winInfo->setName(title.toUtf8().constData());
@ -276,12 +277,6 @@ 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.get());
renderLoopPrivate->notifyFrameCompleted(timestamp);
}
bool X11WindowedOutput::setCursor(const QImage &image, const QPoint &hotspot)
{
if (X11WindowedEglBackend *backend = qobject_cast<X11WindowedEglBackend *>(Compositor::self()->backend())) {

View file

@ -16,13 +16,13 @@
#include <QString>
#include <xcb/xcb.h>
#include <xcb/present.h>
class NETWinInfo;
namespace KWin
{
class SoftwareVsyncMonitor;
class X11WindowedBackend;
class X11WindowedOutput;
class X11WindowedEglBackend;
@ -52,7 +52,6 @@ public:
~X11WindowedOutput() override;
RenderLoop *renderLoop() const override;
SoftwareVsyncMonitor *vsyncMonitor() const;
void init(const QSize &pixelSize, qreal scale);
void resize(const QSize &pixelSize);
@ -81,16 +80,17 @@ public:
void updateEnabled(bool enabled);
void handlePresentCompleteNotify(xcb_present_complete_notify_event_t *event);
private:
void initXInputForWindow();
void vblank(std::chrono::nanoseconds timestamp);
void renderCursorOpengl(X11WindowedEglBackend *backend, const QImage &image, const QPoint &hotspot);
void renderCursorQPainter(X11WindowedQPainterBackend *backend, const QImage &image, const QPoint &hotspot);
xcb_window_t m_window = XCB_WINDOW_NONE;
xcb_present_event_t m_presentEvent = XCB_NONE;
std::unique_ptr<NETWinInfo> m_winInfo;
std::unique_ptr<RenderLoop> m_renderLoop;
std::unique_ptr<SoftwareVsyncMonitor> m_vsyncMonitor;
std::unique_ptr<X11WindowedCursor> m_cursor;
QPoint m_hostPosition;
QRegion m_exposedArea;

View file

@ -7,7 +7,6 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "x11_windowed_qpainter_backend.h"
#include "softwarevsyncmonitor.h"
#include "x11_windowed_backend.h"
#include "x11_windowed_output.h"
@ -128,7 +127,6 @@ void X11WindowedQPainterBackend::removeOutput(Output *output)
void X11WindowedQPainterBackend::present(Output *output)
{
static_cast<X11WindowedOutput *>(output)->vsyncMonitor()->arm();
const auto &rendererOutput = m_outputs[output];
xcb_connection_t *c = m_backend->connection();