Как стать автором
Обновить

Панель корреляции на QtQML/Quick

Время на прочтение5 мин
Количество просмотров4.5K

Всем привет! Я — тимлид команды по разработке десктопных приложений в компании Роджии Европа. Мы разрабатываем программные решения для нефтегазовой отрасли.


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


Обходя вопросы наследия кодовой базы — о нём я упомяну в статье — был один фундаментальный вопрос — с помощью какой технологии делать? Однозначно нам был нужен OpenGL — который уже применяется в MapView и 3d view на базе OpenSceneGraph — но очевидно, что не голый, и с элементами графического интерфейса. OSG отвалился =(. Технологию, удовлетворяющую двум требованиям — граф сцены и GUI на OpenGL — я знал только одну — Qt QML/Quick. О том, что же у нас получилось и чем нам есть поделиться — внутри.


Вступление


Мы начала разрабатывать продукт осенью 2013 года. Каким набором библиотек пользоваться для меня, как фаната Qt, даже и вопроса не стояло. На тот момент мог возникнуть вопрос: использовать 4ю или 5ю (ещё довольно свежую) версию Qt. Мы выбрали пятую и с высоты своего полёта могу только сказать: слава богу!


Весь внешний вид разработан на QtGui/Widgets. Все сцены, где отображаются графики (гамма, пористость, сопротивление, прочее), сделаны на QGraphicsScene/View. Мой совет — не используйте эту связку для серьёзных вещей! Аргументы: скроллБары и не отключаемая снаружи (именно снаружи — без правок самой Qt) логика по центрированию сцены (qgraphicsview.cpp +458 и выше в этом же методе для горизонтали; qgraphicsview.cpp +3816 — какой контроль над матрицей). Если вам это не мешает, то используйте — много удобных штук из коробки.


Что ещё не использовать? NSIS.


Всё было отлично, продукт развивался, задачи делались, количество клиентов росло. Рефакторинг… В общем по прошествии некоторого времени нам стал мешать QGraphicsScene — оптимизировать отрисовку графиков в 40 000 точек мы не спешили, при включении "толстых" линий — всё это добро на ЦПУ тормозило очень сильно.


Попутно с этим устали разрабатывать ГУЙ на виджетах. Либо руками полностью в коде, либо чуть-чуть в дизайнере (из Креатора, .ui + .cpp). Захотелось самомоднейших штучек, вроде декларативного описания графического интерфейса.


Составили список технологий, на которых будем делать:


  • по старинке на QGraphicsView/Scene;
  • для каждого трека использовать отдельный голый QOpenGLWidget;
  • всё окно реализовать на QOpenGLWidget, но GUI самим (или что-то подыскать);
  • предыдущие два пункта + OSG соответственно;
  • Qt QML/Quick,

всего шесть пунктов; обсуждали. Моя харизма перевесила и решили попробовать посмотреть, как поведёт себя прототип на QML.


Прототип


Я открыл пример scenegraph\graph. Посмотрел и закрыл =). Втуплял несколько дней, смотрел другие примеры, но ничего меня не приблежало к заветной цели.


А что было нужно то? Вот как может выглядеть панель корреляции:



Довольно простая структура — список треков скважин, внутри него треки для кривых, шкал; ну и по мелочи. Много текста, в будущем какие-нибудь контролы — кнопки, выпадающие списки, поля ввода и прочее.


Посмотрел sgengine, научился создавать два графа сцены и рисовать их в отведённом вьюпорте. Позже осознал, что при таком варианте QML/Quick не будет, тогда зачем мне всё это?


На самом деле, я уже не помню, какие наркотики, но почему-то я решил обратиться к основам компьютерной графики. Так вот на последних этапах растеризации все координаты сцены переводятся в НКУ (Нормализованные Координаты Устройства; более известные как NDC = Normalized Device Coordinates). Да, я слышал о том, что выход вершинного шейдера — это на самом деле clip space (пространство отсечения) и после происходит ещё аффинное искажение, но всё это для трёхмерного представления, а в 2Д всегда w = 1 и поэтому можно считать, что выход сразу в NDC.


