Графическая сцена 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 программу шейдеров |
|
Используется как уникальный маркер типа в сочетании с QSGMaterial |
|
Удобный способ отрисовки текстурированной геометрии в графической сцене |
|
Удобный способ отрисовки текстурированной геометрии в графической сцене |
|
Удобный способ отрисовки геометрии с цветом по вершинам в графической сцене |
Узлы-помощники
API графической сцены низкоуровневый и ориентирован на производительность, а не на удобство. Написание пользовательских геометрий и материалов с нуля, даже самых простых, требует немалого кода. По этой причине API включает несколько вспомогательных классов, чтобы сделать наиболее распространенные пользовательские узлы легкодоступными.
- QSGSimpleRectNode — подкласс QSGGeometryNode, который определяет прямоугольную геометрию со сплошным цветом.
- QSGSimpleTextureNode — подкласс QSGGeometryNode, который определяет прямоугольную геометрию с текстурным материалом.
Графическая сцена и отрисовка
Отрисовка графической сцены происходит внутри класса QQuickWindow, и нет публичного API для доступа к ней. Однако есть несколько мест в конвейере отрисовки, где пользователь может подключить код приложения. Это можно использовать для добавления пользовательского содержимого графической сцены или для вставки произвольных команд отрисовки, напрямую вызывая графический API (OpenGL, Vulkan, Metal и т. д.), используемый графической сценой. Точки интеграции определяются циклом отрисовки.
Для подробного описания работы рендерера графической сцены см. Стандартный рендерер графической сцены Qt Quick.
Доступны две варианты цикла отрисовки: basic, и threaded. basic однопоточный, в то время как threaded выполняет отрисовку графической сцены в отдельном потоке. Qt пытается выбрать подходящий цикл в зависимости от платформы и, возможно, графических драйверов. Когда это не подходит, или для целей тестирования, переменная среды QSG_RENDER_LOOP может быть использована для принудительного использования заданного цикла. Чтобы проверить, какой цикл отрисовки используется, включите qt.scenegraph.general категорию регистрации.
Примечание: Цикл отрисовки threaded полагается на реализацию графического API для регулирования частоты кадров, например, путем запроса интервала переключения кадров 1 в случае OpenGL. Некоторые графические драйверы позволяют пользователям переопределить это значение и отключить его, игнорируя запрос Qt. Без блокировки в операции обмена буферами (или в другом месте), цикл отрисовки будет запускать анимации слишком быстро и перегружать процессор на 100%. Если известно, что система не может обеспечить регулирование частоты кадров на основе синхронизации отображения, используйте цикл отрисовки basic вместо этого, установив QSG_RENDER_LOOP=basic в среде.
Цикл отрисовки в отдельном потоке ('threaded')
Во многих конфигурациях отрисовка графа сцены происходит в отдельной потоке отрисовки. Это делается для повышения параллельности многоядерных процессоров и более эффективного использования временных задержек, таких как ожидание вызова буфера обмена. Это обеспечивает значительные улучшения производительности, но накладывает определённые ограничения на то, где и когда можно взаимодействовать с графом сцены.
Ниже приведён упрощённый обзор того, как кадр отрисовывается с помощью потоковой петли отрисовки и OpenGL. Шаги аналогичны и для других графических API, за исключением специфики контекста OpenGL.
- В графической сцене QML происходит изменение, вызывая
QQuickItem::update(). Это может быть результатом, например, анимации или пользовательского ввода. В поток отрисовки публикуется событие для инициирования нового кадра. - Поток отрисовки готовится к отрисовке нового кадра и блокирует поток GUI.
- Пока поток отрисовки подготавливает новый кадр, поток GUI вызывает QQuickItem::updatePolish(), чтобы выполнить окончательную обработку элементов перед их отрисовкой.
- Поток GUI заблокирован.
- Сигнал QQuickWindow::beforeSynchronizing() генерируется. Приложения могут создать прямые соединения (используя Qt::DirectConnection) с этим сигналом, чтобы выполнить любую подготовку, необходимую перед вызовами QQuickItem::updatePaintNode().
- Синхронизация состояния QML в граф сцены. Это делается путём вызова функции QQuickItem::updatePaintNode() для всех элементов, которые изменились с момента предыдущего кадра. Это единственное время, когда элементы QML и узлы в графе сцены взаимодействуют.
- Блокировка потока GUI снимается.
- Граф сцены отрисовывается:
- Генерируется сигнал QQuickWindow::beforeRendering(). Приложения могут создать прямые соединения (используя Qt::DirectConnection) с этим сигналом, чтобы использовать пользовательские вызовы графического API, которые будут визуально накладываться под сцену QML.
- Элементы, которые задали QSGNode::UsePreprocess, будут иметь вызываемую функцию QSGNode::preprocess().
- Рендерер обрабатывает узлы.
- Рендерер генерирует состояния и записывает вызовы отрисовки для используемого графического API.
- Генерируется сигнал QQuickWindow::afterRendering(). Приложения могут создать прямые соединения (используя Qt::DirectConnection) с этим сигналом, чтобы выполнить пользовательские вызовы графического API, которые будут визуально накладываться поверх сцены QML.
- Кадр готов. Буферы меняются (OpenGL), или записывается команда отображения, и буферы команд отправляются в графическую очередь (Vulkan, Metal). Генерируется QQuickWindow::frameSwapped().
- Пока поток отрисовки отрисовывает, поток GUI может выполнять анимацию, обрабатывать события и т.д.
Потоковый рендерер используется по умолчанию в Windows с Direct3D 11 и OpenGL при использовании opengl32.dll, Linux, за исключением Mesa llvmpipe, macOS с Metal, мобильных платформах и Embedded Linux с EGLFS, а также с Vulkan независимо от платформы. Всё это может измениться в будущих версиях. Всегда можно принудительно использовать потоковый рендерер, установив QSG_RENDER_LOOP=threaded в среде.
Непотоковая петля отрисовки ('basic')
Непотоковая петля отрисовки используется по умолчанию в Windows с OpenGL, когда не используется стандартная opengl32.dll, macOS с OpenGL и Linux с некоторыми драйверами. В последнем случае это в основном мера предосторожности, поскольку не все сочетания драйверов OpenGL и систем окон были протестированы.
На macOS и OpenGL потоковая петля отрисовки не поддерживается при построении с XCode 10 (SDK 10.14) и новее, так как это включение поддержки прослойки в представлениях на macOS 10.14. Вы можете построить с Xcode 9 (SDK 10.13), чтобы отключить поддержку прослойки, в этом случае потоковая петля отрисовки доступна и используется по умолчанию. Такого ограничения нет в Metal.
Даже при использовании непотоковой петли отрисовки вы должны писать свой код так, как будто вы используете потоковый рендерер, так как несоблюдение этого сделает код непереносимым.
Ниже представлено упрощённое изображение последовательности отрисовки кадра в непотоковом рендерере.
Настройка управления отрисовкой с помощью 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 показывает, как это можно сделать.
Графические API, отличные от OpenGL, также могут следовать этому подходу, хотя QQuickFramebufferObject в настоящее время их не поддерживает. Создание и отрисовка текстуры напрямую с помощью базового API, за которым следует обёртка и использование этого ресурса в сцене Qt Quick в пользовательском QQuickItem, показано в примере Граф сцены - Импорт текстуры Metal. Этот пример использует Metal, но концепции применимы ко всем другим графическим API.
Предупреждение: Начиная с Qt 6.0, прямое использование базового графического API должно быть заключено в вызов QQuickWindow::beginExternalCommands() и QQuickWindow::endExternalCommands(). Эта концепция может быть знакома по QPainter::beginNativePainting(), и служит аналогичной цели: она позволяет графическому движку Qt Quick распознать, что любые кэшированные состояния и предположения о состоянии в текущем записываемом проходе отрисовки, если таковой имеется, теперь недействительны, так как код приложения может изменить его, работая напрямую с базовым графическим API.
Предупреждение: При смешении OpenGL-контента с отрисовкой графа сцены важно, чтобы приложение не оставляло контекст OpenGL в состоянии, когда буферы связаны, атрибуты включены, в буферах z и stencil или аналогичные специальные значения. Это может привести к непредсказуемому поведению.
Предупреждение: Пользовательский код отрисовки должен учитывать многопоточность в том смысле, что он не должен предполагать, что он выполняется в потоке GUI (главном) приложения.
Пользовательские элементы с использованием 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-6.2/qtquick-visualcanvas-scenegraph.html