mobile/android/base/gfx/GLController.java

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:a77408650d2b
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 package org.mozilla.gecko.gfx;
7
8 import org.mozilla.gecko.GeckoAppShell;
9 import org.mozilla.gecko.GeckoEvent;
10 import org.mozilla.gecko.GeckoThread;
11 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
12 import org.mozilla.gecko.util.ThreadUtils;
13
14 import android.util.Log;
15
16 import javax.microedition.khronos.egl.EGL10;
17 import javax.microedition.khronos.egl.EGLConfig;
18 import javax.microedition.khronos.egl.EGLContext;
19 import javax.microedition.khronos.egl.EGLDisplay;
20 import javax.microedition.khronos.egl.EGLSurface;
21
22 /**
23 * EGLPreloadingThread is purely a preloading optimization, not something
24 * we rely on for anything else than performance. We will be initializing
25 * EGL in GLController::initEGL() when we need it, but having EGL initialization
26 * already previously done by EGLPreloadingThread::run() will make it much
27 * faster for GLController to do again.
28 *
29 * For example, here are some timings recorded on two devices:
30 *
31 * Device | EGLPreloadingThread::run() | GLController::initEGL()
32 * -----------------------+----------------------------+------------------------
33 * Nexus S (Android 2.3) | ~ 80 ms | < 0.1 ms
34 * Nexus 10 (Android 4.3) | ~ 35 ms | < 0.1 ms
35 */
36 class EGLPreloadingThread extends Thread
37 {
38 private static final String LOGTAG = "EGLPreloadingThread";
39 private EGL10 mEGL;
40 private EGLDisplay mEGLDisplay;
41
42 public EGLPreloadingThread()
43 {
44 }
45
46 @Override
47 public void run()
48 {
49 mEGL = (EGL10)EGLContext.getEGL();
50 mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
51 if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) {
52 Log.w(LOGTAG, "Can't get EGL display!");
53 return;
54 }
55
56 int[] returnedVersion = new int[2];
57 if (!mEGL.eglInitialize(mEGLDisplay, returnedVersion)) {
58 Log.w(LOGTAG, "eglInitialize failed");
59 return;
60 }
61 }
62 }
63
64 /**
65 * This class is a singleton that tracks EGL and compositor things over
66 * the lifetime of Fennec running.
67 * We only ever create one C++ compositor over Fennec's lifetime, but
68 * most of the Java-side objects (e.g. LayerView, GeckoLayerClient,
69 * LayerRenderer) can all get destroyed and re-created if the GeckoApp
70 * activity is destroyed. This GLController is never destroyed, so that
71 * the mCompositorCreated field and other state variables are always
72 * accurate.
73 */
74 public class GLController {
75 private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
76 private static final String LOGTAG = "GeckoGLController";
77
78 private static GLController sInstance;
79
80 private LayerView mView;
81 private boolean mServerSurfaceValid;
82 private int mWidth, mHeight;
83
84 /* This is written by the compositor thread (while the UI thread
85 * is blocked on it) and read by the UI thread. */
86 private volatile boolean mCompositorCreated;
87
88 private EGL10 mEGL;
89 private EGLDisplay mEGLDisplay;
90 private EGLConfig mEGLConfig;
91 private EGLPreloadingThread mEGLPreloadingThread;
92 private EGLSurface mEGLSurfaceForCompositor;
93
94 private static final int LOCAL_EGL_OPENGL_ES2_BIT = 4;
95
96 private static final int[] CONFIG_SPEC_16BPP = {
97 EGL10.EGL_RED_SIZE, 5,
98 EGL10.EGL_GREEN_SIZE, 6,
99 EGL10.EGL_BLUE_SIZE, 5,
100 EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
101 EGL10.EGL_RENDERABLE_TYPE, LOCAL_EGL_OPENGL_ES2_BIT,
102 EGL10.EGL_NONE
103 };
104
105 private static final int[] CONFIG_SPEC_24BPP = {
106 EGL10.EGL_RED_SIZE, 8,
107 EGL10.EGL_GREEN_SIZE, 8,
108 EGL10.EGL_BLUE_SIZE, 8,
109 EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
110 EGL10.EGL_RENDERABLE_TYPE, LOCAL_EGL_OPENGL_ES2_BIT,
111 EGL10.EGL_NONE
112 };
113
114 private GLController() {
115 mEGLPreloadingThread = new EGLPreloadingThread();
116 mEGLPreloadingThread.start();
117 }
118
119 static GLController getInstance(LayerView view) {
120 if (sInstance == null) {
121 sInstance = new GLController();
122 }
123 sInstance.mView = view;
124 return sInstance;
125 }
126
127 synchronized void serverSurfaceDestroyed() {
128 ThreadUtils.assertOnUiThread();
129
130 mServerSurfaceValid = false;
131
132 if (mEGLSurfaceForCompositor != null) {
133 mEGL.eglDestroySurface(mEGLDisplay, mEGLSurfaceForCompositor);
134 mEGLSurfaceForCompositor = null;
135 }
136
137 // We need to coordinate with Gecko when pausing composition, to ensure
138 // that Gecko never executes a draw event while the compositor is paused.
139 // This is sent synchronously to make sure that we don't attempt to use
140 // any outstanding Surfaces after we call this (such as from a
141 // serverSurfaceDestroyed notification), and to make sure that any in-flight
142 // Gecko draw events have been processed. When this returns, composition is
143 // definitely paused -- it'll synchronize with the Gecko event loop, which
144 // in turn will synchronize with the compositor thread.
145 if (mCompositorCreated) {
146 GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createCompositorPauseEvent());
147 }
148 }
149
150 synchronized void serverSurfaceChanged(int newWidth, int newHeight) {
151 ThreadUtils.assertOnUiThread();
152
153 mWidth = newWidth;
154 mHeight = newHeight;
155 mServerSurfaceValid = true;
156
157 // we defer to a runnable the task of updating the compositor, because this is going to
158 // call back into createEGLSurfaceForCompositor, which will try to create an EGLSurface
159 // against mView, which we suspect might fail if called too early. By posting this to
160 // mView, we hope to ensure that it is deferred until mView is actually "ready" for some
161 // sense of "ready".
162 mView.post(new Runnable() {
163 @Override
164 public void run() {
165 updateCompositor();
166 }
167 });
168 }
169
170 void updateCompositor() {
171 ThreadUtils.assertOnUiThread();
172
173 if (mCompositorCreated) {
174 // If the compositor has already been created, just resume it instead. We don't need
175 // to block here because if the surface is destroyed before the compositor grabs it,
176 // we can handle that gracefully (i.e. the compositor will remain paused).
177 resumeCompositor(mWidth, mHeight);
178 return;
179 }
180
181 if (!AttemptPreallocateEGLSurfaceForCompositor()) {
182 return;
183 }
184
185 // Only try to create the compositor if we have a valid surface and gecko is up. When these
186 // two conditions are satisfied, we can be relatively sure that the compositor creation will
187 // happen without needing to block anyhwere. Do it with a sync gecko event so that the
188 // android doesn't have a chance to destroy our surface in between.
189 if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
190 GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createCompositorCreateEvent(mWidth, mHeight));
191 }
192 }
193
194 void compositorCreated() {
195 // This is invoked on the compositor thread, while the java UI thread
196 // is blocked on the gecko sync event in updateCompositor() above
197 mCompositorCreated = true;
198 }
199
200 public boolean isServerSurfaceValid() {
201 return mServerSurfaceValid;
202 }
203
204 public boolean isCompositorCreated() {
205 return mCompositorCreated;
206 }
207
208 private void initEGL() {
209 if (mEGL != null) {
210 return;
211 }
212
213 // This join() should not be necessary, but makes this code a bit easier to think about.
214 // The EGLPreloadingThread should long be done by now, and even if it's not,
215 // it shouldn't be a problem to be initalizing EGL from two different threads.
216 // Still, having this join() here means that we don't have to wonder about what
217 // kind of caveats might exist with EGL initialization reentrancy on various drivers.
218 try {
219 mEGLPreloadingThread.join();
220 } catch (InterruptedException e) {
221 Log.w(LOGTAG, "EGLPreloadingThread interrupted", e);
222 }
223
224 mEGL = (EGL10)EGLContext.getEGL();
225
226 mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
227 if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) {
228 Log.w(LOGTAG, "Can't get EGL display!");
229 return;
230 }
231
232 // while calling eglInitialize here should not be necessary as it was already called
233 // by the EGLPreloadingThread, it really doesn't cost much to call it again here,
234 // and makes this code easier to think about: EGLPreloadingThread is only a
235 // preloading optimization, not something we rely on for anything else.
236 //
237 // Also note that while calling eglInitialize isn't necessary on Android 4.x
238 // (at least Android's HardwareRenderer does it for us already), it is necessary
239 // on Android 2.x.
240 int[] returnedVersion = new int[2];
241 if (!mEGL.eglInitialize(mEGLDisplay, returnedVersion)) {
242 Log.w(LOGTAG, "eglInitialize failed");
243 return;
244 }
245
246 mEGLConfig = chooseConfig();
247 }
248
249 private EGLConfig chooseConfig() {
250 int[] desiredConfig;
251 int rSize, gSize, bSize;
252 int[] numConfigs = new int[1];
253
254 switch (GeckoAppShell.getScreenDepth()) {
255 case 24:
256 desiredConfig = CONFIG_SPEC_24BPP;
257 rSize = gSize = bSize = 8;
258 break;
259 case 16:
260 default:
261 desiredConfig = CONFIG_SPEC_16BPP;
262 rSize = 5; gSize = 6; bSize = 5;
263 break;
264 }
265
266 if (!mEGL.eglChooseConfig(mEGLDisplay, desiredConfig, null, 0, numConfigs) ||
267 numConfigs[0] <= 0) {
268 throw new GLControllerException("No available EGL configurations " +
269 getEGLError());
270 }
271
272 EGLConfig[] configs = new EGLConfig[numConfigs[0]];
273 if (!mEGL.eglChooseConfig(mEGLDisplay, desiredConfig, configs, numConfigs[0], numConfigs)) {
274 throw new GLControllerException("No EGL configuration for that specification " +
275 getEGLError());
276 }
277
278 // Select the first configuration that matches the screen depth.
279 int[] red = new int[1], green = new int[1], blue = new int[1];
280 for (EGLConfig config : configs) {
281 mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_RED_SIZE, red);
282 mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_GREEN_SIZE, green);
283 mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_BLUE_SIZE, blue);
284 if (red[0] == rSize && green[0] == gSize && blue[0] == bSize) {
285 return config;
286 }
287 }
288
289 throw new GLControllerException("No suitable EGL configuration found");
290 }
291
292 private synchronized boolean AttemptPreallocateEGLSurfaceForCompositor() {
293 if (mEGLSurfaceForCompositor == null) {
294 initEGL();
295 try {
296 mEGLSurfaceForCompositor = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mView.getNativeWindow(), null);
297 // In failure cases, eglCreateWindowSurface should return EGL_NO_SURFACE.
298 // We currently normalize this to null, and compare to null in all our checks.
299 if (mEGLSurfaceForCompositor == EGL10.EGL_NO_SURFACE) {
300 mEGLSurfaceForCompositor = null;
301 }
302 } catch (Exception e) {
303 Log.e(LOGTAG, "eglCreateWindowSurface threw", e);
304 }
305 }
306 if (mEGLSurfaceForCompositor == null) {
307 Log.w(LOGTAG, "eglCreateWindowSurface returned no surface!");
308 }
309 return mEGLSurfaceForCompositor != null;
310 }
311
312 @WrapElementForJNI(allowMultithread = true, stubName = "CreateEGLSurfaceForCompositorWrapper")
313 private synchronized EGLSurface createEGLSurfaceForCompositor() {
314 AttemptPreallocateEGLSurfaceForCompositor();
315 EGLSurface result = mEGLSurfaceForCompositor;
316 mEGLSurfaceForCompositor = null;
317 return result;
318 }
319
320 private String getEGLError() {
321 return "Error " + (mEGL == null ? "(no mEGL)" : mEGL.eglGetError());
322 }
323
324 void resumeCompositor(int width, int height) {
325 // Asking Gecko to resume the compositor takes too long (see
326 // https://bugzilla.mozilla.org/show_bug.cgi?id=735230#c23), so we
327 // resume the compositor directly. We still need to inform Gecko about
328 // the compositor resuming, so that Gecko knows that it can now draw.
329 // It is important to not notify Gecko until after the compositor has
330 // been resumed, otherwise Gecko may send updates that get dropped.
331 if (mCompositorCreated) {
332 GeckoAppShell.scheduleResumeComposition(width, height);
333 GeckoAppShell.sendEventToGecko(GeckoEvent.createCompositorResumeEvent());
334 }
335 }
336
337 public static class GLControllerException extends RuntimeException {
338 public static final long serialVersionUID = 1L;
339
340 GLControllerException(String e) {
341 super(e);
342 }
343 }
344 }

mercurial