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 | |
12 | QT_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 | */ |
64 | QWaylandPresentationTime::QWaylandPresentationTime(QWaylandCompositor *compositor) |
65 | : QWaylandCompositorExtensionTemplate(compositor, *new QWaylandPresentationTimePrivate) |
66 | { |
67 | |
68 | } |
69 | |
70 | /*! |
71 | * Constructs an empty QWaylandPresentationTime object. |
72 | */ |
73 | QWaylandPresentationTime::QWaylandPresentationTime() |
74 | : QWaylandCompositorExtensionTemplate(*new QWaylandPresentationTimePrivate) |
75 | { |
76 | } |
77 | |
78 | /*! |
79 | * Initializes the extension. |
80 | */ |
81 | void 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 | |
101 | QWaylandCompositor *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 | */ |
121 | void 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 | */ |
134 | const struct wl_interface *QWaylandPresentationTime::interface() |
135 | { |
136 | return QWaylandPresentationTimePrivate::interface(); |
137 | } |
138 | |
139 | /*! |
140 | * \internal |
141 | */ |
142 | QByteArray QWaylandPresentationTime::interfaceName() |
143 | { |
144 | return QWaylandPresentationTimePrivate::interfaceName(); |
145 | } |
146 | |
147 | PresentationFeedback::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 | |
154 | void 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 | |
176 | void 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 | |
186 | void 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 | |
198 | void 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 | |
216 | void 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 | |
237 | void 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 | |
250 | void 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 | |
261 | void 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 | |
271 | void PresentationFeedback::discard() |
272 | { |
273 | send_discarded(); |
274 | destroy(); |
275 | } |
276 | |
277 | void 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 | |
293 | void 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 | |
305 | void PresentationFeedback::destroy() |
306 | { |
307 | wl_resource_destroy(resource()->handle); |
308 | } |
309 | |
310 | void PresentationFeedback::wp_presentation_feedback_destroy_resource(Resource *resource) |
311 | { |
312 | Q_UNUSED(resource); |
313 | delete this; |
314 | } |
315 | |
316 | QWaylandPresentationTimePrivate::QWaylandPresentationTimePrivate() |
317 | { |
318 | } |
319 | |
320 | void QWaylandPresentationTimePrivate::wp_presentation_bind_resource(Resource *resource) |
321 | { |
322 | send_clock_id(resource->handle, CLOCK_MONOTONIC); |
323 | } |
324 | |
325 | void 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 | |
336 | QT_END_NAMESPACE |
337 | |
338 | #include "moc_qwaylandpresentationtime_p_p.cpp" |
339 | |
340 | #include "moc_qwaylandpresentationtime_p.cpp" |
341 | |