На современном настольном ЦП (учитывая всю платформу, то есть ОЗУ, шину и другие аспекты) является ли значительной нагрузкой перераспределение нескольких МБ несколько раз в секунду?
В типичных современных распределителях стоимость одного выделения фиксирована и не зависит от размера выделения для «маленьких» выделений. Для больших выделений это O(N) по размеру выделения с очень низкой константой пропорциональности.
Виджет Qt верхнего уровня поддерживается либо буфером QImage
, либо контекстом OpenGL, если вы используете файл QOpenGLWidget
. Изменение размера буфера поддержки окна обрабатывается Qt автоматически — это уже происходит, и вы даже не замечаете этого! Это не имеет большого значения, с точки зрения производительности. Современные распределители не глупы и не фрагментируют кучу.
На описанной выше платформе, когда происходит перераспределение, происходит ли это в кеше или в ОЗУ, если можно сказать?
Это не имеет значения, так как вы все равно собираетесь перезаписать его. Конечно, это помогает, если есть доступные кэш-линии, и повторное использование одного и того же адреса для объекта поможет в этом.
Какова общая идиома в Qt для решения такого рода проблем?
У вас есть слот, который используется для обновления отображаемых данных (например, для обновления изображения или какого-либо параметра), и вызовите QWidget::update()
Рендерим в paintEvent
.
Остальное происходит автоматически. Неважно, сколько времени займет paintEvent
— если это займет много времени, скорость отклика пользовательского интерфейса упадет, но он никогда не будет пытаться отображать устаревшие данные. Нет накопления событий.
Масштабирование изображения обычно выполняется с помощью QImage::scaled
, возвращающего временное изображение, которое вы затем рисуете с помощью QPainter::drawImage
. Да, там есть выделения, но эти выделения быстрые.
Обойти бурю событий производителя изображений очень просто: производитель сигнализирует, когда доступно новое изображение. У потребителя образа есть слот, который принимает образ, копирует его во внутренний элемент и запускает обновление. Обновление вступает в силу, когда элемент управления возвращается в цикл обработки событий и использует самое последнее установленное изображение. Перерисовка будет продолжаться, когда нет других событий для обработки, поэтому не имеет значения, сколько времени это займет: всегда будет отображаться самое последнее изображение. Он никогда не будет «отставать».
Это поведение легко проверить. В приведенном ниже примере ImageSource
создает новые кадры так быстро, как только может (порядка 1 кГц). В каждом кадре отображается текущее время. Viewer
спит в своем paintEvent
, ограничивая частоту обновления экрана менее чем 4 Гц: в реальной жизни он никогда не будет таким медленным, если только вы не работаете на сильно перегретом ядре. На каждое обновление экрана приходится не менее 25 новых кадров. Тем не менее, время, которое вы видите на экране, является текущим временем. Устаревшие кадры автоматически отбрасываются.
// https://github.com/KubaO/stackoverflown/tree/master/questions/update-storm-image-40111359
#include <QtWidgets>
class ImageSource : public QObject {
Q_OBJECT
QImage m_frame{640, 480, QImage::Format_ARGB32_Premultiplied};
QBasicTimer m_timer;
double m_period{};
void timerEvent(QTimerEvent * event) override {
if (event->timerId() != m_timer.timerId()) return;
m_frame.fill(Qt::blue);
QElapsedTimer t;
t.start();
QPainter p{&m_frame};
p.setFont({"Helvetica", 48});
p.setPen(Qt::white);
p.drawText(m_frame.rect(), Qt::AlignCenter,
QStringLiteral("Hello,\nWorld!\n%1").arg(
QTime::currentTime().toString(QStringLiteral("hh:mm:ss.zzz"))));
auto const alpha = 0.001;
m_period = (1.-alpha)*m_period + alpha*(t.nsecsElapsed()*1E-9);
emit newFrame(m_frame, m_period);
}
public:
ImageSource() {
m_timer.start(0, this);
}
Q_SIGNAL void newFrame(const QImage &, double period);
};
class Viewer : public QWidget {
Q_OBJECT
double m_framePeriod;
QImage m_image;
QImage m_scaledImage;
void paintEvent(QPaintEvent *) override {
qDebug() << "Waiting events" << d_ptr->postedEvents;
QPainter p{this};
if (m_image.isNull()) return;
if (m_scaledImage.isNull() || m_scaledImage.size() != size())
m_scaledImage = m_image.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
p.drawImage(0, 0, m_scaledImage);
p.drawText(rect(), Qt::AlignTop | Qt::AlignLeft, QStringLiteral("%1 FPS").arg(1./m_framePeriod));
if (true) QThread::msleep(250);
}
public:
Q_SLOT void setImage(const QImage & image, double period) {
Q_ASSERT(QThread::currentThread() == thread());
m_image = image;
m_scaledImage = {};
m_framePeriod = period;
update();
}
};
class Thread final : public QThread { public: ~Thread() { quit(); wait(); } };
int main(int argc, char ** argv) {
QApplication app{argc, argv};
Viewer viewer;
viewer.setMinimumSize(200, 200);
ImageSource source;
Thread thread;
QObject::connect(&source, &ImageSource::newFrame, &viewer, &Viewer::setImage);
QObject::connect(&thread, &QThread::destroyed, [&]{ source.moveToThread(app.thread()); });
source.moveToThread(&thread);
thread.start();
viewer.show();
return app.exec();
}
#include "main.moc"
Обычно имеет смысл перенести масштабирование изображения на GPU. Этот ответ предлагает полное решение этой проблемы.
person
Kuba hasn't forgotten Monica
schedule
18.10.2016