summaryrefslogtreecommitdiffstats
path: root/src/shared-main-lib/framecontenttracker.cpp
blob: 209ba50401e5b51d96e4a6796be454d28fe6e236 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "framecontenttracker.h"
#include "applicationmanagerwindow.h"

#include <QQuickWindow>
#include <private/qquickwindow_p.h>
#include <private/qsgrhisupport_p.h>
#include <qqmlinfo.h>

using namespace Qt::StringLiterals;

QT_BEGIN_NAMESPACE_AM

/*!
    \qmltype FrameContentTracker
    \inqmlmodule QtApplicationManager
    \ingroup common-instantiatable
    \brief Provides duplicate frame detection for a given window.
    \since 6.9

    FrameContentTracker is used to detect duplicate frames being rendered for a given window.

    The window must be a real toplevel Window (from the \c QtQuick.Window module) or
    an ApplicationManagerWindow from a multi-process app. These however do \b not work:
    WindowObject (from the \c QtApplicationManager.SystemUI module) as well as
    ApplicationManagerWindow in single-process mode.

    This is useful for detecting unnecessary redraws, which can become a performance issue:
    the most likely causes are (a) animations that are not stopped, even though they are not visible
    anymore and (b) Wayland client windows being updated, although they are hidden or the
    compositor is not showing the updated area(s).

    The implementation first copies each rendered frame from the GPU into a QImage on the CPU side.
    Then this frame is compared pixel-by-pixel to the previous frame. If they are the same, the
    duplicateFrames counter is incremented.

    \note Do not use this component in a production environment or even when profiling the
          application, as it will slow down the rendering significantly.

    Please note that when using FrameContentTracker as a MonitorModel data source there's no need to
    set it to \l running as MonitorModel will already call update() as needed.
*/

FrameContentTracker::FrameContentTracker(QObject *parent)
    : QObject(parent)
{
    m_updateTimer.setInterval(1000);
    connect(&m_updateTimer, &QTimer::timeout, this, &FrameContentTracker::update);
}

FrameContentTracker::~FrameContentTracker()
{ }

/*!
    \qmlproperty list<string> FrameContentTracker::roleNames
    \readonly

    Names of the roles provided by FrameContentTracker when used as a MonitorModel data source.

    \sa MonitorModel
*/
QStringList FrameContentTracker::roleNames() const
{
    return { u"duplicateFrames"_s };
}

/*!
    \qmlmethod FrameContentTracker::update

    Updates the property duplicateFrames. Then resets the internal counters for the new time period
    starting from the moment this method is called.

    Note that you normally don't have to call this method directly, as FrameContentTracker does it
    automatically every \l interval milliseconds while \l running is set to \c true.

    \sa running
*/
void FrameContentTracker::update()
{
    m_duplicateFrames = m_sameFrameCounter.fetchAndStoreAcquire(0);
    emit updated();

}

/*!
    \qmlproperty bool FrameContentTracker::running

    If \c true, update() will get called automatically every \l interval milliseconds.

    When using FrameContentTracker as a MonitorModel data source, this property should be kept as
    \c false.

    \sa update() interval
*/
bool FrameContentTracker::running() const
{
    return m_updateTimer.isActive();
}

void FrameContentTracker::setRunning(bool running)
{
    if (running && !m_updateTimer.isActive()) {
        m_updateTimer.start();
        emit runningChanged();
    } else if (!running && m_updateTimer.isActive()) {
        m_updateTimer.stop();
        emit runningChanged();
    }
}

/*!
    \qmlproperty int FrameContentTracker::interval

    The interval, in milliseconds, between update() calls while \l running is \c true.

    \sa update() running
*/
int FrameContentTracker::interval() const
{
    return m_updateTimer.interval();
}

void FrameContentTracker::setInterval(int interval)
{
    if (interval != m_updateTimer.interval()) {
        m_updateTimer.setInterval(interval);
        emit intervalChanged();
    }
}

/*!
    \qmlproperty Object FrameContentTracker::window

    The window to be monitored, from which duplicate frames information will be gathered.
    It must be a real toplevel Window (from the \c QtQuick.Window module) or
    an ApplicationManagerWindow from a multi-process app. These however do \b not work:
    WindowObject (from the \c QtApplicationManager.SystemUI module) as well as
    ApplicationManagerWindow in single-process mode.

    \note Frames are being tracked as soon as this property is set to a valid window instance, even
          if \l running is set to \c false. Setting this property to \c null will stop the tracking.
*/
QObject *FrameContentTracker::window() const
{
    return m_window;
}

void FrameContentTracker::setWindow(QObject *window)
{
    if (m_window == window)
        return;

    if (m_frameAfterRenderingConnection) {
        disconnect(m_frameAfterRenderingConnection);
        m_frameAfterRenderingConnection = { };
    }

    m_window = window;
    m_lastFrame = { };
    m_sameFrameCounter = 0;

    if (m_window) {
        QQuickWindow *quickWindow = qobject_cast<QQuickWindow*>(m_window);
        if (auto *amWindow = qobject_cast<ApplicationManagerWindow *>(m_window)) {
            if (amWindow->isSingleProcess()) {
                qmlWarning(this) << "The content of an application's window in single-process mode"
                                    "cannot be tracked";
                return;
            }
            quickWindow = qobject_cast<QQuickWindow *>(amWindow->backingObject());
        }

        if (quickWindow) {
            m_frameAfterRenderingConnection = connect(quickWindow, &QQuickWindow::afterRendering,
                                                      this, [this, quickWindow]() {
                // we are on the render thread
                auto *wp = QQuickWindowPrivate::get(quickWindow);
                QImage img = QSGRhiSupport::instance()->grabAndBlockInCurrentFrame(
                    wp->rhi, wp->swapchain->currentFrameCommandBuffer());
                if (img == m_lastFrame)
                    ++m_sameFrameCounter;
                else
                    m_lastFrame = std::move(img);
            }, Qt::DirectConnection);
        }

        if (!m_frameAfterRenderingConnection)
            qmlWarning(this) << "The given window is not a QQuickWindow.";
    }

    emit windowChanged();
}

/*!
    \qmlproperty real FrameContentTracker::duplicateFrames
    \readonly

    The number of duplicate frames rendered for the given \l window, since update() was last called
    (either manually or automatically in case \l running is set to \c true).

    \sa window running update()
*/
int FrameContentTracker::duplicateFrames() const
{
    return m_duplicateFrames;
}

QT_END_NAMESPACE_AM

#include "moc_framecontenttracker.cpp"