Graphics

Graphics
    Quite often you’ll have to spend a lot of time defining琢磨 what your application should look like. Whether it is an e-mail application using standard Android widgets小部件 or a game using OpenGL ES, what your applications looks like is one of the first things people will notice when browsing application stores like Android Market or Amazon Appstore.
   An application that looks great but has graphics that are slow to appear on the screen or
slow to get updated刷新慢吞吞 is not likely to become a huge hit巨大成功. Similarly, a game with highquality rendering but low frame rate幀數較低 could easily be ignored by users.
Reviews用戶評價 will ultimately drive the success of your application and it is therefore important your application is more than just a little eye candy不要做華而不實的玩意.
    In this chapter, you learn the basic methods to optimize your layouts with various
techniques and tools as well as some techniques to optimize OpenGL ES rendering to
achieve a better frame rate or lower power consumption.


8.1 Optimizing Layouts


    By now you should already be familiar with XML layouts and the setContentView()
method. A typical use of this method is shown in Listing 8–1. Even though many
consider layouts to be simple to define, especially with the graphical layout interface in
Eclipse, it is easy to get carried away讓人迷失 and define far from optimal layouts. This section provides several easy ways to simplify layouts and accelerate layout inflation加快佈局展開.
Listing 8–1. Typical setContentView() Call
public class MyActivity extends Activity {
private static final String TAG = "MyActivity";
/** Called when the activity is first created. */
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// call to setContentView() to inflate and set layout (defined in main.xml)
setContentView(R.layout.main);
...
}
...
}
While this is a very simple call, a few things happen under the hood when
setContentView() is called:
Android reads the application’s resource data (in the APK file, stored either on internal storage or SD card).
The resource data is parsed and the layout is inflated.
The inflated layout becomes the top-level view of the activity.
   How much time this call will take depends on the complexity of the layout: the bigger the
resource data the slower the parsing is, and the more classes to instantiate the slower
the layout instantiation.
When you create an Android project in Eclipse, a default layout is generated in main.xml,
as shown in Listing 8–2. The TextView’s text is defined in strings.xml.
Listing 8–2. Default Layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="

http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
</LinearLayout>
   A call to setContentView() as shown in Listing 8–1 using this particular layout takes
about 17 milliseconds to complete on a Samsung Galaxy Tab 10.1. While this is quite
quick, it is also an extremely simple layout that is not really representative of a typical
Android application: only two classes will be instantiated (LinearLayout and TextView)
and very few properties are specified in the XML file.
   NOTE: Layouts can be created programmatically可以寫代碼創建佈局 as well but XML is usually preferred.
   After adding multiple widgets to the default layout to reach a total of thirty widgets
(including ScrollView, EditText, and ProgressBar), the call to setContentView() took
more than 163 milliseconds to complete.
   As you can see, the time it takes to inflate a layout grows almost linearly線性增長 with the number of widgets to create. Moreover, the call to setContentView() represented almost 99% of the time spent between the beginning of onCreate() and the end of onResume().


   You can perform your own measurements測量時間開銷 by simply adding widgets to or removing widgets from the layout. Using the graphical layout view of the XML files in Eclipse makes it very easy. If you already have defined your application’s layout, the first thing you should do is to measure how much time it takes to inflate it. Because the layout is typically代表性地 inflated in your activity’s onCreate() method, the time it takes to inflate it will have a direct impact on your activity’s start-up time, as well as your application’s. It is therefore recommended you try to minimize the time spent inflating layouts.
   To achieve this, several techniques are available, most of them based on the same principle: reducing the number of objects to create. You can do this by using different layouts while still achieving the same visual result, by eliminating unnecessary objects, or by deferring the creation of objects推遲創建對象.


8.1.1 RelativeLayout相對佈局


    Linear layouts are typically the first layout application developers learn to use. As a matter of fact, this layout is part of the default layout shown in Listing 8–1 and therefore is one of the first ViewGroups developers get familiar with. It is also an easy layout to understand as a linear layout is basically a container for widgets that are aligned either horizontally or vertically.
   Most developers who are new to Android start nesting嵌套 linear layouts to achieve the desired result. Listing 8–3 shows an example of nested linear layouts.
Listing 8–3. Nested Linear Layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout xmlns:android="
http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="str1"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/text2"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="str2"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
<LinearLayout xmlns:android="
http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="@+id/text3"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="str3"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/text4"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="str4"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
<LinearLayout xmlns:android="
http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="@+id/text5"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="str5"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/text6"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="str6"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
</LinearLayout>
   This layout’s core views are the six text views. The four linear layouts are simply here to
