Wednesday, May 7, 2014

Learn to accelerate.

הפוסט זמין גם בעברית כאן - http://iandroid.co.il/dr-iandroid/?p=16144

Hello again,
I am very excited because this is a brand new post modeled after a brand new lecture i recently gave at GDG Herzeliya, on our last event titled: Android framework to open source strategy.

In this post i would like to focus on hardware acceleration (would be referred in this post as HW accel. to spare me a few characters :P) which a new(-ish) and exciting tool which you MUST know because unlike other great tools, if you do not know the rules for this one it could cause damage rather than simply spare you of its benefits.

So let's get started:

Why?

Motivation.

HW. accel promises to deliver what most developers want most of all:

  • Better looking UIs.
  • Smoother animations.
  • Smaller memory footprints.
  • Less stress on the CPU.

How?

Hardware acceleration - prolog.

First thing's first, hardware acceleration availability is as follows:

  • API < 11 - Not available.
  • API >= 11 (HC 3.x) - Available, off.
  • API >= 14 (ICS 4.x) - Available, on.

The concept of HW. accel. suggests the usage of a hardware component which will ensure the following:
  • Specially tailored HW. design for a specific task or set of tasks (non-generic = faster).
  • Specially tailored APIs (non-generic = faster).
  • Separate pre-allocated resources.

In Android's case, when HW. accel is enabled we use the GPU to draw the Canvas for all the views (all views have Canvas objects linked to them) in the HW drawing model by utilizing OpenGL methods and functions directly.


HW drawing model?

There are 2 drawing models used by Android:

The Software drawing model:
  1. Invalidate View Hierarchy.
  2. Draw Hierarchy.

This is done immediately after Invalidate() is called and causes the calling View to be redrawn but also any affiliated View since the mechanism for figuring out the diff is minimal to non-existing.

The Hardware drawing model.
  1. Invalidate View Hierarchy.
  2. Record the display lists.
  3. Update the display lists.
  4. Draw Hierarchy.
The first 3 items are done immediately for the calling View (and only the calling View) but the 4th action is called on the G-sync (possible due to the display lists mechanism that saves the state), which means that the advantages that the HW drawing model has upon its software equivalent are:
  1. Not drawing immediately saves resources for the system.
  2. Drawing with the GPU frees the CPU to other things. 
  3. Drawing on the G - Sync means that it is also synchronized better with the screen and CPU for smoother graphics.
  4. The display lists mechanism saves the diff and state so only changed pixels are redrawn (even on the calling View).
  5. Only the calling View gets invalidated, which is more efficient (although many developers discover that they relied on this false positive behaviour and have UI bugs "appear" when they start using HW accel).

Shouldn't i always use hardware acceleration?

The short answer is no.

Since OpenGL imports are at the core of the Android HW accel. there are a few issues which might arise:
  • Imports take a lot of time and therefore Google started with support for the set of basic functions that power the standard Views so not everything that you're using is supported and this changes from API level to the next (for an example, the popular and useful clipRect(...) is not supported until API level 18
  • OpenGL impl. and func. differs ever so slightly between GPU models causing it to act different on different devices for no apparent reason.
  • OpenGL and HW. accel. requires extra RAM.

What's the worst that could happen?

  • Invisible UI elements,
  • Memory related crashes.
  • Badly rendered pixels.
All of which will most likely be only reproducible on specific devices and API levels.

What to do?

Make sure if you have a problem and fix it.... dah!

Find out if you have a problem.

If you're only using standard Views and/or your designated API level is 18 you have nothing to worry about, if that is not the case, there still might be no reason to worry, try the following steps:
  1. Check the following list to see if your operations are supported on your designated API levels here: http://developer.android.com/guide/topics/graphics/hardware-accel.html#drawing-support.
  2. Test your code with all that Android (and every 3rd party you can think of) offers you with great tools like; systrace, GLTracer and traceView and here's a great session on that by Mr. Ran Nachmany here.
  3. Test on as many physical devices that you can obtain (in Israel you can apply to use Google campus's device library here.

Fix problems (if applicable).

1. Have you tried turning it off and on again?

Try to identify where the problem might be and switch HW accel. off to verify whether this particular portion might work better without HW accel. while still getting being able to enjoy HW accel.'s perks where needed.
You can do that by utilizing the different control levels that Android suggests:

Application:



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

Activity:


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


Window:


getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,           WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
);

Layer:


myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    Layers also requires you to distinguish between layer types:
    1. LAYER_TYPE_NONE - not backed by any off screen buffer, rendered in the SW model.
    2. LAYER_TYPE_HARDWARE - if HW accel. is on, layer is backed by HW texture buffer and rendered in the HW model, else it is the same as in the case of LAYER_TYPE_SOFTWARE.
    3. LAYER_TYPE_SOFTWARE - backed by a Bitmap as if it was a buffer, rendered in the SW model.
    The rule of thumb when it comes to layers is that LAYER_TYPE_HARDWARE is usually best for performance while LAYER_TYPE_SOFTWARE works best for compatibility between GPUs and API levels.

    2. Use what was already created and beware of unnecessary modifications.

    Alterations are expensive in HW accel.:
    • Objects such as Shapes/Circle/Paths create new texture mask instances whenever they're changed.
    • Bitmaps are reloaded to the GPU whenever they’re altered.
    For that reason it is highly important to pay extra attention to only edit what needs to be changed or use the EditMode design pattern, to which an example is here:
    private class PieView extends View {
          public PieView(Context context) {
              super(context);
              if (!isInEditMode()) {
                  setLayerType(View.LAYER_TYPE_HARDWARE, null);
              }
          }
    ...
    }

    It looks like the suggestion on how to have custom Views appear in Eclipse and actually it is the same for a similar reason, it means that while it is edited it will use the SW drawing model but afterwards it will be loaded and reused with the HW drawing model.

    *An important exception to that rule is any and all usages of alpha and alpha manipulations which are created in an off-screen buffer which should be dealt in a hardware layer type and does not create any new textures or instances when done by HW. accel.

    3. new is bad.

    Just like creating new instances is bad in other examples of repeditaly running code (like getView in ListViews) since it creates a lot of runtime memory "garbage" which in turn invokes the garbage collector repeatedly which is expensive and may cause severe performance issues so does creating such instances in rendering methods (such as onDraw) is imperative to avoid.
    Most common examples of unnecessarily created objects are Path and Paint, the simple pattern is to create them as global members and simply change their values when needed.

    4. Avoid overdraw.


    The rule of thumb is to never draw more than X2.5 of the number of available pixels (Important: "invisible pixels" in Bitmaps count, so maybe "the expert" had a point after all).
    If you're state is different you'll need to merge and/or remove layers or expect performance issues in general and especially with HW accel.

    5. Reduce the number of views.


    Less views means smaller display lists and less to draw (for HW and SW drawing models), the exception to that rule is if you're using only standard views and a reduction means to start using custom views, in that case, only do so if the reduction is very big.

    5. Monitor the state.

    There are 2 ways to tell whether the View you're working in is drawn by the HW or SW models:

    Please use them and when you do so, here's a tip i learned the hard way:
    Prefer Canvas.isHardwareAccelerated() especially within the drawing code since a view attached to a hardware accel. window can still be drawn in the SW model (like when it’s drawn to a bitmap for caching).

    And that's about it, i hope you learned something new and that the post was written well.

    Thank you.


    הפוסט זמין גם בעברית כאן - http://iandroid.co.il/dr-iandroid/?p=16144

    Royi is a Google Developer Expert for Android in 2013, a mentor at Google's CampusTLV for Android and (last but not least) the Android clients group leader at Vidmind, an OTT TV and Video Platform Provider. www.vidmind.com