| Dirk Dougherty | 22558d0 | 2009-12-10 16:25:06 -0800 | [diff] [blame] | 1 | page.title=Introducing GLSurfaceView |
| 2 | @jd:body |
| 3 | |
| 4 | |
| 5 | <p>The {@link android android.opengl.GLSurfaceView} class makes it |
| 6 | easier for you to use OpenGL ES rendering in your applications by:</p> |
| 7 | |
| 8 | <ul> |
| 9 | <li>Providing the glue code to connect OpenGL ES to the {@link |
| 10 | android.view.View} system.</li> |
| 11 | <li>Providing the glue code to make OpenGL ES work with the {@link |
| 12 | android.app.Activity} life-cycle.</li> |
| 13 | <li>Making it easy to choose an appropriate frame buffer pixel format.</li> |
| 14 | <li>Creating and managing a separate rendering thread, to enable smooth |
| 15 | animation.</li> |
| 16 | <li>Providing easy-to-use debugging tools for tracing OpenGL ES API calls and |
| 17 | checking for errors.</li> |
| 18 | </ul> |
| 19 | |
| 20 | <p>GLSurfaceView is a good base for building an application that uses OpenGL ES |
| 21 | for part or all of its rendering. A 2D or 3D action game would be a good |
| 22 | candidate, as would a 2D or 3D data visualization application such as <a |
| 23 | href="http://www.youtube.com/watch?v=4PRfVKzuUJ4&fmt=18" title="Google Maps |
| 24 | StreetView">Google Maps StreetView</a>.</p> |
| 25 | |
| 26 | <h3>A simple GLSurfaceView application</h3> |
| 27 | |
| 28 | <p>Here's the source code to the simplest possible OpenGL ES application:</p> |
| 29 | |
| 30 | <pre>package com.example.android.apis.graphics; |
| 31 | |
| 32 | import javax.microedition.khronos.egl.EGLConfig; |
| 33 | import javax.microedition.khronos.opengles.GL10; |
| 34 | |
| 35 | import android.app.Activity; |
| 36 | import android.opengl.GLSurfaceView; |
| 37 | import android.os.Bundle; |
| 38 | |
| 39 | public class ClearActivity extends Activity { |
| 40 | @Override |
| 41 | protected void onCreate(Bundle savedInstanceState) { |
| 42 | super.onCreate(savedInstanceState); |
| 43 | mGLView = new GLSurfaceView(this); |
| 44 | mGLView.setRenderer(new ClearRenderer()); |
| 45 | setContentView(mGLView); |
| 46 | } |
| 47 | |
| 48 | @Override |
| 49 | protected void onPause() { |
| 50 | super.onPause(); |
| 51 | mGLView.onPause(); |
| 52 | } |
| 53 | |
| 54 | @Override |
| 55 | protected void onResume() { |
| 56 | super.onResume(); |
| 57 | mGLView.onResume(); |
| 58 | } |
| 59 | |
| 60 | private GLSurfaceView mGLView; |
| 61 | } |
| 62 | |
| 63 | class ClearRenderer implements GLSurfaceView.Renderer { |
| 64 | public void onSurfaceCreated(GL10 gl, EGLConfig config) { |
| 65 | // Do nothing special. |
| 66 | } |
| 67 | |
| 68 | public void onSurfaceChanged(GL10 gl, int w, int h) { |
| 69 | gl.glViewport(0, 0, w, h); |
| 70 | } |
| 71 | |
| 72 | public void onDrawFrame(GL10 gl) { |
| 73 | gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); |
| 74 | } |
| 75 | }</pre> |
| 76 | |
| 77 | <p>This program doesn't do much: it clears the screen to black on every frame. |
| 78 | But it is a complete OpenGL application that correctly implements the |
| 79 | Android activity life-cycle. It pauses rendering when the activity is |
| 80 | paused, and resumes it when the activity is resumed. You could use this |
| 81 | application as the basis for non-interactive demonstration programs. |
| 82 | Just add more OpenGL calls to the <code>ClearRenderer.onDrawFrame()</code> method. |
| 83 | Notice that you don't even need to subclass the <code>GLSurfaceView</code> view.</p> |
| 84 | |
| 85 | <p>The {@link android.opengl.GLSurfaceView.Renderer} interface has three methods:</p> |
| 86 | |
| 87 | <ul> |
| 88 | <li>The |
| 89 | <code>onSurfaceCreated()</code> method is called at the start of rendering, and |
| 90 | whenever the OpenGL ES drawing context has to be recreated. (The |
| 91 | drawing context is typically lost and recreated when the activity is |
| 92 | paused and resumed.) <code>OnSurfaceCreated()</code> is a good place to create |
| 93 | long-lived OpenGL resources such as textures.</li> |
| 94 | <li>The <code>onSurfaceChanged()</code> |
| 95 | method is called when the surface changes size. It's a good place to |
| 96 | set your OpenGL viewport. You may also want to set your camera here, if |
| 97 | it's a fixed camera that doesn't move around the scene.</li> |
| 98 | <li>The <code>onDrawFrame()</code> method is called every frame, and is |
| 99 | responsible for drawing the scene. You would typically start by calling |
| 100 | <code>glClear</code> to clear the framebuffer, followed by other OpenGL ES calls |
| 101 | to draw the current scene.</li> |
| 102 | </ul> |
| 103 | |
| 104 | <h3>How about user input?</h3> |
| 105 | |
| 106 | <p>If you want an interactive application (such as a game), you will typically |
| 107 | subclass <code>GLSurfaceView</code>, because that's an easy way of obtaining |
| 108 | input events. Here's a slightly longer example showing how to do that:</p> |
| 109 | |
| 110 | <pre>package com.google.android.ClearTest; |
| 111 | |
| 112 | import javax.microedition.khronos.egl.EGLConfig; |
| 113 | import javax.microedition.khronos.opengles.GL10; |
| 114 | |
| 115 | import android.app.Activity; |
| 116 | import android.content.Context; |
| 117 | import android.opengl.GLSurfaceView; |
| 118 | import android.os.Bundle; |
| 119 | import android.view.MotionEvent; |
| 120 | |
| 121 | public class ClearActivity extends Activity { |
| 122 | @Override |
| 123 | protected void onCreate(Bundle savedInstanceState) { |
| 124 | super.onCreate(savedInstanceState); |
| 125 | mGLView = new ClearGLSurfaceView(this); |
| 126 | setContentView(mGLView); |
| 127 | } |
| 128 | |
| 129 | @Override |
| 130 | protected void onPause() { |
| 131 | super.onPause(); |
| 132 | mGLView.onPause(); |
| 133 | } |
| 134 | |
| 135 | @Override |
| 136 | protected void onResume() { |
| 137 | super.onResume(); |
| 138 | mGLView.onResume(); |
| 139 | } |
| 140 | |
| 141 | private GLSurfaceView mGLView; |
| 142 | } |
| 143 | |
| 144 | class ClearGLSurfaceView extends GLSurfaceView { |
| 145 | public ClearGLSurfaceView(Context context) { |
| 146 | super(context); |
| 147 | mRenderer = new ClearRenderer(); |
| 148 | setRenderer(mRenderer); |
| 149 | } |
| 150 | |
| 151 | public boolean onTouchEvent(final MotionEvent event) { |
| 152 | queueEvent(new Runnable(){ |
| 153 | public void run() { |
| 154 | mRenderer.setColor(event.getX() / getWidth(), |
| 155 | event.getY() / getHeight(), 1.0f); |
| 156 | }}); |
| 157 | return true; |
| 158 | } |
| 159 | |
| 160 | ClearRenderer mRenderer; |
| 161 | } |
| 162 | |
| 163 | class ClearRenderer implements GLSurfaceView.Renderer { |
| 164 | public void onSurfaceCreated(GL10 gl, EGLConfig config) { |
| 165 | // Do nothing special. |
| 166 | } |
| 167 | |
| 168 | public void onSurfaceChanged(GL10 gl, int w, int h) { |
| 169 | gl.glViewport(0, 0, w, h); |
| 170 | } |
| 171 | |
| 172 | public void onDrawFrame(GL10 gl) { |
| 173 | gl.glClearColor(mRed, mGreen, mBlue, 1.0f); |
| 174 | gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); |
| 175 | } |
| 176 | |
| 177 | public void setColor(float r, float g, float b) { |
| 178 | mRed = r; |
| 179 | mGreen = g; |
| 180 | mBlue = b; |
| 181 | } |
| 182 | |
| 183 | private float mRed; |
| 184 | private float mGreen; |
| 185 | private float mBlue; |
| 186 | }</pre> |
| 187 | |
| 188 | <p>This application clears the screen for every frame. When you tap on the |
| 189 | screen, it sets the clear color based on the (x,y) coordinates of your touch |
| 190 | event. Note the use of <code>queueEvent()</code> in |
| 191 | <code>ClearGLSurfaceView.onTouchEvent()</code>. The <code>queueEvent()</code> |
| 192 | method is used to safely communicate between the UI thread and the rendering |
| 193 | thread. If you prefer, you can use some other Java cross-thread communication |
| 194 | technique, such as synchronized methods on the <code>Renderer</code> class |
| 195 | itself. However, queueing events is often the simplest way of dealing with |
| 196 | cross-thread communication.</p> |
| 197 | |
| 198 | <h3>Other GLSurfaceView samples</h3> |
| 199 | |
| 200 | <p>Tired |
| 201 | of just clearing the screen? You can find more interesting samples in |
| 202 | the API Demos sample included in the Android SDK. All the OpenGL ES samples have been |
| 203 | converted to use the <code>GLSurfaceView</code> view:</p> |
| 204 | |
| 205 | <ul> |
| 206 | <li>GLSurfaceView - a spinning triangle</li> |
| 207 | <li>Kube - a cube puzzle demo</li> |
| 208 | <li>Translucent GLSurfaceView - shows how to display 3D graphics on a translucent background</li> |
| 209 | <li>Textured Triangle - shows how to draw a textured 3D triangle</li> |
| 210 | <li>Sprite Text - shows how to draw text into a texture and then composite it into a 3D scene</li> |
| 211 | <li>Touch Rotate - shows how to rotate a 3D object in response to user input.</li> |
| 212 | </ul> |
| 213 | |
| 214 | <h3>Choosing a surface</h3> |
| 215 | |
| 216 | <p><code>GLSurfaceView</code> |
| 217 | helps you choose the type of surface to render to. Different Android |
| 218 | devices support different types of surfaces, with no common subset. |
| 219 | This makes it tricky problem to choose the best available surface on |
| 220 | each device. </p> |
| 221 | |
| 222 | <p>By default, <code>GLSurfaceView</code> tries to find a surface that's as |
| 223 | close as possible to a 16-bit RGB frame buffer with a 16-bit depth |
| 224 | buffer. Depending upon your application's needs you may want to change |
| 225 | this behavior. For example, the Translucent GLSurfaceView sample needs |
| 226 | an Alpha channel in order to render translucent data. <code>GLSurfaceView</code> |
| 227 | provides an overloaded <code>setEGLSurfaceChooser()</code> method to give |
| 228 | you control over which surface type is chosen:</p> |
| 229 | |
| 230 | <dl> |
| 231 | <dt><code>setEGLConfigChooser(boolean needDepth)</code></dt> |
| 232 | <dd>Choose a config that's closest to R5G6B5 with or without a 16-bit framebuffer</dd> |
| 233 | <dt><code>setEGLConfigChooser(int redSize, int greenSize,int blueSize, |
| 234 | int alphaSize,int depthSize, int stencilSize)</code></dt> |
| 235 | <dd>Choose the config with the fewest number of bits per pixel that has at least |
| 236 | as many bits-per-channel as specified in the constructor.</dd> |
| 237 | <dt><code>setEGLConfigChooser(EGLConfigChooser configChooser)</code></dt> |
| 238 | <dd>Allow total control over choosing a configuration. You pass in your own |
| 239 | implementation of <code>EGLConfigChooser</code>, which gets to inspect the |
| 240 | device's capabilities and choose a configuration.</dd> |
| 241 | </dl> |
| 242 | |
| 243 | <h3>Continuous rendering versus render-when-dirty</h3> |
| 244 | |
| 245 | <p>Most 3D applications, such as games or simulations, are continuously |
| 246 | animated. But some 3D applications are more reactive: they wait passively until |
| 247 | the user does something, and then react to it. For those types of applications, |
| 248 | the default <code>GLSurfaceView</code> behavior of continuously redrawing the |
| 249 | screen is a waste of time. If you are developing a reactive application, you can |
| 250 | call <code>GLSurfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY)</code>, which |
| 251 | turns off the continuous animation. Then you call |
| 252 | <code>GLSurfaceView.requestRender()</code> whenever you want to re-render.</p> |
| 253 | |
| 254 | <h3>Help With debugging</h3> |
| 255 | |
| 256 | <p><code>GLSurfaceView</code> has a handy built-in feature for debugging OpenGL ES |
| 257 | applications: the <code>GLSurfaceView.setDebugFlags()</code> method can be used |
| 258 | to enable logging and/or error checking your OpenGL ES calls. Call this method |
| 259 | in your <code>GLSurfaceView</code>'s constructor, before calling |
| 260 | <code>setRenderer()</code>:</p> |
| 261 | |
| 262 | <pre>public ClearGLSurfaceView(Context context) { |
| 263 | super(context); |
| 264 | // Turn on error-checking and logging |
| 265 | setDebugFlags(DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS); |
| 266 | mRenderer = new ClearRenderer(); |
| 267 | setRenderer(mRenderer); |
| 268 | }</pre> |