|
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 } |