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

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: replaced hide logic with one counter Created 3 years, 11 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.os.Build;
11 import android.view.SurfaceHolder;
12 import android.view.SurfaceView;
13 import android.view.View;
14 import android.view.ViewGroup;
15 import android.widget.FrameLayout;
16
17 /**
18 * Manage multiple SurfaceViews for the compositor, so that transitions between
aelias_OOO_until_Jul13 2017/01/12 21:46:05 Could you copy your design doc https://docs.google
liberato (no reviews please) 2017/01/19 18:12:34 Done, and in this comment.
19 * surfaces with and without an alpha channel can be visually smooth.
20 *
21 * This class allows a client to request a 'translucent' or 'opaque' surface, an d we will signal via
22 * SurfaceHolder.Callback when it's ready. We guarantee that the client will re ceive surfaceCreated
23 * / surfaceDestroyed only for a surface that represents the most recently reque sted PixelFormat.
24 *
25 * Internally, we maintain two SurfaceViews, since calling setFormat() to change the PixelFormat
26 * results in a visual glitch as the surface is torn down. crbug.com/679902
27 *
28 * The client has the responsibility to call doneWithUnownedSurface() at some po int between when we
29 * call back its surfaceCreated, when it is safe for us to hide the SurfaceView with the wrong
30 * format.
31 *
32 * We do not guarantee that the surface will always actually have that format, t hough, if we're set
33 * up to always provide a translucent surface. This helps low-memory devices, b ut not requiring two
34 * SurfaceViews. However, it does require that the compositor use the EGL confi g to turn off alpha
35 * blending. For low memory devices, this already happens, since it selects bet ween 565 and 8888
36 * EGL configs. In this case, we will still provide synthetic surfaceCreated / surfaceDestroyed
37 * messages to the client, even though the underlying surface isn't changing.
38 */
39 class CompositorSurfaceManager implements SurfaceHolder.Callback {
40 private static class SurfaceState {
41 public SurfaceView surfaceView;
42
43 // Have we started destroying |surfaceView|, but haven't been notified o f it yet?
44 public boolean destroyPending;
45
46 public SurfaceState(Context context, int format, SurfaceHolder.Callback callback) {
47 surfaceView = new SurfaceView(context);
48 surfaceView.setZOrderMediaOverlay(true);
49 surfaceView.setVisibility(View.VISIBLE);
50 surfaceHolder().setFormat(format);
51 surfaceHolder().addCallback(callback);
52 }
53
54 public SurfaceHolder surfaceHolder() {
55 return surfaceView.getHolder();
56 }
57
58 public boolean isValid() {
59 return surfaceHolder().getSurface().isValid();
60 }
61 }
62
63 // SurfaceView with a translucent PixelFormat.
64 private SurfaceState mTranslucent;
65
66 // SurfaceView with an opaque PixelFormat.
67 private SurfaceState mOpaque;
68
69 // Surface that we last gave to the client with surfaceCreated. Cleared whe n we call
70 // surfaceDestroyed.
71 private SurfaceState mOwnedByClient;
72
73 // Surface that was most recently requested by the client.
74 private SurfaceState mRequestedByClient;
75
76 // Client that we notify about surface change events.
77 private SurfaceHolder.Callback mClient;
78
79 // View to which we'll attach the SurfaceView.
80 private final ViewGroup mParentView;
81
82 public CompositorSurfaceManager(ViewGroup parentView, SurfaceHolder.Callback client) {
83 mParentView = parentView;
84 mClient = client;
85
86 mTranslucent = new SurfaceState(parentView.getContext(), PixelFormat.TRA NSLUCENT, this);
87 mOpaque = new SurfaceState(mParentView.getContext(), PixelFormat.OPAQUE, this);
88 }
89
90 /**
91 * Turn off everything.
92 */
93 public void shutDown() {
94 mTranslucent.surfaceHolder().removeCallback(this);
95 if (mOpaque != null) mOpaque.surfaceHolder().removeCallback(this);
96 }
97
98 /**
99 * Called by the client to request a surface. Once called, we guarantee tha t the next call to
100 * onSurfaceAvailable will match the most recent value of |format|. If the surface is already
101 * available for use, then we'll elide the created callback. Note that |for mat| must be either
102 * OPAQUE or TRANSLUCENT.
103 */
104 public void requestSurface(int format) {
105 mRequestedByClient = (format == PixelFormat.TRANSLUCENT) ? mTranslucent : mOpaque;
106
107 // Is the requested surface ready? "Ready" means that it's valid, and t hat we don't have a
108 // destroy in-flight for it.
109 if (mRequestedByClient.isValid() && !mRequestedByClient.destroyPending) {
110 // The client has requested a surface that's already valid and isn't being torn down.
111 // It might be requesting the same surface it already has, or it mig ht have toggled
112 // back to one it had before without notifying doneWithUnownedSurfac e. In either case,
113 // we elide the surface created / destroyed callbacks. We might wan t to return whether
114 // we've elided them, but CompositorView doesn't care.
115 mOwnedByClient = mRequestedByClient;
116 return;
117 }
118
119 // Surface is not valid, or it'll be destroyed soon. If it's not valid now, then start
120 // construction. Once it's ready, we'll signal the client if it still w ants this surface.
121 // If destruction is pending, then we must wait for it to complete. Whe n we're notified
122 // that it is destroyed, we'll re-start construction if the client still wants this surface.
123 // Note that we could send a surfaceDestroyed for the owned surface, if there is one, but we
124 // defer it until later so that the compositor can still use it.
125 if (!mRequestedByClient.isValid()) startSurfaceConstruction(mRequestedBy Client);
126 }
127
128 /**
129 * Called to notify us that the client no longer needs the surface that it d oesn't own. This
130 * tells us that we may destroy it. Note that it's okay if it never had an unowned surface.
131 */
132 public void doneWithUnownedSurface() {
133 if (mOwnedByClient == null) return;
134
135 SurfaceState unowned = (mOwnedByClient == mTranslucent) ? mOpaque : mTra nslucent;
136
137 if (mRequestedByClient == unowned) {
138 // Client is giving us back a surface that it's since requested but hasn't gotten yet.
139 // Do nothing. It will be notified when the new surface is ready, a nd it can call us
140 // again.
141 return;
142 }
143
144 // Start destruction of this surface.
145 postSurfaceDestruction(unowned);
146 }
147
148 /**
149 * Return the currently owned SurfaceHolder, if any.
150 */
151 public SurfaceHolder getHolder() {
152 return mOwnedByClient != null ? mOwnedByClient.surfaceHolder() : null;
153 }
154
155 /**
156 * Destroy and re-create the surface. Useful for a JB workaround needed by CompositorView.
157 */
158 public void recreateSurfaceForJellyBean() {
159 assert Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2;
160
161 // If they don't have a surface, then they'll get a new one anyway.
162 if (mOwnedByClient == null) return;
163
164 // Notify the client that it no longer owns this surface, then destroy i t. When destruction
165 // completes, we will recreate it automatically, since it will look like the client since
166 // re-requested it. That's why we send surfaceDestroyed here rather tha n letting our
167 // surfaceDestroyed do it when destruction completes. If we just starte d destruction while
168 // the client still owns the surface, then our surfaceDestroyed would as sume that android
169 // initiated the destruction since |mSurfaceDestroyed| would imply that the client didn't.
170
171 mParentView.post(new Runnable() {
172 @Override
173 public void run() {
174 if (mOwnedByClient == null) return;
175 SurfaceState owned = mOwnedByClient;
176 mClient.surfaceDestroyed(mOwnedByClient.surfaceHolder());
177 mOwnedByClient = null;
178 startSurfaceDestruction(owned);
179 }
180 });
181 }
182
183 @Override
184 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
185 SurfaceState state = getStateForHolder(holder);
186 assert state != null;
187
188 // If this is the surface that the client currently cares about, then no tify the client.
189 // Note that surfaceChanged is guaranteed to come only after surfaceCrea ted. Also, if the
190 // client has requested a different surface but hasn't gotten it yet, th en skip this.
191 if (state == mOwnedByClient && state == mRequestedByClient) {
192 mClient.surfaceChanged(holder, format, width, height);
193 }
194 }
195
196 @Override
197 public void surfaceCreated(SurfaceHolder holder) {
198 SurfaceState state = getStateForHolder(holder);
199 assert state != null;
200
201 if (state != mRequestedByClient) {
202 // Surface is created, but it's not the one that's been requested mo st recently. Just
203 // destroy it again.
204 postSurfaceDestruction(state);
205 return;
206 }
207
208 // The client requested a surface, and it's now available. If the clien t owns a
209 // surface, then notify it that it doesn't. Note that the client can't own |state| at
210 // this point, since we would have removed ownership when we got surface Destroyed.
211 if (mOwnedByClient != null) mClient.surfaceDestroyed(mOwnedByClient.surf aceHolder());
212
213 mOwnedByClient = mRequestedByClient;
214 mClient.surfaceCreated(mOwnedByClient.surfaceHolder());
215 }
216
217 @Override
218 public void surfaceDestroyed(SurfaceHolder holder) {
219 SurfaceState state = getStateForHolder(holder);
220 assert state != null;
221
222 // We might not have requested destruction, but clear the flag in case w e did.
223 state.destroyPending = false;
224
225 // If the client owns this surface, then notify it synchronously that it no longer does.
226 // This can happen if Android destroys the surface on its own.
227 if (state == mOwnedByClient) {
228 mClient.surfaceDestroyed(holder);
229 mOwnedByClient = null;
230
231 // Do not re-request the surface here. If android gives the surface back, then we'll
232 // re-signal the client about construction.
233 return;
234 }
235
236 // The client doesn't own this surface, but might want it.
237 // If the client has requested this surface, then start construction on it. The client will
238 // be notified when it completes. This can happen if the client re-requ ests a surface after
239 // we start destruction on it from a previous request, for example. We post this for later,
240 // since we might be called while removing |state| from the view tree. I n general, posting
241 // from here is good.
242 if (state == mRequestedByClient) {
243 postSurfaceConstruction(state);
244 } else {
245 // This isn't the requested surface. If android destroyed it, then also unhook it so
246 // that it isn't recreated later. If we did, then it's already not attached, and this
247 // will do nothing.
248 postSurfaceDestruction(state);
249 }
250 }
251
252 /**
253 * Update the background drawable on all surfaces.
254 */
255 public void setBackgroundDrawable(Drawable background) {
256 mTranslucent.surfaceView.setBackgroundDrawable(background);
257 if (mOpaque != null) mOpaque.surfaceView.setBackgroundDrawable(backgroun d);
258 }
259
260 /**
261 * Set |willNotDraw| on all surfaces.
262 */
263 public void setWillNotDraw(boolean willNotDraw) {
264 mTranslucent.surfaceView.setWillNotDraw(willNotDraw);
265 if (mOpaque != null) mOpaque.surfaceView.setWillNotDraw(willNotDraw);
266 }
267
268 /**
269 * Return the SurfaceState for |holder|, or null if it isn't either.
270 */
271 private SurfaceState getStateForHolder(SurfaceHolder holder) {
272 if (mTranslucent.surfaceHolder() == holder) return mTranslucent;
273
274 if (mOpaque != null && mOpaque.surfaceHolder() == holder) return mOpaque ;
275
276 return null;
277 }
278
279 private void startSurfaceConstruction(SurfaceState state) {
280 if (state.surfaceView.getParent() != null) return;
281
282 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
283 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATC H_PARENT);
284 mParentView.addView(state.surfaceView, lp);
285 mParentView.bringChildToFront(state.surfaceView);
286 mParentView.postInvalidateOnAnimation();
287 }
288
289 private void postSurfaceConstruction(final SurfaceState state) {
290 mParentView.post(new Runnable() {
291 @Override
292 public void run() {
293 startSurfaceConstruction(state);
294 }
295 });
296 }
297
298 private void startSurfaceDestruction(SurfaceState state) {
299 // If we're called while we're not attached, then do nothing. This make s it easier for the
300 // client, since it doesn't have to keep track of whether the outgoing s urface has been
301 // destroyed or not.
302 if (state.surfaceView.getParent() == null) return;
303
304 state.destroyPending = true;
305 mParentView.removeView(state.surfaceView);
306 }
307
308 private void postSurfaceDestruction(final SurfaceState state) {
309 mParentView.post(new Runnable() {
310 @Override
311 public void run() {
312 startSurfaceDestruction(state);
313 }
314 });
315 }
316 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698