summaryrefslogtreecommitdiffstats
path: root/src/compositor/extensions/qwaylandpresentationtime.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/compositor/extensions/qwaylandpresentationtime.cpp')
-rw-r--r--src/compositor/extensions/qwaylandpresentationtime.cpp340
1 files changed, 340 insertions, 0 deletions
diff --git a/src/compositor/extensions/qwaylandpresentationtime.cpp b/src/compositor/extensions/qwaylandpresentationtime.cpp
new file mode 100644
index 000000000..637fc5d68
--- /dev/null
+++ b/src/compositor/extensions/qwaylandpresentationtime.cpp
@@ -0,0 +1,340 @@
+// Copyright (C) 2021 LG Electronics Inc.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwaylandpresentationtime_p.h"
+#include "qwaylandpresentationtime_p_p.h"
+
+#include <time.h>
+#include <QQuickWindow>
+#include <QtWaylandCompositor/QWaylandView>
+#include <QtWaylandCompositor/QWaylandQuickItem>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \qmltype PresentationTime
+ * \instantiates QWaylandPresentationTime
+ * \inqmlmodule QtWayland.Compositor.PresentationTime
+ * \since 6.3
+ * \brief Provides tracking the timing when a frame is presented on screen.
+ *
+ * The PresentationTime extension provides a way to track rendering timing
+ * for a surface. Client can request feedbacks associated with a surface,
+ * then compositor send events for the feedback with the time when the surface
+ * is presented on-screen.
+ *
+ * PresentationTime corresponds to the Wayland \c wp_presentation interface.
+ *
+ * To provide the functionality of the presentationtime extension in a compositor, create
+ * an instance of the PresentationTime component and add it to the list of extensions
+ * supported by the compositor:
+ *
+ * Then, call sendFeedback() when a surface is presented on screen.
+ * Usually, the timing can be obtained from drm page flip event.
+ *
+ * \qml
+ * import QtWayland.Compositor.PresentationTime
+ *
+ * WaylandCompositor {
+ * PresentationTime {
+ * id: presentationTime
+ * }
+ * }
+ * \endqml
+ */
+
+/*!
+ * \class QWaylandPresentationTime
+ * \inmodule QtWaylandCompositor
+ * \since 6.3
+ * \brief The QWaylandPresentationTime class is an extension to get timing for on-screen presentation.
+ *
+ * The QWaylandPresentationTime extension provides a way to track rendering timing
+ * for a surface. Client can request feedbacks associated with a surface,
+ * then compositor send events for the feedback with the time when the surface
+ * is presented on-screen.
+ *
+ * QWaylandPresentationTime corresponds to the Wayland \c wp_presentation interface.
+ */
+
+
+/*!
+ * Constructs a QWaylandPresentationTime object for \a compositor.
+ */
+QWaylandPresentationTime::QWaylandPresentationTime(QWaylandCompositor *compositor)
+ : QWaylandCompositorExtensionTemplate(compositor, *new QWaylandPresentationTimePrivate)
+{
+
+}
+
+/*!
+ * Constructs an empty QWaylandPresentationTime object.
+ */
+QWaylandPresentationTime::QWaylandPresentationTime()
+ : QWaylandCompositorExtensionTemplate(*new QWaylandPresentationTimePrivate)
+{
+}
+
+/*!
+ * Initializes the extension.
+ */
+void QWaylandPresentationTime::initialize()
+{
+ Q_D(QWaylandPresentationTime);
+
+ if (isInitialized()) {
+ qWarning() << "QWaylandPresentationTime is already initialized";
+ return;
+ }
+
+ QWaylandCompositor *compositor = this->compositor();
+ if (compositor == nullptr) {
+ qWarning() << "Failed to find QWaylandCompositor when initializing QWaylandPresentationTime";
+ return;
+ }
+
+ QWaylandCompositorExtensionTemplate::initialize();
+
+ d->init(compositor->display(), /* version */ 1);
+}
+
+QWaylandCompositor *QWaylandPresentationTime::compositor() const
+{
+ return qobject_cast<QWaylandCompositor *>(extensionContainer());
+}
+
+/*!
+ * \qmlmethod void PresentationTime::sendFeedback(Window window, int sequence, int sec, int nsec)
+ *
+ * Interface to notify that a frame is presented on screen using \a window.
+ * If your platform supports DRM events, \c page_flip_handler is the proper timing to send it.
+ * The \a sequence is the refresh counter. \a sec and \a nsec hold the
+ * seconds and nanoseconds parts of the presentation timestamp, respectively.
+ */
+
+/*!
+ * Interface to notify that a frame is presented on screen using \a window.
+ * If your platform supports DRM events, \c page_flip_handler is the proper timing to send it.
+ * The \a sequence is the refresh counter. \a tv_sec and \a tv_nsec hold the
+ * seconds and nanoseconds parts of the presentation timestamp, respectively.
+ */
+void QWaylandPresentationTime::sendFeedback(QQuickWindow *window, quint64 sequence, quint64 tv_sec, quint32 tv_nsec)
+{
+ if (!window)
+ return;
+
+ quint32 refresh_nsec = window->screen()->refreshRate() != 0 ? 1000000000 / window->screen()->refreshRate() : 0;
+
+ emit presented(sequence, tv_sec, tv_nsec, refresh_nsec);
+}
+
+/*!
+ * Returns the Wayland interface for the QWaylandPresentationTime.
+ */
+const struct wl_interface *QWaylandPresentationTime::interface()
+{
+ return QWaylandPresentationTimePrivate::interface();
+}
+
+/*!
+ * \internal
+ */
+QByteArray QWaylandPresentationTime::interfaceName()
+{
+ return QWaylandPresentationTimePrivate::interfaceName();
+}
+
+PresentationFeedback::PresentationFeedback(QWaylandPresentationTime *pTime, QWaylandSurface *surface, struct ::wl_client *client, uint32_t id, int version)
+ : wp_presentation_feedback(client, id, version)
+ , m_presentationTime(pTime)
+{
+ setSurface(surface);
+}
+
+void PresentationFeedback::setSurface(QWaylandSurface *qwls)
+{
+ if (!qwls) {
+ discard();
+ return;
+ }
+
+ m_surface = qwls;
+
+ connect(qwls, &QWaylandSurface::damaged, this, &PresentationFeedback::onSurfaceCommit);
+ connect(qwls, &QWaylandSurface::destroyed, this, &PresentationFeedback::discard);
+
+ QWaylandView *view = qwls ? qwls->primaryView() : nullptr;
+ //The surface has not committed yet.
+ if (!view) {
+ connect(qwls, &QWaylandSurface::hasContentChanged, this, &PresentationFeedback::onSurfaceMapped);
+ return;
+ }
+
+ maybeConnectToWindow(view);
+}
+
+void PresentationFeedback::onSurfaceCommit()
+{
+ // There is a new commit before sync so that discard this feedback.
+ if (m_committed) {
+ discard();
+ return;
+ }
+ m_committed = true;
+}
+
+void PresentationFeedback::onSurfaceMapped()
+{
+ QWaylandView *view = m_surface->primaryView();
+ if (!view) {
+ qWarning() << "The mapped surface has no view";
+ discard();
+ return;
+ }
+
+ maybeConnectToWindow(view);
+}
+
+void PresentationFeedback::maybeConnectToWindow(QWaylandView *view)
+{
+ QWaylandQuickItem *item = view ? qobject_cast<QWaylandQuickItem *>(view->renderObject()) : nullptr;
+ if (!item) {
+ qWarning() << "QWaylandPresentationTime only works with QtQuick compositors" << view;
+ discard();
+ return;
+ }
+
+ connect(item, &QQuickItem::windowChanged, this, &PresentationFeedback::onWindowChanged);
+ // wait for having window
+ if (!item->window()) {
+ return;
+ }
+
+ connectToWindow(item->window());
+}
+
+void PresentationFeedback::onWindowChanged()
+{
+ QWaylandQuickItem *item = qobject_cast<QWaylandQuickItem *>(sender());
+ QQuickWindow *window = item ? item->window() : nullptr;
+
+ if (!window) {
+ qWarning() << "QWaylandPresentationTime only works with QtQuick compositors" << item;
+ discard();
+ /* Actually, the commit is not discarded yet. If the related item has new window,
+ the commit can be presented on screen. So we can choose not to discard the feedback
+ until item has new window or the surface is destroyed. */
+ return;
+ }
+
+ // Check if the connected window is changed
+ if (m_connectedWindow && m_connectedWindow != window)
+ m_connectedWindow->disconnect(this);
+
+ connectToWindow(window);
+}
+
+void PresentationFeedback::connectToWindow(QQuickWindow *window)
+{
+ if (!window) {
+ discard();
+ return;
+ }
+
+ m_connectedWindow = window;
+
+ connect(window, &QQuickWindow::beforeSynchronizing, this, &PresentationFeedback::onSync);
+ connect(window, &QQuickWindow::afterFrameEnd, this, &PresentationFeedback::onSwapped);
+}
+
+void PresentationFeedback::onSync()
+{
+ QQuickWindow *window = qobject_cast<QQuickWindow *>(sender());
+
+ if (m_committed) {
+ disconnect(m_surface, &QWaylandSurface::damaged, this, &PresentationFeedback::onSurfaceCommit);
+ disconnect(window, &QQuickWindow::beforeSynchronizing, this, &PresentationFeedback::onSync);
+ m_sync = true;
+ }
+}
+
+void PresentationFeedback::onSwapped()
+{
+ QQuickWindow *window = qobject_cast<QQuickWindow *>(sender());
+
+ if (m_sync) {
+ disconnect(window, &QQuickWindow::afterFrameEnd, this, &PresentationFeedback::onSwapped);
+ connect(m_presentationTime, &QWaylandPresentationTime::presented, this, &PresentationFeedback::sendPresented);
+ }
+}
+
+void PresentationFeedback::discard()
+{
+ send_discarded();
+ destroy();
+}
+
+void PresentationFeedback::sendSyncOutput()
+{
+ QWaylandCompositor *compositor = presentationTime()->compositor();
+ if (!compositor) {
+ qWarning() << "No compositor container to send sync_output";
+ return;
+ }
+
+ QWaylandView *view = surface()->primaryView();
+ QWaylandOutput *output = view ? view->output() : nullptr;
+ struct ::wl_resource *r = output ? output->resourceForClient(QWaylandClient::fromWlClient(compositor, resource()->client())) : nullptr;
+
+ if (r)
+ send_sync_output(r);
+}
+
+void PresentationFeedback::sendPresented(quint64 sequence, quint64 tv_sec, quint32 tv_nsec, quint32 refresh_nsec)
+{
+ sendSyncOutput();
+
+ send_presented(tv_sec >> 32, tv_sec, tv_nsec, refresh_nsec, sequence >> 32, sequence,
+ QtWaylandServer::wp_presentation_feedback::kind_vsync
+ | QtWaylandServer::wp_presentation_feedback::kind_hw_clock
+ | QtWaylandServer::wp_presentation_feedback::kind_hw_completion);
+
+ destroy();
+}
+
+void PresentationFeedback::destroy()
+{
+ wl_resource_destroy(resource()->handle);
+}
+
+void PresentationFeedback::wp_presentation_feedback_destroy_resource(Resource *resource)
+{
+ Q_UNUSED(resource);
+ delete this;
+}
+
+QWaylandPresentationTimePrivate::QWaylandPresentationTimePrivate()
+{
+}
+
+void QWaylandPresentationTimePrivate::wp_presentation_bind_resource(Resource *resource)
+{
+ send_clock_id(resource->handle, CLOCK_MONOTONIC);
+}
+
+void QWaylandPresentationTimePrivate::wp_presentation_feedback(Resource *resource, struct ::wl_resource *surface, uint32_t callback)
+{
+ Q_Q(QWaylandPresentationTime);
+
+ QWaylandSurface *qwls = QWaylandSurface::fromResource(surface);
+ if (!qwls)
+ return;
+
+ new PresentationFeedback(q, qwls, resource->client(), callback, /* version */ 1);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qwaylandpresentationtime_p_p.cpp"
+
+#include "moc_qwaylandpresentationtime_p.cpp"