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

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

Powered by Google App Engine
This is Rietveld 408576698