Spec-Zone.ru › Qt 5.6

Рендерер графа сцен Qt Quick

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

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

Примечание: Даже в том случае, если кадр уникален в каждом случае, а всё загружается с нуля, стандартный рендерер будет работать хорошо.

Элементы Qt Quick в сцене QML заполняют дерево экземпляров QSGNode. После создания это дерево представляет полное описание того, как должен быть отрисован определённый кадр. Оно не содержит никаких ссылок на элементы Qt Quick и на большинстве платформ будет обрабатываться и отображаться в отдельном потоке. Рендерер — это самодостаточная часть графа сцен, которая проходит по дереву QSGNode и использует геометрию, определённую в QSGGeometryNode, и состояние шейдера, определённое в QSGMaterial, для планирования изменений состояния OpenGL и вызовов отрисовки.

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

Стандартный рендерер фокусируется на двух основных стратегиях оптимизации отрисовки. Объединение вызовов отрисовки и сохранение геометрии на GPU.

Объединение

В то время как традиционный 2D API, такой как QPainter, Cairo или Context2D, написан для обработки тысяч отдельных вызовов отрисовки в каждом кадре, OpenGL — это чистый API аппаратного обеспечения и работает лучше всего, когда количество вызовов отрисовки очень низкое, а изменения состояния минимальны. Рассмотрим следующий пример использования:

Самый простой способ отобразить этот список — по ячейке. Сначала рисуется фон. Это прямоугольник определенного цвета. В терминах OpenGL это означает выбор программы шейдера для заливки твёрдым цветом, установку цвета заливки, установку матрицы преобразования, содержащей смещения x и y, а затем использование, например, glDrawArrays для отрисовки двух треугольников, составляющих прямоугольник. Далее рисуется значок. В терминах OpenGL это означает выбор программы шейдера для отрисовки текстур, выбор активной текстуры для использования, установку матрицы преобразования, включение альфа-смешивания и затем, например, glDrawArrays для отрисовки двух треугольников, составляющих ограничивающую прямоугольник значка. Текст и разделительная линия между ячейками следуют аналогичной схеме. И этот процесс повторяется для каждой ячейки в списке, поэтому для более длинного списка накладные расходы, накладываемые изменениями состояния OpenGL и вызовами отрисовки, полностью перевешивают преимущества использования ускоренного аппаратным обеспечением API.

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

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

Непрозрачные примитивы

Рендерер разделяет непрозрачные примитивы и примитивы, требующие альфа-смешивания. Используя буфер глубины OpenGL и присваивая каждому примитиву уникальную позицию z, рендерер может свободно переупорядочивать непрозрачные примитивы, не учитывая их расположение на экране и то, какие другие элементы они перекрывают. Проанализировав состояние материала каждого примитива, рендерер создаст пакеты непрозрачных элементов. Из набора элементов ядра Qt Quick это включает в себя элементы Rectangle с непрозрачными цветами и полностью непрозрачными изображениями, такими как JPEG или BMP.

Другое преимущество использования непрозрачных примитивов заключается в том, что для непрозрачных примитивов не требуется включение GL_BLEND, что может быть довольно дорого, особенно на мобильных и встроенных графических процессорах.

Непрозрачные примитивы отрисовываются спереди назад с включенными glDepthMask и GL_DEPTH_TEST. На графических процессорах, которые внутренне выполняют проверку по раннему z, это означает, что фрагментный шейдер не нужно запускать для пикселей или блоков пикселей, которые скрыты. Обратите внимание, что рендерер всё равно должен учитывать эти узлы, а вершинный шейдер всё равно выполняется для каждой вершины в этих примитивах, поэтому, если приложение знает, что что-то полностью скрыто, лучше всего явно скрыть его с помощью Item::visible или Item::opacity.

Примечание: Item::z используется для управления порядком стекирования элемента по отношению к его соседям. Он не имеет прямой связи с рендерером и буфером глубины OpenGL.

Примитивы с альфа-смешиванием

После отрисовки непрозрачных примитивов рендерер отключит glDepthMask, включит GL_BLEND и отрисует все примитивы с альфа-смешиванием сзади наперед.

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

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

В случае справа фон «Элемента 4» перекрывает текст «Элемента 3», поэтому в этом случае каждый из фонов и текстов должен отрисовываться отдельными вызовами.

С точки зрения z примитивы с альфа-каналом вложены между непрозрачными узлами и могут инициировать раннюю проверку по z, когда она доступна, но опять же, установка Item::visible в ложь всегда быстрее.

Смешивание с 3D-примитивами