help with positioning.
   This layout exposes two issues:
As linear layouts are nested, the layout hierarchy becomes deeper (causing layout and key handling to be slower按鍵處理變慢).
Out of ten objects, four exist only for positioning.
    These two issues are easily solved by replacing all these linear layouts with a single
relative layout, as shown in Listing 8–4.
Listing 8–4. Relative Layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="
http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:text="@string/str1"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/text2"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_toRightOf="@id/text1"
android:layout_alignParentTop="true"
android:text="@string/str2"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/text3"
android:layout_alignParentLeft="true"
android:layout_below="@id/text1"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="@string/str3"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/text4"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_toRightOf="@id/text3"
android:layout_below="@id/text2"
android:text="@string/str4"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/text5"
android:layout_alignParentLeft="true"
android:layout_below="@id/text3"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="@string/str5"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/text6"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_toRightOf="@id/text5"
android:layout_below="@id/text4"
android:text="@string/str6"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
    As you can see, all six text views are now within a single relative layout, and therefore
only seven objects are created instead of 10. The layout is also not as deep as it used to
be: the text views are now one level higher只是在文字視圖的上一層. The key to positioning the widgets is in the layout_*** attributes. Android defines many such attributes that you can use to determine the positioning of the various elements in your layout:
layout_above
layout_alignBaseline
layout_alignBottom
layout_alignLeft
layout_alignRight
layout_alignTop
layout_alignParentBottom
layout_alignParentLeft
layout_alignParentRight
layout_alignParentTop
layout_alignWithParentIfMissing
layout_below
layout_centerHorizontal
layout_centerInParent
layout_centerVertical
layout_column
layout_columnSpan
layout_gravity
layout_height
layout_margin
layout_marginBottom
layout_marginLeft
layout_marginRight
layout_marginTop
layout_row
layout_rowSpan
layout_scale
layout_span
layout_toLeftOf
layout_toRightOf
layout_weight
layout_width
layout_x
layout_y
    NOTE: Some attributes are specific to a certain type of layout. For example, the
layout_column, layout_columnSpan, layout_row, and layout_rowSpan are to be used
with the grid layout.

   Relative layouts are especially important in list items條目列表中非常重要 as it is quite common for  applications to show ten or more such items on the screen at any given time.


8.1.2 Merging Layouts合併佈局

     Another way to reduce the height of the layout hierarchy is to merge layouts with the
<merge /> tag. Quite often the top element of your own layout will be a FrameLayout, as
shown in Listing 8–5.
Listing 8–5. Frame Layout
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="

http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/my_top_layout" >
<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
</FrameLayout>
    Because the parent of an activity’s content view is also a FrameLayout, you would end
up with two FrameLayout objects in the layout:
Your own FrameLayout
The parent of the activity’s content view, another FrameLayout,
which has only one child (your own FrameLayout)
Figure 8–1 shows the layout you would obtain assuming your own FrameLayout had two
children: an ImageView and a TextView.
Figure 8–1. FrameLayout child of another FrameLayout
    This is one FrameLayout too many, and you can reduce the height of the layout by
replacing your own FrameLayout with a <merge /> tag. By doing so, Android simply
attaches the children of the <merge /> tag to the parent FrameLayout. Listing 8–6 shows
the new XML layout.
Listing 8–6. Merge Tag
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="
http://schemas.android.com/apk/res/android">
<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
</merge>
    As you can see, it is just a matter of replacing the <FrameLayout /> tag with a <merge /> tag. Figure 8–2 shows the resulting layout.
Figure 8–2. <FrameLayout /> replaced With <merge />


8.1.3 Reusing Layouts

    Similar to the #include directive in C or C++, Android supports the <include /> tag in the
XML layouts. Simply put, the <include /> tag includes another XML layout, as shown in
Listing 8–7.
The <include /> tag can be used for two purposes:
You want to use the same layout multiple times.
Your layout has a common part and also parts that depend on the device configuration (for example, screen orientation—landscape or
portrait).
Listing 8–7 shows how you can include a layout multiple times while overriding some of
the included layout’s parameters.
Listing 8–7. Including Layout Multiple Times
<LinearLayout xmlns:android="

http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<include android:id="@+id/myid1" android:layout="@layout/mylayout"
android:layout_margin="9dip" />
<include android:id="@+id/myid2" android:layout="@layout/mylayout"
android:layout_margin="3dip" />
<include android:id="@+id/myid3" android:layout="@layout/mylayout" />
</LinearLayout>
    Listing 8–8 shows how you can include a layout just once, but depending on the
