LINUX.ORG.RU

Вывод графиков в Qt


0

3

У меня есть данные, которые поступают с частотой 1000 значений в секунду с нескольких каналов. Нужно построить графики для каждого канала. Я делаю это с помощью QTimer, который сначала забирает данные (забирает с частотой 100 Гц, данные буферизуются устройством) и каждый второй кадр обновляет графики (чтобы частота обновления была 50 Гц, а не 100, потому что всё равно у монитора 60 Гц и больше нет смысла), которые рисуются на QGraphicsView.

QGraphicsScene *graphScene, *tempGraphScene;
...
        tempGraphScene->clear();
	static const QColor colors[8] = {QColor(255, 0, 0), QColor(0, 255, 0), QColor(0, 0, 255), QColor(255, 255, 0),
		QColor(0, 255, 255), QColor(255, 0, 255), QColor(128, 128, 128), QColor(0, 0, 0)};
	for (int i = 0; i < ui->channelListWidget->count(); i++) {
		QPen pen(colors[i % 8]);
		QString name = ui->channelListWidget->item(i)->text();
		int t = 0, prev_t = 0, prev_y = 0;
		foreach (double value, dataBuffer[name]) {
			int y = 256 - value / 2.56 * 256.0;
			if (t > 0) {
				tempGraphScene->addLine(prev_t, prev_y, t, y, pen);
			}
			prev_t = t;
			prev_y = y;
			t++;
		}
	}
	{
		QGraphicsScene *tmp = tempGraphScene;
		tempGraphScene = graphScene;
		graphScene = tmp;
	}
	ui->graphView->setScene(graphScene);

Ну так вот. Оно лагает. Если убрать отрисовку, оставить только чтение данных и запихивание в QList'ы, то всё хорошо, процессор загружен всего на 5% и всё счастливы. Но с отрисовкой FPS падает раза в 4 и получается очень печально (данные теряются, потому что программа на ПК не успевает их забирать и буфер устройства переполняется). Попробовал выводить данные каждый 100-ый кадр, то есть всего с частотой 1 Гц. В итоге FPS чтения данных получается 96-97. То есть отрисовка настолько тормозная, что за это время успевает потеряться 3-4 кадра данных (буфер устройства способен вместить 32 сэмпла данных со всех каналов, если не успевать их забирать, то он очищается).

Вероятно, я рисую график как быдлокодер. Мне очень стыдно и я хочу узнать как это делать быстро и правильно.

★★★★★

Очевидно, что QGraphicsScene, слабо подходит для отрисовки часто меняющихся данных. Например addLine создаёт каждый раз довольно таки не тривиальный обьект, что само по себе должно наводить на мысль...

Выход - юзать QPainer, OpenGL, либо какую нибудь специализированную либу типо этой или что там ещё есть с более кошерной лицензией.

Ещё бы я посоветовал «данные» с «каналов» не гонять через виджеты, а и в виджетах, и при отрисовке, брать через какой нибудь унифицированный интерфейс (например из модели).

batbko ()
Ответ на: комментарий от batbko

Сделал QPixmap. На нём рисую с помощью QPainter. А потом добавляю QPixmap на QGraphicScene. Но только вот я каждый раз сначала очищаю сцену, а потом добавляю пиксмап, хотя он один и тот же. Просто на изменения QPixmap сцена реагировать не хочет, однако если QPixmap локальная переменная, то графика выводиться неадекватно (то есть получается, что addPixmap создаёт ссылку, а не копию и в теории изменение pixmap должен отразиться на сцене).

Производительность почти идеальная, но нагрузка на процессор 42% и при активном сколле бывают выпадения 1 чтения данных из 100 за секунду.

KivApple ★★★★★ ()
Последнее исправление: KivApple (всего исправлений: 3)
Ответ на: комментарий от KivApple

Сделал QPixmap. На нём рисую с помощью QPainter.

Это должно быть быстро.

А потом добавляю QPixmap на QGraphicScene.

А вот это не быстро.

Лучше создать свой QWidget, у котором в

QWidget::paintEvent(QPaintEvent * event)
на виджете рисуется (QPainter p(this)) либо подготовленный QPixmap, либо напрямую график. Ну и в resizeEvent подгоняется размер масштаб.

Kosyak ★★★★ ()
Ответ на: комментарий от Kosyak

Ну и по таймеру у виджета repaint() дергать (или update(), смотря как программа устроена).

Kosyak ★★★★ ()

можно взять готовый qwt, с qt5 тоже собирается, с qt4 во всех дистрибутивах

anonymous ()

Забульбень OpenGL'ный виджет, одним потоком формируй данные, другим обновляй картинку.

А вообще, попробовал бы MathGL!

Eddy_Em ☆☆☆☆☆ ()

буфер устройства способен вместить 32 сэмпла данных со всех каналов, если не успевать их забирать, то он очищается

Тут только выносить чтение в отдельный поток. В котором ты с большей частотой читаешь данные и помещаешь их в многократной больший буфер.

А с отрисовкой уже решишь, так или иначе. Например, задействуй сторонние Qt-виджеты для рисования графиков.

AlexVR ★★★★★ ()

тут дело не в том, что ты слишком медленно рисуешь, а в том, что ты блокируешь чтение из девайса.

делать надо в 2 потока.

один поток читает из девайса в буфер, например, 1000 раз в секунду.

другой поток читает из буфера, и рисует текущий стейт, например, 15 раз в секунду.

во время модификации или чтения из буфера, юзай мутекс.

перед отрисовкой, копируй нужный кусок буфера в другой (временный) буфер, чтобы не блокировать другой поток на время отрисовки.

waker ★★★★★ ()

Может надо не сцену удалять-добавлять каждый раз, а итемы модифицировать как-то?

nanoolinux ★★★★ ()
Ответ на: комментарий от batbko

фиксить тормоза отрисовки надо начинать, когда правильно работает алгоритм. если скорость отрисовки влияет на корректность чтения данных — значит с алгоритмом что-то не то.

waker ★★★★★ ()
Ответ на: комментарий от waker

Ну в этом смысле я согласен, но читай мой первый коммент - он все данные через виджеты протягивает, это да фиговый подход учитвая что эвент луп на это дело один, и на таймер скорее всего тот же, кстати.

Но судя по заверению TC устройство само рулит своим буффером, и производительности чтения отуда хватает, а вот отрисовка тупит.

Это я к тому, что если оно работает и с приемлемой скоростью, а узкое место отрисовка - то в первую очередь нужно убрать этот ботлнек, а остальное может оказаться оверкиллом для текущей задачи.

batbko ()
Ответ на: комментарий от batbko

Это я к тому, что если оно работает и с приемлемой скоростью, а узкое место отрисовка - то в первую очередь нужно убрать этот ботлнек, а остальное может оказаться оверкиллом для текущей задачи.

и куда ты его уберешь? рисовать-то надо.

waker ★★★★★ ()
Ответ на: комментарий от waker

Ну рисовать то можно по разному - TC выбрал заведомо ущербный способ. Любой, из предложенных в топике, быстрее в разы. А от того, что ты начнешь данные полить быстрее, ФПС у тебя больше не станет, пока ты UI поток напрягаешь не по делу, вещами которые не предусмотренны для того что бы пересоздаваться 10n раз в секунду.

Я не исключаю, что после эффективной отрисовки, может потребоваться оптимизировать полинг данных с железки, но это значит что не все юзекейсы были упомянуты в топике... Пока нужно только рисовать с приемлемой частотой - тащи данные, хоть на порядок быстрее, хоть на 2, поставленной задачи всё равно не добьешься.

Т.е. судя по описанию добыча данных занимает n, а отрисовка 100n. Т.е суммарно 101n. Т.к. и то и другое происходит в одном потоке, то это задачи конкурирующие. Но вынеся полинг данных в отдельный поток, мы выиграем только n времени, а 100n останутся на долю отрисовки. Очевидно - мы должны сократить время отрисовки, ибо 80/20 и всё такое:)

Единственное, чего позволит добиться отдельный поток - это то, что повысится вероятность заснять больше данных, но, скорее всего, на какую то величину снизиться актуальность этих данных при визуализации, но на неё можно и забить, видимо. Т.е. это актуально, только если стоит задача просирать как можно меньше кадров, но TC и так снимает только 100 кадров, из доступной 1000.

batbko ()
Ответ на: комментарий от batbko

Любой, из предложенных в топике, быстрее в разы.

ты пробовал рисовать динамические графики на 50 fps в окошке хотя бы на полэкрана? кроме opengl с аппаратным ускорением, в линуксе нет API способных на это. а opengl не у всех как следует работает, или вообще может быть софтовый, особенно если в виртуалке. гораздо надежнее рисовать с максимально возможным fps + ограничивать по необходимости, даже если 5 раз в секунду - и хрен с ним. а читать из девайса хоть на 1000 fps в другом потоке, без зависимости от графического fps.

waker ★★★★★ ()
Ответ на: комментарий от batbko

Но вынеся полинг данных в отдельный поток, мы выиграем только n времени, а 100n останутся на долю отрисовки.

100n у тебя получится, если ты вообще за секунду успеешь 100 кадров нарисовать. что врядли. поэтому смотри выше что я пишу. я предлагаю рисовать столько кадров, сколько успеваешь — хоть 5, хоть 50, не влияя на чтение из девайса.

тут дело не в оптимизации, а в корректности. если ты будешь отрисовкой блокировать чтение из девайса — у тебя график будет пропускать куски данных.

waker ★★★★★ ()
Ответ на: комментарий от batbko

если сделать то что ты описал — это позволит получить корректный график на 1 отдельно взятой машине, с быстрой графикой и CPU, без виртуалки, причем все это рухнет если юзер например решит youtube посмотреть в другом окне. мой способ гораздо надежнее, потому что чтение с девайса можно организовать в потоке с высоким приоритетом, в виде цикла с коротким usleep, который будет жив даже если проц под 100% грузануть другими ненужностями.

waker ★★★★★ ()
Ответ на: комментарий от waker

ты пробовал рисовать динамические графики на 50 fps в окошке хотя бы на полэкрана? кроме opengl с аппаратным ускорением, в линуксе нет API способных на это

Если честно было это давненько, но в MCBC, ещё на Qt4, тот же QPainter, при довольно насыщенной отрисовке, давал и 30 фпс и 40, хотя может там была попроще графика...
Но речь не об фпс, а о соотношении времени затрачиваемого на отрисовку(одну операцию отрисовки) и получения снэпшота данных.

причем все это рухнет если юзер например решит youtube посмотреть в другом окне.

Прям таки рухнет?:)

мой способ гораздо надежнее

Ктож спорит, но ещё - он сложнее.

сколько успеваешь — хоть 5, хоть 50, не влияя на чтение из девайса

Мсье упускает из виду то, что изначально, для TC, видимо не стояла такая задача, ибо он и так пропускал каждый 10 кадр(данных). Иначе, тут однозначно должна иметь место очередь, из которой данные выдираются и отрисовываются в UI потоке, но такой график(зависит от скорости I/O девайса, конечно же), может заметно отставать по актуальности, и с течением времени работы программы, это отставание коли оно появится - будет расти.

100n у тебя получится, если ты вообще за секунду успеешь 100 кадров нарисовать.

В данном случае величина n никак не связанна с количеством кадров,
она введенна, лишь для того что бы показать на какой примерно порядок различаются скорости получения данных, достаточной плотности и отрисовки.

Это я к тому, что да, правильней полить в отдельном потоке, и давать UI потоку либо взять текущий актуальный, снэпшот либо сковырнуть снэпшот с очереди, это, даже в 99% случаях будет эффективней. Но! Не стоит заморачиваться, если буффер железки и так поддерживает актуальный снэпшот, и если I/O из буфера железки дешевле или примерно равно по стоимости локу, ну и конечно не нужны все данные, что кстати можно всё равно не успеть:)

Вообщем если интересно, надо кастануть ТС и дорасспросить его :)
А именно в данном случае надо понять, что входит в понятие

получить корректный график

batbko ()
Ответ на: комментарий от batbko

Прям таки рухнет?:)

отрисовка тормознется, со всеми вытекающими.

но ещё - он сложнее.

в нем нет ничего сложного, если понимать принцип.

но такой график(зависит от скорости I/O девайса, конечно же), может заметно отставать по актуальности, и с течением времени работы программы, это отставание коли оно появится - будет расти.

зачем отставать? нужно просто всегда визуализировать крайний видимый кусок данных.

Не стоит заморачиваться, если буффер железки и так поддерживает актуальный снэпшот

читай внимательно топик:

буфер устройства способен вместить 32 сэмпла данных со всех каналов, если не успевать их забирать, то он очищается

если бы буфер железки умел отдать последние N сэмплов, не меньше размера графика, быстро, и гарантированно - значит вся буферизация уже реализована где-то в драйвере или библиотеке для работы с девайсом, и городить свою еще одну нет никакого смысла. но тут явно не тот случай.

waker ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.