Графическая сцена Qt Quick
Графическая сцена в Qt Quick
Qt Quick 2 использует отдельную графическую сцену, которая затем просматривается и отрисовывается с помощью графического API, такого как OpenGL ES, OpenGL, Vulkan, Metal или Direct 3D. Использование графической сцены для графики вместо традиционных императивных систем рисования (QPainter и аналогичных) означает, что сцену, подлежащую отрисовке, можно сохранять между кадрами, и весь набор примитивов для отрисовки известен до начала отрисовки. Это открывает возможности для ряда оптимизаций, таких как пакетная отрисовка для минимизации изменений состояния и отбрасывание скрытых примитивов.
Например, предположим, что пользовательский интерфейс содержит список из десяти элементов, где каждый элемент имеет цвет фона, значок и текст. Использование традиционных методов рисования приведет к 30 вызовам рисования и аналогичному количеству изменений состояния. Графическая сцена, с другой стороны, может перегруппировать примитивы для отрисовки таким образом, чтобы все фоны отрисовывались одним вызовом, затем все значки, затем весь текст, уменьшая общее количество вызовов рисования только до 3. Такая пакетная обработка и сокращение изменений состояния могут значительно улучшить производительность на некоторых устройствах.
Графическая сцена тесно связана с Qt Quick 2.0 и не может использоваться автономно. Графической сценой управляет и её отрисовывает класс QQuickWindow, а пользовательские типы элементов могут добавлять свои графические примитивы в графическую сцену с помощью вызова QQuickItem::updatePaintNode().
Графическая сцена представляет собой графическое представление сцены элементов, независимой структуры, содержащей достаточную информацию для отрисовки всех элементов. После её настройки её можно обрабатывать и отрисовывать независимо от состояния элементов. На многих платформах графическая сцена будет даже отрисовываться в отдельном потоке отрисовки, в то время как поток GUI готовит состояние следующего кадра.
Примечание: Большая часть информации на этой странице относится к встроенному, по умолчанию поведению графической сцены Qt Quick. При использовании альтернативной адаптации графической сцены, такой как software адаптация, не все концепции могут применяться. Более подробную информацию об различных адаптациях графической сцены см. в разделе Адаптации графической сцены.
Структура графической сцены Qt Quick
Графическая сцена состоит из ряда предопределенных типов узлов, каждый из которых выполняет определенную задачу. Хотя мы называем это графической сценой, более точное определение – древовидная структура узлов. Дерево строится из типов QQuickItem в QML-сцене, и внутренне сцена обрабатывается рендерером, который отрисовывает сцену. Узлы сами не содержат активного кода рисования, ни виртуальной paint() функции.
Несмотря на то, что дерево узлов в основном строится внутренне существующими типами Qt Quick QML, пользователи также могут добавлять полные поддеревья со своим содержимым, включая поддеревья, представляющие 3D-модели.
Узлы
Самый важный узел для пользователей – QSGGeometryNode. Он используется для определения пользовательской графики путем определения её геометрии и материала. Геометрия определяется с помощью QSGGeometry и описывает форму или сетку графического примитива. Это может быть линия, прямоугольник, многоугольник, множество разрозненных прямоугольников или сложная 3D-сетка. Материал определяет, как заполняются пиксели в этой форме.
Узел может иметь любое количество дочерних узлов, и узлы геометрии будут отрисованы так, чтобы они появлялись в порядке дочерних узлов, а родители находились позади своих дочерних узлов.
Примечание: Это ничего не говорит о фактическом порядке отрисовки в рендерере. Гарантируется только визуальный вывод.
Доступные узлы:
Реализует функцию обрезки в графической сцене |
|
Используется для всего отрисовываемого содержимого в графической сцене |
|
Базовый класс для всех узлов в графической сцене |
|
Используется для изменения непрозрачности узлов |
|
Реализует преобразования в графической сцене |
Пользовательские узлы добавляются в графическую сцену путем наследования от QQuickItem::updatePaintNode() и установки флага QQuickItem::ItemHasContents.
Предупреждение: Критически важно, чтобы операции с родной графикой (OpenGL, Vulkan, Metal и т. д.) и взаимодействие с графической сценой происходили исключительно в потоке отрисовки, в первую очередь во время вызова updatePaintNode(). Правило — использовать только классы с префиксом «QSG» внутри функции QQuickItem::updatePaintNode().
Для получения более подробной информации см. Графическая сцена — Пользовательская геометрия.
Предварительная обработка
Узлы имеют виртуальную функцию QSGNode::preprocess(), которая вызывается перед отрисовкой графической сцены. Подклассы узлов могут установить флаг QSGNode::UsePreprocess и переопределить функцию QSGNode::preprocess(), чтобы выполнить окончательную подготовку своего узла. Например, разделение кривой Безье на правильный уровень детализации для текущего коэффициента масштабирования или обновление части текстуры.
Владение узлами
Владение узлами осуществляется либо явно создателем, либо графической сценой путем установки флага QSGNode::OwnedByParent. Назначение владения графической сцене часто предпочтительнее, так как это упрощает очистку, когда графическая сцена существует вне потока GUI.
Материалы
Материал описывает, как заполняется внутренняя часть геометрии в QSGGeometryNode. Он инкапсулирует шейдеры графики для стадий вершин и фрагментов графического конвейера и предоставляет достаточную гибкость в достижении целей, хотя большинство элементов Qt Quick сами используют только очень базовые материалы, такие как заполнение однородным цветом и текстурой.
Для пользователей, которые хотят просто применить пользовательское затемнение к типу QML-элемента, это можно сделать непосредственно в QML с помощью типа ShaderEffect.
Ниже приведен полный список классов материалов:
Удобный способ отрисовки геометрии сплошным цветом в графической сцене |
|
Инкапсулирует состояние отрисовки для программы шейдеров |
|
Представляет программу шейдеров, независимую от графического API |
|
Представляет программу шейдеров OpenGL в рендерере |
|
Используется в качестве уникального маркера типа в сочетании с QSGMaterial |
|
Удобный способ отрисовки текстурированной геометрии в графической сцене |
|
Удобный способ отрисовки текстурированной геометрии в графической сцене |
|
Удобный способ отрисовки геометрии с цветом по вершинам в графической сцене |
Для получения более подробной информации см. Графическая сцена — Простой материал
Узлы по умолчанию
API графической сцены является низкоуровневым и ориентирован на производительность, а не на удобство использования. Написание пользовательских геометрий и материалов с нуля, даже самых базовых, требует значительного кода. По этой причине API включает несколько вспомогательных классов, чтобы сделать наиболее распространенные пользовательские узлы легкодоступными.
- QSGSimpleRectNode — подкласс QSGGeometryNode, который определяет прямоугольную геометрию с материалом сплошного цвета.
- QSGSimpleTextureNode — подкласс QSGGeometryNode, который определяет прямоугольную геометрию с текстурным материалом.
Графическая сцена и отрисовка
Отрисовка графической сцены происходит внутренне в классе QQuickWindow, и нет публичного API для доступа к ней. Однако есть несколько мест в конвейере отрисовки, где пользователь может прикрепить код приложения. Это можно использовать для добавления пользовательского содержимого графической сцены или для вставки произвольных команд отрисовки, напрямую вызывая графический API (OpenGL, Vulkan, Metal и т. д.), который используется графической сценой. Точки интеграции определяются циклом отрисовки.
Для подробного описания того, как работает рендерер графической сцены, см. По умолчанию рендерер графической сцены Qt Quick.
Доступны три варианта цикла отрисовки: basic, windows, и threaded. Из них basic и windows однопоточные, в то время как threaded выполняет отрисовку графической сцены в выделенном потоке. Qt пытается выбрать подходящий цикл в зависимости от платформы и, возможно, графических драйверов, которые используются. Если это неудовлетворительно или для целей тестирования, переменная среды QSG_RENDER_LOOP может быть использована для принудительного использования заданного цикла. Чтобы проверить, какой цикл отрисовки используется, включите qt.scenegraph.general категорию ведения журнала.
Примечание: Циклы отрисовки threaded и windows полагаются на реализацию графического API для регулирования частоты кадров, например, путем запроса интервала переключения кадров 1 в случае OpenGL. Некоторые графические драйверы позволяют пользователям переопределить этот параметр и отключить его, игнорируя запрос Qt. Без блокировки в операции обмена буферами (или в другом месте) цикл отрисовки будет запускать анимации слишком быстро и перегружать процессор на 100%. Если известно, что система не может обеспечить регулирование частоты кадров на основе вертикальной синхронизации, используйте цикл отрисовки basic вместо этого, установив QSG_RENDER_LOOP=basic в переменной среды.
Цикл отрисовки в потоке ("потоковый")
На многих конфигурациях отрисовка графа сцены происходит в отдельном потоке рендеринга. Это делается для повышения параллельности многоядерных процессоров и лучшего использования времени простоя, например, ожидания вызова буфера обмена. Это обеспечивает значительное улучшение производительности, но накладывает определенные ограничения на то, где и когда можно взаимодействовать с графом сцены.
Ниже приведён простой обзор того, как кадр отрисовывается с помощью потокового цикла рендеринга и OpenGL. Шаги остаются теми же и для других графических API, за исключением специфики контекста OpenGL.
- В графическом элементе QML происходит изменение, вызывая
QQuickItem::update(). Это может быть результатом, например, анимации или пользовательского ввода. В поток рендеринга отправляется событие для инициирования нового кадра. - Поток рендеринга готовится к отрисовке нового кадра и блокирует поток пользовательского интерфейса.
- Пока поток рендеринга готовит новый кадр, поток пользовательского интерфейса вызывает QQuickItem::updatePolish() для окончательной обработки элементов перед их отрисовкой.
- Поток пользовательского интерфейса заблокирован.
- Издаётся сигнал QQuickWindow::beforeSynchronizing(). Приложения могут устанавливать прямые соединения (используя Qt::DirectConnection) с этим сигналом для выполнения необходимой подготовки перед вызовами QQuickItem::updatePaintNode().
- Синхронизация состояния QML в граф сцены. Это делается путём вызова функции QQuickItem::updatePaintNode() для всех элементов, которые изменились с момента предыдущего кадра. Это единственное время, когда элементы QML и узлы в графе сцены взаимодействуют.
- Блокировка потока пользовательского интерфейса снимается.
- Граф сцены отрисовывается:
- Издаётся сигнал QQuickWindow::beforeRendering(). Приложения могут устанавливать прямые соединения (используя Qt::DirectConnection) с этим сигналом для использования пользовательских вызовов графического API, которые будут отображаться визуально под сценой QML.
- Элементы, у которых задан флаг QSGNode::UsePreprocess, будут вызывать свою функцию QSGNode::preprocess().
- Рендерер обрабатывает узлы.
- Рендерер генерирует состояния и записывает вызовы отрисовки для используемого графического API.
- Издаётся сигнал QQuickWindow::afterRendering(). Приложения могут устанавливать прямые соединения (используя Qt::DirectConnection) с этим сигналом для выдачи пользовательских вызовов графического API, которые будут отображаться визуально поверх сцены QML.
- Кадр готов. Буферы обмениваются (OpenGL) или записывается команда смены (Vulkan, Metal), а буферы команд отправляются в очередь графики (Vulkan, Metal). Издаётся QQuickWindow::frameSwapped().
- Пока поток рендеринга выполняет отрисовку, поток пользовательского интерфейса свободен для продвижения анимаций, обработки событий и т. д.
Потоковый рендерер используется по умолчанию на Windows с opengl32.dll, Linux (за исключением Mesa llvmpipe), macOS с Metal, мобильных платформах и Embedded Linux с EGLFS, а также с Vulkan независимо от платформы, но это может измениться. Всегда можно принудительно использовать потоковый рендерер, установив QSG_RENDER_LOOP=threaded в среде.
Циклы рендеринга без потоков ("basic" и "windows")
Цикл рендеринга без потоков используется по умолчанию на Windows с ANGLE или нестандартной реализацией opengl32, macOS с OpenGL и Linux с некоторыми драйверами. В последнем случае это в основном предосторожность, так как не все комбинации драйверов OpenGL и систем окон были протестированы. В то же время такие реализации, как ANGLE или Mesa llvmpipe, вообще не могут нормально работать с потоковым рендерингом, поэтому для них крайне важно не использовать потоковый рендеринг.
На macOS и OpenGL потоковый цикл рендеринга не поддерживается при построении с XCode 10 (10.14 SDK) или более поздних версий, так как это подключает поддержку слоистых представлений на macOS 10.14. Вы можете скомпилировать с Xcode 9 (10.13 SDK), чтобы отключить поддержку слоистых представлений, в этом случае потоковый цикл рендеринга доступен и используется по умолчанию. С Metal таких ограничений нет.
По умолчанию windows используется для рендеринга без потоков на Windows с ANGLE, в то время как basic используется для всех других платформ, когда нужен рендеринг без потоков.
Даже при использовании цикла рендеринга без потоков вы должны писать код так, как будто вы используете потоковый рендерер, так как несоблюдение этого правила сделает код непереносимым.
Ниже приведена упрощенная иллюстрация последовательности отрисовки кадра в рендерере без потоков.
Настройка управления рендерингом с QQuickRenderControl
При использовании QQuickRenderControl ответственность за управление циклом рендеринга передаётся приложению. В этом случае встроенный цикл рендеринга не используется. Вместо этого приложение должно вызывать шаги полировки, синхронизации и рендеринга в соответствующее время. Можно реализовать как потоковый, так и не потоковый сценарий, аналогичный показанным выше.
Совмещение графа сцены и родного графического API
Граф сцены предлагает два метода для интеграции предоставленных приложением графических команд: непосредственное выполнение команд OpenGL, Vulkan, Metal и т. д., а также создание текстурированного узла в графе сцены.
Подключившись к сигналам QQuickWindow::beforeRendering и QQuickWindow::afterRendering, приложения могут напрямую выполнять вызовы OpenGL в том же контексте, что и отрисовка графа сцены. С такими API, как Vulkan или Metal, приложения могут запросить родные объекты, такие как буфер команд графа сцены, через QSGRendererInterface и записывать в него команды по своему усмотрению. Как показывают имена сигналов, пользователь может отрисовывать содержимое как под, так и над сценой Qt Quick. Преимущество интеграции таким образом заключается в том, что не требуется дополнительный фреймбуфер и память для выполнения рендеринга, и исключается, возможно, дорогостоящая операция текстурирования. Недостатком является то, что Qt Quick решает, когда вызывать сигналы, и это единственное время, когда OpenGL-приложение разрешается рисовать.
Пример Графа сцены - OpenGL под QML предоставляет пример использования этих сигналов с OpenGL.
Пример Графа сцены - Direct3D 11 под QML предоставляет пример использования этих сигналов с Direct3D.
Пример Графа сцены - Metal под QML предоставляет пример использования этих сигналов с Metal.
Пример Графа сцены - Vulkan под QML предоставляет пример использования этих сигналов с Vulkan.
Другой вариант, доступный только для OpenGL, заключается в создании QQuickFramebufferObject, отрисовке в нём и отображении его в графе сцены как текстуры. Пример Графа сцены - Отрисовка FBO демонстрирует, как это можно сделать. Также возможно комбинировать несколько контекстов рендеринга и потоков для создания содержимого, отображаемого в графе сцены. Примеры Граф сцены - Отрисовка FBO в потоке показывают, как это можно сделать.
Графические API, отличные от OpenGL, также могут использовать этот подход, хотя QQuickFramebufferObject их в настоящее время не поддерживает. Создание и отрисовка непосредственно в текстуре с использованием базового API, за которым следует обертывание и использование этого ресурса в сцене Qt Quick в пользовательском QQuickItem, продемонстрировано в примере Граф сцены - Импорт текстуры Metal. Этот пример использует Metal, но концепции применимы и ко всем другим графическим API.
Предупреждение: При смешивании содержимого OpenGL с рендерингом графа сцены важно, чтобы приложение не оставляло контекст OpenGL в состоянии, когда связаны буферы, включены атрибуты, установлены специальные значения в буфере z или буфере трафарета или подобное. Это может привести к непредсказуемому поведению.
Предупреждение: Пользовательский код рендеринга должен учитывать многопоточность в том смысле, что он не должен предполагать выполнения в потоке пользовательского интерфейса (главном потоке) приложения.
Пользовательские элементы с использованием QPainter
QQuickItem предоставляет подкласс QQuickPaintedItem, который позволяет пользователям отрисовывать содержимое с помощью QPainter.
Предупреждение: Использование QQuickPaintedItem использует косвенную 2D-поверхность для рендеринга её содержимого, либо с помощью программной растризации, либо с помощью объекта OpenGL framebuffer (FBO), поэтому рендеринг является двухэтапным процессом. Сначала растрируется поверхность, затем поверхность выводится. Использование API графа сцены напрямую всегда значительно быстрее.
Поддержка ведения логов
Граф сцены поддерживает ряд категорий ведения логов. Они могут быть полезны для отслеживания проблем с производительностью и ошибок, а также для Qt-разработчиков.
-
qt.scenegraph.time.texture- записывает время, затраченное на загрузку текстур -
qt.scenegraph.time.compilation- записывает время, затраченное на компиляцию шейдеров -
qt.scenegraph.time.renderer- записывает время, затраченное на различные шаги рендеринга -
qt.scenegraph.time.renderloop- записывает время, затраченное на различные шаги цикла рендеринга -
qt.scenegraph.time.glyph- записывает время, затраченное на подготовку глифов с полем расстояния -
qt.scenegraph.general- записывает общую информацию о различных частях графа сцены и стеке графики -
qt.scenegraph.renderloop- создаёт подробный лог различных этапов отрисовки. Этот режим логгирования в первую очередь полезен для разработчиков Qt.
Также доступна устаревшая переменная среды QSG_INFO. Установка её в ненулевое значение включает категорию qt.scenegraph.general.
Примечание: В случае проблем с графикой или при сомнениях в том, какой цикл рендеринга или графический API используется, всегда запускайте приложение с включёнными по крайней мере qt.scenegraph.general и qt.rhi.*, или QSG_INFO=1. Это обеспечит вывод необходимой информации об инициализации в отладку.
Бэкенд графа сцены
Помимо публичного API, граф сцены имеет адаптивный слой, который открывает возможности для аппаратных адаптаций. Это недокументированный, внутренний и частный плагин API, который позволяет командам по адаптации оборудования максимально использовать возможности своего оборудования. В него входит:
- Пользовательские текстуры; конкретно реализация QQuickWindow::createTextureFromImage и внутреннее представление текстуры, используемой типами Image и BorderImage.
- Пользовательский рендерер; адаптивный слой позволяет плагину определить, как проходить и рендерить график сцены, что позволяет оптимизировать алгоритм рендеринга для конкретного оборудования или использовать расширения, которые повышают производительность.
- Пользовательская реализация графика сцены многих стандартных типов QML, включая рендеринг текста и шрифтов.
- Пользовательский драйвер анимации; позволяет системе анимации подключаться к вертикальной перезаписи дисплея на низком уровне для плавного рендеринга.
- Пользовательский цикл рендеринга; обеспечивает больший контроль над тем, как QML обрабатывает несколько окон.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.15/qtquick-visualcanvas-scenegraph.html