device’s screen orientation, either layout-land/mylayout.xml or layout-port/mylayout.xml
will be included. (It is assumed here there are two versions of mylayout.xml, one in the
res/layout-land directory and the other in the res/layout-port directory.)
Listing 8–8. Including Layout Depending On Screen Orientation
<LinearLayout xmlns:android="
http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<include android:id="@+id/myid" android:layout="@layout/mylayout" />
</LinearLayout>
When a layout is included, it is possible to override some of the layout’s parameters,
such as:
The root view’s id (android:id)
The layout parameters (android:layout_*)
NOTE: Overriding the layout’s parameters is optional. Only the android:layout attribute is
mandatory in the <include /> tag.
    As Listing 8–8 demonstrates, the inclusion of the layout is done dynamically包含的佈局是動態處理 when the layout is inflated. Should the inclusion be done at compile time Android would not know which of the two layouts to include (layout-land/mylayout.xml or layoutport/ mylayout.xml). This is different from the #include directive in C or C++, which is
handled by the preprocessor at compile time.


8.1.4 View Stubs

   As we saw in Chapter 1, lazy initialization推遲初始化 is a convenient technique to defer
instantiations, improve performance, and potentially save memory (when objects never
have to be created)如果對象從未被創建過.
   Android defines the ViewStub class for that purpose. A ViewStub is a lightweight invisible view that you can use in your layout to allow you to lazily inflate layout resources推遲展開佈局 when you need them. Listing 8–9, a modified version of Listing 8–8, shows how to use ViewStub in an XML layout.
Listing 8–9. ViewStub In XML
<LinearLayout xmlns:android="

http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ViewStub
android:id="@+id/mystubid"
android:inflatedId=”@+id/myid”
android:layout="@layout/mylayout" />
</LinearLayout>
   When the LineaLayout is inflated, it will contain only one child, a ViewStub. The layout
the ViewStub is referring to, @layout/mylayout, could be a very complicated layout,
requiring significant time to be inflated, but it is not inflated yet. To inflate the layout
defined in mylayout.xml, you have two options in your code, as shown in Listing 8–10
and Listing 8–11.
Listing 8–10. Inflating Layout In Code
ViewStub stub = (ViewStub) findViewById(R.id.mystubid);
View inflatedView = stub.inflate(); // inflatedView will be the layout defined in
mylayout.xml
Listing 8–11. Inflating Layout In Code Using setVisibility()
View view = findViewById(R.id.mystubid);
view.setVisibility(View.VISIBLE); // view stub replaced with inflated layout
view = findViewById(R.id.myid); // we need to get the newly inflated view now
    While the first way of inflating the layout (in Listing 8–10) seems more convenient, some
may argue it has a slight problem: the code is aware of the fact that the view is a stub
and explicitly needs to inflate the layout. In most cases, this won’t be an issue though,
and it will be the recommended way of inflating a layout when your application uses
ViewStub in its layout.

   The second way of inflating the layout, shown in Listing 8–11, is more generic所以更通用 as it does not refer to the ViewStub class. However, the code as shown above is still aware of the fact that it uses a ViewStub since it uses two distinct ids: R.id.mystubid and R.id.myid. To be fully generic, the layout should be defined as shown in Listing 8–12, and inflating the layout should be done as shown in Listing 8–13. Listing 8–12 is identical to Listing 8–9 except for the fact that only one id is created, R.id.myid, instead of two. Similarly,
    Listing 8–13 is identical to Listing 8–11 except for R.id.mystubid being replaced with
R.id.myid. Listing 8–12. ViewStub In XML Without Overriding ID
<LinearLayout xmlns:android="
http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ViewStub
android:id="@+id/myid"
android:layout="@layout/mylayout" />
</LinearLayout>
Listing 8–13. Inflating Layout In Code Using setVisibility()
View view = findViewById(R.id.myid);
view.setVisibility(View.VISIBLE); // view stub replaced with inflated layout
view = findViewById(R.id.myid); // we need to get the newly inflated view now
   As a matter of fact, Listing 8–13 would be valid whether or not a ViewStub is used in the
layout. If it is important for your code to be generic and work fine regardless of the use
of ViewStub in the layout, then this approach is preferred這種方法是首選. However, the two calls to findViewById() would affect performance negatively. To partially fix this problem, since a ViewStub will be removed from its parent when setVisibility(View.VISIBLE) is called, you could first check whether the view still has a parent before calling findViewById() the second time. While this is not optimal as a second call to findViewById() would still be needed when you used a ViewStub in your layout, it would guarantee that findViewById() is only called once when you did not use a ViewStub. The modified code is shown in Listing 8–14.
Listing 8–14. Calling findViewById() Once When Possible
View view = findViewById(R.id.myid);
view.setVisibility(View.VISIBLE); // view stub replaced with inflated layout (if
stub is used in layout)
if (view.getParent() == null) {
// a stub was used so we need to find the newly inflated view that replaced it
view = findViewById(R.id.myid);
} else {
// nothing to do, the view we found the first time is what we want
}
    What is shown in Listing 8–13 and Listing 8–14 is uncommon, and you usually won’t
have to follow this approach. Typically, it will be acceptable for your code to know that
ViewStubs are used in the layouts and therefore the simple way of inflating stubs shown
in Listing 8–10 will be sufficient.


8.2  Layout Tools佈局工具


   To assist you in creating the best layouts, the Android SDK provides two easy-to-use
tools: hierarchyviewer and layoutopt. You can find these tools, among others, in the SDK
tools directory.

8.2.1 Hierarchy Viewer 層級視圖

   The Android SDK comes with a very useful tool to view and analyze your application’s
layout: hierarchyviewer. As a matter of fact, Figure 8–1 and Figure 8–2 were generated
using that tool. In addition to showing you the detailed layout of your application, this
tool also measures how much time it takes to measure, lay out, and draw each widget,
and identifies which widgets took longer to measure, lay out, and draw.
   You can use hierarchyviewer as a standalone tool獨立的工具 or directly in Eclipse with the “Hierarchy View” perspective.


8.2.2 layoutopt

    The Android SDK comes with another tool that can help you with your layouts:
layoutopt. This tool analyzes your layout files and can recommend changes in order to
make the layout more efficient.
For example, using layoutopt on the layout from Listing 8–5 results in the following
output:
The root-level <FrameLayout/> can be replaced with <merge/>
    As it turns out, this is exactly what Listing 8–6 did. Running layoutopt in Listing 8–6 did
not result in any recommendation.
TIP: Use the latest version of the layoutopt tool to make sure you get the best results.
   Make sure all issues reported by layoutopt are taken care of before you release your
application. Unoptimized layouts can slow down your application, and it is usually quite
easy to remedy糾正 such layouts.


8.3  OpenGL ES

    Three-dimensional rendering三維渲染 is becoming a more and more important feature of today’s Android devices and applications. While becoming an expert in 3D rendering would take quite some time, the following section introduces some simple techniques that are easy to implement as well as some basic concepts you need to be aware of in order to start working with 3D rendering. If you want to learn more about OpenGL ES for Android, you can refer to “Pro OpenGL ES for Android” by Mike Smithwick and Mayank Verma.

 
   Most recent Android devices support both OpenGL ES 1.1 and OpenGL ES 2.0 while
older devices would support only OpenGL ES 1.1. As of December 2011, about 90% of
the devices connecting to Android Market support OpenGL ES 2.0.
   While OpenGL ES is a standard from the Khronos Group, several implementations exist.
The most common GPUs supporting OpenGL ES in Android devices are:
ARM Mali (example: Mali 400-MP4)
Imagination Technologies PowerVR (example: PowerVR SGX543)
Nvidia GeForce (Nvidia Tegra)
Qualcomm Adreno (acquired from AMD, formerly ATI Imageon, and
integrated into Qualcomm Snapdragon)

8.3.1 Extensions擴展

    The OpenGL standard supports extensions, allowing new features to be incorporated合併 in certain GPUs. For example, the following extensions are supported by the Samsung