Хорошо, NDC, дальше то что? То, что если ширина вашего окна 800 пикселей, то NDC-координата центра нулевого пикселя есть -1; координата центра 799-го равна 1. Короче, ndcX = -1 + 2 * i / 799. Теперь представим, что есть прямоугольник от 100 до 300 и я хочу отрисовать всю сцену не в целое окно, а в него. Используя вот эти обрывочные знания, я посчитаю ndcX100, ndcX300, потом прокину их в вершинный шейдер и там, после стандартных


gl_Position = matrix * position;

линейно "заверну" gl_Position.x в [ndcX100; ndcX300]. Аналогично поступаем для вертикальной составляющей. Такой трюк позволит порождать сцены в любом выбранном прямоугольнике сцены. Вот с этими знаниями пример graph и стал подвергаться изменениям. Посмотреть на приход можно здесь — graph; вся соль в shaders/line.vsh.


SceneItem/Scene


Следующие три месяца было написание ТЗ, получилось 12 листов А4 =). Мы параллельно этому обдумывали архитектуру. Взяли MVC… он же MVP… он же Hierarchical MVC/MVP… или даже PAC — всё это условности, важна хорошая декомпозиция.


В общем, мы подготовили пример сцены. Исходники доступны тут — SceneSample. Получился некий framework для создания приложений с графиками на QtQML/Quick. Прошу не забывать, что этот код всё таки выступает в качестве примера. Да, уже в полуготовности и выглядит более-менее аккуратно, но не готов.


Scene — главный игрок. Этот класс следит за своими NDC-координатами и обновляет соответствующие матрицы. С ним плотно дружит SceneCamera. Следующая сущность, достойная упоминания — SceneItem. Сам по себе бесполезен, только содержит некую базовую логику; наследуйте ему — подобно LineStrip — и реализуйте необходимое. При этом в updatePaintNode нужно использовать производные от SceneMaterial — FlatColorMaterial в качестве эталона. Остальные сущности тоже чем-то занимаются =), всякие манипуляторы, тулы. Многие из классов не прокинуты в QML и без C++ с такой "либой" не обойтись; вы же помните, что не готово?


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


Плюсы подхода:


  • всё рисуется в одном графе сцены;
  • мы не правили Qt — остаётся возможность добавлять на Сцену обычные QML-контролы так, что z-order между ними и кривыми (или другими SceneItem) корректный;
  • меньшее расходование памяти в сравнении с другими подходами.

Минусы


  • сложная убермашина;
  • знания OpenGL и GLSL обязательны;
  • полуготовое решение.

Конечно же при разработке мы столкнулись с некоторыми трудностями. Одной из них был


Баг с z-order


Когда мы первый раз попробовали отображать сцену с кривыми, то увидели такие картины:



С первого взгляда было не понятно, "мы же всё сделали правильно!" Смутно догадывались, что gl_Position.z почему-то ошибочный, но почему именно ночью понимать было тяжело. Мы не сдавались: подглядели, что Qt правит шейдеры и дописывает код по изменению gl_Position.z, подумали. Через некоторое время осенило: мы испортили данные матрицы по изменению z, а Qt в них передаёт свои значения! Таким образом происходит отображение значения Item.z из QML в z из OpenGL (SceneMaterial.cpp +20):



Баг с clip: true


Однажды в чат бизнес-команда присылает скрин, где пропала левая линия координатной сетки.



Наши Q&A помучали программу и нашли шаги стабильного воспроизведения: ставим масштабирование для монитора не кратное 100% и линии "мелькают". Артём посидел, подумал и нашёл, что когда clip: true и айтем прямоугольный то используется glScissor, но его аргументы — целочисленные пиксельные координаты! У QML-х айтемов они вещественные и получалось, что растеризация линии попадала на следующий/предыдущий пиксель, а ножницы резали по текущему.


Починили сцену так: width: Math.round(metrics.width + leftPadding + 2 * rightPadding + 0.5). Соответственно айтем сцены должен всегда иметь целочисленные координаты, чтобы избежать подобных артефактов.


В заключении приведу КДПВ



Всем спасибо за внимание!

Теги:
Хабы:
Всего голосов 10: ↑10 и ↓0+10
Комментарии11

Публикации

Истории

Работа

QT разработчик
4 вакансии
Программист C++
106 вакансий

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань