Commit 2e31bd80 authored by klausw's avatar klausw Committed by Commit bot

Revert of Improve transition between opaque and translucent compositor views....

Revert of Improve transition between opaque and translucent compositor views. (patchset #24 id:460001 of https://codereview.chromium.org/2201483002/ )

Reason for revert:
This change unfortunately breaks WebVR and probably VrShell, I confirmed via
bisect that 5fe29f76 is the last working change
right before this. It's plausible since it touches code such as detachForVR and
related code, seems like they don't agree on which surfaces should be visible.

Original issue's description:
> Improve transition between opaque and translucent compositor views.
>
> On Android, when we'd like to use a SurfaceView for video playback,
> we must enable the alpha channel on the CompositorView.  Otherwise,
> we cannot draw a hole to see the video's SurfaceView.  We can't keep
> transparency enabled all the time since it can cause power
> regressions on some hardware.
>
> However, because SurfaceView destroys and recreates the surface when
> changing the pixel format, there is a visible flash during the
> transition.  This CL removes that.
>
> It causes the CompositorView to have two SurfaceView child views,
> rather than extending SurfaceView directly.  When a request is made
> to switch the output format, it makes the change to the background
> SurfaceView, and swaps it to the front.  It also keeps the outgoing
> SurfaceView around until the new one is in use by the compositor.
>
> There are a few interesting bits:
>
> - SurfaceViews are invisible until the first buffer is posted.  So,
>   we can continue to see the outgoing view until the compositor is
>   ready to use the new one.
> - All buffers are released by the outgoing SurfaceView when its
>   visibility is set to Gone.  'dumpsys SurfaceFlinger' shows that the
>   same amount of gralloc memory is used in steady state.
> - No power regression was observed on a Nexus 5.
> - Unfortunately, new SurfaceViews are z-ordered behind existing ones,
>   which makes it critical that we guess when to delete the outgoing
>   (topmost) SurfaceView.  We currently time one frame, and then hide
>   it.  Empirically, this works fine.
> - This might seem like a more natural extension to
>   CompositorViewHolder, but the CompositorView currently owns the
>   native compositor.  Since CompositorViewHolder is already fairly
>   complicated, it wasn't clear that it would be a good idea to
>   refactor that into CVH.
>
> Another approach is to use EGL to change the buffer format to include
> an alpha channel, or not.  This avoids the power regression, since
> opaque surfaces and buffers without alpha channels are treated the
> same by SurfaceFlinger.  However, it causes problems for virtualized
> contexts.  In particular, the off-screen contexts will have an alpha
> channel at all times, so they cannot be shared with the compositor
> context without one.  For some hardware (e.g., QC) that requires the
> use of virtualized GL contexts rather than share groups, this may
> have a stability regression.  So, we don't try it.
>
> Please see https://goo.gl/aAmQzR for the design doc.
>
> BUG=629130
>
> Review-Url: https://codereview.chromium.org/2201483002
> Cr-Commit-Position: refs/heads/master@{#456142}
> Committed: https://chromium.googlesource.com/chromium/src/+/1e87636c63028418b779dadeaadbe4e5f487c76e

TBR=aelias@chromium.org,dtrainor@chromium.org,mdjones@chromium.org,tedchoc@chromium.org,watk@chromium.org,liberato@chromium.org
# Skipping CQ checks because original CL landed less than 1 days ago.
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
BUG=629130

Review-Url: https://codereview.chromium.org/2742133002
Cr-Commit-Position: refs/heads/master@{#456245}
parent dd3c8c6e
......@@ -343,7 +343,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
// Set up the animation placeholder to be the SurfaceView. This disables the
// SurfaceView's 'hole' clipping during animations that are notified to the window.
mWindowAndroid.setAnimationPlaceholderView(mCompositorViewHolder.getCompositorView());
mWindowAndroid.setAnimationPlaceholderView(mCompositorViewHolder.getSurfaceView());
// Inform the WindowAndroid of the keyboard accessory view.
mWindowAndroid.setKeyboardAccessoryView((ViewGroup) findViewById(R.id.keyboard_accessory));
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.compositor;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
/**
* Manage multiple SurfaceViews for the compositor, so that transitions between
* surfaces with and without an alpha channel can be visually smooth.
*
* This class allows a client to request a 'translucent' or 'opaque' surface, and we will signal via
* SurfaceHolder.Callback when it's ready. We guarantee that the client will receive surfaceCreated
* / surfaceDestroyed only for a surface that represents the most recently requested PixelFormat.
*
* Internally, we maintain two SurfaceViews, since calling setFormat() to change the PixelFormat
* results in a visual glitch as the surface is torn down. crbug.com/679902
*
* The client has the responsibility to call doneWithUnownedSurface() at some point between when we
* call back its surfaceCreated, when it is safe for us to hide the SurfaceView with the wrong
* format. It is okay if it requests multiple surfaces without calling doneWithUnownedSurface.
*
* If the client requests the same format more than once in a row, it will still receive destroyed /
* created / changed messages for it, even though we won't tear it down.
*
* The full design doc is at https://goo.gl/aAmQzR .
*/
class CompositorSurfaceManager implements SurfaceHolder.Callback {
private static class SurfaceState {
public SurfaceView surfaceView;
// Do we expect a surfaceCreated?
public boolean createPending;
// Have we started destroying |surfaceView|, but haven't received surfaceDestroyed yet?
public boolean destroyPending;
// Last PixelFormat that we received, or UNKNOWN if we don't know / don't want to cache it.
public int format;
// Last known width, height for thsi surface.
public int width;
public int height;
// Parent ViewGroup, or null.
private ViewGroup mParent;
public SurfaceState(Context context, int format, SurfaceHolder.Callback callback) {
surfaceView = new SurfaceView(context);
surfaceView.setZOrderMediaOverlay(true);
surfaceView.setVisibility(View.VISIBLE);
surfaceHolder().setFormat(format);
surfaceHolder().addCallback(callback);
// Set this to UNKNOWN until we get a format back.
this.format = PixelFormat.UNKNOWN;
}
public SurfaceHolder surfaceHolder() {
return surfaceView.getHolder();
}
public boolean isValid() {
return surfaceHolder().getSurface().isValid();
}
// Attach to |parent|, such that isAttached() will be correct immediately. Otherwise,
// attaching and detaching can cause surfaceCreated / surfaceDestroyed callbacks without
// View.hasParent being up to date.
public void attachTo(ViewGroup parent, FrameLayout.LayoutParams lp) {
mParent = parent;
mParent.addView(surfaceView, lp);
}
public void detachFromParent() {
final ViewGroup parent = mParent;
// Since removeView can call surfaceDestroyed before returning, be sure that isAttached
// will return false.
mParent = null;
parent.removeView(surfaceView);
}
public boolean isAttached() {
return mParent != null;
}
}
// SurfaceView with a translucent PixelFormat.
private final SurfaceState mTranslucent;
// SurfaceView with an opaque PixelFormat.
private final SurfaceState mOpaque;
// Surface that we last gave to the client with surfaceCreated. Cleared when we call
// surfaceDestroyed on |mClient|. Note that it's not necessary that Android has notified us
// the surface has been destroyed; we deliberately keep it around until the client tells us that
// it's okay to get rid of it.
private SurfaceState mOwnedByClient;
// Surface that was most recently requested by the client.
private SurfaceState mRequestedByClient;
// Client that we notify about surface change events.
private SurfaceHolder.Callback mClient;
// View to which we'll attach the SurfaceView.
private final ViewGroup mParentView;
public CompositorSurfaceManager(ViewGroup parentView, SurfaceHolder.Callback client) {
mParentView = parentView;
mClient = client;
mTranslucent = new SurfaceState(parentView.getContext(), PixelFormat.TRANSLUCENT, this);
mOpaque = new SurfaceState(mParentView.getContext(), PixelFormat.OPAQUE, this);
}
/**
* Turn off everything.
*/
public void shutDown() {
mTranslucent.surfaceHolder().removeCallback(this);
mOpaque.surfaceHolder().removeCallback(this);
}
/**
* Called by the client to request a surface. Once called, we guarantee that the next call to
* surfaceCreated will match the most recent value of |format|. If the surface is already
* available for use, then we'll send synthetic callbacks as though it were destroyed and
* recreated. Note that |format| must be either OPAQUE or TRANSLUCENT.
*/
public void requestSurface(int format) {
mRequestedByClient = (format == PixelFormat.TRANSLUCENT) ? mTranslucent : mOpaque;
// If destruction is pending, then we must wait for it to complete. When we're notified
// that it is destroyed, we'll re-start construction if the client still wants this surface.
// Note that we could send a surfaceDestroyed for the owned surface, if there is one, but we
// defer it until later so that the client can still use it until the new one is ready.
if (mRequestedByClient.destroyPending) return;
// The requested surface isn't being torn down.
// If the surface isn't attached yet, then attach it. Otherwise, we're still waiting for
// the surface to be created, or we've already received surfaceCreated for it.
if (!mRequestedByClient.isAttached()) {
attachSurfaceNow(mRequestedByClient);
assert mRequestedByClient.isAttached();
return;
}
// Surface is not pending destroy, and is attached. See if we need to send any synthetic
// callbacks to the client. If we're expecting a callback from Android, then we'll handle
// it when it arrives instead.
if (mRequestedByClient.createPending) return;
// Surface is attached and no create is pending. Send a synthetic create. Note that, if
// Android destroyed the surface itself, then we'd have set |createPending| at that point.
// We don't check |isValid| here, since, technically, there could be a destroy in flight
// from Android. It's okay; we'll just notify the client at that point. Either way, we
// must tell the client that it now owns the surface.
// Send a notification about any owned surface. Note that this can be |mRequestedByClient|
// which is fine. We'll send destroy / create for it. Also note that we don't actually
// start tear-down of the owned surface; the client notifies us via doneWithUnownedSurface
// when it is safe to do that.
disownClientSurface(mOwnedByClient);
// The client now owns |mRequestedByClient|. Notify it that it's ready.
mOwnedByClient = mRequestedByClient;
mClient.surfaceCreated(mOwnedByClient.surfaceHolder());
// See if we're expecting a surfaceChanged. If not, then send a synthetic one.
if (mOwnedByClient.format != PixelFormat.UNKNOWN) {
mClient.surfaceChanged(mOwnedByClient.surfaceHolder(), mOwnedByClient.width,
mOwnedByClient.height, mOwnedByClient.format);
}
}
/**
* Called to notify us that the client no longer needs the surface that it doesn't own. This
* tells us that we may destroy it. Note that it's okay if it never had an unowned surface.
*/
public void doneWithUnownedSurface() {
if (mOwnedByClient == null) return;
SurfaceState unowned = (mOwnedByClient == mTranslucent) ? mOpaque : mTranslucent;
if (mRequestedByClient == unowned) {
// Client is giving us back a surface that it's since requested but hasn't gotten yet.
// Do nothing. It will be notified when the new surface is ready, and it can call us
// again for the other surface, if it wants.
return;
}
// Start destruction of this surface. To prevent recursive call-backs to the client, we
// post this for later.
detachSurfaceLater(unowned);
}
/**
* Return the currently owned SurfaceHolder, if any.
*/
public SurfaceHolder getHolder() {
return mOwnedByClient != null ? mOwnedByClient.surfaceHolder() : null;
}
/**
* Destroy and re-create the surface. Useful for a JB workaround needed by CompositorView.
*/
public void recreateSurfaceForJellyBean() {
// If they don't have a surface, then they'll get a new one anyway.
if (mOwnedByClient == null) return;
// Notify the client that it no longer owns this surface, then destroy it. When destruction
// completes, we will recreate it automatically, since it will look like the client since
// re-requested it. That's why we send surfaceDestroyed here rather than letting our
// surfaceDestroyed do it when destruction completes. If we just started destruction while
// the client still owns the surface, then our surfaceDestroyed would assume that Android
// initiated the destruction, and wait for Android to recreate it.
mParentView.post(new Runnable() {
@Override
public void run() {
if (mOwnedByClient == null) return;
SurfaceState owned = mOwnedByClient;
mClient.surfaceDestroyed(mOwnedByClient.surfaceHolder());
mOwnedByClient = null;
detachSurfaceNow(owned);
}
});
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
SurfaceState state = getStateForHolder(holder);
assert state != null;
// If this is the surface that the client currently cares about, then notify the client.
// Note that surfaceChanged is guaranteed to come only after surfaceCreated. Also, if the
// client has requested a different surface but hasn't gotten it yet, then skip this.
if (state == mOwnedByClient && state == mRequestedByClient) {
state.width = width;
state.height = height;
state.format = format;
mClient.surfaceChanged(holder, format, width, height);
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
SurfaceState state = getStateForHolder(holder);
assert state != null;
// Note that |createPending| might not be set, if Android destroyed and recreated this
// surface on its own.
if (state != mRequestedByClient) {
// Surface is created, but it's not the one that's been requested most recently. Just
// destroy it again.
detachSurfaceLater(state);
return;
}
// No create is pending.
state.createPending = false;
// A surfaceChanged should arrive.
state.format = PixelFormat.UNKNOWN;
// The client requested a surface, and it's now available. If the client owns a surface,
// then notify it that it doesn't. Note that the client can't own |state| at this point,
// since we would have removed ownership when we got surfaceDestroyed. It's okay if the
// client doesn't own either surface.
assert mOwnedByClient != state;
disownClientSurface(mOwnedByClient);
// The client now owns this surface, so notify it.
mOwnedByClient = mRequestedByClient;
mClient.surfaceCreated(mOwnedByClient.surfaceHolder());
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
SurfaceState state = getStateForHolder(holder);
assert state != null;
// If no destroy is pending, then Android chose to destroy this surface and will, hopefully,
// re-create it at some point. Otherwise, a destroy is either posted or has already
// detached this SurfaceView. If it's already detached, then the destruction is complete
// and we can clear |destroyPending|. Otherwise, Android has destroyed this surface while
// our destroy was posted, and might even return it before it runs. When the post runs, it
// can sort that out based on whether the surface is valid or not.
if (!state.destroyPending) {
state.createPending = true;
} else if (!state.isAttached()) {
state.destroyPending = false;
}
state.format = PixelFormat.UNKNOWN;
// If the client owns this surface, then notify it synchronously that it no longer does.
// This can happen if Android destroys the surface on its own. It's also possible that
// we've detached it, if a destroy was pending. Either way, notify the client.
if (state == mOwnedByClient) {
disownClientSurface(mOwnedByClient);
// Do not re-request the surface here. If android gives the surface back, then we'll
// re-signal the client about construction.
return;
}
// The client doesn't own this surface, but might want it.
// If the client has requested this surface, then start construction on it. The client will
// be notified when it completes. This can happen if the client re-requests a surface after
// we start destruction on it from a previous request, for example. We post this for later,
// since we might be called while removing |state| from the view tree. In general, posting
// from here is good.
if (state == mRequestedByClient && !state.isAttached()) {
attachSurfaceLater(state);
} else if (state != mRequestedByClient && state.isAttached()) {
// This isn't the requested surface. If android destroyed it, then also unhook it so
// that it isn't recreated later.
detachSurfaceLater(state);
}
}
/**
* Update the background drawable on all surfaces.
*/
public void setBackgroundDrawable(Drawable background) {
mTranslucent.surfaceView.setBackgroundDrawable(background);
mOpaque.surfaceView.setBackgroundDrawable(background);
}
/**
* Set |willNotDraw| on all surfaces.
*/
public void setWillNotDraw(boolean willNotDraw) {
mTranslucent.surfaceView.setWillNotDraw(willNotDraw);
mOpaque.surfaceView.setWillNotDraw(willNotDraw);
}
/**
* Return the SurfaceState for |holder|, or null if it isn't either.
*/
private SurfaceState getStateForHolder(SurfaceHolder holder) {
if (mTranslucent.surfaceHolder() == holder) return mTranslucent;
if (mOpaque.surfaceHolder() == holder) return mOpaque;
return null;
}
/**
* Attach |state| to |mParentView| immedaitely.
*/
private void attachSurfaceNow(SurfaceState state) {
if (state.isAttached()) return;
// If there is a destroy in-flight for this surface, then do nothing.
if (state.destroyPending) return;
state.createPending = true;
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
state.attachTo(mParentView, lp);
mParentView.bringChildToFront(state.surfaceView);
mParentView.postInvalidateOnAnimation();
}
/**
* Post a Runnable to attach |state|. This is helpful, since one cannot directly interact with
* the View heirarchy during Surface callbacks.
*/
private void attachSurfaceLater(final SurfaceState state) {
// We shouldn't try to post construction if there's an in-flight destroy.
assert !state.destroyPending;
state.createPending = true;
mParentView.post(new Runnable() {
@Override
public void run() {
attachSurfaceNow(state);
}
});
}
/**
* Cause the client to disown |state| if it currently owns it. This involves notifying it that
* the surface has been destroyed (recall that ownership involves getting created). It's okay
* if |state| is null or isn't owned by the client.
*/
private void disownClientSurface(SurfaceState state) {
if (mOwnedByClient != state || state == null) return;
mClient.surfaceDestroyed(mOwnedByClient.surfaceHolder());
mOwnedByClient = null;
}
/**
* Detach |state| from |mParentView| immediately.
*/
private void detachSurfaceNow(SurfaceState state) {
// If we're called while we're not attached, then do nothing. This makes it easier for the
// client, since it doesn't have to keep track of whether the outgoing surface has been
// destroyed or not. The client will be notified (or has already) when the surface is
// destroyed, if it currently owns it.
if (state.isAttached()) {
// We are attached. If the surface is not valid, then Android has destroyed it for some
// other reason, and we should clean up. Otherwise, just wait for Android to finish.
final boolean valid = state.isValid();
// If the surface is valid, then we expect a callback to surfaceDestroyed eventually.
state.destroyPending = valid;
// Note that this might call back surfaceDestroyed before returning!
state.detachFromParent();
// If the surface was valid before, then we expect a surfaceDestroyed callback, which
// might have arrived during removeView. Either way, that callback will finish cleanup
// of |state|.
if (valid) return;
}
// The surface isn't attached, or was attached but wasn't currently valid. Either way,
// we're not going to get a destroy, so notify the client now if needed.
disownClientSurface(state);
// If the client has since re-requested the surface, then start construction.
if (state == mRequestedByClient) attachSurfaceNow(mRequestedByClient);
}
/**
* Post detachment of |state|. This is safe during Surface callbacks.
*/
private void detachSurfaceLater(final SurfaceState state) {
// If |state| is not attached, then do nothing. There might be a destroy pending from
// Android, but in any case leave it be.
if (!state.isAttached()) return;
state.destroyPending = true;
mParentView.post(new Runnable() {
@Override
public void run() {
detachSurfaceNow(state);
}
});
}
}
// Copyright 2016 The Chromium Authors. All rights reserved.
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
......@@ -9,7 +9,6 @@ import android.content.Context;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.Display;
import android.view.MotionEvent;
......@@ -18,9 +17,9 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import org.chromium.base.CommandLine;
import org.chromium.base.Log;
import org.chromium.base.TraceEvent;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
......@@ -48,7 +47,7 @@ import java.util.List;
* The is the {@link View} displaying the ui compositor results; including webpages and tabswitcher.
*/
@JNINamespace("android")
public class CompositorView extends FrameLayout implements SurfaceHolder.Callback2 {
public class CompositorView extends SurfaceView implements SurfaceHolder.Callback2 {
private static final String TAG = "CompositorView";
private static final long NANOSECONDS_PER_MILLISECOND = 1000000;
......@@ -56,14 +55,6 @@ public class CompositorView extends FrameLayout implements SurfaceHolder.Callbac
private final Rect mCacheAppRect = new Rect();
private final int[] mCacheViewPosition = new int[2];
private CompositorSurfaceManager mCompositorSurfaceManager;
private boolean mOverlayVideoEnabled;
private boolean mAlwaysTranslucent;
// Are we waiting to hide the outgoing surface until the foreground has something to display?
// If == 0, then no. If > 0, then yes. We'll hide when it transitions from one to zero.
private int mFramesUntilHideBackground;
private long mNativeCompositorView;
private final LayoutRenderHost mRenderHost;
private boolean mEnableTabletTabStack;
......@@ -82,9 +73,14 @@ public class CompositorView extends FrameLayout implements SurfaceHolder.Callbac
private TabContentManager mTabContentManager;
private View mRootView;
private int mSurfaceWidth;
private int mSurfaceHeight;
private boolean mPreloadedResources;
private List<Runnable> mDrawingFinishedCallbacks;
// The current SurfaceView pixel format. Defaults to OPAQUE.
private int mCurrentPixelFormat = PixelFormat.OPAQUE;
/**
* Creates a {@link CompositorView}. This can be called only after the native library is
* properly loaded.
......@@ -95,8 +91,8 @@ public class CompositorView extends FrameLayout implements SurfaceHolder.Callbac
super(c);
mRenderHost = host;
resetFlags();
setVisibility(View.INVISIBLE);
setZOrderMediaOverlay(true);
}
/**
......@@ -165,7 +161,7 @@ public class CompositorView extends FrameLayout implements SurfaceHolder.Callbac
* Should be called for cleanup when the CompositorView instance is no longer used.
*/
public void shutDown() {
mCompositorSurfaceManager.shutDown();
getHolder().removeCallback(this);
if (mNativeCompositorView != 0) nativeDestroy(mNativeCompositorView);
mNativeCompositorView = 0;
}
......@@ -186,23 +182,9 @@ public class CompositorView extends FrameLayout implements SurfaceHolder.Callbac
mNativeCompositorView = nativeInit(lowMemDevice,
windowAndroid.getNativePointer(), layerTitleCache, tabContentManager);
// compositor_impl_android.cc will use 565 EGL surfaces if and only if we're using a low
// memory device, and no alpha channel is desired. Otherwise, it will use 8888. Since
// SurfaceFlinger doesn't need the eOpaque flag to optimize out alpha blending during
// composition if the buffer has no alpha channel, we can avoid using the extra background
// surface (and the memory it requires) in the low memory case. The output buffer will
// either have an alpha channel or not, depending on whether the compositor needs it. We
// can keep the surface translucent all the times without worrying about the impact on power
// usage during SurfaceFlinger composition. We might also want to set |mAlwaysTranslucent|
// on non-low memory devices, if we are running on hardware that implements efficient alpha
// blending.
mAlwaysTranslucent = lowMemDevice;
mCompositorSurfaceManager = new CompositorSurfaceManager(this, this);
// In case the background drawable was set before now, update it.
mCompositorSurfaceManager.setBackgroundDrawable(getBackground());
mCompositorSurfaceManager.setWillNotDraw(willNotDraw());
mCompositorSurfaceManager.requestSurface(getSurfacePixelFormat());
assert !getHolder().getSurface().isValid()
: "Surface created before native library loaded.";
getHolder().addCallback(this);
// Cover the black surface before it has valid content.
setBackgroundColor(Color.WHITE);
......@@ -232,31 +214,14 @@ public class CompositorView extends FrameLayout implements SurfaceHolder.Callbac
return super.onTouchEvent(e);
}
/**
* @see SurfaceView#getHolder
*/
SurfaceHolder getHolder() {
return mCompositorSurfaceManager.getHolder();
}
/**
* Enables/disables overlay video mode. Affects alpha blending on this view.
* @param enabled Whether to enter or leave overlay video mode.
*/
public void setOverlayVideoMode(boolean enabled) {
mCurrentPixelFormat = enabled ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
getHolder().setFormat(mCurrentPixelFormat);
nativeSetOverlayVideoMode(mNativeCompositorView, enabled);
mOverlayVideoEnabled = enabled;
// Request the new surface, even if it's the same as the old one. We'll get a synthetic
// destroy / create / changed callback in that case, possibly before this returns.
mCompositorSurfaceManager.requestSurface(getSurfacePixelFormat());
// Note that we don't know if we'll get a surfaceCreated / surfaceDestoyed for this surface.
// We do know that if we do get one, then it will be for the surface that we just requested.
}
private int getSurfacePixelFormat() {
return (mOverlayVideoEnabled || mAlwaysTranslucent) ? PixelFormat.TRANSLUCENT
: PixelFormat.OPAQUE;
}
@Override
......@@ -275,24 +240,22 @@ public class CompositorView extends FrameLayout implements SurfaceHolder.Callbac
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (mNativeCompositorView == 0) return;
nativeSurfaceChanged(mNativeCompositorView, format, width, height, holder.getSurface());
mRenderHost.onPhysicalBackingSizeChanged(width, height);
mSurfaceWidth = width;
mSurfaceHeight = height;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (mNativeCompositorView == 0) return;
nativeSurfaceCreated(mNativeCompositorView);
mFramesUntilHideBackground = 2;
mRenderHost.onSurfaceCreated();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mNativeCompositorView == 0) return;
nativeSurfaceDestroyed(mNativeCompositorView);
}
......@@ -321,7 +284,29 @@ public class CompositorView extends FrameLayout implements SurfaceHolder.Callbac
*/
@CalledByNative
private void onJellyBeanSurfaceDisconnectWorkaround(boolean inOverlayMode) {
mCompositorSurfaceManager.recreateSurfaceForJellyBean();
// There is a bug in JellyBean because of which we will not be able to
// reconnect to the existing Surface after we launch a new GPU process.
// We simply trick the JB Android code to allocate a new Surface.
// It does a strict comparison between the current format and the requested
// one, even if they are the same in practice. Furthermore, the format
// does not matter here since the producer-side EGL config overwrites it
// (but transparency might matter).
switch (mCurrentPixelFormat) {
case PixelFormat.OPAQUE:
mCurrentPixelFormat = PixelFormat.RGBA_8888;
break;
case PixelFormat.RGBA_8888:
mCurrentPixelFormat = inOverlayMode
? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
break;
case PixelFormat.TRANSLUCENT:
mCurrentPixelFormat = PixelFormat.RGBA_8888;
break;
default:
assert false;
Log.e(TAG, "Unknown current pixel format.");
}
getHolder().setFormat(mCurrentPixelFormat);
}
/**
......@@ -348,20 +333,6 @@ public class CompositorView extends FrameLayout implements SurfaceHolder.Callbac
@CalledByNative
private void didSwapBuffers() {
// If we're in the middle of a surface swap, then see if we've received a new frame yet for
// the new surface before hiding the outgoing surface.
if (mFramesUntilHideBackground > 1) {
// We need at least one more frame before we hide the outgoing surface. Make sure that
// there will be a frame.
mFramesUntilHideBackground--;
requestRender();
} else if (mFramesUntilHideBackground == 1) {
// We can hide the outgoing surface, since the incoming one has a frame. It's okay if
// we've don't have an unowned surface.
mFramesUntilHideBackground = 0;
mCompositorSurfaceManager.doneWithUnownedSurface();
}
List<Runnable> runnables = mDrawingFinishedCallbacks;
mDrawingFinishedCallbacks = null;
if (runnables == null) return;
......@@ -410,24 +381,6 @@ public class CompositorView extends FrameLayout implements SurfaceHolder.Callbac
TraceEvent.end("CompositorView:finalizeLayers");
}
@Override
public void setWillNotDraw(boolean willNotDraw) {
if (mCompositorSurfaceManager != null) {
mCompositorSurfaceManager.setWillNotDraw(willNotDraw);
}
}
@Override
public void setBackgroundDrawable(Drawable background) {
// We override setBackgroundDrawable since that's the common entry point from all the
// setBackground* calls in View. We still call to setBackground on the SurfaceView because
// SetBackgroundDrawable is deprecated, and the semantics are the same I think.
super.setBackgroundDrawable(background);
if (mCompositorSurfaceManager != null) {
mCompositorSurfaceManager.setBackgroundDrawable(background);
}
}
// Implemented in native
private native long nativeInit(boolean lowMemDevice, long nativeWindowAndroid,
LayerTitleCache layerTitleCache, TabContentManager tabContentManager);
......
......@@ -20,6 +20,7 @@ import android.util.AttributeSet;
import android.util.Pair;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
......@@ -417,9 +418,9 @@ public class CompositorViewHolder extends FrameLayout
}
/**
* @return The SurfaceView proxy used by the Compositor.
* @return The SurfaceView used by the Compositor.
*/
public View getCompositorView() {
public SurfaceView getSurfaceView() {
return mCompositorView;
}
......@@ -975,7 +976,7 @@ public class CompositorViewHolder extends FrameLayout
mTabModelSelector = null;
mLayerTitleCache.setTabModelSelector(null);
setTab(null);
getCompositorView().setVisibility(View.INVISIBLE);
getSurfaceView().setVisibility(View.INVISIBLE);
return selector;
}
......@@ -985,7 +986,7 @@ public class CompositorViewHolder extends FrameLayout
* @param tabModelSelector
*/
public void onExitVR(TabModelSelector tabModelSelector) {
getCompositorView().setVisibility(View.VISIBLE);
getSurfaceView().setVisibility(View.VISIBLE);
attachToTabModelSelector(tabModelSelector);
}
......
......@@ -126,7 +126,6 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/childaccounts/ChildAccountFeedbackReporter.java",
"java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java",
"java/src/org/chromium/chrome/browser/childaccounts/ExternalFeedbackReporter.java",
"java/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManager.java",
"java/src/org/chromium/chrome/browser/compositor/CompositorView.java",
"java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java",
"java/src/org/chromium/chrome/browser/compositor/Invalidator.java",
......@@ -1561,7 +1560,6 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/ShortcutHelperTest.java",
"junit/src/org/chromium/chrome/browser/SSLClientCertificateRequestTest.java",
"junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java",
"junit/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerTest.java",
"junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionControllerTest.java",
"junit/src/org/chromium/chrome/browser/cookies/CanonicalCookieTest.java",
"junit/src/org/chromium/chrome/browser/crash/LogcatExtractionRunnableUnitTest.java",
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.compositor;
import android.app.Activity;
import android.graphics.PixelFormat;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.FrameLayout;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowSurfaceView;
import org.chromium.base.test.util.Feature;
import org.chromium.testing.local.LocalRobolectricTestRunner;
import java.util.Set;
/**
* Unit tests for the CompositorSurfaceManager.
*/
@RunWith(LocalRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class CompositorSurfaceManagerTest {
@Mock
private SurfaceHolder.Callback mCallback;
private CompositorSurfaceManager mManager;
private FrameLayout mLayout;
/**
* Implementation of a SurfaceView shadow that provides additional functionality for controlling
* the state of the underlying (fake) Surface.
*/
@Implements(SurfaceView.class)
public static class MyShadowSurfaceView extends ShadowSurfaceView {
private final MyFakeSurfaceHolder mHolder = new MyFakeSurfaceHolder();
/**
* Robolectric's FakeSurfaceHolder doesn't keep track of the format, etc.
*/
public static class MyFakeSurfaceHolder extends ShadowSurfaceView.FakeSurfaceHolder {
/**
* Fake surface that lets us control whether it's valid or not.
*/
public static class MyFakeSurface extends Surface {
public boolean valid = false;
@Override
public boolean isValid() {
return valid;
}
}
private int mFormat = PixelFormat.UNKNOWN;
private final MyFakeSurface mSurface = new MyFakeSurface();
@Implementation
public void setFormat(int format) {
mFormat = format;
}
public int getFormat() {
return mFormat;
}
// Return a surface that we can control if it's valid or not.
@Override
public Surface getSurface() {
return getFakeSurface();
}
public MyFakeSurface getFakeSurface() {
return mSurface;
}
}
public MyShadowSurfaceView() {}
@Implementation
public SurfaceHolder getHolder() {
return getMyFakeSurfaceHolder();
}
@Override
public FakeSurfaceHolder getFakeSurfaceHolder() {
return getMyFakeSurfaceHolder();
}
public MyFakeSurfaceHolder getMyFakeSurfaceHolder() {
return mHolder;
}
}
@Before
public void beforeTest() {
MockitoAnnotations.initMocks(this);
Activity activity = Robolectric.buildActivity(Activity.class).setup().get();
mLayout = new FrameLayout(activity);
mManager = new CompositorSurfaceManager(mLayout, mCallback);
}
private void runDelayedTasks() {
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
}
/**
* Return the callback for |view|, or null. Will get mad if there's more than one.
*/
private SurfaceHolder.Callback callbackFor(SurfaceView view) {
MyShadowSurfaceView viewShadow = (MyShadowSurfaceView) Shadows.shadowOf(view);
ShadowSurfaceView.FakeSurfaceHolder viewHolder = viewShadow.getFakeSurfaceHolder();
Set<SurfaceHolder.Callback> callbacks = viewHolder.getCallbacks();
// Zero or one is okay.
assertThat(callbacks.size(), lessThan(2));
if (callbacks.size() == 1) return callbacks.iterator().next();
return null;
}
private MyShadowSurfaceView.MyFakeSurfaceHolder fakeHolderFor(SurfaceView view) {
MyShadowSurfaceView viewShadow = (MyShadowSurfaceView) Shadows.shadowOf(view);
return viewShadow.getMyFakeSurfaceHolder();
}
private void setSurfaceValid(SurfaceView view, boolean valid) {
fakeHolderFor(view).getFakeSurface().valid = valid;
}
/**
* Find and return the SurfaceView with format |format|.
*/
private SurfaceView findSurface(int format) {
final int childCount = mLayout.getChildCount();
for (int i = 0; i < childCount; i++) {
final SurfaceView child = (SurfaceView) mLayout.getChildAt(i);
if (fakeHolderFor(child).getFormat() == format) return child;
}
return null;
}
/**
* Request the pixel format |format|, and return the SurfaceView for it if it's attached. You
* are responsible for sending surfaceCreated / Changed to |mManager| if you want it to think
* that Android has provided the Surface.
*/
private SurfaceView requestSurface(int format) {
mManager.requestSurface(format);
runDelayedTasks();
return findSurface(format);
}
/**
* Request format |format|, and send created / changed callbacks to |mManager| as if Android
* had provided the underlying Surface.
*/
private SurfaceView requestThenCreateSurface(int format) {
SurfaceView view = requestSurface(format);
setSurfaceValid(view, true);
callbackFor(view).surfaceCreated(view.getHolder());
final int actualFormat =
(format == PixelFormat.OPAQUE) ? PixelFormat.RGB_565 : PixelFormat.RGBA_8888;
final int width = 320;
final int height = 240;
callbackFor(view).surfaceChanged(view.getHolder(), actualFormat, width, height);
return view;
}
@Test
@Feature("Compositor")
@Config(shadows = {MyShadowSurfaceView.class})
public void testRequestOpaqueSurface() {
// Request a SurfaceView, and test in detail that it worked.
SurfaceView opaque = requestSurface(PixelFormat.OPAQUE);
verify(mCallback, times(0)).surfaceCreated(ArgumentMatchers.<SurfaceHolder>any());
verify(mCallback, times(0))
.surfaceChanged(
ArgumentMatchers.<SurfaceHolder>any(), anyInt(), anyInt(), anyInt());
verify(mCallback, times(0)).surfaceDestroyed(ArgumentMatchers.<SurfaceHolder>any());
// Check that there's an opaque SurfaceView .
assertEquals(1, mLayout.getChildCount());
assertTrue(fakeHolderFor(opaque).getFormat() == PixelFormat.OPAQUE);
// Verify that we are notified when the surface is created.
callbackFor(opaque).surfaceCreated(opaque.getHolder());
verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(0)).surfaceDestroyed(ArgumentMatchers.<SurfaceHolder>any());
// Verify that we are notified when the surface is changed.
final int format = PixelFormat.RGB_565;
final int width = 320;
final int height = 240;
callbackFor(opaque).surfaceChanged(opaque.getHolder(), format, width, height);
verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(1)).surfaceChanged(opaque.getHolder(), format, width, height);
verify(mCallback, times(0)).surfaceDestroyed(ArgumentMatchers.<SurfaceHolder>any());
// Verify that we are notified when the surface is destroyed.
callbackFor(opaque).surfaceDestroyed(opaque.getHolder());
verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(1)).surfaceChanged(opaque.getHolder(), format, width, height);
verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
}
@Test
@Feature("Compositor")
@Config(shadows = {MyShadowSurfaceView.class})
public void testRequestOpaqueThenTranslucentSurface() {
// Request opaque then translucent.
SurfaceView opaque = requestThenCreateSurface(PixelFormat.OPAQUE);
SurfaceView translucent = requestThenCreateSurface(PixelFormat.TRANSLUCENT);
// Verify that we received a destroy for |opaque| and created / changed for |translucent|.
verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
verify(mCallback, times(1)).surfaceCreated(translucent.getHolder());
verify(mCallback, times(1))
.surfaceChanged(eq(translucent.getHolder()), anyInt(), anyInt(), anyInt());
// Both views should be present.
assertEquals(2, mLayout.getChildCount());
// Only the translucent surface should be left. Note that the old view is still valid.
mManager.doneWithUnownedSurface();
runDelayedTasks();
assertEquals(1, mLayout.getChildCount());
assertNotNull(findSurface(PixelFormat.TRANSLUCENT));
}
@Test
@Feature("Compositor")
@Config(shadows = {MyShadowSurfaceView.class})
public void testRequestSameSurface() {
// Request an opaque surface, get it, then request it again. Verify that we get synthetic
// create / destroy callbacks.
SurfaceView opaque = requestThenCreateSurface(PixelFormat.OPAQUE);
verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(1))
.surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyInt());
verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
// Surface is curerntly valid. Request again. We should get back a destroy and create.
assertEquals(opaque, requestSurface(PixelFormat.OPAQUE));
verify(mCallback, times(2)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(2))
.surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyInt());
verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
assertEquals(1, mLayout.getChildCount());
}
@Test
@Feature("Compositor")
@Config(shadows = {MyShadowSurfaceView.class})
public void testRequestSameSurfaceBeforeReady() {
// Request an opaque surface, then request it again before the first one shows up.
SurfaceView opaque = requestSurface(PixelFormat.OPAQUE);
verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(0))
.surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyInt());
verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
// Request again. We shouldn't get any callbacks, since the surface is still pending.
assertEquals(opaque, requestSurface(PixelFormat.OPAQUE));
verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(0))
.surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyInt());
verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
// Only the opaque view should be attached.
assertEquals(1, mLayout.getChildCount());
// When the surface is created, we should get notified created / changed, but not destroyed.
callbackFor(opaque).surfaceCreated(opaque.getHolder());
verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
callbackFor(opaque).surfaceChanged(opaque.getHolder(), PixelFormat.RGB_565, 320, 240);
verify(mCallback, times(1))
.surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyInt());
verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
}
@Test
@Feature("Compositor")
@Config(shadows = {MyShadowSurfaceView.class})
public void testRequestDifferentSurfacesBeforeReady() {
// Request an opaque surface, then request the translucent one before the it one shows up.
SurfaceView opaque = requestSurface(PixelFormat.OPAQUE);
verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(0))
.surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyInt());
verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
// Request translucent. We should get no callbacks, but both views should be attached.
SurfaceView translucent = requestSurface(PixelFormat.TRANSLUCENT);
verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(0)).surfaceCreated(translucent.getHolder());
assertEquals(2, mLayout.getChildCount());
// If the opaque surface arrives, we shouldn't hear about it. It should be detached, since
// we've requested the other one.
callbackFor(opaque).surfaceCreated(opaque.getHolder());
runDelayedTasks();
assertEquals(1, mLayout.getChildCount());
verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(0)).surfaceCreated(translucent.getHolder());
verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
// When we create the translucent surface, we should be notified.
callbackFor(translucent).surfaceCreated(translucent.getHolder());
verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(1)).surfaceCreated(translucent.getHolder());
}
@Test
@Feature("Compositor")
@Config(shadows = {MyShadowSurfaceView.class})
public void testPendingSurfaceChangedCallback() {
// Request an opaque surface, and request it again between 'created' and 'changed'. We
// should get a synthetic 'created', but a real 'changed' callback.
SurfaceView opaque = requestSurface(PixelFormat.OPAQUE);
callbackFor(opaque).surfaceCreated(opaque.getHolder());
runDelayedTasks();
// Sanity check.
verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(0))
.surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyInt());
// Re-request while 'changed' is still pending. We should get a synthetic 'destroyed' and
// synthetic 'created'.
assertEquals(opaque, requestSurface(PixelFormat.OPAQUE));
verify(mCallback, times(2)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
verify(mCallback, times(0))
.surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyInt());
// Send 'changed', and expect that we'll receive it.
callbackFor(opaque).surfaceChanged(opaque.getHolder(), PixelFormat.RGB_565, 320, 240);
verify(mCallback, times(1))
.surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyInt());
}
@Test
@Feature("Compositor")
@Config(shadows = {MyShadowSurfaceView.class})
public void testJellyBeanWorkaround() {
// See if recreateSurfaceForJellyBean destroys / re-creates the surface.
// should get a synthetic 'created', but a real 'changed' callback.
SurfaceView opaque = requestThenCreateSurface(PixelFormat.OPAQUE);
verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
assertEquals(1, mLayout.getChildCount());
// We should be notified that the surface was destroyed via synthetic callback, and the
// surface should be detached.
mManager.recreateSurfaceForJellyBean();
verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
assertEquals(0, mLayout.getChildCount());
// When the surface really is destroyed, it should be re-attached. We should not be
// notified again, though.
callbackFor(opaque).surfaceDestroyed(opaque.getHolder());
verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
assertEquals(1, mLayout.getChildCount());
// When the surface is re-created, we should be notified.
callbackFor(opaque).surfaceCreated(opaque.getHolder());
verify(mCallback, times(2)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
assertEquals(1, mLayout.getChildCount());
}
@Test
@Feature("Compositor")
@Config(shadows = {MyShadowSurfaceView.class})
public void testRequestSurfaceDuringDestruction() {
// If we re-request a surface while we're tearing it down, it should be re-attached and
// given back to us once the destruction completes.
SurfaceView opaque = requestThenCreateSurface(PixelFormat.OPAQUE);
SurfaceView translucent = requestThenCreateSurface(PixelFormat.TRANSLUCENT);
mManager.doneWithUnownedSurface();
// The transparent surface should be attached, and the opaque one detached.
assertEquals(1, mLayout.getChildCount());
assertNotNull(findSurface(PixelFormat.TRANSLUCENT));
// Re-request the opaque surface. Nothing should happen until it's destroyed. It should
// not be re-attached, since that is also deferred until destruction.
assertEquals(null, requestSurface(PixelFormat.OPAQUE));
assertEquals(1, mLayout.getChildCount());
assertNotNull(findSurface(PixelFormat.TRANSLUCENT));
// When the opaque surface is destroyed, then it should be re-attached. No callbacks shoud
// have arrived yet, except for initial creation and (synthetic) destroyed when we got the
// translucent surface.
callbackFor(opaque).surfaceDestroyed(opaque.getHolder());
assertEquals(2, mLayout.getChildCount());
verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
verify(mCallback, times(0)).surfaceDestroyed(translucent.getHolder());
// When the opaque surface becomes available, we'll get the synthetic destroy for the
// translucent one that we lost ownership of, and the real create for the opaque one.
callbackFor(opaque).surfaceCreated(opaque.getHolder());
assertEquals(2, mLayout.getChildCount());
verify(mCallback, times(2)).surfaceCreated(opaque.getHolder());
verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
verify(mCallback, times(1)).surfaceDestroyed(translucent.getHolder());
}
}
......@@ -175,12 +175,6 @@ gpu::gles2::ContextCreationAttribHelper GetCompositorContextAttributes(
// specified
// (IOW check that a <= 0 && rgb > 0 && rgb <= 565) then alpha should be
// -1.
// TODO(liberato): This condition is memorized in ComositorView.java, to
// avoid using two surfaces temporarily during alpha <-> no alpha
// transitions. If these mismatch, then we risk a power regression if the
// SurfaceView is not marked as eOpaque (FORMAT_OPAQUE), and we have an
// EGL surface with an alpha channel. SurfaceFlinger needs at least one of
// those hints to optimize out alpha blending.
attributes.alpha_size = 0;
attributes.red_size = 5;
attributes.green_size = 6;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment