Home | History | Annotate | Download | only in graphics
      1 <html devsite>
      2   <head>
      3     <title>SurfaceView and GLSurfaceView</title>
      4     <meta name="project_path" value="/_project.yaml" />
      5     <meta name="book_path" value="/_book.yaml" />
      6   </head>
      7   <body>
      8   <!--
      9       Copyright 2017 The Android Open Source Project
     10 
     11       Licensed under the Apache License, Version 2.0 (the "License");
     12       you may not use this file except in compliance with the License.
     13       You may obtain a copy of the License at
     14 
     15           http://www.apache.org/licenses/LICENSE-2.0
     16 
     17       Unless required by applicable law or agreed to in writing, software
     18       distributed under the License is distributed on an "AS IS" BASIS,
     19       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     20       See the License for the specific language governing permissions and
     21       limitations under the License.
     22   -->
     23 
     24 
     25 
     26 <p>The Android app framework UI is based on a hierarchy of objects that start
     27 with View. All UI elements go through a complicated measurement and layout
     28 process that fits them into a rectangular area, and all visible View objects are
     29 rendered to a SurfaceFlinger-created Surface that was set up by the
     30 WindowManager when the app was brought to the foreground. The app's UI thread
     31 performs layout and rendering to a single buffer (regardless of the number of
     32 Layouts and Views and whether or not the Views are hardware-accelerated).</p>
     33 
     34 <p>A SurfaceView takes the same parameters as other views, so you can give it a
     35 position and size, and fit other elements around it. When it comes time to
     36 render, however, the contents are completely transparent; The View part of a
     37 SurfaceView is just a see-through placeholder.</p>
     38 
     39 <p>When the SurfaceView's View component is about to become visible, the
     40 framework asks the WindowManager to ask SurfaceFlinger to create a new Surface.
     41 (This doesn't happen synchronously, which is why you should provide a callback
     42 that notifies you when the Surface creation finishes.) By default, the new
     43 Surface is placed behind the app UI Surface, but the default Z-ordering can be
     44 overridden to put the Surface on top.</p>
     45 
     46 <p>Whatever you render onto this Surface will be composited by SurfaceFlinger,
     47 not by the app. This is the real power of SurfaceView: The Surface you get can
     48 be rendered by a separate thread or a separate process, isolated from any
     49 rendering performed by the app UI, and the buffers go directly to
     50 SurfaceFlinger. You can't totally ignore the UI thread&mdash;you still have to
     51 coordinate with the Activity lifecycle and you may need to adjust something if
     52 the size or position of the View changes&mdash;but you have a whole Surface all
     53 to yourself. Blending with the app UI and other layers is handled by the
     54 Hardware Composer.</p>
     55 
     56 <p>The new Surface is the producer side of a BufferQueue, whose consumer is a
     57 SurfaceFlinger layer. You can update the Surface with any mechanism that can
     58 feed a BufferQueue, such as surface-supplied Canvas functions, attach an
     59 EGLSurface and draw on it with GLES, or configure a MediaCodec video decoder to
     60 write to it.</p>
     61 
     62 <h2 id=composition>Composition and the Hardware Scaler</h2>
     63 
     64 <p>Let's take a closer look at <code>dumpsys SurfaceFlinger</code>. The
     65 following output was taken while playing a movie in Grafika's "Play video
     66 (SurfaceView)" activity on a Nexus 5 in portrait orientation; the video is QVGA
     67 (320x240):</p>
     68 <p><pre>
     69     type    |          source crop              |           frame           name
     70 ------------+-----------------------------------+--------------------------------
     71         HWC | [    0.0,    0.0,  320.0,  240.0] | [   48,  411, 1032, 1149] SurfaceView
     72         HWC | [    0.0,   75.0, 1080.0, 1776.0] | [    0,   75, 1080, 1776] com.android.grafika/com.android.grafika.PlayMovieSurfaceActivity
     73         HWC | [    0.0,    0.0, 1080.0,   75.0] | [    0,    0, 1080,   75] StatusBar
     74         HWC | [    0.0,    0.0, 1080.0,  144.0] | [    0, 1776, 1080, 1920] NavigationBar
     75   FB TARGET | [    0.0,    0.0, 1080.0, 1920.0] | [    0,    0, 1080, 1920] HWC_FRAMEBUFFER_TARGET
     76 </pre></p>
     77 
     78 <ul>
     79 <li>The <strong>list order</strong> is back to front: the SurfaceView's Surface
     80 is in the back, the app UI layer sits on top of that, followed by the status and
     81 navigation bars that are above everything else.</li>
     82 <li>The <strong>source crop</strong> values indicate the portion of the
     83 Surface's buffer that SurfaceFlinger will display. The app UI was given a
     84 Surface equal to the full size of the display (1080x1920), but as there is no
     85 point rendering and compositing pixels that will be obscured by the status and
     86 navigation bars, the source is cropped to a rectangle that starts 75 pixels from
     87 the top and ends 144 pixels from the bottom. The status and navigation bars have
     88 smaller Surfaces, and the source crop describes a rectangle that begins at the
     89 top left (0,0) and spans their content.</li>
     90 <li>The <strong>frame</strong> values specify the rectangle where pixels
     91 appear on the display. For the app UI layer, the frame matches the source crop
     92 because we are copying (or overlaying) a portion of a display-sized layer to the
     93 same location in another display-sized layer. For the status and navigation
     94 bars, the size of the frame rectangle is the same, but the position is adjusted
     95 so the navigation bar appears at the bottom of the screen.</li>
     96 <li>The <strong>SurfaceView layer</strong> holds our video content. The source crop
     97 matches the video size, which SurfaceFlinger knows because the MediaCodec
     98 decoder (the buffer producer) is dequeuing buffers that size. The frame
     99 rectangle has a completely different size&mdash;984x738.</li>
    100 </ul>
    101 
    102 <p>SurfaceFlinger handles size differences by scaling the buffer contents to
    103 fill the frame rectangle, upscaling or downscaling as needed. This particular
    104 size was chosen because it has the same aspect ratio as the video (4:3), and is
    105 as wide as possible given the constraints of the View layout (which includes
    106 some padding at the edges of the screen for aesthetic reasons).</p>
    107 
    108 <p>If you started playing a different video on the same Surface, the underlying
    109 BufferQueue would reallocate buffers to the new size automatically, and
    110 SurfaceFlinger would adjust the source crop. If the aspect ratio of the new
    111 video is different, the app would need to force a re-layout of the View to match
    112 it, which causes the WindowManager to tell SurfaceFlinger to update the frame
    113 rectangle.</p>
    114 
    115 <p>If you're rendering on the Surface through some other means (such as GLES),
    116 you can set the Surface size using the <code>SurfaceHolder#setFixedSize()</code>
    117 call. For example, you could configure a game to always render at 1280x720,
    118 which would significantly reduce the number of pixels that must be touched to
    119 fill the screen on a 2560x1440 tablet or 4K television. The display processor
    120 handles the scaling. If you don't want to letter- or pillar-box your game, you
    121 could adjust the game's aspect ratio by setting the size so that the narrow
    122 dimension is 720 pixels but the long dimension is set to maintain the aspect
    123 ratio of the physical display (e.g. 1152x720 to match a 2560x1600 display).
    124 For an example of this approach, see Grafika's "Hardware scaler exerciser"
    125 activity.</p>
    126 
    127 <h2 id=glsurfaceview>GLSurfaceView</h2>
    128 
    129 <p>The GLSurfaceView class provides helper classes for managing EGL contexts,
    130 inter-thread communication, and interaction with the Activity lifecycle. That's
    131 it. You do not need to use a GLSurfaceView to use GLES.</p>
    132 
    133 <p>For example, GLSurfaceView creates a thread for rendering and configures an
    134 EGL context there. The state is cleaned up automatically when the activity
    135 pauses. Most apps won't need to know anything about EGL to use GLES with
    136 GLSurfaceView.</p>
    137 
    138 <p>In most cases, GLSurfaceView is very helpful and can make working with GLES
    139 easier. In some situations, it can get in the way. Use it if it helps, don't
    140 if it doesn't.</p>
    141 
    142 <h2 id=activity>SurfaceView and the Activity Lifecycle</h2>
    143 
    144 <p>When using a SurfaceView, it's considered good practice to render the Surface
    145 from a thread other than the main UI thread. This raises some questions about
    146 the interaction between that thread and the Activity lifecycle.</p>
    147 
    148 <p>For an Activity with a SurfaceView, there are two separate but interdependent
    149 state machines:</p>
    150 
    151 <ol>
    152 <li>Application onCreate/onResume/onPause</li>
    153 <li>Surface created/changed/destroyed</li>
    154 </ol>
    155 
    156 <p>When the Activity starts, you get callbacks in this order:</p>
    157 
    158 <ul>
    159 <li>onCreate</li>
    160 <li>onResume</li>
    161 <li>surfaceCreated</li>
    162 <li>surfaceChanged</li>
    163 </ul>
    164 
    165 <p>If you hit back you get:</p>
    166 
    167 <ul>
    168 <li>onPause</li>
    169 <li>surfaceDestroyed (called just before the Surface goes away)</li>
    170 </ul>
    171 
    172 <p>If you rotate the screen, the Activity is torn down and recreated and you
    173 get the full cycle. You can tell it's a quick restart by checking
    174 <code>isFinishing()</code>. It might be possible to start/stop an Activity so
    175 quickly that <code>surfaceCreated()</code> might actually happen after
    176 <code>onPause()</code>.</p>
    177 
    178 <p>If you tap the power button to blank the screen, you get only
    179 <code>onPause()</code>&mdash;no <code>surfaceDestroyed()</code>. The Surface
    180 remains alive, and rendering can continue. You can even keep getting
    181 Choreographer events if you continue to request them. If you have a lock
    182 screen that forces a different orientation, your Activity may be restarted when
    183 the device is unblanked; but if not, you can come out of screen-blank with the
    184 same Surface you had before.</p>
    185 
    186 <p>This raises a fundamental question when using a separate renderer thread with
    187 SurfaceView: Should the lifespan of the thread be tied to that of the Surface or
    188 the Activity? The answer depends on what you want to happen when the screen
    189 goes blank: (1) start/stop the thread on Activity start/stop or (2) start/stop
    190 the thread on Surface create/destroy.</p>
    191 
    192 <p>Option 1 interacts well with the app lifecycle. We start the renderer thread
    193 in <code>onResume()</code> and stop it in <code>onPause()</code>. It gets a bit
    194 awkward when creating and configuring the thread because sometimes the Surface
    195 will already exist and sometimes it won't (e.g. it's still alive after toggling
    196 the screen with the power button). We have to wait for the surface to be
    197 created before we do some initialization in the thread, but we can't simply do
    198 it in the <code>surfaceCreated()</code> callback because that won't fire again
    199 if the Surface didn't get recreated. So we need to query or cache the Surface
    200 state, and forward it to the renderer thread.</p>
    201 
    202 <p class="note"><strong>Note:</strong> Be careful when passing objects
    203 between threads. It is best to pass the Surface or SurfaceHolder through a
    204 Handler message (rather than just stuffing it into the thread) to avoid issues
    205 on multi-core systems. For details, refer to
    206 <a href="http://developer.android.com/training/articles/smp.html">Android
    207 SMP Primer</a>.</p>
    208 
    209 <p>Option 2 is appealing because the Surface and the renderer are logically
    210 intertwined. We start the thread after the Surface has been created, which
    211 avoids some inter-thread communication concerns, and Surface created/changed
    212 messages are simply forwarded. We need to ensure rendering stops when the
    213 screen goes blank and resumes when it un-blanks; this could be a simple matter
    214 of telling Choreographer to stop invoking the frame draw callback. Our
    215 <code>onResume()</code> will need to resume the callbacks if and only if the
    216 renderer thread is running. It may not be so trivial though&mdash;if we animate
    217 based on elapsed time between frames, we could have a very large gap when the
    218 next event arrives; an explicit pause/resume message may be desirable.</p>
    219 
    220 <p class="note"><strong>Note:</strong> For an example of Option 2, see Grafika's
    221 "Hardware scaler exerciser."</p>
    222 
    223 <p>Both options are primarily concerned with how the renderer thread is
    224 configured and whether it's executing. A related concern is extracting state
    225 from the thread when the Activity is killed (in <code>onPause()</code> or
    226 <code>onSaveInstanceState()</code>); in such cases, Option 1 works best because
    227 after the renderer thread has been joined its state can be accessed without
    228 synchronization primitives.</p>
    229 
    230   </body>
    231 </html>
    232