1// Copyright (C) 2021 LG Electronics Inc.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qwaylandpresentationtime_p.h"
5#include "qwaylandpresentationtime_p_p.h"
6
7#include <time.h>
8#include <QQuickWindow>
9#include <QtWaylandCompositor/QWaylandView>
10#include <QtWaylandCompositor/QWaylandQuickItem>
11
12QT_BEGIN_NAMESPACE
13
14/*!
15 * \qmltype PresentationTime
16 * \instantiates QWaylandPresentationTime
17 * \inqmlmodule QtWayland.Compositor.PresentationTime
18 * \since 6.3
19 * \brief Provides tracking the timing when a frame is presented on screen.
20 *
21 * The PresentationTime extension provides a way to track rendering timing
22 * for a surface. Client can request feedbacks associated with a surface,
23 * then compositor send events for the feedback with the time when the surface
24 * is presented on-screen.
25 *
26 * PresentationTime corresponds to the Wayland \c wp_presentation interface.
27 *
28 * To provide the functionality of the presentationtime extension in a compositor, create
29 * an instance of the PresentationTime component and add it to the list of extensions
30 * supported by the compositor:
31 *
32 * Then, call sendFeedback() when a surface is presented on screen.
33 * Usually, the timing can be obtained from drm page flip event.
34 *
35 * \qml
36 * import QtWayland.Compositor.PresentationTime
37 *
38 * WaylandCompositor {
39 * PresentationTime {
40 * id: presentationTime
41 * }
42 * }
43 * \endqml
44 */
45
46/*!
47 * \class QWaylandPresentationTime
48 * \inmodule QtWaylandCompositor
49 * \since 6.3
50 * \brief The QWaylandPresentationTime class is an extension to get timing for on-screen presentation.
51 *
52 * The QWaylandPresentationTime extension provides a way to track rendering timing
53 * for a surface. Client can request feedbacks associated with a surface,
54 * then compositor send events for the feedback with the time when the surface
55 * is presented on-screen.
56 *
57 * QWaylandPresentationTime corresponds to the Wayland \c wp_presentation interface.
58 */
59
60
61/*!
62 * Constructs a QWaylandPresentationTime object for \a compositor.
63 */
64QWaylandPresentationTime::QWaylandPresentationTime(QWaylandCompositor *compositor)
65 : QWaylandCompositorExtensionTemplate(compositor, *new QWaylandPresentationTimePrivate)
66{
67
68}
69
70/*!
71 * Constructs an empty QWaylandPresentationTime object.
72 */
73QWaylandPresentationTime::QWaylandPresentationTime()
74 : QWaylandCompositorExtensionTemplate(*new QWaylandPresentationTimePrivate)
75{
76}
77
78/*!
79 * Initializes the extension.
80 */
81void QWaylandPresentationTime::initialize()
82{
83 Q_D(QWaylandPresentationTime);
84
85 if (isInitialized()) {
86 qWarning() << "QWaylandPresentationTime is already initialized";
87 return;
88 }
89
90 QWaylandCompositor *compositor = this->compositor();
91 if (compositor == nullptr) {
92 qWarning() << "Failed to find QWaylandCompositor when initializing QWaylandPresentationTime";
93 return;
94 }
95
96 QWaylandCompositorExtensionTemplate::initialize();
97
98 d->init(compositor->display(), /* version */ 1);
99}
100
101QWaylandCompositor *QWaylandPresentationTime::compositor() const
102{
103 return qobject_cast<QWaylandCompositor *>(object: extensionContainer());
104}
105
106/*!
107 * \qmlmethod void PresentationTime::sendFeedback(Window window, int sequence, int sec, int nsec)
108 *
109 * Interface to notify that a frame is presented on screen using \a window.
110 * If your platform supports DRM events, \c page_flip_handler is the proper timing to send it.
111 * The \a sequence is the refresh counter. \a sec and \a nsec hold the
112 * seconds and nanoseconds parts of the presentation timestamp, respectively.
113 */
114
115/*!
116 * Interface to notify that a frame is presented on screen using \a window.
117 * If your platform supports DRM events, \c page_flip_handler is the proper timing to send it.
118 * The \a sequence is the refresh counter. \a tv_sec and \a tv_nsec hold the
119 * seconds and nanoseconds parts of the presentation timestamp, respectively.
120 */
121void QWaylandPresentationTime::sendFeedback(QQuickWindow *window, quint64 sequence, quint64 tv_sec, quint32 tv_nsec)
122{
123 if (!window)
124 return;
125
126 quint32 refresh_nsec = window->screen()->refreshRate() != 0 ? 1000000000 / window->screen()->refreshRate() : 0;
127
128 emit presented(sequence, tv_sec, tv_nsec, refresh_nsec);
129}
130
131/*!
132 * Returns the Wayland interface for the QWaylandPresentationTime.
133 */
134const struct wl_interface *QWaylandPresentationTime::interface()
135{
136 return QWaylandPresentationTimePrivate::interface();
137}
138
139/*!
140 * \internal
141 */
142QByteArray QWaylandPresentationTime::interfaceName()
143{
144 return QWaylandPresentationTimePrivate::interfaceName();
145}
146
147PresentationFeedback::PresentationFeedback(QWaylandPresentationTime *pTime, QWaylandSurface *surface, struct ::wl_client *client, uint32_t id, int version)
148 : wp_presentation_feedback(client, id, version)
149 , m_presentationTime(pTime)
150{
151 setSurface(surface);
152}
153
154void PresentationFeedback::setSurface(QWaylandSurface *qwls)
155{
156 if (!qwls) {
157 discard();
158 return;
159 }
160
161 m_surface = qwls;
162
163 connect(sender: qwls, signal: &QWaylandSurface::damaged, context: this, slot: &PresentationFeedback::onSurfaceCommit);
164 connect(sender: qwls, signal: &QWaylandSurface::destroyed, context: this, slot: &PresentationFeedback::discard);
165
166 QWaylandView *view = qwls ? qwls->primaryView() : nullptr;
167 //The surface has not committed yet.
168 if (!view) {
169 connect(sender: qwls, signal: &QWaylandSurface::hasContentChanged, context: this, slot: &PresentationFeedback::onSurfaceMapped);
170 return;
171 }
172
173 maybeConnectToWindow(view);
174}
175
176void PresentationFeedback::onSurfaceCommit()
177{
178 // There is a new commit before sync so that discard this feedback.
179 if (m_committed) {
180 discard();
181 return;
182 }
183 m_committed = true;
184}
185
186void PresentationFeedback::onSurfaceMapped()
187{
188 QWaylandView *view = m_surface->primaryView();
189 if (!view) {
190 qWarning() << "The mapped surface has no view";
191 discard();
192 return;
193 }
194
195 maybeConnectToWindow(view);
196}
197
198void PresentationFeedback::maybeConnectToWindow(QWaylandView *view)
199{
200 QWaylandQuickItem *item = view ? qobject_cast<QWaylandQuickItem *>(object: view->renderObject()) : nullptr;
201 if (!item) {
202 qWarning() << "QWaylandPresentationTime only works with QtQuick compositors" << view;
203 discard();
204 return;
205 }
206
207 connect(sender: item, signal: &QQuickItem::windowChanged, context: this, slot: &PresentationFeedback::onWindowChanged);
208 // wait for having window
209 if (!item->window()) {
210 return;
211 }
212
213 connectToWindow(item->window());
214}
215
216void PresentationFeedback::onWindowChanged()
217{
218 QWaylandQuickItem *item = qobject_cast<QWaylandQuickItem *>(object: sender());
219 QQuickWindow *window = item ? item->window() : nullptr;
220
221 if (!window) {
222 qWarning() << "QWaylandPresentationTime only works with QtQuick compositors" << item;
223 discard();
224 /* Actually, the commit is not discarded yet. If the related item has new window,
225 the commit can be presented on screen. So we can choose not to discard the feedback
226 until item has new window or the surface is destroyed. */
227 return;
228 }
229
230 // Check if the connected window is changed
231 if (m_connectedWindow && m_connectedWindow != window)
232 m_connectedWindow->disconnect(receiver: this);
233
234 connectToWindow(window);
235}
236
237void PresentationFeedback::connectToWindow(QQuickWindow *window)
238{
239 if (!window) {
240 discard();
241 return;
242 }
243
244 m_connectedWindow = window;
245
246 connect(sender: window, signal: &QQuickWindow::beforeSynchronizing, context: this, slot: &PresentationFeedback::onSync);
247 connect(sender: window, signal: &QQuickWindow::afterFrameEnd, context: this, slot: &PresentationFeedback::onSwapped);
248}
249
250void PresentationFeedback::onSync()
251{
252 QQuickWindow *window = qobject_cast<QQuickWindow *>(object: sender());
253
254 if (m_committed) {
255 disconnect(sender: m_surface, signal: &QWaylandSurface::damaged, receiver: this, slot: &PresentationFeedback::onSurfaceCommit);
256 disconnect(sender: window, signal: &QQuickWindow::beforeSynchronizing, receiver: this, slot: &PresentationFeedback::onSync);
257 m_sync = true;
258 }
259}
260
261void PresentationFeedback::onSwapped()
262{
263 QQuickWindow *window = qobject_cast<QQuickWindow *>(object: sender());
264
265 if (m_sync) {
266 disconnect(sender: window, signal: &QQuickWindow::afterFrameEnd, receiver: this, slot: &PresentationFeedback::onSwapped);
267 connect(sender: m_presentationTime, signal: &QWaylandPresentationTime::presented, context: this, slot: &PresentationFeedback::sendPresented);
268 }
269}
270
271void PresentationFeedback::discard()
272{
273 send_discarded();
274 destroy();
275}
276
277void PresentationFeedback::sendSyncOutput()
278{
279 QWaylandCompositor *compositor = presentationTime()->compositor();
280 if (!compositor) {
281 qWarning() << "No compositor container to send sync_output";
282 return;
283 }
284
285 QWaylandView *view = surface()->primaryView();
286 QWaylandOutput *output = view ? view->output() : nullptr;
287 struct ::wl_resource *r = output ? output->resourceForClient(client: QWaylandClient::fromWlClient(compositor, wlClient: resource()->client())) : nullptr;
288
289 if (r)
290 send_sync_output(r);
291}
292
293void PresentationFeedback::sendPresented(quint64 sequence, quint64 tv_sec, quint32 tv_nsec, quint32 refresh_nsec)
294{
295 sendSyncOutput();
296
297 send_presented(tv_sec >> 32, tv_sec, tv_nsec, refresh_nsec, sequence >> 32, sequence,
298 QtWaylandServer::wp_presentation_feedback::kind_vsync
299 | QtWaylandServer::wp_presentation_feedback::kind_hw_clock
300 | QtWaylandServer::wp_presentation_feedback::kind_hw_completion);
301
302 destroy();
303}
304
305void PresentationFeedback::destroy()
306{
307 wl_resource_destroy(resource()->handle);
308}
309
310void PresentationFeedback::wp_presentation_feedback_destroy_resource(Resource *resource)
311{
312 Q_UNUSED(resource);
313 delete this;
314}
315
316QWaylandPresentationTimePrivate::QWaylandPresentationTimePrivate()
317{
318}
319
320void QWaylandPresentationTimePrivate::wp_presentation_bind_resource(Resource *resource)
321{
322 send_clock_id(resource->handle, CLOCK_MONOTONIC);
323}
324
325void QWaylandPresentationTimePrivate::wp_presentation_feedback(Resource *resource, struct ::wl_resource *surface, uint32_t callback)
326{
327 Q_Q(QWaylandPresentationTime);
328
329 QWaylandSurface *qwls = QWaylandSurface::fromResource(resource: surface);
330 if (!qwls)
331 return;
332
333 new PresentationFeedback(q, qwls, resource->client(), callback, /* version */ 1);
334}
335
336QT_END_NAMESPACE
337
338#include "moc_qwaylandpresentationtime_p_p.cpp"
339
340#include "moc_qwaylandpresentationtime_p.cpp"
341

source code of qtwayland/src/compositor/extensions/qwaylandpresentationtime.cpp