Граф сцен может поддерживать псевдо-3D и полноценные 3D-примитивы. Например, можно реализовать эффект «изгиба страницы» с помощью ShaderEffect или реализовать тор с тиснением с помощью QSGGeometry и пользовательского материала. При этом необходимо учитывать, что стандартный рендерер уже использует буфер глубины.

Рендерер изменяет вершинный шейдер, возвращаемый из QSGMaterialShader::vertexShader(), и сжимает значения z вершин после применения матриц модели-представления и проекции, а затем добавляет небольшое смещение по z, чтобы расположить их в правильной позиции z.

Сжатие предполагает, что значения z находятся в диапазоне от 0 до 1.

Атлас текстур

Активная текстура — это уникальное состояние OpenGL, что означает, что несколько примитивов, использующих разные текстуры OpenGL, не могут быть объединены. Поэтому граф сцен Qt Quick позволяет создавать несколько экземпляров QSGTexture как меньшие подрегионы большей текстуры; атлас текстур.

Самое большое преимущество атласов текстур заключается в том, что несколько экземпляров QSGTexture теперь ссылаются на один и тот же экземпляр текстуры OpenGL. Это позволяет объединять вызовы отрисовки с текстурами, такие как элементы Image, элементы BorderImage, элементы ShaderEffect, а также типы C++, такие как QSGSimpleTextureNode и настраиваемые QSGGeometryNodes, использующие текстуры.

Примечание: Большие текстуры не попадают в атлас текстур.

Атласные текстуры создаются путём передачи QQuickWindow::TextureCanUseAtlas в QQuickWindow::createTextureFromImage().

Примечание: Атласные текстуры не имеют координат текстуры в диапазоне от 0 до 1. Используйте QSGTexture::normalizedTextureSubRect() для получения координат атласных текстур.

Граф сцен использует эвристики, чтобы определить, каким должен быть размер атласа и какой порог размера для попадания в атлас. Если необходимы другие значения, можно переопределить их с помощью переменных среды QSG_ATLAS_WIDTH=[width], QSG_ATLAS_HEIGHT=[height] и QSG_ATLAS_SIZE_LIMIT=[size]. Изменение этих значений в основном будет интересно для поставщиков платформ.

Корни объединения

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

Узлы преобразования

Каждый элемент Qt Quick вставляет в дерево графа сцен QSGTransformNode для управления его x, y, масштабом или поворотом. Дочерние элементы будут размещены под этим узлом преобразования. Стандартный рендерер отслеживает состояние узлов преобразования между кадрами и анализирует поддеревья, чтобы определить, является ли узел преобразования хорошим кандидатом на то, чтобы стать корнем набора пакетов. Узел преобразования, который изменяется между кадрами и имеет довольно сложное поддерево, может стать корнем пакета.

QSGGeometryNodes в поддереве корня пакета предварительно преобразуются относительно корня на ЦП. Затем они загружаются и сохраняются на графическом процессоре. При изменении преобразования рендереру нужно обновить только матрицу корня, а не каждый отдельный элемент, что делает прокрутку списков и сетки очень быстрой. Для последовательных кадров, пока узлы не добавляются и не удаляются, рендеринг списка фактически бесплатный. При вводе нового содержимого в поддерево пакет, который его получает, перестраивается, но это всё ещё относительно быстро. Обычно есть несколько неизменных кадров на каждый кадр с добавленными или удаленными узлами при прокрутке сетки или списка.

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

Порог узлов и вершин для того, чтобы узел преобразования стал корнем пакета, можно переопределить, используя переменные среды QSG_RENDERER_BATCH_NODE_THRESHOLD=[count] и QSG_RENDERER_BATCH_VERTEX_THRESHOLD=[count]. Переопределение этих флагов будет в основном полезно для поставщиков платформ.

Примечание: Под корнем пакета создаётся один пакет для каждого уникального набора состояния материала и типа геометрии.

Обрезка

При установке Item::clip в значение true, создается QSGClipNode с прямоугольником в своей геометрии. По умолчанию рендерер применяет это обрезку с помощью обрезки в OpenGL. Если элемент повернут на угол, не равный 90 градусам, используется буфер трафарета OpenGL. Элемент Qt Quick Item поддерживает только установку прямоугольника в качестве обрезки через QML, но API графа сцены и по умолчанию рендерер могут использовать любую форму для обрезки.

При применении обрезки к поддереву, это поддерево должно быть отрисовано с уникальным состоянием OpenGL. Это означает, что когда Item::clip имеет значение true, кэширование этого элемента ограничено его дочерними элементами. Когда есть много дочерних элементов, таких как ListView или GridView, или сложные дочерние элементы, такие как TextArea, это нормально. Тем не менее, следует использовать обрезку на меньших элементах с осторожностью, поскольку она препятствует кэшированию. Это включает метку кнопки, текстовое поле или делегат списка и ячейки таблицы.

Буферы вершин

Каждый пакет использует объект буфера вершин (VBO) для хранения данных на графическом процессоре. Этот буфер вершин сохраняется между кадрами и обновляется, когда изменяется часть графа сцены, которую он представляет.

По умолчанию рендерер загружает данные в VBO, используя GL_STATIC_DRAW. Можно выбрать другую стратегию загрузки, установив переменную среды QSG_RENDERER_BUFFER_STRATEGY=[strategy]. Допустимые значения — stream и dynamic. Изменение этого значения полезно в основном для поставщиков платформ.

Сглаживание

Граф сцены поддерживает два типа сглаживания. По умолчанию примитивы, такие как прямоугольники и изображения, будут сглаживаться путем добавления дополнительных вершин по краю примитивов, так что края будут плавно переходить в прозрачность. Мы называем этот метод сглаживанием вершин. Если пользователь запрашивает контекст OpenGL с многократной выборкой, установив QSurfaceFormat с выборками, превышающими 0 с помощью QQuickWindow::setFormat(), граф сцены будет отдавать предпочтение сглаживанию с многократной выборкой (MSAA). Эти два метода повлияют на то, как происходит рендеринг внутри, и имеют разные ограничения.

Также можно переопределить метод сглаживания, установив переменную среды QSG_ANTIALIASING_METHOD в значение vertex или msaa.

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

Сглаживание вершин

Сглаживание вершин можно включить и выключить на основе каждого элемента, используя свойство Item::antialiasing. Он будет работать независимо от того, какую поддержку предоставляет основное оборудование, и обеспечивает более качественное сглаживание, как для обычно отрисовываемых примитивов, так и для примитивов, захваченных в буферы кадров, например, с помощью типа ShaderEffectSource.

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

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

Сглаживание с многократной выборкой

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

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

При использовании сглаживания с многократной выборкой контент, отрисовываемый в буферы кадров, требует дополнительных расширений для поддержки многократной выборки буферов кадров. Обычно это GL_EXT_framebuffer_multisample и GL_EXT_framebuffer_blit. Большинство настольных чипов имеют эти расширения, но они менее распространены в встраиваемых чипах. Когда аппаратное обеспечение не поддерживает многократную выборку буферов кадров, контент, отрисовываемый в буферы кадров, не будет сглажен, включая содержимое ShaderEffectSource.

Производительность

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

  • Высокая производительность достигается за счет эффективного кэширования, при котором геометрия загружается как можно реже. Установив переменную среды QSG_RENDERER_DEBUG=render, рендерер будет выводить статистику о том, насколько хорошо работает кэширование, сколько пакетов, какие пакеты сохраняются, а какие непрозрачные и нет. При стремлении к оптимальной производительности загрузки должны происходить только в случае реальной необходимости, пакеты должны быть менее 10, и по крайней мере 3-4 из них должны быть непрозрачными.
  • По умолчанию рендерер не выполняет обрезку области просмотра на стороне процессора и не выполняет обнаружение скрытия. Если что-то не должно быть видно, это не должно отображаться. Используйте Item::visible: false для элементов, которые не должны отображаться. Основная причина отсутствия такой логики заключается в том, что это добавляет дополнительные затраты, которые также повредят приложениям, которые позаботились о правильном поведении.
  • Убедитесь, что используется атлас текстур. Элементы Image и BorderImage будут использовать его, если изображение не слишком большое. Для текстур, созданных на C++, передавайте QQuickWindow::TextureCanUseAtlas при вызове QQuickWindow::createTexture(). Установив переменную среды QSG_ATLAS_OVERLAY, все текстуры атласа будут окрашены, чтобы их можно было легко идентифицировать в приложении.
  • Используйте непрозрачные примитивы, где это возможно. Непрозрачные примитивы обрабатываются рендерером быстрее и быстрее отрисовываются на графическом процессоре. Например, файлы PNG часто имеют альфа-канал, даже если каждый пиксель полностью непрозрачный. Файлы JPG всегда непрозрачны. При предоставлении изображений QQuickImageProvider или создании изображений с помощью QQuickWindow::createTextureFromImage(), используйте формат QImage::Format_RGB32, если это возможно.
  • Будьте внимательны к тому, что перекрывающиеся составные элементы, как на иллюстрации выше, не могут быть объединены в пакеты.
  • Обрезка нарушает кэширование. Никогда не используйте ее на основе каждого элемента, внутри ячеек таблиц, делегатов элементов или аналогичных элементов. Вместо обрезки текста используйте обрезку. Вместо обрезки изображения создайте QQuickImageProvider, который возвращает обрезанное изображение.
  • Кэширование работает только для 16-битных индексов. Все встроенные элементы используют 16-битные индексы, но пользовательская геометрия может также использовать 32-битные индексы.
  • Некоторые флаги материалов препятствуют кэшированию, наиболее ограничивающим является QSGMaterial::RequiresFullMatrix, который препятствует всему кэшированию.
  • В приложениях с одноцветным фоном необходимо устанавливать его с помощью QQuickWindow::setColor() вместо использования элемента Rectangle на верхнем уровне. QQuickWindow::setColor() будет использоваться в вызове glClear(), что потенциально быстрее.
  • Элементы изображения с mipmapping не размещаются в глобальном атласе и не кэшируются.

Если приложение работает плохо, убедитесь, что рендеринг является узким местом. Используйте профилировщик! Переменная среды QSG_RENDER_TIMING=1 выведет ряд полезных параметров времени, которые могут помочь в определении проблемы.

Визуализация

Для визуализации различных аспектов рендеринга по умолчанию графа сцены можно установить переменную среды QSG_VISUALIZE на одно из значений, описанных в каждом разделе ниже. Мы предоставляем примеры вывода некоторых переменных, используя следующий код QML:

import QtQuick 2.2

Rectangle {
    width: 200
    height: 140

    ListView {
        id: clippedList
        x: 20
        y: 20
        width: 70
        height: 100
        clip: true
        model: ["Item A", "Item B", "Item C", "Item D"]

        delegate: Rectangle {
            color: "lightblue"
            width: parent.width
            height: 25

            Text {
                text: modelData
                anchors.fill: parent
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
            }
        }
    }

    ListView {
        id: clippedDelegateList
        x: clippedList.x + clippedList.width + 20
        y: 20
        width: 70
        height: 100
        clip: true
        model: ["Item A", "Item B", "Item C", "Item D"]

        delegate: Rectangle {
            color: "lightblue"
            width: parent.width
            height: 25
            clip: true

            Text {
                text: modelData
                anchors.fill: parent
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
            }
        }
    }
}

Для ListView слева мы установили его свойство clip в значение true. Для ListView справа мы также установили свойство clip каждого делегата в значение true для иллюстрации влияния обрезки на кэширование.

"Original"

Оригинал

Примечание: Визуализируемые элементы не учитывают обрезку, а порядок рендеринга произвольный.

Визуализация пакетов

Установление QSG_VISUALIZE в значение batches визуализирует пакеты в рендерере. Объединенные пакеты отображаются сплошным цветом, а не объединенные пакеты — с диагональным узором линий. Несколько уникальных цветов означают хорошее кэширование. Не объединенные пакеты — это плохо, если они содержат много отдельных узлов.

"batches"

QSG_VISUALIZE=batches

Визуализация обрезки

Установление QSG_VISUALIZE в значение clip рисует красные области поверх сцены, чтобы указать обрезку. Поскольку элементы Qt Quick по умолчанию не обрезают, обычно не визуализируется никакая обрезка.

QSG_VISUALIZE=clip

Визуализация изменений

Установление QSG_VISUALIZE в значение changes визуализирует изменения в рендерере. Изменения в графе сцены визуализируются мигающим наложением случайного цвета. Изменения в примитиве визуализируются сплошным цветом, а изменения в предке, такие как изменения матрицы или непрозрачности, визуализируются узором.

Визуализация ненужной отрисовки

Установка QSG_VISUALIZE в значение overdraw визуализирует ненужную отрисовку в рендерере. Визуализирует все элементы в 3D, чтобы выделить ненужную отрисовку. Этот режим также может использоваться для обнаружения геометрии за пределами области просмотра до некоторой степени. Непрозрачные элементы отрисовываются с зеленоватым оттенком, а полупрозрачные — с красным. Границы области просмотра отрисовываются синим цветом. Непрозрачный контент проще обрабатывать графу сцены, и его обычно быстрее отрисовывать.

Обратите внимание, что прямоугольник корня в приведенном выше коде избыточен, так как окно также белое, поэтому отрисовка прямоугольника — пустая трата ресурсов в этом случае. Изменение его на элемент может обеспечить небольшой прирост производительности.

"overdraw-1"

"overdraw-2"

QSG_VISUALIZE=overdraw

© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/archives/qt-5.6/qtquick-visualcanvas-scenegraph-renderer.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API