Аппаратное ускорение

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

Аппаратное ускорение включено по умолчанию. Если в вашем приложении используются только стандартные composable-функции, включение этой функции на глобальном уровне не должно привести к нежелательным эффектам при отрисовке. Однако, поскольку аппаратное ускорение поддерживается не для всех операций рисования 2D, его включение может повлиять на некоторые из ваших пользовательских вызовов рисования. Проблемы обычно проявляются в виде невидимых элементов, исключений или неправильно отображаемых пикселей. Чтобы исправить это, Android дает вам возможность включить или отключить аппаратное ускорение на нескольких уровнях. Подробнее об управлении аппаратным ускорением…

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

Также ознакомьтесь со статьей OpenGL with the Framework APIs (OpenGL с API фреймворка).

Управление аппаратным ускорением

Управлять аппаратным ускорением можно на следующих уровнях:

  • Приложение
  • Действия
  • Окно
  • Composable

Уровень приложения

Чтобы включить аппаратное ускорение для всего приложения, добавьте в файл манифеста Android следующий атрибут в тег <application>:

<application android:hardwareAccelerated="true" ...>

Уровень активности

Если приложение работает некорректно, когда аппаратное ускорение включено на уровне приложения, вы можете управлять им на уровне отдельных действий. Чтобы включить или отключить аппаратное ускорение на уровне действия, используйте атрибут android:hardwareAccelerated для элемента <activity>. В следующем примере аппаратное ускорение включено для всего приложения, но отключено для одного действия:

<application android:hardwareAccelerated="true">
    <activity ... />
    <activity android:hardwareAccelerated="false" />
</application>

Уровень окна

Если вам нужен более точный контроль, вы можете включить аппаратное ускорение для определенного окна с помощью следующего кода:

window.setFlags(
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)

Уровень composable-функции

В Compose нет переключателя для отключения аппаратного ускорения для отдельных composable-функций.

Чтобы отрисовать composable-функцию на отдельном слое, используйте Modifier.graphicsLayer. Это позволяет изменять свойства преобразования (такие как alpha, scaleX, scaleY, translationX, translationY, rotationX, rotationY, rotationZ и transformOrigin) без повторного выполнения кода отрисовки composable-функции. Для оптимальной производительности всегда используйте лямбда-форму модификатора, чтобы задать эти свойства.

Чтобы принудительно использовать буфер вне экрана для сложных операций рисования, например настраиваемого смешивания в слое, используйте CompositingStrategy.Offscreen. Подробнее о модификаторах графики…

Если у вас есть пользовательская операция рисования, которая строго требует программного рендеринга, вы можете разместить устаревшее представление с помощью AndroidView и вызвать setLayerType(View.LAYER_TYPE_SOFTWARE, null) в этом представлении.

Поддержка операций рисования

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

В таблице ниже описан уровень поддержки различных операций на разных уровнях API.

Первый поддерживаемый уровень API
Холст
drawBitmapMesh() (массив цветов) 18
drawPicture() 23
drawPosText() 16
drawTextOnPath() 16
drawVertices() 29
setDrawFilter() 16
clipPath() 18
clipRegion() 18
clipRect(Region.Op.XOR) 18
clipRect(Region.Op.Difference) 18
clipRect(Region.Op.ReverseDifference) 18
clipRect() с поворотом/перспективой 18
Краска
setAntiAlias() (для текста) 18
setAntiAlias() (для линий) 16
setFilterBitmap() 17
setLinearText() ✗
setMaskFilter() ✗
setPathEffect() (для линий); 28
setShadowLayer() (кроме текста) 28
setStrokeCap() (для линий) 18
setStrokeCap() (для точек) 19
setSubpixelText() 28
Xfermode
PorterDuff.Mode.DARKEN (буфер кадров) 28
PorterDuff.Mode.LIGHTEN (буфер кадров) 28
PorterDuff.Mode.OVERLAY (буфер кадра) 28
Шейдер
ComposeShader внутри ComposeShader 28
Шейдеры одного типа в ComposeShader 28
Локальная матрица в ComposeShader 18

Масштабирование холста

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

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

Операция рисования, которую нужно масштабировать. Первый поддерживаемый уровень API
drawText() 18
drawPosText() 28
drawTextOnPath() 28
Простые фигуры 17
Сложные фигуры 28
drawPath() 28
Слой тени 28

Если нужная вам операция рисования не ускоряется аппаратно, выполните рендеринг затронутого рисунка в программный объект Bitmap (или ImageBitmap) за пределами экрана и нарисуйте результат. Остальные элементы интерфейса будут обрабатываться с аппаратным ускорением.

Полезные советы

Переход на аппаратное ускорение 2D-графики может мгновенно повысить производительность, но вам все равно следует спроектировать свое приложение так, чтобы оно эффективно использовало графический процессор, следуя этим рекомендациям:

Минимизируйте сложность макета и рекомпозицию
Не делайте дерево макета слишком глубоким и ограничьте количество рекомпозиций. Отложите чтение состояния до самой узкой области, чтобы изменение перерисовывало наименьшую возможную область. Например, считывайте состояние анимации внутри Modifier.graphicsLayer { }, а не в теле composable-функции. Подробнее о производительности Jetpack Compose…
Избегайте перерисовки
Не рисуйте слишком много слоев друг на друге. Удалите все элементы интерфейса, которые полностью скрыты другими непрозрачными элементами. Если вам нужно нарисовать несколько слоев, наложенных друг на друга, попробуйте объединить их в один слой. Для современного оборудования действует следующее правило: за один кадр не должно отрисовываться более чем в 2,5 раза больше пикселей, чем есть на экране (прозрачные пиксели в растровом изображении тоже считаются).
Не создавайте объекты рендеринга в методах рисования
Распространенная ошибка – создавать новые объекты Paint или Path при каждом вызове метода отрисовки. Это заставляет сборщик мусора работать чаще, а также обходит кэши и оптимизации в аппаратном конвейере. Чтобы избежать этого, используйте объекты повторно и изменяйте их:
  • Используйте стандартные методы. Стандартные методы DrawScope (например, drawRect и drawCircle) уже повторно используют объекты Paint, поэтому разработчику не нужно выделять память.
  • Изменяйте, а не переназначайте. При написании специальной логики используйте path.rewind, чтобы очистить существующий Path, а не создавать новый Path.
  • Эффективно удерживайте состояние. Внутри composable-функции выделяйте объекты один раз с помощью remember { Path() }. Если вы создаете многократно используемые расширения пользовательских модификаторов, реализуйте пользовательский Modifier.Node с помощью DrawModifierNode, чтобы выделять и повторно использовать объекты, не вызывая новых выделений кучи.
Не изменяйте фигуры слишком часто
Сложные фигуры, контуры и круги отрисовываются с помощью масок текстур. Каждый раз, когда вы создаете или изменяете контур, аппаратный конвейер создает новую маску, что может быть ресурсоемким.
Не изменяйте битовые карты слишком часто
Каждый раз, когда вы меняете содержимое растрового изображения, оно снова загружается как текстура графического процессора при следующем его рисовании.
Используйте альфа-канал с осторожностью
Когда вы делаете composable-функцию полупрозрачной с помощью Modifier.alpha или Animation API, она обычно отрисовывается в закадровом буфере, что удваивает требуемую скорость заполнения. Чтобы избежать дополнительных затрат на закадровый буфер для неперекрывающегося контента, задайте CompositingStrategy.ModulateAlpha. Для отдельных вызовов отрисовки применяйте альфа-канал непосредственно к команде рисования (например, с помощью color = Color.Red.copy(alpha = 0.5f)), не создавая слой.

Дополнительные ресурсы

Просмотр контента