diff options
Diffstat (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerRenderer.java')
-rw-r--r-- | mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerRenderer.java | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerRenderer.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerRenderer.java new file mode 100644 index 000000000..1a087cc2a --- /dev/null +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerRenderer.java @@ -0,0 +1,275 @@ +/* -*- 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; + +import org.mozilla.gecko.AppConstants; +import org.mozilla.gecko.GeckoAppShell; +import org.mozilla.gecko.mozglue.DirectBufferAllocator; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.opengl.GLES20; +import android.os.SystemClock; +import android.util.Log; + +import org.mozilla.gecko.annotation.WrapForJNI; +import org.mozilla.gecko.util.ThreadUtils; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.ArrayList; +import java.util.List; + +import javax.microedition.khronos.egl.EGLConfig; + +/** + * The layer renderer implements the rendering logic for a layer view. + */ +public class LayerRenderer { + private static final String LOGTAG = "GeckoLayerRenderer"; + + /* + * 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 long NANOS_PER_MS = 1000000; + private static final int MAX_SCROLL_SPEED_TO_REQUEST_ZOOM_RENDER = 5; + + private final LayerView mView; + private ByteBuffer mCoordByteBuffer; + private FloatBuffer mCoordBuffer; + private int mMaxTextureSize; + + private long mLastFrameTime; + private final CopyOnWriteArrayList<RenderTask> mTasks; + + // Dropped frames display + private final int[] mFrameTimings; + private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames; + + private IntBuffer mPixelBuffer; + private List<LayerView.ZoomedViewListener> mZoomedViewListeners; + private float mLastViewLeft; + private float mLastViewTop; + + public LayerRenderer(LayerView view) { + mView = view; + + mTasks = new CopyOnWriteArrayList<RenderTask>(); + mLastFrameTime = System.nanoTime(); + + mFrameTimings = new int[60]; + mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0; + + mZoomedViewListeners = new ArrayList<LayerView.ZoomedViewListener>(); + } + + public void destroy() { + if (mCoordByteBuffer != null) { + DirectBufferAllocator.free(mCoordByteBuffer); + mCoordByteBuffer = null; + mCoordBuffer = null; + } + mZoomedViewListeners.clear(); + } + + void onSurfaceCreated(EGLConfig config) { + createDefaultProgram(); + } + + public void createDefaultProgram() { + int maxTextureSizeResult[] = new int[1]; + GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0); + mMaxTextureSize = maxTextureSizeResult[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); + } + } + } + + /** Used by robocop for testing purposes. Not for production use! */ + IntBuffer getPixels() { + IntBuffer pixelBuffer = IntBuffer.allocate(mView.getWidth() * mView.getHeight()); + synchronized (pixelBuffer) { + mPixelBuffer = pixelBuffer; + mView.requestRender(); + try { + pixelBuffer.wait(); + } catch (InterruptedException ie) { + } + mPixelBuffer = null; + } + return pixelBuffer; + } + + private void updateDroppedFrames(long 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; + mCurrentFrame = (mCurrentFrame + 1) % mFrameTimings.length; + + int averageTime = mFrameTimingsSum / mFrameTimings.length; + } + + public Frame createFrame(ImmutableViewportMetrics metrics) { + return new Frame(metrics); + } + + public class Frame { + // The timestamp recording the start of this frame. + private long mFrameStartTime; + // A fixed snapshot of the viewport metrics that this frame is using to render content. + private final ImmutableViewportMetrics mFrameMetrics; + + public Frame(ImmutableViewportMetrics metrics) { + mFrameMetrics = metrics; + } + + /** This function is invoked via JNI; be careful when modifying signature. */ + @WrapForJNI + public void beginDrawing() { + mFrameStartTime = System.nanoTime(); + + // Run through pre-render tasks + runRenderTasks(mTasks, false, mFrameStartTime); + } + + + private void maybeRequestZoomedViewRender() { + // Concurrently update of mZoomedViewListeners should not be an issue here + // because the following line is just a short-circuit + if (mZoomedViewListeners.size() == 0) { + return; + } + + // When scrolling fast, do not request zoomed view render to avoid to slow down + // the scroll in the main view. + // Speed is estimated using the offset changes between 2 display frame calls + final float viewLeft = Math.round(mFrameMetrics.getViewport().left); + final float viewTop = Math.round(mFrameMetrics.getViewport().top); + boolean shouldWaitToRender = false; + + if (Math.abs(mLastViewLeft - viewLeft) > MAX_SCROLL_SPEED_TO_REQUEST_ZOOM_RENDER || + Math.abs(mLastViewTop - viewTop) > MAX_SCROLL_SPEED_TO_REQUEST_ZOOM_RENDER) { + shouldWaitToRender = true; + } + + mLastViewLeft = viewLeft; + mLastViewTop = viewTop; + + if (shouldWaitToRender) { + return; + } + + ThreadUtils.postToUiThread(new Runnable() { + @Override + public void run() { + for (LayerView.ZoomedViewListener listener : mZoomedViewListeners) { + listener.requestZoomedViewRender(); + } + } + }); + } + + + /** This function is invoked via JNI; be careful when modifying signature. */ + @WrapForJNI + public void endDrawing() { + PanningPerfAPI.recordFrameTime(); + + runRenderTasks(mTasks, true, mFrameStartTime); + maybeRequestZoomedViewRender(); + + /* Used by robocop for testing purposes */ + IntBuffer pixelBuffer = mPixelBuffer; + if (pixelBuffer != null) { + synchronized (pixelBuffer) { + pixelBuffer.position(0); + GLES20.glReadPixels(0, 0, Math.round(mFrameMetrics.getWidth()), + Math.round(mFrameMetrics.getHeight()), GLES20.GL_RGBA, + GLES20.GL_UNSIGNED_BYTE, pixelBuffer); + pixelBuffer.notify(); + } + } + + // Remove background color once we've painted. GeckoLayerClient is + // responsible for setting this flag before current document is + // composited. + if (mView.getPaintState() == LayerView.PAINT_BEFORE_FIRST) { + mView.post(new Runnable() { + @Override + public void run() { + mView.setSurfaceBackgroundColor(Color.TRANSPARENT); + } + }); + mView.setPaintState(LayerView.PAINT_AFTER_FIRST); + } + mLastFrameTime = mFrameStartTime; + } + } + + public void updateZoomedView(final ByteBuffer data) { + ThreadUtils.postToUiThread(new Runnable() { + @Override + public void run() { + for (LayerView.ZoomedViewListener listener : mZoomedViewListeners) { + data.position(0); + listener.updateView(data); + } + } + }); + } + + public void addZoomedViewListener(LayerView.ZoomedViewListener listener) { + ThreadUtils.assertOnUiThread(); + mZoomedViewListeners.add(listener); + } + + public void removeZoomedViewListener(LayerView.ZoomedViewListener listener) { + ThreadUtils.assertOnUiThread(); + mZoomedViewListeners.remove(listener); + } +} |