Galaxy Tab 10.1 (based on Nvidia Tegra 2):
GL_NV_platform_binary
GL_OES_rgb8_rgba8
GL_OES_EGL_sync
GL_OES_fbo_render_mipmap
GL_NV_depth_nonlinear
GL_NV_draw_path
GL_NV_texture_npot_2D_mipmap
GL_OES_EGL_p_w_picpath
GL_OES_EGL_p_w_picpath_external
GL_OES_vertex_half_float
GL_NV_framebuffer_vertex_attrib_array
GL_NV_coverage_sample
GL_OES_mapbuffer
GL_ARB_draw_buffers
GL_EXT_Cg_shaders
GL_EXT_packed_float
GL_OES_texture_half_float
GL_OES_texture_float
GL_EXT_texture_array
GL_OES_compressed_ETC1_RGB8_texture
GL_EXT_texture_compression_latc
GL_EXT_texture_compression_dxt1
GL_EXT_texture_compression_s3tc
GL_EXT_texture_filter_anisotropic
GL_NV_get_text_p_w_picpath
GL_NV_read_buffer
GL_NV_shader_framebuffer_fetch
GL_NV_fbo_color_attachements
GL_EXT_bgra
GL_EXT_texture_format_BGRA8888
GL_EXT_unpack_subp_w_picpath
GL_NV_texture_compression_st3c_update
Listing 8–15 shows how to retrieve the list of the extensions a device supports. Since OpenGL ES 2.0 is currently not supported by the Android emulator, make sure you run that code on an actual device.
Listing 8–15. OpenGL ES Extensions
// list of extensions returned as a single string (parse it to find a specific
extension)
String extensions = GLES20.glGetString(GLES20.GL_EXTENSIONS);
Log.d(TAG, "Extensions: " + extensions);
    You will need an OpenGL context to be able to successfully execute the code in
Listing 8–15 and retrieve the list of extensions. For example, you can execute the code
in your GLSurfaceView.Renderer’s onSurfaceChanged() method. If you run that code and
no OpenGL is available, then you will see a “call to OpenGL ES API with no current
context” message in LogCat.
As you can see, the extensions above can be separated into multiple groups:
GL_OES_*
GL_ARB_*
GL_EXT_*
GL_NV_*
    The GL_OES_* extensions are extensions that have been approved by the Khronos OpenGL ES Working Group. Even though these extensions are not required by OpenGL ES, they are widely available.
   The GL_ARB_* extensions have been approved by the OpenGL Architecture Review  Board. The GL_EXT_* extensions have been agreed upon by multiple vendors, whereas the GL_NV_* extensions are specific to Nvidia. By looking at what extensions are supported, you can usually tell which company provides a device’s GPU:
ARM extensions use the GL_ARM prefix.
Imagination Technologies extensions use the GL_IMG prefix.
Nvidia extensions use the GL_NV prefix.
Qualcomm extensions use the GL_QUALCOMM, GL_AMD and
GL_ATI prefixes.
NOTE: Visit

http://www.khronos.org/registry/gles and
http://www.opengl.org/registry for more information.
    Because not all devices support the same extensions (hello, fragmentation), you have to be extremely careful when optimizing for a specific device, as something that works on
one device may not work on another. Typical examples include using Non-Power-Of-
Two (NPOT) textures or a texture compression format紋理壓縮格式 that is supported on the device you test your application on but not on other devices. If you plan on supported non- Android devices, also check which extensions these devices support. For example,
current Apple iPhone/iPod/iPad devices are all Imagination Technologies PowerVRbased
(which may or may not share the same extensions) but future models may not be
PowerVR-based.


8.3.2 Texture Compression

    Textures will define what your OpenGL ES applications will look like. While it is easy to
use uncompressed textures, such textures can quickly bloat your application讓你的應用沉贅不堪. For example, an uncompressed 256x256 RGBA8888 texture could use 256 kilobytes of memory.
   Uncompressed textures impact performance negatively as they are not as cacheable as
compressed texture (because of their size), and they require more memory access
(affecting also power consumption). Applications that use uncompressed textures are
also bigger and therefore require more time to be downloaded and installed. Besides,
the amount of memory on Android devices is typically limited, so your applications
should use as little memory as possible.
   In the list above, the following extensions show which texture compression formats are
supported by the Samsung Galaxy Tab 10.1:
GL_OES_compressed_ETC1_RGB8_texture
GL_EXT_texture_compression_latc
GL_EXT_texture_compression_dxt1
GL_EXT_texture_compression_s3tc
    The ETC1 compression format was created by Ericsson (ETC stands for Ericsson
Texture Compression) and is supported by most Android devices (and by all Android
devices that support OpenGL ES 2.0). It is therefore the safest choice to target as many
devices as possible. This compression format uses 4 bits per pixel instead of 24 (ETC1
does not support alpha) and therefore achieves a 6x compression ratio: including the
16-byte header, a 256-by-256 ETC1 texture would use 32,784 bytes.
   Multiple tools exist to create ETC1 textures. The Android SDK includes etc1tool, a
command-line tool to encode PNG p_w_picpaths to compressed ETC1 p_w_picpaths. For example,
the following line shows how to compress lassen.png (183 kilobytes) and generate the
difference between the original p_w_picpath and the compressed one:
etc1tool lassen.png --encode --showDifference lassen_diff.png
   The output file, if not specified, is generated based on the name of the input file. In this
particular case, the output file will be lassen.pkm (33 kilobytes). Figure 8–3 shows the
difference between the compressed p_w_picpath and the original.
Figure 8–3. Original p_w_picpath (left) and difference with compressed p_w_picpath (right)
   NOTE: etc1tool can also be used to decode an ETC1 p_w_picpath into a PNG p_w_picpath. For more information about etc1tool, refer to

http://d.android.com/guide/developing/tools/etc1tool.html.
   Unfortunately, the Android SDK’s etc1tool does not provide many options. For those
who want to be able to fine tune the compression and visual quality, another tool is
recommended: etcpack. The etcpack tool can be downloaded from the Ericsson
website (
http://devtools.ericsson.com/etc) and allows for more options:
Compression speed (slower speed = higher quality)
Error metric (perceptual or non-perceptual)
Orientation
    You should always use the best quality possible before releasing your application when
ETC1 textures are generated offline (that is, not on the Android device itself). Since the
human eye is more sensitive to green than red or blue, you should also select  “perceptual可感知” as the error metric誤差度量. (The algorithm will make green be closer to its original value at the expense of red and blue, decreasing the peak signal-to-noise ratio信噪比.)
     While the difference may not be obvious, there is no reason to release an application
that uses lower-quality textures as a higher-quality ETC1 texture will be the same size as
a lower-quality one. Even though generating higher-quality textures takes more time and
therefore many developers choose to work with lower-quality textures during
development, you should always remember to switch to high-quality textures before
releasing your application.
     Alternatively, you can use the ARM Mali GPU Compression Tool, available for free at
http://www.malideveloper.com/developer-resources/tools. This tool offers options
similar to etcpack but with a graphical interface (a command-line version is also
provided). Figure 8–4 shows the ARM Mali GPU Compression Tool in action with the
same input file as above.
Figure 8–4. ARM Mali GPU compression tool
Figure 8–5 shows the difference in quality between a higher-quality (slow) compression
and a lower-quality (fast) compression. Once again, the differences are not obvious.
    Figure 8–5. Higher quality (left), medium (middle), and lower-quality (right)
As you can see on the ARM Mali developer website, many other tools are available. You
can find many authoring and debugging tools on the ARM, Imagination Technologies,
Nvidia, and Qualcomm websites:
http://www.malideveloper.com/developer-resources/tools
http://www.imgtec.com/powervr/insider/sdkdownloads
http://developer.nvidia.com/tegra-android-development-pack
http://developer.qualcomm.com/develop/mobiletechnologies/
graphics-optimization-adreno
    For example, Imagination Technologies also provides a tool to create compressed
textures, PVRTexTool, and the Nvidia website offers a complete SDK (including Android
SDK, NDK, Eclipse, and sample code).
  NOTE: You may have to register to gain access to the various tools and documents on these websites.
Starting in Android 2.2 (API level 8), the following classes are defined to help you work
with ETC1 textures:
android.opengl.ETC1
android.opengl.ETC1Util
android.opengl.ETC1Util.ETC1Texture
Your application can compress p_w_picpaths into ETC1 textures dynamically with the
ETC1.encodeImage() and ETC1Util.compressTexture() methods. This is useful when
ETC1 textures cannot be generated offline, for example when the textures are based on
pictures stored on the device (perhaps a picture of one of the user’s contacts).
   TIP: Even though ETC1 does not support transparency透明度 (there is no alpha component in the compressed texture), you can use another single-channel texture that contains only the transparency information and combine the two textures in a shader.

1. 其他紋理壓縮格式Other Texture Compression Formats
    While ETC1 is the most common texture compression format, other formats exist and
are supported by some devices. Among these are:
PowerVR Texture Compression (PVRTC)
ATI Texture Compression (ATC or ATITC)
S3 Texture Compression (S3TC), with DXT1 to DXT5 variants
   You should experiment with various compression formats depending on which devices
you target. Because a particular GPU may be optimized for its own compression format
(for example, PowerVR GPU optimized for PVRTC compression format), you may
achieve better results with a proprietary compression format than with the more
standard ETC1 compression format.
   NOTE: You can also use Apple’s iOS texturetool to generate PVRTC textures.
Manifest Your OpenGL application’s manifest should specify two things:
Which OpenGL version it requires the device to support
Which texture compression formats the application supports
Listing 8–16 shows how to specify the device should support OpenGL ES 2.0 and
shows the application supports only two texture compression formats: ETC1 and
PVRTC.
Listing 8–16. Manifest and OpenGL
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="
http://schemas.android.com/apk/res/android"
package="com.apress.proandroid.opengl"
android:versionCode="1" android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<uses-feature android:glEsVersion="0x00020000" />
<supports-gl-texture android:name="GL_OES_compressed_ETC1_RGB8_texture" />
<supports-gl-texture android:name="GL_IMG_texture_compression_pvrtc" />
...
</manifest>
    NOTE: The OpenGL ES version is a 16.16 number, so 0x0002000 is for OpenGL ES 2.0.

    Android Market will use this information to filter applications when a device connects to
Market. For example, a device that supports only OpenGL ES 1.1 will not see
applications that use OpenGL ES 2.0. Similarly, a device that supports only ETC1
texture compression will not see applications that support only PVRTC and ATC
textures.
    If your application does not specify any texture compression format in its manifest, then
Android Market will not apply any filtering based on compression format (and therefore
will assume your application supports all compression formats). This could lead to users
installing your application only to find out that the application does not work, potentially
leading to bad reviews給出負面評論.


8.3.3 Mipmaps

  
    Often, objects in a 3D scene appear in the background場景中出現在背景的圖像 and do not use that much space on the screen. For example, using a 256x256 texture on an object that is only 10x10 pixels on the screen is a waste of resources (memory, bandwidth). Mipmaps solve this by providing multiple levels of detail for a texture, as shown in Figure 8–6.
Fige 8–6. Mipmaps from 256x256 to 1x1
   While a mipmap set uses about 33% more memory than its lone original p_w_picpath, it may improve not only performance but also visual quality.
   As you can see in Figure 8–6, each p_w_picpath is a version of the original p_w_picpath but at a different level of detail. Obviously, it is quite hard to see a flower in the 1x1 version of the texture.
   The ARM Mali GPU Texture Compression Tool can generate mipmaps for you. Instead of generating only a single .pkm file, the tool will generate all levels of details all the way to 1x1 and will create a single .pkm file for each. Your application will then have to load these different levels one by one, for example by calling ETC1Util.loadTexture() or GLES20.glTexImage2D().
    Because not all devices are equal, they may not all require the same levels of detail不必須有相同的細節層次. For  example, a Google TV device with a resolution of 1920x1080 (HD resolution) would
typically require a higher level of detail than a Samsung Nexus S with a resolution of 800x480 (WVGA). Consequently, while a 256x256 texture may be needed for a Google TV device, the Nexus S may never have any use for such texture and would be ok using a 128x128 texture.
   NOTE: Do not simply rely on the resolution of a device to determine what your application should
do. For example, Sony Google TV televisions (1920x1080 resolution) use a PowerVR SGX535 at
400MHz, which is not as powerful as the PowerVR SGX540 at 384MHz found in the newer
Samsung Galaxy Nexus (1280x720 resolution).
    How the textures will be rendered also depends on what texture parameters are set with one of the glTexParameter() methods (for example, glTexParameteri()). For example, you can set the minifying function by calling glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER, param) with param being one of the following values:
GL_NEAREST
GL_LINEAR
GL_NEAREST_MIPMAP_NEAREST
GL_NEAREST_MIPMAP_LINEAR
GL_LINEAR_MIPMAP_NEAREST
GL_LINEAR_MIPMAP_LINEAR
    While GL_NEAREST is generally faster than GL_LINEAR, and GL_LINEAR is faster than
the other four, better visual quality will be achieved with slower functions. What parameters your application will choose may depend on the user’s preferences (if you provide a way for the user to configure the quality of rendering自行配置渲染質量), however many users won’t have the patience to try various settings to find a combination that works well on their devices. As a consequence, your application should do its best to determine the OpenGL configuration it should use.
    Many things can be configured in OpenGL and some default values favor visual quality over performance, so you should become familiar with what your application can configure. For example, as far as textures are concerned, the documentation on

http://www.khronos.org/opengles/sdk/docs/man/ will show you all the different
parameters you can set.


8.3.4 多個Multiple APKs


    Because you may want to support multiple texture compression formats (optimizing as
much as possible for each device) and because mipmaps take more space, your
application may go over the size limit defined by Android Market (currently 50
megabytes(M字節)).
   If that happens, you have basically three options:
Reduce the size of your application, for example by supporting only
ETC1 texture compression.
Download textures from a remote server after the application is installed.

