Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(241)

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManager.java

Issue 2201483002: Improve transition between opaque and translucent compositor views. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: now without VR-breaking badness Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.compositor;
6
7 import android.content.Context;
8 import android.graphics.PixelFormat;
9 import android.graphics.drawable.Drawable;
10 import android.view.SurfaceHolder;
11 import android.view.SurfaceView;
12 import android.view.View;
13 import android.view.ViewGroup;
14 import android.widget.FrameLayout;
15
16 /**
17 * Manage multiple SurfaceViews for the compositor, so that transitions between
18 * surfaces with and without an alpha channel can be visually smooth.
19 *
20 * This class allows a client to request a 'translucent' or 'opaque' surface, an d we will signal via
21 * SurfaceHolder.Callback when it's ready. We guarantee that the client will re ceive surfaceCreated
22 * / surfaceDestroyed only for a surface that represents the most recently reque sted PixelFormat.
23 *
24 * Internally, we maintain two SurfaceViews, since calling setFormat() to change the PixelFormat
25 * results in a visual glitch as the surface is torn down. crbug.com/679902
26 *
27 * The client has the responsibility to call doneWithUnownedSurface() at some po int between when we
28 * call back its surfaceCreated, when it is safe for us to hide the SurfaceView with the wrong
29 * format. It is okay if it requests multiple surfaces without calling doneWith UnownedSurface.
30 *
31 * If the client requests the same format more than once in a row, it will still receive destroyed /
32 * created / changed messages for it, even though we won't tear it down.
33 *
34 * The full design doc is at https://goo.gl/aAmQzR .
35 */
36 class CompositorSurfaceManager implements SurfaceHolder.Callback {
37 private static class SurfaceState {
38 public SurfaceView surfaceView;
39
40 // Do we expect a surfaceCreated?
41 public boolean createPending;
42
43 // Have we started destroying |surfaceView|, but haven't received surfac eDestroyed yet?
44 public boolean destroyPending;
45
46 // Last PixelFormat that we received, or UNKNOWN if we don't know / don' t want to cache it.
47 public int format;
48
49 // Last known width, height for thsi surface.
50 public int width;
51 public int height;
52
53 // Parent ViewGroup, or null.
54 private ViewGroup mParent;
55
56 public SurfaceState(Context context, int format, SurfaceHolder.Callback callback) {
57 surfaceView = new SurfaceView(context);
58 surfaceView.setZOrderMediaOverlay(true);
59 surfaceView.setVisibility(View.VISIBLE);
60 surfaceHolder().setFormat(format);
61 surfaceHolder().addCallback(callback);
62
63 // Set this to UNKNOWN until we get a format back.
64 this.format = PixelFormat.UNKNOWN;
65 }
66
67 public SurfaceHolder surfaceHolder() {
68 return surfaceView.getHolder();
69 }
70
71 public boolean isValid() {
72 return surfaceHolder().getSurface().isValid();
73 }
74
75 // Attach to |parent|, such that isAttached() will be correct immediatel y. Otherwise,
76 // attaching and detaching can cause surfaceCreated / surfaceDestroyed c allbacks without
77 // View.hasParent being up to date.
78 public void attachTo(ViewGroup parent, FrameLayout.LayoutParams lp) {
79 mParent = parent;
80 mParent.addView(surfaceView, lp);
81 }
82
83 public void detachFromParent() {
84 final ViewGroup parent = mParent;
85 // Since removeView can call surfaceDestroyed before returning, be s ure that isAttached
86 // will return false.
87 mParent = null;
88 parent.removeView(surfaceView);
89 }
90
91 public boolean isAttached() {
92 return mParent != null;
93 }
94 }
95
96 // SurfaceView with a translucent PixelFormat.
97 private final SurfaceState mTranslucent;
98
99 // SurfaceView with an opaque PixelFormat.
100 private final SurfaceState mOpaque;
101
102 // Surface that we last gave to the client with surfaceCreated. Cleared whe n we call
103 // surfaceDestroyed on |mClient|. Note that it's not necessary that Android has notified us
104 // the surface has been destroyed; we deliberately keep it around until the client tells us that
105 // it's okay to get rid of it.
106 private SurfaceState mOwnedByClient;
107
108 // Surface that was most recently requested by the client.
109 private SurfaceState mRequestedByClient;
110
111 // Client that we notify about surface change events.
112 private SurfaceHolder.Callback mClient;
113
114 // View to which we'll attach the SurfaceView.
115 private final ViewGroup mParentView;
116
117 public CompositorSurfaceManager(ViewGroup parentView, SurfaceHolder.Callback client) {
118 mParentView = parentView;
119 mClient = client;
120
121 mTranslucent = new SurfaceState(parentView.getContext(), PixelFormat.TRA NSLUCENT, this);
122 mOpaque = new SurfaceState(mParentView.getContext(), PixelFormat.OPAQUE, this);
123 }
124
125 /**
126 * Turn off everything.
127 */
128 public void shutDown() {
129 mTranslucent.surfaceHolder().removeCallback(this);
130 mOpaque.surfaceHolder().removeCallback(this);
131 }
132
133 /**
134 * Called by the client to request a surface. Once called, we guarantee tha t the next call to
135 * surfaceCreated will match the most recent value of |format|. If the surf ace is already
136 * available for use, then we'll send synthetic callbacks as though it were destroyed and
137 * recreated. Note that |format| must be either OPAQUE or TRANSLUCENT.
138 */
139 public void requestSurface(int format) {
140 mRequestedByClient = (format == PixelFormat.TRANSLUCENT) ? mTranslucent : mOpaque;
141
142 // If destruction is pending, then we must wait for it to complete. Whe n we're notified
143 // that it is destroyed, we'll re-start construction if the client still wants this surface.
144 // Note that we could send a surfaceDestroyed for the owned surface, if there is one, but we
145 // defer it until later so that the client can still use it until the ne w one is ready.
146 if (mRequestedByClient.destroyPending) return;
147
148 // The requested surface isn't being torn down.
149
150 // If the surface isn't attached yet, then attach it. Otherwise, we're still waiting for
151 // the surface to be created, or we've already received surfaceCreated f or it.
152 if (!mRequestedByClient.isAttached()) {
153 attachSurfaceNow(mRequestedByClient);
154 assert mRequestedByClient.isAttached();
155 return;
156 }
157
158 // Surface is not pending destroy, and is attached. See if we need to s end any synthetic
159 // callbacks to the client. If we're expecting a callback from Android, then we'll handle
160 // it when it arrives instead.
161 if (mRequestedByClient.createPending) return;
162
163 // Surface is attached and no create is pending. Send a synthetic creat e. Note that, if
164 // Android destroyed the surface itself, then we'd have set |createPendi ng| at that point.
165 // We don't check |isValid| here, since, technically, there could be a d estroy in flight
166 // from Android. It's okay; we'll just notify the client at that point. Either way, we
167 // must tell the client that it now owns the surface.
168
169 // Send a notification about any owned surface. Note that this can be | mRequestedByClient|
170 // which is fine. We'll send destroy / create for it. Also note that w e don't actually
171 // start tear-down of the owned surface; the client notifies us via done WithUnownedSurface
172 // when it is safe to do that.
173 disownClientSurface(mOwnedByClient);
174
175 // The client now owns |mRequestedByClient|. Notify it that it's ready.
176 mOwnedByClient = mRequestedByClient;
177 mClient.surfaceCreated(mOwnedByClient.surfaceHolder());
178
179 // See if we're expecting a surfaceChanged. If not, then send a synthet ic one.
180 if (mOwnedByClient.format != PixelFormat.UNKNOWN) {
181 mClient.surfaceChanged(mOwnedByClient.surfaceHolder(), mOwnedByClien t.width,
182 mOwnedByClient.height, mOwnedByClient.format);
183 }
184 }
185
186 /**
187 * Called to notify us that the client no longer needs the surface that it d oesn't own. This
188 * tells us that we may destroy it. Note that it's okay if it never had an unowned surface.
189 */
190 public void doneWithUnownedSurface() {
191 if (mOwnedByClient == null) return;
192
193 SurfaceState unowned = (mOwnedByClient == mTranslucent) ? mOpaque : mTra nslucent;
194
195 if (mRequestedByClient == unowned) {
196 // Client is giving us back a surface that it's since requested but hasn't gotten yet.
197 // Do nothing. It will be notified when the new surface is ready, a nd it can call us
198 // again for the other surface, if it wants.
199 return;
200 }
201
202 // Start destruction of this surface. To prevent recursive call-backs t o the client, we
203 // post this for later.
204 detachSurfaceLater(unowned);
205 }
206
207 /**
208 * Return the currently owned SurfaceHolder, if any.
209 */
210 public SurfaceHolder getHolder() {
211 return mOwnedByClient != null ? mOwnedByClient.surfaceHolder() : null;
212 }
213
214 /**
215 * Destroy and re-create the surface. Useful for a JB workaround needed by CompositorView.
216 */
217 public void recreateSurfaceForJellyBean() {
218 // If they don't have a surface, then they'll get a new one anyway.
219 if (mOwnedByClient == null) return;
220
221 // Notify the client that it no longer owns this surface, then destroy i t. When destruction
222 // completes, we will recreate it automatically, since it will look like the client since
223 // re-requested it. That's why we send surfaceDestroyed here rather tha n letting our
224 // surfaceDestroyed do it when destruction completes. If we just starte d destruction while
225 // the client still owns the surface, then our surfaceDestroyed would as sume that Android
226 // initiated the destruction, and wait for Android to recreate it.
227
228 mParentView.post(new Runnable() {
229 @Override
230 public void run() {
231 if (mOwnedByClient == null) return;
232 SurfaceState owned = mOwnedByClient;
233 mClient.surfaceDestroyed(mOwnedByClient.surfaceHolder());
234 mOwnedByClient = null;
235 detachSurfaceNow(owned);
236 }
237 });
238 }
239
240 @Override
241 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
242 SurfaceState state = getStateForHolder(holder);
243 assert state != null;
244
245 // If this is the surface that the client currently cares about, then no tify the client.
246 // Note that surfaceChanged is guaranteed to come only after surfaceCrea ted. Also, if the
247 // client has requested a different surface but hasn't gotten it yet, th en skip this.
248 if (state == mOwnedByClient && state == mRequestedByClient) {
249 state.width = width;
250 state.height = height;
251 state.format = format;
252 mClient.surfaceChanged(holder, format, width, height);
253 }
254 }
255
256 @Override
257 public void surfaceCreated(SurfaceHolder holder) {
258 SurfaceState state = getStateForHolder(holder);
259 assert state != null;
260 // Note that |createPending| might not be set, if Android destroyed and recreated this
261 // surface on its own.
262
263 if (state != mRequestedByClient) {
264 // Surface is created, but it's not the one that's been requested mo st recently. Just
265 // destroy it again.
266 detachSurfaceLater(state);
267 return;
268 }
269
270 // No create is pending.
271 state.createPending = false;
272
273 // A surfaceChanged should arrive.
274 state.format = PixelFormat.UNKNOWN;
275
276 // The client requested a surface, and it's now available. If the clien t owns a surface,
277 // then notify it that it doesn't. Note that the client can't own |stat e| at this point,
278 // since we would have removed ownership when we got surfaceDestroyed. It's okay if the
279 // client doesn't own either surface.
280 assert mOwnedByClient != state;
281 disownClientSurface(mOwnedByClient);
282
283 // The client now owns this surface, so notify it.
284 mOwnedByClient = mRequestedByClient;
285 mClient.surfaceCreated(mOwnedByClient.surfaceHolder());
286 }
287
288 @Override
289 public void surfaceDestroyed(SurfaceHolder holder) {
290 SurfaceState state = getStateForHolder(holder);
291 assert state != null;
292
293 // If no destroy is pending, then Android chose to destroy this surface and will, hopefully,
294 // re-create it at some point. Otherwise, a destroy is either posted or has already
295 // detached this SurfaceView. If it's already detached, then the destru ction is complete
296 // and we can clear |destroyPending|. Otherwise, Android has destroyed this surface while
297 // our destroy was posted, and might even return it before it runs. Whe n the post runs, it
298 // can sort that out based on whether the surface is valid or not.
299 if (!state.destroyPending) {
300 state.createPending = true;
301 } else if (!state.isAttached()) {
302 state.destroyPending = false;
303 }
304
305 state.format = PixelFormat.UNKNOWN;
306
307 // If the client owns this surface, then notify it synchronously that it no longer does.
308 // This can happen if Android destroys the surface on its own. It's als o possible that
309 // we've detached it, if a destroy was pending. Either way, notify the client.
310 if (state == mOwnedByClient) {
311 disownClientSurface(mOwnedByClient);
312
313 // Do not re-request the surface here. If android gives the surface back, then we'll
314 // re-signal the client about construction.
315 return;
316 }
317
318 // The client doesn't own this surface, but might want it.
319 // If the client has requested this surface, then start construction on it. The client will
320 // be notified when it completes. This can happen if the client re-requ ests a surface after
321 // we start destruction on it from a previous request, for example. We post this for later,
322 // since we might be called while removing |state| from the view tree. I n general, posting
323 // from here is good.
324 if (state == mRequestedByClient && !state.isAttached()) {
325 attachSurfaceLater(state);
326 } else if (state != mRequestedByClient && state.isAttached()) {
327 // This isn't the requested surface. If android destroyed it, then also unhook it so
328 // that it isn't recreated later.
329 detachSurfaceLater(state);
330 }
331 }
332
333 /**
334 * Update the background drawable on all surfaces.
335 */
336 public void setBackgroundDrawable(Drawable background) {
337 mTranslucent.surfaceView.setBackgroundDrawable(background);
338 mOpaque.surfaceView.setBackgroundDrawable(background);
339 }
340
341 /**
342 * Set |willNotDraw| on all surfaces.
343 */
344 public void setWillNotDraw(boolean willNotDraw) {
345 mTranslucent.surfaceView.setWillNotDraw(willNotDraw);
346 mOpaque.surfaceView.setWillNotDraw(willNotDraw);
347 }
348
349 public void setVisibility(int visibility) {
350 mTranslucent.surfaceView.setVisibility(visibility);
351 mOpaque.surfaceView.setVisibility(visibility);
352 }
353
354 /**
355 * Return the SurfaceState for |holder|, or null if it isn't either.
356 */
357 private SurfaceState getStateForHolder(SurfaceHolder holder) {
358 if (mTranslucent.surfaceHolder() == holder) return mTranslucent;
359
360 if (mOpaque.surfaceHolder() == holder) return mOpaque;
361
362 return null;
363 }
364
365 /**
366 * Attach |state| to |mParentView| immedaitely.
367 */
368 private void attachSurfaceNow(SurfaceState state) {
369 if (state.isAttached()) return;
370
371 // If there is a destroy in-flight for this surface, then do nothing.
372 if (state.destroyPending) return;
373
374 state.createPending = true;
375 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
376 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATC H_PARENT);
377 state.attachTo(mParentView, lp);
378 mParentView.bringChildToFront(state.surfaceView);
379 mParentView.postInvalidateOnAnimation();
380 }
381
382 /**
383 * Post a Runnable to attach |state|. This is helpful, since one cannot dir ectly interact with
384 * the View heirarchy during Surface callbacks.
385 */
386 private void attachSurfaceLater(final SurfaceState state) {
387 // We shouldn't try to post construction if there's an in-flight destroy .
388 assert !state.destroyPending;
389 state.createPending = true;
390
391 mParentView.post(new Runnable() {
392 @Override
393 public void run() {
394 attachSurfaceNow(state);
395 }
396 });
397 }
398
399 /**
400 * Cause the client to disown |state| if it currently owns it. This involve s notifying it that
401 * the surface has been destroyed (recall that ownership involves getting cr eated). It's okay
402 * if |state| is null or isn't owned by the client.
403 */
404 private void disownClientSurface(SurfaceState state) {
405 if (mOwnedByClient != state || state == null) return;
406
407 mClient.surfaceDestroyed(mOwnedByClient.surfaceHolder());
408 mOwnedByClient = null;
409 }
410
411 /**
412 * Detach |state| from |mParentView| immediately.
413 */
414 private void detachSurfaceNow(SurfaceState state) {
415 // If we're called while we're not attached, then do nothing. This make s it easier for the
416 // client, since it doesn't have to keep track of whether the outgoing s urface has been
417 // destroyed or not. The client will be notified (or has already) when the surface is
418 // destroyed, if it currently owns it.
419 if (state.isAttached()) {
420 // We are attached. If the surface is not valid, then Android has d estroyed it for some
421 // other reason, and we should clean up. Otherwise, just wait for A ndroid to finish.
422 final boolean valid = state.isValid();
423
424 // If the surface is valid, then we expect a callback to surfaceDest royed eventually.
425 state.destroyPending = valid;
426
427 // Note that this might call back surfaceDestroyed before returning!
428 state.detachFromParent();
429
430 // If the surface was valid before, then we expect a surfaceDestroye d callback, which
431 // might have arrived during removeView. Either way, that callback will finish cleanup
432 // of |state|.
433 if (valid) return;
434 }
435
436 // The surface isn't attached, or was attached but wasn't currently vali d. Either way,
437 // we're not going to get a destroy, so notify the client now if needed.
438 disownClientSurface(state);
439
440 // If the client has since re-requested the surface, then start construc tion.
441 if (state == mRequestedByClient) attachSurfaceNow(mRequestedByClient);
442 }
443
444 /**
445 * Post detachment of |state|. This is safe during Surface callbacks.
446 */
447 private void detachSurfaceLater(final SurfaceState state) {
448 // If |state| is not attached, then do nothing. There might be a destro y pending from
449 // Android, but in any case leave it be.
450 if (!state.isAttached()) return;
451
452 state.destroyPending = true;
453 mParentView.post(new Runnable() {
454 @Override
455 public void run() {
456 detachSurfaceNow(state);
457 }
458 });
459 }
460 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698