Bug 711959 - Add RenderTask and the necessary mechanisms to use it. r=kats
authorAugustin Trancart <augustin.trancart@gmail.com>
Wed, 04 Sep 2013 14:07:11 -0400
changeset 145496 d6515c4d6cf852624deff2be262cfe9e948f5ebf
parent 145495 6366f575b4c623a06c110cc81334047a9623970a
child 145497 05c2d7b68a229d4221c25e1052689e6473ecd51f
push id25214
push userkwierso@gmail.com
push dateThu, 05 Sep 2013 00:02:20 +0000
treeherdermozilla-central@99bd249e5a20 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs711959
milestone26.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 711959 - Add RenderTask and the necessary mechanisms to use it. r=kats
mobile/android/base/Makefile.in
mobile/android/base/gfx/LayerRenderer.java
mobile/android/base/gfx/LayerView.java
mobile/android/base/gfx/RenderTask.java
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -199,16 +199,17 @@ FENNEC_JAVA_FILES = \
   gfx/NinePatchTileLayer.java \
   gfx/PanningPerfAPI.java \
   gfx/PanZoomController.java \
   gfx/PanZoomTarget.java \
   gfx/PluginLayer.java \
   gfx/PointUtils.java \
   gfx/ProgressiveUpdateData.java \
   gfx/RectUtils.java \
+  gfx/RenderTask.java \
   gfx/ScrollbarLayer.java \
   gfx/SimpleScaleGestureDetector.java \
   gfx/SingleTileLayer.java \
   gfx/SubdocumentScrollHelper.java \
   gfx/TextLayer.java \
   gfx/TextureGenerator.java \
   gfx/TextureReaper.java \
   gfx/TileLayer.java \
--- a/mobile/android/base/gfx/LayerRenderer.java
+++ b/mobile/android/base/gfx/LayerRenderer.java
@@ -5,16 +5,17 @@
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.gfx.Layer.RenderContext;
+import org.mozilla.gecko.gfx.RenderTask;
 import org.mozilla.gecko.mozglue.DirectBufferAllocator;
 
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
@@ -45,29 +46,35 @@ public class LayerRenderer implements Ta
      * The amount of time a frame is allowed to take to render before we declare it a dropped
      * frame.
      */
     private static final int MAX_FRAME_TIME = 16;   /* 1000 ms / 60 FPS */
 
     private static final int FRAME_RATE_METER_WIDTH = 128;
     private static final int FRAME_RATE_METER_HEIGHT = 32;
 
+    private static final long NANOS_PER_MS = 1000000;
+    private static final int NANOS_PER_SECOND = 1000000000;
+
     private final LayerView mView;
     private final NinePatchTileLayer mShadowLayer;
     private TextLayer mFrameRateLayer;
     private final ScrollbarLayer mHorizScrollLayer;
     private final ScrollbarLayer mVertScrollLayer;
     private final FadeRunnable mFadeRunnable;
     private ByteBuffer mCoordByteBuffer;
     private FloatBuffer mCoordBuffer;
     private RenderContext mLastPageContext;
     private int mMaxTextureSize;
     private int mBackgroundColor;
     private int mOverscrollColor;
 
+    private long mLastFrameTime;
+    private final CopyOnWriteArrayList<RenderTask> mTasks;
+
     private CopyOnWriteArrayList<Layer> mExtraLayers = new CopyOnWriteArrayList<Layer>();
 
     // Dropped frames display
     private int[] mFrameTimings;
     private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames;
 
     // Render profiling output
     private int mFramesRendered;
@@ -133,16 +140,20 @@ public class LayerRenderer implements Ta
         mOverscrollColor = view.getContext().getResources().getColor(R.color.background_normal);
 
         CairoImage shadowImage = new BufferedCairoImage(view.getShadowPattern());
         mShadowLayer = new NinePatchTileLayer(shadowImage);
 
         Bitmap scrollbarImage = view.getScrollbarImage();
         IntSize size = new IntSize(scrollbarImage.getWidth(), scrollbarImage.getHeight());
         scrollbarImage = expandCanvasToPowerOfTwo(scrollbarImage, size);
+
+        mTasks = new CopyOnWriteArrayList<RenderTask>();
+        mLastFrameTime = System.nanoTime();
+
         mVertScrollLayer = new ScrollbarLayer(this, scrollbarImage, size, true);
         mHorizScrollLayer = new ScrollbarLayer(this, diagonalFlip(scrollbarImage), new IntSize(size.height, size.width), false);
         mFadeRunnable = new FadeRunnable();
 
         mFrameTimings = new int[60];
         mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0;
 
         // Initialize the FloatBuffer that will be used to store all vertices and texture
@@ -236,16 +247,40 @@ public class LayerRenderer implements Ta
         GLES20.glDisableVertexAttribArray(mPositionHandle);
         GLES20.glUseProgram(0);
     }
 
     public int getMaxTextureSize() {
         return mMaxTextureSize;
     }
 
+    public void postRenderTask(RenderTask aTask) {
+        mTasks.add(aTask);
+        mView.requestRender();
+    }
+
+    public void removeRenderTask(RenderTask aTask) {
+        mTasks.remove(aTask);
+    }
+
+    private void runRenderTasks(CopyOnWriteArrayList<RenderTask> tasks, boolean after, long frameStartTime) {
+        for (RenderTask task : tasks) {
+            if (task.runAfter != after) {
+                continue;
+            }
+
+            boolean stillRunning = task.run(frameStartTime - mLastFrameTime, frameStartTime);
+
+            // Remove the task from the list if its finished
+            if (!stillRunning) {
+                tasks.remove(task);
+            }
+        }
+    }
+
     public void addLayer(Layer layer) {
         synchronized (mExtraLayers) {
             if (mExtraLayers.contains(layer)) {
                 mExtraLayers.remove(layer);
             }
 
             mExtraLayers.add(layer);
         }
@@ -294,17 +329,17 @@ public class LayerRenderer implements Ta
     }
 
     private RenderContext createContext(RectF viewport, RectF pageRect, float zoomFactor, PointF offset) {
         return new RenderContext(viewport, pageRect, zoomFactor, offset, mPositionHandle, mTextureHandle,
                                  mCoordBuffer);
     }
 
     private void updateDroppedFrames(long frameStartTime) {
-        int frameElapsedTime = (int)(SystemClock.uptimeMillis() - frameStartTime);
+        int frameElapsedTime = (int)((System.nanoTime() - frameStartTime) / NANOS_PER_MS);
 
         /* Update the running statistics. */
         mFrameTimingsSum -= mFrameTimings[mCurrentFrame];
         mFrameTimingsSum += frameElapsedTime;
         mDroppedFrames -= (mFrameTimings[mCurrentFrame] + 1) / MAX_FRAME_TIME;
         mDroppedFrames += (frameElapsedTime + 1) / MAX_FRAME_TIME;
 
         mFrameTimings[mCurrentFrame] = frameElapsedTime;
@@ -451,25 +486,28 @@ public class LayerRenderer implements Ta
                                         (screenSize.height - bottom) + (bottom - top));
             scissorRect.offset(Math.round(-mRenderOffset.x), Math.round(-mRenderOffset.y));
 
             return scissorRect;
         }
 
         /** This function is invoked via JNI; be careful when modifying signature. */
         public void beginDrawing() {
-            mFrameStartTime = SystemClock.uptimeMillis();
+            mFrameStartTime = System.nanoTime();
 
             TextureReaper.get().reap();
             TextureGenerator.get().fill();
 
             mUpdated = true;
 
             Layer rootLayer = mView.getLayerClient().getRoot();
 
+            // Run through pre-render tasks
+            runRenderTasks(mTasks, false, mFrameStartTime);
+
             if (!mPageContext.fuzzyEquals(mLastPageContext) && !mView.isFullScreen()) {
                 // The viewport or page changed, so show the scrollbars again
                 // as per UX decision. Don't do this if we're in full-screen mode though.
                 mVertScrollLayer.unfade();
                 mHorizScrollLayer.unfade();
                 mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY);
             } else if (mFadeRunnable.timeToFade()) {
                 boolean stillFading = mVertScrollLayer.fade() | mHorizScrollLayer.fade();
@@ -599,22 +637,24 @@ public class LayerRenderer implements Ta
                 PanningPerfAPI.recordCheckerboard(checkerboard);
                 if (checkerboard < 0.0f || checkerboard > 1.0f) {
                     Log.e(LOGTAG, "Checkerboard value out of bounds: " + checkerboard);
                 }
 
                 mCompleteFramesRendered += 1.0f - checkerboard;
                 mFramesRendered ++;
 
-                if (mFrameStartTime - mProfileOutputTime > 1000) {
+                if (mFrameStartTime - mProfileOutputTime > NANOS_PER_SECOND) {
                     mProfileOutputTime = mFrameStartTime;
                     printCheckerboardStats();
                 }
             }
 
+            runRenderTasks(mTasks, true, mFrameStartTime);
+
             /* Draw the FPS. */
             if (mFrameRateLayer != null) {
                 updateDroppedFrames(mFrameStartTime);
 
                 GLES20.glEnable(GLES20.GL_BLEND);
                 GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
                 mFrameRateLayer.draw(mScreenContext);
             }
@@ -647,16 +687,17 @@ public class LayerRenderer implements Ta
                 mView.post(new Runnable() {
                     @Override
                     public void run() {
                         mView.getChildAt(0).setBackgroundColor(Color.TRANSPARENT);
                     }
                 });
                 mView.setPaintState(LayerView.PAINT_AFTER_FIRST);
             }
+            mLastFrameTime = mFrameStartTime;
         }
     }
 
     @Override
     public void onTabChanged(final Tab tab, Tabs.TabEvents msg, Object data) {
         // Sets the background of the newly selected tab. This background color
         // gets cleared in endDrawing(). This function runs on the UI thread,
         // but other code that touches the paint state is run on the compositor
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -396,16 +396,24 @@ public class LayerView extends FrameLayo
     public void addLayer(Layer layer) {
         mRenderer.addLayer(layer);
     }
 
     public void removeLayer(Layer layer) {
         mRenderer.removeLayer(layer);
     }
 
+    public void postRenderTask(RenderTask task) {
+        mRenderer.postRenderTask(task);
+    }
+
+    public void removeRenderTask(RenderTask task) {
+        mRenderer.removeRenderTask(task);
+    }
+
     public int getMaxTextureSize() {
         return mRenderer.getMaxTextureSize();
     }
 
     /** Used by robocop for testing purposes. Not for production use! */
     public IntBuffer getPixels() {
         return mRenderer.getPixels();
     }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/RenderTask.java
@@ -0,0 +1,80 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+/**
+ * A class used to schedule a callback to occur when the next frame is drawn.
+ * Subclasses must redefine the internalRun method, not the run method.
+ */
+public abstract class RenderTask {
+    /**
+     * Whether to run the task after the render, or before.
+     */
+    public final boolean runAfter;
+
+    /**
+     * Time when this task has first run, in ns. Useful for tasks which run for a specific duration.
+     */
+    private long mStartTime;
+
+    /**
+     * Whether we should initialise mStartTime on the next frame run.
+     */
+    private boolean mResetStartTime = true;
+
+    /**
+     * The callback to run on each frame. timeDelta is the time elapsed since
+     * the last call, in nanoseconds. Returns true if it should continue
+     * running, or false if it should be removed from the task queue. Returning
+     * true implicitly schedules a redraw.
+     *
+     * This method first initializes the start time if resetStartTime has been invoked,
+     * then calls internalRun.
+     *
+     * Note : subclasses should override internalRun.
+     *
+     * @param timeDelta the time between the beginning of last frame and the beginning of this frame, in ns.
+     * @param currentFrameStartTime the startTime of the current frame, in ns.
+     * @return true if animation should be run at the next frame, false otherwise
+     * @see RenderTask#internalRun(long, long)
+     */
+    public final boolean run(long timeDelta, long currentFrameStartTime) {
+        if (mResetStartTime) {
+            mStartTime = currentFrameStartTime;
+            mResetStartTime = false;
+        }
+        return internalRun(timeDelta, currentFrameStartTime);
+    }
+
+    /**
+     * Abstract method to be overridden by subclasses.
+     * @param timeDelta the time between the beginning of last frame and the beginning of this frame, in ns
+     * @param currentFrameStartTime the startTime of the current frame, in ns.
+     * @return true if animation should be run at the next frame, false otherwise
+     */
+    protected abstract boolean internalRun(long timeDelta, long currentFrameStartTime);
+
+    public RenderTask(boolean aRunAfter) {
+        runAfter = aRunAfter;
+    }
+
+    /**
+     * Get the start time of this task.
+     * It is the start time of the first frame this task was run on.
+     * @return the start time in ns
+     */
+    public long getStartTime() {
+        return mStartTime;
+    }
+
+    /**
+     * Schedule a reset of the recorded start time next time {@link RenderTask#run(long, long)} is run.
+     * @see RenderTask#getStartTime()
+     */
+    public void resetStartTime() {
+        mResetStartTime = true;
+    }
+}