Generate multiple APKs, each with its own set of textures.
    Android Market lets you publish different APKs for your application, each targeting a different configuration. For example, you could have one APK using only ETC1 textures and a second one using only PVRTC textures—that is, an APK optimized for PowerVRbased Android devices. These APKs will share the same Android Market listing, and Android Market will take care of selecting the right APK for each device. Users won’t have to worry about downloading and installing the right APK as the selection is automatic and transparent.


   NOTE: Not all Android application stores support this feature, so if you plan on distributing your
application in multiple stores, try to use a single APK for all devices whenever possible.
   Of course, textures may not be the sole reason why you would need or want to release multiple APKs. For example, you may want to release a smaller APK for older devicesand a larger APK with more features for newer devices. While shipping multiple APKs is possible, it makes your maintenance and release process more complicated and therefore it is recommended you try to ship a single APK whenever possible.


8.3.5 Shaders 着色

    OpenGL ES 2.0 supports the OpenGL ES Shading Language, replacing the fixed function transformation固定變換函數 and fragment pipeline of OpenGL ES 1.x. Based on C, the language allows you to have more control over the OpenGL pipeline by writing your own vertex頂點 and fragment shaders.
   Like any C program, shaders can go from very simple to extremely complex. While there
is no single rule you have to follow, you should try to reduce complexity as much as
possible in your shaders as it can have a significant impact on performance這會非常影響性能.

8.3.6 Scene Complexity場景複雜性

   Obviously, a complex scene will take longer to render than a simple one擅長複雜運算. An easy way to
increase the frame rate therefore is to simplify the scenes to render while maintaining an acceptable visual quality. For example, as you have already seen with textures, objects that are further away can be less detailed and can be made of fewer triangles. Simpler objects will use less memory and bandwidth內存和寬帶較少.


8.3.7 Culling 消隱

   Even though GPUs are good at geometry幾乎運算 and can determine what has to be rendered,
your application should do its best to eliminate objects that are outside of the viewing frustum平截頭體 so it does not send draw commands for objects that will simply be discarded because they are not visible.
   There are numerous methods to cull objects or even triangles and while these methods are outside the scope of this book, a lower-than-expected frame rate may be caused by poor culling. For example, it is pretty easy to quickly eliminate objects that are behind the camera.
    NOTE: More often than not you want to enable back face culling so that back facing triangles in
objects are not rendered.


 

8.3.8 Render Mode渲染模式

    By default, the OpenGL renderer keeps rendering the scene, regardless of what might have changed. In some cases, the scene may not change between frames, and you may want to explicitly tell the renderer to only render the scene when you request it. You can achieve this by changing the GLSurfaceView’s render mode.
   You can set a GLSurfaceView’s render mode by calling setRenderMode() with one of the
following two values:
RENDERMODE_CONTINUOUSLY
RENDERMODE_WHEN_DIRTY
   When the render mode is set to RENDERMODE_WHEN_DIRTY, the renderer will render the scene when the surface is created, or when GLSurfaceView.requestRender() is called.


8.3.9 Power Consumption

   One of the great qualities of modern GPUs is that they can fall asleep or at least slow down very quickly during periods of inactivity. For example, the GPU can shut down (partially or fully) between two frames if it has nothing to do for a while, reducing power consumption and therefore increasing battery life.
   Once you have achieved an acceptable frame rate for your application, you may still want to optimize further if only to reduce power consumption. The faster a frame is being rendered, the sooner the GPU can idle and the longer the battery life(Gpu閒置時間越長電池就越耐用) (and the longer a user can use your application). For some applications, one of the great ways to increase battery life with OpenGL is to render frames only when the scene changed(當場景變化才渲染幀) (as described above with the RENDERMODE_WHEN_DIRTY render mode).


Summary

   The latest Android devices are very powerful pieces of hardware and are capable of great graphics, both 2D and 3D. While some optimizations may become less important than just a couple of years ago, new challenges present themselves with the higher and higher resolutions the devices support, the almost ubiquitous support for OpenGL ES 2.0, and the increasingly higher expectations from users. This chapter only scratched the surface of OpenGL ES and only introduced some techniques that are easy to
implement and yet can give tremendous results. Luckily for you, there exist many resources to learn about OpenGL, whether you are looking for beginner or advanced material. Also remember to refer to the documentation offered by the GPU vendors (ARM, Imagination Technologies, Nvidia, Qualcomm) as they explain ways to optimize rendering for their own GPU.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章