Tips, tricks, tools, and techniques used by engineers from the Android UI Graphics team for getting the best performance and smoothest UI from Android applications.
Android now first re-orders the commands to find optimal batches for the GPU. This avoids changing GPU state. Then Android merges commands together to reduce the number of draw calls to the GPU. In this example we start with 8 in-order calls and end up with 3 out-of- order draw calls.
Until now Android would always render drawing commands in order. Android now first re-orders the commands to find optimal batches for the GPU. This avoids changing GPU state. Then Android merges commands together to reduce the number of draw calls to the GPU. In this example we start with 8 in-order calls and end up with 3 out-of- order draw calls.
Android would always render drawing commands in order. Android now first re-orders the commands to find optimal batches for the GPU. This avoids changing GPU state. Then Android merges commands together to reduce the number of draw calls to the GPU. In this example we start with 8 in-order calls and end up with 3 out-of- order draw calls.
Android would always render drawing commands in order. Android now first re-orders the commands to find optimal batches for the GPU. This avoids changing GPU state. Then Android merges commands together to reduce the number of draw calls to the GPU. In this example we start with 8 in-order calls and end up with 3 out-of- order draw calls.
However the renderer now uses Renderscript to generate drop shadows and thus use all the available core (4 cores in this example.) Paths are also now generated on background threads, as shown in yellow.
rendering. However the renderer now uses Renderscript to generate drop shadows and thus use all the available core (4 cores in this example.) Paths are also now generated on background threads, as shown in yellow.
UI thread performing normal rendering. However the renderer now uses Renderscript to generate drop shadows and thus use all the available core (4 cores in this example.) Paths are also now generated on background threads, as shown in yellow.
show the UI thread performing normal rendering. However the renderer now uses Renderscript to generate drop shadows and thus use all the available core (4 cores in this example.) Paths are also now generated on background threads, as shown in yellow.
hardware accelerated non-rectangular clipping This includes clipping with paths (circles, curves, rounded rects, etc.) and transformed rects (3D rotations for instance)
shape Path clip = getPath(); canvas.clipPath(clip); // Draw the content for (int i = 0; i < mLines,length; i++) { TextLine line = mLines[i]; canvas.drawText(line.text, line.x, line.y, mPaint); } } Non-rectangular clips can also be achieved by specifying the Region.Op parameter of the various clip*() methods (you can add, xor, subtract, etc.). Non-rect clipping can be triggered by rotations.
“Show GPU overdraw” feature Apps will be highlighted with various colors. Each color represents the amount of overdraw for each pixel. 1x means the pixel was drawn twice, 2x means the pixel was drawn 3 times, etc.
“Show GPU overdraw” feature Apps will be highlighted with various colors. Each color represents the amount of overdraw for each pixel. 1x means the pixel was drawn twice, 2x means the pixel was drawn 3 times, etc.
“Show GPU overdraw” feature Apps will be highlighted with various colors. Each color represents the amount of overdraw for each pixel. 1x means the pixel was drawn twice, 2x means the pixel was drawn 3 times, etc.
In developer options you can now turn on a “Show GPU overdraw” feature Apps will be highlighted with various colors. Each color represents the amount of overdraw for each pixel. 1x means the pixel was drawn twice, 2x means the pixel was drawn 3 times, etc.
Update display lists Process display lists Swap buffers In Android 4.1 we introduced a new rendering profiling tool in developer settings This tool was useful to create performance graphs such as this one It was however difficult to use since it required command line tools and a spreadsheet
freq sched In a future update of Android systrace is also easier to run, no need to change developer options All you need is to run the script from the command line In this example we’re tracing graphics (gfx), UI toolkit (view), CPU frequencies (freq) and the kernel scheduler (sched)
information. If you enable OpenGL tracing in developer options you will be able to see each individual OpenGL call in systrace (use the gfx tag when running systrace.) You can also enable glGetError() checks after each GL call.
parent) { Trace.beginSection("getView"); if (view == null) { view = createView(); } // Trace time spent binding data Trace.beginSection("bind"); bindView(pos, view); Trace.endSection(); Trace.endSection(); return view; } We’re introducing a new API, android.os.Trace, to let you add your own information to systrace. Systrace events are lightweight and add almost no overhead. Using it is easy: simply wrap the section of code to trace with beginSection/endSection. You can nest sections.
You won’t see your events by default, you must specify the package name of your app using the -a option when invoking systrace. You can specify several packages and combine it with the built-in tags such as gfx and view.
getBitmap(); // Enable trilinear filtering b.setHasMipMap(true); } Here is how you can enable trilinear filtering on a Bitmap You can also enable trilinear filtering on a BitmapDrawable if you prefer
layer canvas.save(); canvas.saveLayer(x, y, width, height, Canvas.CLIP_TO_LAYER_SAVE_FLAG); // Draw stuff canvas.drawBitmap(bugDroid, 0.0f, 0.0f, null); canvas.restore(); } The first type of layers is called “clipped layer”. Such layers are created by passing the flag Canvas.CLIP_TO_LAYER_SAVE_FLAG when calling Canvas.saveLayer().
only the drawing commands that intersect with that layer will be rendered. They are also rendered only in that layer. In practice this means the renderer must create an offscreen render target (bitmap in software, FBO+texture in hardware.)
the drawing commands that intersect with that layer will be rendered. They are also rendered only in that layer. In practice this means the renderer must create an offscreen render target (bitmap in software, FBO+texture in hardware.)
the drawing commands that intersect with that layer will be rendered. They are also rendered only in that layer. In practice this means the renderer must create an offscreen render target (bitmap in software, FBO+texture in hardware.)
layer canvas.save(); canvas.saveLayer(x, y, width, height, 0); // Draw stuff canvas.drawBitmap(bugDroid, 0.0f, 0.0f, null); canvas.restore(); } The second type of layers is called “unclipped layer”. Such layers are created by NOT passing the flag Canvas.CLIP_TO_LAYER_SAVE_FLAG when calling Canvas.saveLayer().
in any layer it intersects, including the original canvas. In this example you can see the bitmap is drawn in both the layer we’ve created and the original drawing surface. This is how fading edges are implemented on Android. Unclipped layers are expensive: each command is executed N times (N=number of active layers.)
any layer it intersects, including the original canvas. In this example you can see the bitmap is drawn in both the layer we’ve created and the original drawing surface. This is how fading edges are implemented on Android. Unclipped layers are expensive: each command is executed N times (N=number of active layers.)
any layer it intersects, including the original canvas. In this example you can see the bitmap is drawn in both the layer we’ve created and the original drawing surface. This is how fading edges are implemented on Android. Unclipped layers are expensive: each command is executed N times (N=number of active layers.)
a View. Some will trigger an animation, some won’t. What matters is they all trigger the same side effect: the creation of an expensive offscreen buffer.
Each line shows a different way to set alpha on a View. Some will trigger an animation, some won’t. What matters is they all trigger the same side effect: the creation of an expensive offscreen buffer.
Canvas.saveLayerAlpha(l, t, r, b, 127, Canvas.CLIP_TO_LAYER_SAVE_FLAG); == Each line shows a different way to set alpha on a View. Some will trigger an animation, some won’t. What matters is they all trigger the same side effect: the creation of an expensive offscreen buffer.
apply the alpha? Here is an example. Let’s imagine a View containing 3 photos (bitmaps.) If we animate the opacity to 50% without using a separate buffer the result would look like this one.
apply the alpha? Here is an example. Let’s imagine a View containing 3 photos (bitmaps.) If we animate the opacity to 50% without using a separate buffer the result would look like this one.
(int) (0xFF * alpha) << 24 | baseTextColor & 0xFFFFFF; textView.setTextColor(newTextColor); If you are setting alpha on a TextView and you don’t have a background you can simply bake the alpha in the text color instead. This also applies to background colors, drawing primitives, etc.
(int) (255 * slider.getProgress() / 100.0f); paint.setAlpha(alpha); canvas.draw*(..., paint); If you are writing a custom view and the drawing commands don’t overlap, you can simply set the alpha on the paint.
view.animate().alpha(0).withLayer(); By far the easiest thing to do is to use a hardware layer. The extra buffer copy will happen only once (or every time the view changes) instead of on every frame.
Don't lie to us! return false; } If you have a custom view and you know your content does not overlap, override this method and return false. This will trigger an automatic optimization in the hardware rendering pipeline that will bypass the use of a separate buffer.
of the Canvas int w = canvas.getWidth(); int h = canvas.getHeight(); canvas.drawRect(0, 0, w, h, mPaint); } Here is code I have seen in many applications The result of these calls might surprise you
hardware, the API returns the dimensions of the View In software, the API returns the dimensions of the window... or the Bitmap if you draw into a bitmap
(size of the View) (size of the window) In hardware, the API returns the dimensions of the View In software, the API returns the dimensions of the window... or the Bitmap if you draw into a bitmap
canvas.clipRect(l, t, r, b); // Rotate the jar canvas.rotate(-30.0f, pX, pY); // Draw the jar canvas.drawBitmap(mJellyBeans, x, y, null); } You should be very careful when applying rotations with clipping Here is a simple example of code that clips first, then rotates before drawing a bitmap
canvas.rotate(-30.0f, pX, pY); // Keep the jellybeans canvas.clipRect(l, t, r, b); // Draw the jar canvas.drawBitmap(mJellyBeans, x, y, null); } What if we change the order of operations and rotate then clip?
GPU. To use the stencil buffer we must clear it and draw into it. This means that transformed clipping operations cause extra drawing commands to be executed This also means we’re using more fillrate
GPU. To use the stencil buffer we must clear it and draw into it. This means that transformed clipping operations cause extra drawing commands to be executed This also means we’re using more fillrate
clip Rect clip = canvas.getClipBounds(); // ??? Log.d("I/O", "clip = " + clip); } What would you expect the getClipBounds() API to return? (Note: use the variant that takes a REct to avoid allocations)
View) In hardware, the API returns the dimensions of the View In software, the API returns the dimensions of the dirty rect Discussion about multiple invalidates (dirty unions)
170,125, 470, 275 (bounds of the View) (bounds of the dirty rect) In hardware, the API returns the dimensions of the View In software, the API returns the dimensions of the dirty rect Discussion about multiple invalidates (dirty unions)
specific Canvas calls can prevent reordering. We refer to these calls as “barriers”. Reordering can only happen before or after but cannot work across barriers.
non-rectangular clips. Be careful as non-rect clips can be introduced by rotations. For instance, rotating a View without setting a layer on that view first will cause non-rect clipping to occur.
saveLayer() (or its variant saveLayerAlpha().) Be careful as saveLayerAlpha() can be triggered on your behalf by calling View.setAlpha() as discussed earlier.