Hardware acceleration

The Android 2D rendering pipeline supports hardware acceleration, meaning that all drawing operations that are performed on the canvas use the GPU. Because of the increased resources required to enable hardware acceleration, your app will consume more RAM.

Hardware acceleration is enabled by default. If your application uses only standard composables, turning it on globally shouldn't cause any adverse drawing effects. However, because hardware acceleration isn't supported for all of the 2D drawing operations, turning it on might affect some of your custom drawing calls. Problems usually manifest themselves as invisible elements, exceptions, or wrongly rendered pixels. To remedy this, Android gives you the option to enable or disable hardware acceleration at multiple levels. See Control hardware acceleration.

If your application performs custom drawing, test your application on actual hardware devices with hardware acceleration turned on to find any problems. The Support for drawing operations section describes known issues with hardware acceleration and how to work around them.

Also see OpenGL with the Framework APIs.

Control hardware acceleration

You can control hardware acceleration at the following levels:

  • Application
  • Activity
  • Window
  • Composable

Application level

In your Android manifest file, add the following attribute to the <application> tag to enable hardware acceleration for your entire application:

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

Activity level

If your application does not behave properly with hardware acceleration turned on globally, you can control it for individual activities as well. To enable or disable hardware acceleration at the activity level, you can use the android:hardwareAccelerated attribute for the <activity> element. The following example enables hardware acceleration for the entire application but disables it for one activity:

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

Window level

If you need even more fine-grained control, you can enable hardware acceleration for a given window with the following code:

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

Composable level

In Compose, there is no per-composable switch to disable hardware acceleration.

To render a composable into its own layer, use Modifier.graphicsLayer. This lets transform properties (such as alpha, scaleX, scaleY, translationX, translationY, rotationX, rotationY, rotationZ, and transformOrigin) change without rerunning the composable's drawing code. For the best performance, always use the lambda form of the modifier to set these properties.

To explicitly force an off-screen buffer for advanced drawing operations, such as custom blending within the layer, use CompositingStrategy.Offscreen. For more information, see Graphics modifiers.

If you have a custom drawing operation that strictly requires software rendering, you can host a legacy View using AndroidView and call setLayerType(View.LAYER_TYPE_SOFTWARE, null) on that view.

Support for drawing operations

When hardware accelerated, the 2D rendering pipeline supports the most commonly used Canvas drawing operations as well as many less-used operations. All of the drawing operations that are used to render applications that ship with Android, standard composables, and common advanced visual effects such as reflections and tiled textures are supported.

The following table describes the support level of various operations across API levels:

First supported API level
Canvas
drawBitmapMesh() (colors array) 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() with rotation/perspective 18
Paint
setAntiAlias() (for text) 18
setAntiAlias() (for lines) 16
setFilterBitmap() 17
setLinearText()
setMaskFilter()
setPathEffect() (for lines) 28
setShadowLayer() (other than text) 28
setStrokeCap() (for lines) 18
setStrokeCap() (for points) 19
setSubpixelText() 28
Xfermode
PorterDuff.Mode.DARKEN (framebuffer) 28
PorterDuff.Mode.LIGHTEN (framebuffer) 28
PorterDuff.Mode.OVERLAY (framebuffer) 28
Shader
ComposeShader inside ComposeShader 28
Same type shaders inside ComposeShader 28
Local matrix on ComposeShader 18

Canvas scaling

The hardware accelerated 2D rendering pipeline was built first to support unscaled drawing, with some drawing operations degrading quality significantly at higher scale values. These operations are implemented as textures drawn at scale 1.0, transformed by the GPU. Starting in API level 28, all drawing operations can scale without issue.

The following table shows when implementation was changed to correctly handle large scales:

Drawing operation to be scaled First supported API level
drawText() 18
drawPosText() 28
drawTextOnPath() 28
Simple Shapes 17
Complex Shapes 28
drawPath() 28
Shadow layer 28

If a drawing operation you depend on isn't hardware accelerated, render the affected drawing into an off-screen software Bitmap (or ImageBitmap) and draw the result. The rest of your UI keeps the hardware-accelerated path.

Tips and tricks

Switching to hardware accelerated 2D graphics can instantly increase performance, but you should still design your application to use the GPU effectively by following these recommendations:

Minimize layout complexity and recomposition
Keep the layout tree shallow, and limit how much recomposes. Defer state reads to the narrowest scope, so that a change redraws the smallest possible region. For example, read animated state inside Modifier.graphicsLayer { } rather than in a composable's body. For more information, see Jetpack Compose performance.
Avoid overdraw
Don't draw too many layers on top of each other. Remove any UI elements that are completely obscured by other opaque elements on top of them. If you need to draw several layers blended on top of each other, consider merging them into a single layer. A good rule of thumb with current hardware is to not draw more than 2.5 times the number of pixels on screen per frame (transparent pixels in a bitmap count!).
Don't create render objects in draw methods
A common mistake is to create a new Paint or a new Path every time a rendering method is invoked. This forces the garbage collector to run more often and also bypasses caches and optimizations in the hardware pipeline. To avoid this, reuse and mutate your objects:
  • Use standard methods: Standard DrawScope methods (like drawRect and drawCircle) already reuse Paint objects internally without requiring developer allocation.
  • Mutate instead of re-allocating: When writing custom logic, use path.rewind to clear an existing Path rather than instantiating a new Path.
  • Hold state efficiently: Inside a composable, allocate objects once using remember { Path() }. If you are building reusable custom modifier extensions, implement a custom Modifier.Node using DrawModifierNode to allocate and reuse the objects without causing new heap allocations.
Don't modify shapes too often
Complex shapes, paths, and circles for example, are rendered using texture masks. Every time you create or modify a path, the hardware pipeline creates a new mask, which can be expensive.
Don't modify bitmaps too often
Every time you change the content of a bitmap, it is uploaded again as a GPU texture the next time you draw it.
Use alpha with care
When you make a composable translucent using Modifier.alpha or Compose animation APIs, it is typically rendered in an off-screen buffer which doubles the required fill-rate. To avoid the off-screen buffer overhead for non-overlapping content, set CompositingStrategy.ModulateAlpha. For individual draw calls, apply alpha directly to the drawing command (like with color = Color.Red.copy(alpha = 0.5f)) without creating a layer.

Additional resources

Views content