Bug 827844 - Fix flickering when layout changes on Firefox/android. r=kats,bgirard
authorChris Lord <chrislord.net@gmail.com>
Thu, 10 Jan 2013 17:32:43 +0000
changeset 118439 f9ef215299560fe911e42d13f5280af2f236fad2
parent 118438 b28889afccbee0dd3709230913d85acc0138f723
child 118440 934e8b48a5394b3de7b34e1e7fa60539cbd50088
push id24166
push userMs2ger@gmail.com
push dateFri, 11 Jan 2013 13:57:41 +0000
treeherdermozilla-central@63c4b0f66a0c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats, bgirard
bugs827844
milestone21.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 827844 - Fix flickering when layout changes on Firefox/android. r=kats,bgirard Fix flickering that can occur when the surface size changes due to a layout change (such as the virtual keyboard appearing/disappearing) on Android.
gfx/layers/ipc/CompositorParent.cpp
gfx/layers/ipc/CompositorParent.h
mobile/android/base/GeckoAppShell.java
mobile/android/base/gfx/GLController.java
mobile/android/base/gfx/GeckoLayerClient.java
mobile/android/base/gfx/LayerView.java
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -379,18 +379,16 @@ CompositorParent::SetEGLSurfaceSize(int 
   if (mLayerManager) {
     static_cast<LayerManagerOGL*>(mLayerManager.get())->SetSurfaceSize(mEGLSurfaceSize.width, mEGLSurfaceSize.height);
   }
 }
 
 void
 CompositorParent::ResumeCompositionAndResize(int width, int height)
 {
-  mWidgetSize.width = width;
-  mWidgetSize.height = height;
   SetEGLSurfaceSize(width, height);
   ResumeComposition();
 }
 
 /*
  * This will execute a pause synchronously, waiting to make sure that the compositor
  * really is paused.
  */
@@ -998,32 +996,32 @@ CompositorParent::TransformShadowTree(Ti
       (mScrollOffset.x / tempScaleDiffX - metricsScrollOffset.x) * mXScale,
       (mScrollOffset.y / tempScaleDiffY - metricsScrollOffset.y) * mYScale);
     treeTransform = gfx3DMatrix(ViewTransform(-scrollCompensation,
                                               gfxSize(mXScale, mYScale)));
 
     // If the contents can fit entirely within the widget area on a particular
     // dimenson, we need to translate and scale so that the fixed layers remain
     // within the page boundaries.
-    if (mContentRect.width * tempScaleDiffX < mWidgetSize.width) {
+    if (mContentRect.width * tempScaleDiffX < metrics.mCompositionBounds.width) {
       offset.x = -metricsScrollOffset.x;
-      scaleDiff.height = NS_MIN(1.0f, mWidgetSize.width / (float)mContentRect.width);
+      scaleDiff.height = NS_MIN(1.0f, metrics.mCompositionBounds.width / (float)mContentRect.width);
     } else {
       offset.x = clamped(mScrollOffset.x / tempScaleDiffX, (float)mContentRect.x,
-                         mContentRect.XMost() - mWidgetSize.width / tempScaleDiffX) -
+                         mContentRect.XMost() - metrics.mCompositionBounds.width / tempScaleDiffX) -
                  metricsScrollOffset.x;
       scaleDiff.height = tempScaleDiffX;
     }
 
-    if (mContentRect.height * tempScaleDiffY < mWidgetSize.height) {
+    if (mContentRect.height * tempScaleDiffY < metrics.mCompositionBounds.height) {
       offset.y = -metricsScrollOffset.y;
-      scaleDiff.width = NS_MIN(1.0f, mWidgetSize.height / (float)mContentRect.height);
+      scaleDiff.width = NS_MIN(1.0f, metrics.mCompositionBounds.height / (float)mContentRect.height);
     } else {
       offset.y = clamped(mScrollOffset.y / tempScaleDiffY, (float)mContentRect.y,
-                         mContentRect.YMost() - mWidgetSize.height / tempScaleDiffY) -
+                         mContentRect.YMost() - metrics.mCompositionBounds.height / tempScaleDiffY) -
                  metricsScrollOffset.y;
       scaleDiff.width = tempScaleDiffY;
     }
 
     // The transform already takes the resolution scale into account.  Since we
     // will apply the resolution scale again when computing the effective
     // transform, we must apply the inverse resolution scale here.
     gfx3DMatrix computedTransform = treeTransform * currentTransform;
@@ -1111,18 +1109,16 @@ CompositorParent::AllocPLayers(const Lay
                                int32_t* aMaxTextureSize)
 {
   MOZ_ASSERT(aId == 0);
 
   // mWidget doesn't belong to the compositor thread, so it should be set to
   // NULL before returning from this method, to avoid accessing it elsewhere.
   nsIntRect rect;
   mWidget->GetClientBounds(rect);
-  mWidgetSize.width = rect.width;
-  mWidgetSize.height = rect.height;
 
   *aBackend = aBackendHint;
 
   if (aBackendHint == mozilla::layers::LAYERS_OPENGL) {
     nsRefPtr<LayerManagerOGL> layerManager;
     layerManager =
       new LayerManagerOGL(mWidget, mEGLSurfaceSize.width, mEGLSurfaceSize.height, mRenderToEGLSurface);
     mWidget = NULL;
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -270,17 +270,16 @@ private:
   TimeStamp mExpectedComposeTime;
 #endif
 
   bool mPaused;
   float mXScale;
   float mYScale;
   nsIntPoint mScrollOffset;
   nsIntRect mContentRect;
-  nsIntSize mWidgetSize;
 
   // When this flag is set, the next composition will be the first for a
   // particular document (i.e. the document displayed on the screen will change).
   // This happens when loading a new page or switching tabs. We notify the
   // front-end (e.g. Java on Android) about this so that it take the new page
   // size and zoom into account when providing us with the next view transform.
   bool mIsFirstPaint;
 
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -241,18 +241,24 @@ public class GeckoAppShell
     public static native void notifySmsDeleted(boolean aDeleted, int aRequestId);
     public static native void notifySmsDeleteFailed(int aError, int aRequestId);
     public static native void notifyNoMessageInList(int aRequestId);
     public static native void notifyListCreated(int aListId, int aMessageId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId);
     public static native void notifyGotNextMessage(int aMessageId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId);
     public static native void notifyReadingMessageListFailed(int aError, int aRequestId);
 
     public static native void scheduleComposite();
+
+    // Pausing and resuming the compositor is a synchronous request, so be
+    // careful of possible deadlock. Resuming the compositor will also cause
+    // a composition, so there is no need to schedule a composition after
+    // resuming.
     public static native void schedulePauseComposition();
     public static native void scheduleResumeComposition(int width, int height);
+
     public static native float computeRenderIntegrity();
 
     public static native SurfaceBits getSurfaceBits(Surface surface);
 
     public static native void onFullScreenPluginHidden(View view);
 
     private static class GeckoMediaScannerClient implements MediaScannerConnectionClient {
         private String mFile = "";
--- a/mobile/android/base/gfx/GLController.java
+++ b/mobile/android/base/gfx/GLController.java
@@ -69,16 +69,20 @@ public class GLController {
 
     synchronized void surfaceChanged(int newWidth, int newHeight) {
         mWidth = newWidth;
         mHeight = newHeight;
         mSurfaceValid = true;
         notifyAll();
     }
 
+    public boolean hasValidSurface() {
+        return mSurfaceValid;
+    }
+
     private void initEGL() {
         mEGL = (EGL10)EGLContext.getEGL();
 
         mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
         if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) {
             throw new GLControllerException("eglGetDisplay() failed");
         }
 
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -608,24 +608,31 @@ public class GeckoLayerClient implements
         // the compositor resuming, so that Gecko knows that it can now draw.
         if (mCompositorCreated) {
             GeckoAppShell.scheduleResumeComposition(width, height);
             GeckoAppShell.sendEventToGecko(GeckoEvent.createCompositorResumeEvent());
         }
     }
 
     /** Implementation of LayerView.Listener */
+    public void sizeChanged(int width, int height) {
+        // We need to make sure a draw happens synchronously at this point,
+        // but resizing the surface before the SurfaceView has resized will
+        // cause a visible jump.
+        compositionResumeRequested(mWindowSize.width, mWindowSize.height);
+    }
+
+    /** Implementation of LayerView.Listener */
     public void surfaceChanged(int width, int height) {
         setViewportSize(width, height);
 
         // We need to make this call even when the compositor isn't currently
         // paused (e.g. during an orientation change), to make the compositor
         // aware of the changed surface.
         compositionResumeRequested(width, height);
-        renderRequested();
     }
 
     /** Implementation of LayerView.Listener */
     public void compositorCreated() {
         mCompositorCreated = true;
     }
 
     /** Implementation of PanZoomTarget */
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -149,17 +149,17 @@ public class LayerView extends FrameLayo
             mTextureView.setSurfaceTextureListener(new SurfaceTextureListener());
             mTextureView.setBackgroundColor(Color.WHITE);
             addView(mTextureView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
         } else {
             // This will stop PropertyAnimator from creating a drawing cache (i.e. a bitmap)
             // from a SurfaceView, which is just not possible (the bitmap will be transparent).
             setWillNotCacheDrawing(false);
 
-            mSurfaceView = new SurfaceView(getContext());
+            mSurfaceView = new LayerSurfaceView(getContext(), this);
             mSurfaceView.setBackgroundColor(Color.WHITE);
             addView(mSurfaceView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
 
             SurfaceHolder holder = mSurfaceView.getHolder();
             holder.addCallback(new SurfaceListener());
             holder.setFormat(PixelFormat.RGB_565);
         }
     }
@@ -311,17 +311,46 @@ public class LayerView extends FrameLayo
     Bitmap getShadowPattern() {
         return getDrawable(R.drawable.shadow);
     }
 
     Bitmap getScrollbarImage() {
         return getDrawable(R.drawable.scrollbar);
     }
 
+    /* When using a SurfaceView (mSurfaceView != null), resizing happens in two
+     * phases. First, the LayerView changes size, then, often some frames later,
+     * the SurfaceView changes size. Because of this, we need to split the
+     * resize into two phases to avoid jittering.
+     *
+     * The first phase is the LayerView size change. mListener is notified so
+     * that a synchronous draw can be performed (otherwise a blank frame will
+     * appear).
+     *
+     * The second phase is the SurfaceView size change. At this point, the
+     * backing GL surface is resized and another synchronous draw is performed.
+     * Gecko is also sent the new window size, and this will likely cause an
+     * extra draw a few frames later, after it's re-rendered and caught up.
+     *
+     * In the case that there is no valid GL surface (for example, when
+     * resuming, or when coming back from the awesomescreen), or we're using a
+     * TextureView instead of a SurfaceView, the first phase is skipped.
+     */
     private void onSizeChanged(int width, int height) {
+        if (!mGLController.hasValidSurface() || mSurfaceView == null) {
+            surfaceChanged(width, height);
+            return;
+        }
+
+        if (mListener != null) {
+            mListener.sizeChanged(width, height);
+        }
+    }
+
+    private void surfaceChanged(int width, int height) {
         mGLController.surfaceChanged(width, height);
 
         if (mListener != null) {
             mListener.surfaceChanged(width, height);
         }
     }
 
     private void onDestroyed() {
@@ -351,16 +380,17 @@ public class LayerView extends FrameLayo
         }
     }
 
     public interface Listener {
         void compositorCreated();
         void renderRequested();
         void compositionPauseRequested();
         void compositionResumeRequested(int width, int height);
+        void sizeChanged(int width, int height);
         void surfaceChanged(int width, int height);
     }
 
     private class SurfaceListener implements SurfaceHolder.Callback {
         public void surfaceChanged(SurfaceHolder holder, int format, int width,
                                                 int height) {
             onSizeChanged(width, height);
         }
@@ -368,16 +398,34 @@ public class LayerView extends FrameLayo
         public void surfaceCreated(SurfaceHolder holder) {
         }
 
         public void surfaceDestroyed(SurfaceHolder holder) {
             onDestroyed();
         }
     }
 
+    /* A subclass of SurfaceView to listen to layout changes, as
+     * View.OnLayoutChangeListener requires API level 11.
+     */
+    private class LayerSurfaceView extends SurfaceView {
+        LayerView mParent;
+
+        public LayerSurfaceView(Context aContext, LayerView aParent) {
+            super(aContext);
+            mParent = aParent;
+        }
+
+        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            if (changed) {
+                mParent.surfaceChanged(right - left, bottom - top);
+            }
+        }
+    }
+
     private class SurfaceTextureListener implements TextureView.SurfaceTextureListener {
         public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
             // We don't do this for surfaceCreated above because it is always followed by a surfaceChanged,
             // but that is not the case here.
             onSizeChanged(width, height);
         }
 
         public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {