mobile/android/base/gfx/LayerRenderer.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/gfx/LayerRenderer.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,725 @@
     1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     1.5 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +package org.mozilla.gecko.gfx;
    1.10 +
    1.11 +import org.mozilla.gecko.GeckoAppShell;
    1.12 +import org.mozilla.gecko.R;
    1.13 +import org.mozilla.gecko.Tab;
    1.14 +import org.mozilla.gecko.Tabs;
    1.15 +import org.mozilla.gecko.gfx.Layer.RenderContext;
    1.16 +import org.mozilla.gecko.gfx.RenderTask;
    1.17 +import org.mozilla.gecko.mozglue.DirectBufferAllocator;
    1.18 +
    1.19 +import android.content.Context;
    1.20 +import android.content.SharedPreferences;
    1.21 +import android.content.res.Resources;
    1.22 +import android.graphics.Bitmap;
    1.23 +import android.graphics.Canvas;
    1.24 +import android.graphics.Color;
    1.25 +import android.graphics.Matrix;
    1.26 +import android.graphics.PointF;
    1.27 +import android.graphics.Rect;
    1.28 +import android.graphics.RectF;
    1.29 +import android.opengl.GLES20;
    1.30 +import android.os.SystemClock;
    1.31 +import android.util.Log;
    1.32 +import org.mozilla.gecko.mozglue.JNITarget;
    1.33 +
    1.34 +import java.nio.ByteBuffer;
    1.35 +import java.nio.ByteOrder;
    1.36 +import java.nio.FloatBuffer;
    1.37 +import java.nio.IntBuffer;
    1.38 +import java.util.concurrent.CopyOnWriteArrayList;
    1.39 +
    1.40 +import javax.microedition.khronos.egl.EGLConfig;
    1.41 +
    1.42 +/**
    1.43 + * The layer renderer implements the rendering logic for a layer view.
    1.44 + */
    1.45 +public class LayerRenderer implements Tabs.OnTabsChangedListener {
    1.46 +    private static final String LOGTAG = "GeckoLayerRenderer";
    1.47 +    private static final String PROFTAG = "GeckoLayerRendererProf";
    1.48 +
    1.49 +    /*
    1.50 +     * The amount of time a frame is allowed to take to render before we declare it a dropped
    1.51 +     * frame.
    1.52 +     */
    1.53 +    private static final int MAX_FRAME_TIME = 16;   /* 1000 ms / 60 FPS */
    1.54 +
    1.55 +    private static final int FRAME_RATE_METER_WIDTH = 128;
    1.56 +    private static final int FRAME_RATE_METER_HEIGHT = 32;
    1.57 +
    1.58 +    private static final long NANOS_PER_MS = 1000000;
    1.59 +    private static final int NANOS_PER_SECOND = 1000000000;
    1.60 +
    1.61 +    private final LayerView mView;
    1.62 +    private TextLayer mFrameRateLayer;
    1.63 +    private final ScrollbarLayer mHorizScrollLayer;
    1.64 +    private final ScrollbarLayer mVertScrollLayer;
    1.65 +    private final FadeRunnable mFadeRunnable;
    1.66 +    private ByteBuffer mCoordByteBuffer;
    1.67 +    private FloatBuffer mCoordBuffer;
    1.68 +    private RenderContext mLastPageContext;
    1.69 +    private int mMaxTextureSize;
    1.70 +    private int mBackgroundColor;
    1.71 +    private int mOverscrollColor;
    1.72 +
    1.73 +    private long mLastFrameTime;
    1.74 +    private final CopyOnWriteArrayList<RenderTask> mTasks;
    1.75 +
    1.76 +    private CopyOnWriteArrayList<Layer> mExtraLayers = new CopyOnWriteArrayList<Layer>();
    1.77 +
    1.78 +    // Dropped frames display
    1.79 +    private int[] mFrameTimings;
    1.80 +    private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames;
    1.81 +
    1.82 +    // Render profiling output
    1.83 +    private int mFramesRendered;
    1.84 +    private float mCompleteFramesRendered;
    1.85 +    private boolean mProfileRender;
    1.86 +    private long mProfileOutputTime;
    1.87 +
    1.88 +    private IntBuffer mPixelBuffer;
    1.89 +
    1.90 +    // Used by GLES 2.0
    1.91 +    private int mProgram;
    1.92 +    private int mPositionHandle;
    1.93 +    private int mTextureHandle;
    1.94 +    private int mSampleHandle;
    1.95 +    private int mTMatrixHandle;
    1.96 +
    1.97 +    // column-major matrix applied to each vertex to shift the viewport from
    1.98 +    // one ranging from (-1, -1),(1,1) to (0,0),(1,1) and to scale all sizes by
    1.99 +    // a factor of 2 to fill up the screen
   1.100 +    public static final float[] DEFAULT_TEXTURE_MATRIX = {
   1.101 +        2.0f, 0.0f, 0.0f, 0.0f,
   1.102 +        0.0f, 2.0f, 0.0f, 0.0f,
   1.103 +        0.0f, 0.0f, 2.0f, 0.0f,
   1.104 +        -1.0f, -1.0f, 0.0f, 1.0f
   1.105 +    };
   1.106 +
   1.107 +    private static final int COORD_BUFFER_SIZE = 20;
   1.108 +
   1.109 +    // The shaders run on the GPU directly, the vertex shader is only applying the
   1.110 +    // matrix transform detailed above
   1.111 +
   1.112 +    // Note we flip the y-coordinate in the vertex shader from a
   1.113 +    // coordinate system with (0,0) in the top left to one with (0,0) in
   1.114 +    // the bottom left.
   1.115 +
   1.116 +    public static final String DEFAULT_VERTEX_SHADER =
   1.117 +        "uniform mat4 uTMatrix;\n" +
   1.118 +        "attribute vec4 vPosition;\n" +
   1.119 +        "attribute vec2 aTexCoord;\n" +
   1.120 +        "varying vec2 vTexCoord;\n" +
   1.121 +        "void main() {\n" +
   1.122 +        "    gl_Position = uTMatrix * vPosition;\n" +
   1.123 +        "    vTexCoord.x = aTexCoord.x;\n" +
   1.124 +        "    vTexCoord.y = 1.0 - aTexCoord.y;\n" +
   1.125 +        "}\n";
   1.126 +
   1.127 +    // We use highp because the screenshot textures
   1.128 +    // we use are large and we stretch them alot
   1.129 +    // so we need all the precision we can get.
   1.130 +    // Unfortunately, highp is not required by ES 2.0
   1.131 +    // so on GPU's like Mali we end up getting mediump
   1.132 +    public static final String DEFAULT_FRAGMENT_SHADER =
   1.133 +        "precision highp float;\n" +
   1.134 +        "varying vec2 vTexCoord;\n" +
   1.135 +        "uniform sampler2D sTexture;\n" +
   1.136 +        "void main() {\n" +
   1.137 +        "    gl_FragColor = texture2D(sTexture, vTexCoord);\n" +
   1.138 +        "}\n";
   1.139 +
   1.140 +    public LayerRenderer(LayerView view) {
   1.141 +        mView = view;
   1.142 +        try {
   1.143 +            mOverscrollColor = view.getContext().getResources().getColor(R.color.background_normal);
   1.144 +        } catch (Resources.NotFoundException nfe) { mOverscrollColor = Color.BLACK; }
   1.145 +        
   1.146 +        Bitmap scrollbarImage = view.getScrollbarImage();
   1.147 +        IntSize size = new IntSize(scrollbarImage.getWidth(), scrollbarImage.getHeight());
   1.148 +        scrollbarImage = expandCanvasToPowerOfTwo(scrollbarImage, size);
   1.149 +
   1.150 +        mTasks = new CopyOnWriteArrayList<RenderTask>();
   1.151 +        mLastFrameTime = System.nanoTime();
   1.152 +
   1.153 +        mVertScrollLayer = new ScrollbarLayer(this, scrollbarImage, size, true);
   1.154 +        mHorizScrollLayer = new ScrollbarLayer(this, diagonalFlip(scrollbarImage), new IntSize(size.height, size.width), false);
   1.155 +        mFadeRunnable = new FadeRunnable();
   1.156 +
   1.157 +        mFrameTimings = new int[60];
   1.158 +        mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0;
   1.159 +
   1.160 +        // Initialize the FloatBuffer that will be used to store all vertices and texture
   1.161 +        // coordinates in draw() commands.
   1.162 +        mCoordByteBuffer = DirectBufferAllocator.allocate(COORD_BUFFER_SIZE * 4);
   1.163 +        mCoordByteBuffer.order(ByteOrder.nativeOrder());
   1.164 +        mCoordBuffer = mCoordByteBuffer.asFloatBuffer();
   1.165 +
   1.166 +        Tabs.registerOnTabsChangedListener(this);
   1.167 +    }
   1.168 +
   1.169 +    private Bitmap expandCanvasToPowerOfTwo(Bitmap image, IntSize size) {
   1.170 +        IntSize potSize = size.nextPowerOfTwo();
   1.171 +        if (size.equals(potSize)) {
   1.172 +            return image;
   1.173 +        }
   1.174 +        // make the bitmap size a power-of-two in both dimensions if it's not already.
   1.175 +        Bitmap potImage = Bitmap.createBitmap(potSize.width, potSize.height, image.getConfig());
   1.176 +        new Canvas(potImage).drawBitmap(image, new Matrix(), null);
   1.177 +        return potImage;
   1.178 +    }
   1.179 +
   1.180 +    private Bitmap diagonalFlip(Bitmap image) {
   1.181 +        Matrix rotation = new Matrix();
   1.182 +        rotation.setValues(new float[] { 0, 1, 0, 1, 0, 0, 0, 0, 1 }); // transform (x,y) into (y,x)
   1.183 +        Bitmap rotated = Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), rotation, true);
   1.184 +        return rotated;
   1.185 +    }
   1.186 +
   1.187 +    public void destroy() {
   1.188 +        DirectBufferAllocator.free(mCoordByteBuffer);
   1.189 +        mCoordByteBuffer = null;
   1.190 +        mCoordBuffer = null;
   1.191 +        mHorizScrollLayer.destroy();
   1.192 +        mVertScrollLayer.destroy();
   1.193 +        if (mFrameRateLayer != null) {
   1.194 +            mFrameRateLayer.destroy();
   1.195 +        }
   1.196 +        Tabs.unregisterOnTabsChangedListener(this);
   1.197 +    }
   1.198 +
   1.199 +    void onSurfaceCreated(EGLConfig config) {
   1.200 +        checkMonitoringEnabled();
   1.201 +        createDefaultProgram();
   1.202 +        activateDefaultProgram();
   1.203 +    }
   1.204 +
   1.205 +    public void createDefaultProgram() {
   1.206 +        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DEFAULT_VERTEX_SHADER);
   1.207 +        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DEFAULT_FRAGMENT_SHADER);
   1.208 +
   1.209 +        mProgram = GLES20.glCreateProgram();
   1.210 +        GLES20.glAttachShader(mProgram, vertexShader);   // add the vertex shader to program
   1.211 +        GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
   1.212 +        GLES20.glLinkProgram(mProgram);                  // creates OpenGL program executables
   1.213 +
   1.214 +        // Get handles to the vertex shader's vPosition, aTexCoord, sTexture, and uTMatrix members.
   1.215 +        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
   1.216 +        mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord");
   1.217 +        mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture");
   1.218 +        mTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uTMatrix");
   1.219 +
   1.220 +        int maxTextureSizeResult[] = new int[1];
   1.221 +        GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0);
   1.222 +        mMaxTextureSize = maxTextureSizeResult[0];
   1.223 +    }
   1.224 +
   1.225 +    // Activates the shader program.
   1.226 +    public void activateDefaultProgram() {
   1.227 +        // Add the program to the OpenGL environment
   1.228 +        GLES20.glUseProgram(mProgram);
   1.229 +
   1.230 +        // Set the transformation matrix
   1.231 +        GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false, DEFAULT_TEXTURE_MATRIX, 0);
   1.232 +
   1.233 +        // Enable the arrays from which we get the vertex and texture coordinates
   1.234 +        GLES20.glEnableVertexAttribArray(mPositionHandle);
   1.235 +        GLES20.glEnableVertexAttribArray(mTextureHandle);
   1.236 +
   1.237 +        GLES20.glUniform1i(mSampleHandle, 0);
   1.238 +
   1.239 +        // TODO: Move these calls into a separate deactivate() call that is called after the
   1.240 +        // underlay and overlay are rendered.
   1.241 +    }
   1.242 +
   1.243 +    // Deactivates the shader program. This must be done to avoid crashes after returning to the
   1.244 +    // Gecko C++ compositor from Java.
   1.245 +    public void deactivateDefaultProgram() {
   1.246 +        GLES20.glDisableVertexAttribArray(mTextureHandle);
   1.247 +        GLES20.glDisableVertexAttribArray(mPositionHandle);
   1.248 +        GLES20.glUseProgram(0);
   1.249 +    }
   1.250 +
   1.251 +    public int getMaxTextureSize() {
   1.252 +        return mMaxTextureSize;
   1.253 +    }
   1.254 +
   1.255 +    public void postRenderTask(RenderTask aTask) {
   1.256 +        mTasks.add(aTask);
   1.257 +        mView.requestRender();
   1.258 +    }
   1.259 +
   1.260 +    public void removeRenderTask(RenderTask aTask) {
   1.261 +        mTasks.remove(aTask);
   1.262 +    }
   1.263 +
   1.264 +    private void runRenderTasks(CopyOnWriteArrayList<RenderTask> tasks, boolean after, long frameStartTime) {
   1.265 +        for (RenderTask task : tasks) {
   1.266 +            if (task.runAfter != after) {
   1.267 +                continue;
   1.268 +            }
   1.269 +
   1.270 +            boolean stillRunning = task.run(frameStartTime - mLastFrameTime, frameStartTime);
   1.271 +
   1.272 +            // Remove the task from the list if its finished
   1.273 +            if (!stillRunning) {
   1.274 +                tasks.remove(task);
   1.275 +            }
   1.276 +        }
   1.277 +    }
   1.278 +
   1.279 +    public void addLayer(Layer layer) {
   1.280 +        synchronized (mExtraLayers) {
   1.281 +            if (mExtraLayers.contains(layer)) {
   1.282 +                mExtraLayers.remove(layer);
   1.283 +            }
   1.284 +
   1.285 +            mExtraLayers.add(layer);
   1.286 +        }
   1.287 +    }
   1.288 +
   1.289 +    public void removeLayer(Layer layer) {
   1.290 +        synchronized (mExtraLayers) {
   1.291 +            mExtraLayers.remove(layer);
   1.292 +        }
   1.293 +    }
   1.294 +
   1.295 +    private void printCheckerboardStats() {
   1.296 +        Log.d(PROFTAG, "Frames rendered over last 1000ms: " + mCompleteFramesRendered + "/" + mFramesRendered);
   1.297 +        mFramesRendered = 0;
   1.298 +        mCompleteFramesRendered = 0;
   1.299 +    }
   1.300 +
   1.301 +    /** Used by robocop for testing purposes. Not for production use! */
   1.302 +    IntBuffer getPixels() {
   1.303 +        IntBuffer pixelBuffer = IntBuffer.allocate(mView.getWidth() * mView.getHeight());
   1.304 +        synchronized (pixelBuffer) {
   1.305 +            mPixelBuffer = pixelBuffer;
   1.306 +            mView.requestRender();
   1.307 +            try {
   1.308 +                pixelBuffer.wait();
   1.309 +            } catch (InterruptedException ie) {
   1.310 +            }
   1.311 +            mPixelBuffer = null;
   1.312 +        }
   1.313 +        return pixelBuffer;
   1.314 +    }
   1.315 +
   1.316 +    private RenderContext createScreenContext(ImmutableViewportMetrics metrics, PointF offset) {
   1.317 +        RectF viewport = new RectF(0.0f, 0.0f, metrics.getWidth(), metrics.getHeight());
   1.318 +        RectF pageRect = metrics.getPageRect();
   1.319 +
   1.320 +        return createContext(viewport, pageRect, 1.0f, offset);
   1.321 +    }
   1.322 +
   1.323 +    private RenderContext createPageContext(ImmutableViewportMetrics metrics, PointF offset) {
   1.324 +        RectF viewport = metrics.getViewport();
   1.325 +        RectF pageRect = metrics.getPageRect();
   1.326 +        float zoomFactor = metrics.zoomFactor;
   1.327 +
   1.328 +        return createContext(new RectF(RectUtils.round(viewport)), pageRect, zoomFactor, offset);
   1.329 +    }
   1.330 +
   1.331 +    private RenderContext createContext(RectF viewport, RectF pageRect, float zoomFactor, PointF offset) {
   1.332 +        if (mCoordBuffer == null) {
   1.333 +            throw new IllegalStateException();
   1.334 +        }
   1.335 +        return new RenderContext(viewport, pageRect, zoomFactor, offset, mPositionHandle, mTextureHandle,
   1.336 +                                 mCoordBuffer);
   1.337 +    }
   1.338 +
   1.339 +    private void updateDroppedFrames(long frameStartTime) {
   1.340 +        int frameElapsedTime = (int)((System.nanoTime() - frameStartTime) / NANOS_PER_MS);
   1.341 +
   1.342 +        /* Update the running statistics. */
   1.343 +        mFrameTimingsSum -= mFrameTimings[mCurrentFrame];
   1.344 +        mFrameTimingsSum += frameElapsedTime;
   1.345 +        mDroppedFrames -= (mFrameTimings[mCurrentFrame] + 1) / MAX_FRAME_TIME;
   1.346 +        mDroppedFrames += (frameElapsedTime + 1) / MAX_FRAME_TIME;
   1.347 +
   1.348 +        mFrameTimings[mCurrentFrame] = frameElapsedTime;
   1.349 +        mCurrentFrame = (mCurrentFrame + 1) % mFrameTimings.length;
   1.350 +
   1.351 +        int averageTime = mFrameTimingsSum / mFrameTimings.length;
   1.352 +        mFrameRateLayer.beginTransaction();     // called on compositor thread
   1.353 +        try {
   1.354 +            mFrameRateLayer.setText(averageTime + " ms/" + mDroppedFrames);
   1.355 +        } finally {
   1.356 +            mFrameRateLayer.endTransaction();
   1.357 +        }
   1.358 +    }
   1.359 +
   1.360 +    /* Given the new dimensions for the surface, moves the frame rate layer appropriately. */
   1.361 +    private void moveFrameRateLayer(int width, int height) {
   1.362 +        mFrameRateLayer.beginTransaction();     // called on compositor thread
   1.363 +        try {
   1.364 +            Rect position = new Rect(width - FRAME_RATE_METER_WIDTH - 8,
   1.365 +                                    height - FRAME_RATE_METER_HEIGHT + 8,
   1.366 +                                    width - 8,
   1.367 +                                    height + 8);
   1.368 +            mFrameRateLayer.setPosition(position);
   1.369 +        } finally {
   1.370 +            mFrameRateLayer.endTransaction();
   1.371 +        }
   1.372 +    }
   1.373 +
   1.374 +    void checkMonitoringEnabled() {
   1.375 +        /* Do this I/O off the main thread to minimize its impact on startup time. */
   1.376 +        new Thread(new Runnable() {
   1.377 +            @Override
   1.378 +            public void run() {
   1.379 +                Context context = mView.getContext();
   1.380 +                SharedPreferences preferences = context.getSharedPreferences("GeckoApp", 0);
   1.381 +                if (preferences.getBoolean("showFrameRate", false)) {
   1.382 +                    IntSize frameRateLayerSize = new IntSize(FRAME_RATE_METER_WIDTH, FRAME_RATE_METER_HEIGHT);
   1.383 +                    mFrameRateLayer = TextLayer.create(frameRateLayerSize, "-- ms/--");
   1.384 +                    moveFrameRateLayer(mView.getWidth(), mView.getHeight());
   1.385 +                }
   1.386 +                mProfileRender = Log.isLoggable(PROFTAG, Log.DEBUG);
   1.387 +            }
   1.388 +        }).start();
   1.389 +    }
   1.390 +
   1.391 +    /*
   1.392 +     * create a vertex shader type (GLES20.GL_VERTEX_SHADER)
   1.393 +     * or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
   1.394 +     */
   1.395 +    public static int loadShader(int type, String shaderCode) {
   1.396 +        int shader = GLES20.glCreateShader(type);
   1.397 +        GLES20.glShaderSource(shader, shaderCode);
   1.398 +        GLES20.glCompileShader(shader);
   1.399 +        return shader;
   1.400 +    }
   1.401 +
   1.402 +    public Frame createFrame(ImmutableViewportMetrics metrics) {
   1.403 +        return new Frame(metrics);
   1.404 +    }
   1.405 +
   1.406 +    class FadeRunnable implements Runnable {
   1.407 +        private boolean mStarted;
   1.408 +        private long mRunAt;
   1.409 +
   1.410 +        void scheduleStartFade(long delay) {
   1.411 +            mRunAt = SystemClock.elapsedRealtime() + delay;
   1.412 +            if (!mStarted) {
   1.413 +                mView.postDelayed(this, delay);
   1.414 +                mStarted = true;
   1.415 +            }
   1.416 +        }
   1.417 +
   1.418 +        void scheduleNextFadeFrame() {
   1.419 +            if (mStarted) {
   1.420 +                Log.e(LOGTAG, "scheduleNextFadeFrame() called while scheduled for starting fade");
   1.421 +            }
   1.422 +            mView.postDelayed(this, 1000L / 60L); // request another frame at 60fps
   1.423 +        }
   1.424 +
   1.425 +        boolean timeToFade() {
   1.426 +            return !mStarted;
   1.427 +        }
   1.428 +
   1.429 +        @Override
   1.430 +        public void run() {
   1.431 +            long timeDelta = mRunAt - SystemClock.elapsedRealtime();
   1.432 +            if (timeDelta > 0) {
   1.433 +                // the run-at time was pushed back, so reschedule
   1.434 +                mView.postDelayed(this, timeDelta);
   1.435 +            } else {
   1.436 +                // reached the run-at time, execute
   1.437 +                mStarted = false;
   1.438 +                mView.requestRender();
   1.439 +            }
   1.440 +        }
   1.441 +    }
   1.442 +
   1.443 +    public class Frame {
   1.444 +        // The timestamp recording the start of this frame.
   1.445 +        private long mFrameStartTime;
   1.446 +        // A fixed snapshot of the viewport metrics that this frame is using to render content.
   1.447 +        private ImmutableViewportMetrics mFrameMetrics;
   1.448 +        // A rendering context for page-positioned layers, and one for screen-positioned layers.
   1.449 +        private RenderContext mPageContext, mScreenContext;
   1.450 +        // Whether a layer was updated.
   1.451 +        private boolean mUpdated;
   1.452 +        private final Rect mPageRect;
   1.453 +        private final Rect mAbsolutePageRect;
   1.454 +        private final PointF mRenderOffset;
   1.455 +
   1.456 +        public Frame(ImmutableViewportMetrics metrics) {
   1.457 +            mFrameMetrics = metrics;
   1.458 +
   1.459 +            // Work out the offset due to margins
   1.460 +            Layer rootLayer = mView.getLayerClient().getRoot();
   1.461 +            mRenderOffset = mFrameMetrics.getMarginOffset();
   1.462 +            mPageContext = createPageContext(metrics, mRenderOffset);
   1.463 +            mScreenContext = createScreenContext(metrics, mRenderOffset);
   1.464 +
   1.465 +            RectF pageRect = mFrameMetrics.getPageRect();
   1.466 +            mAbsolutePageRect = RectUtils.round(pageRect);
   1.467 +
   1.468 +            PointF origin = mFrameMetrics.getOrigin();
   1.469 +            pageRect.offset(-origin.x, -origin.y);
   1.470 +            mPageRect = RectUtils.round(pageRect);
   1.471 +        }
   1.472 +
   1.473 +        private void setScissorRect() {
   1.474 +            Rect scissorRect = transformToScissorRect(mPageRect);
   1.475 +            GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
   1.476 +            GLES20.glScissor(scissorRect.left, scissorRect.top,
   1.477 +                             scissorRect.width(), scissorRect.height());
   1.478 +        }
   1.479 +
   1.480 +        private Rect transformToScissorRect(Rect rect) {
   1.481 +            IntSize screenSize = new IntSize(mFrameMetrics.getSize());
   1.482 +
   1.483 +            int left = Math.max(0, rect.left);
   1.484 +            int top = Math.max(0, rect.top);
   1.485 +            int right = Math.min(screenSize.width, rect.right);
   1.486 +            int bottom = Math.min(screenSize.height, rect.bottom);
   1.487 +
   1.488 +            Rect scissorRect = new Rect(left, screenSize.height - bottom, right,
   1.489 +                                        (screenSize.height - bottom) + (bottom - top));
   1.490 +            scissorRect.offset(Math.round(-mRenderOffset.x), Math.round(-mRenderOffset.y));
   1.491 +
   1.492 +            return scissorRect;
   1.493 +        }
   1.494 +
   1.495 +        /** This function is invoked via JNI; be careful when modifying signature. */
   1.496 +        @JNITarget
   1.497 +        public void beginDrawing() {
   1.498 +            mFrameStartTime = System.nanoTime();
   1.499 +
   1.500 +            TextureReaper.get().reap();
   1.501 +            TextureGenerator.get().fill();
   1.502 +
   1.503 +            mUpdated = true;
   1.504 +
   1.505 +            Layer rootLayer = mView.getLayerClient().getRoot();
   1.506 +
   1.507 +            // Run through pre-render tasks
   1.508 +            runRenderTasks(mTasks, false, mFrameStartTime);
   1.509 +
   1.510 +            if (!mPageContext.fuzzyEquals(mLastPageContext) && !mView.isFullScreen()) {
   1.511 +                // The viewport or page changed, so show the scrollbars again
   1.512 +                // as per UX decision. Don't do this if we're in full-screen mode though.
   1.513 +                mVertScrollLayer.unfade();
   1.514 +                mHorizScrollLayer.unfade();
   1.515 +                mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY);
   1.516 +            } else if (mFadeRunnable.timeToFade()) {
   1.517 +                boolean stillFading = mVertScrollLayer.fade() | mHorizScrollLayer.fade();
   1.518 +                if (stillFading) {
   1.519 +                    mFadeRunnable.scheduleNextFadeFrame();
   1.520 +                }
   1.521 +            }
   1.522 +            mLastPageContext = mPageContext;
   1.523 +
   1.524 +            /* Update layers. */
   1.525 +            if (rootLayer != null) {
   1.526 +                // Called on compositor thread.
   1.527 +                mUpdated &= rootLayer.update(mPageContext);
   1.528 +            }
   1.529 +
   1.530 +            if (mFrameRateLayer != null) {
   1.531 +                // Called on compositor thread.
   1.532 +                mUpdated &= mFrameRateLayer.update(mScreenContext);
   1.533 +            }
   1.534 +
   1.535 +            mUpdated &= mVertScrollLayer.update(mPageContext);  // called on compositor thread
   1.536 +            mUpdated &= mHorizScrollLayer.update(mPageContext); // called on compositor thread
   1.537 +
   1.538 +            for (Layer layer : mExtraLayers) {
   1.539 +                mUpdated &= layer.update(mPageContext); // called on compositor thread
   1.540 +            }
   1.541 +        }
   1.542 +
   1.543 +        /** Retrieves the bounds for the layer, rounded in such a way that it
   1.544 +         * can be used as a mask for something that will render underneath it.
   1.545 +         * This will round the bounds inwards, but stretch the mask towards any
   1.546 +         * near page edge, where near is considered to be 'within 2 pixels'.
   1.547 +         * Returns null if the given layer is null.
   1.548 +         */
   1.549 +        private Rect getMaskForLayer(Layer layer) {
   1.550 +            if (layer == null) {
   1.551 +                return null;
   1.552 +            }
   1.553 +
   1.554 +            RectF bounds = RectUtils.contract(layer.getBounds(mPageContext), 1.0f, 1.0f);
   1.555 +            Rect mask = RectUtils.roundIn(bounds);
   1.556 +
   1.557 +            // If the mask is within two pixels of any page edge, stretch it over
   1.558 +            // that edge. This is to avoid drawing thin slivers when masking
   1.559 +            // layers.
   1.560 +            if (mask.top <= 2) {
   1.561 +                mask.top = -1;
   1.562 +            }
   1.563 +            if (mask.left <= 2) {
   1.564 +                mask.left = -1;
   1.565 +            }
   1.566 +
   1.567 +            // Because we're drawing relative to the page-rect, we only need to
   1.568 +            // take into account its width and height (and not its origin)
   1.569 +            int pageRight = mPageRect.width();
   1.570 +            int pageBottom = mPageRect.height();
   1.571 +
   1.572 +            if (mask.right >= pageRight - 2) {
   1.573 +                mask.right = pageRight + 1;
   1.574 +            }
   1.575 +            if (mask.bottom >= pageBottom - 2) {
   1.576 +                mask.bottom = pageBottom + 1;
   1.577 +            }
   1.578 +
   1.579 +            return mask;
   1.580 +        }
   1.581 +
   1.582 +        private void clear(int color) {
   1.583 +            GLES20.glClearColor(((color >> 16) & 0xFF) / 255.0f,
   1.584 +                                ((color >> 8) & 0xFF) / 255.0f,
   1.585 +                                (color & 0xFF) / 255.0f,
   1.586 +                                0.0f);
   1.587 +            // The bits set here need to match up with those used
   1.588 +            // in gfx/layers/opengl/LayerManagerOGL.cpp.
   1.589 +            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT |
   1.590 +                           GLES20.GL_DEPTH_BUFFER_BIT);
   1.591 +        }
   1.592 +
   1.593 +        /** This function is invoked via JNI; be careful when modifying signature. */
   1.594 +        @JNITarget
   1.595 +        public void drawBackground() {
   1.596 +            // Any GL state which is changed here must be restored in
   1.597 +            // CompositorOGL::RestoreState
   1.598 +
   1.599 +            GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
   1.600 +
   1.601 +            // Draw the overscroll background area as a solid color
   1.602 +            clear(mOverscrollColor);
   1.603 +
   1.604 +            // Update background color.
   1.605 +            mBackgroundColor = mView.getBackgroundColor();
   1.606 +
   1.607 +            // Clear the page area to the page background colour.
   1.608 +            setScissorRect();
   1.609 +            clear(mBackgroundColor);
   1.610 +            GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
   1.611 +        }
   1.612 +
   1.613 +        // Draws the layer the client added to us.
   1.614 +        void drawRootLayer() {
   1.615 +            Layer rootLayer = mView.getLayerClient().getRoot();
   1.616 +            if (rootLayer == null) {
   1.617 +                return;
   1.618 +            }
   1.619 +
   1.620 +            rootLayer.draw(mPageContext);
   1.621 +        }
   1.622 +
   1.623 +        @JNITarget
   1.624 +        public void drawForeground() {
   1.625 +            // Any GL state which is changed here must be restored in
   1.626 +            // CompositorOGL::RestoreState
   1.627 +
   1.628 +            /* Draw any extra layers that were added (likely plugins) */
   1.629 +            if (mExtraLayers.size() > 0) {
   1.630 +                for (Layer layer : mExtraLayers) {
   1.631 +                    layer.draw(mPageContext);
   1.632 +                }
   1.633 +            }
   1.634 +
   1.635 +            /* Draw the vertical scrollbar. */
   1.636 +            if (mPageRect.height() > mFrameMetrics.getHeight())
   1.637 +                mVertScrollLayer.draw(mPageContext);
   1.638 +
   1.639 +            /* Draw the horizontal scrollbar. */
   1.640 +            if (mPageRect.width() > mFrameMetrics.getWidth())
   1.641 +                mHorizScrollLayer.draw(mPageContext);
   1.642 +
   1.643 +            /* Measure how much of the screen is checkerboarding */
   1.644 +            Layer rootLayer = mView.getLayerClient().getRoot();
   1.645 +            if ((rootLayer != null) &&
   1.646 +                (mProfileRender || PanningPerfAPI.isRecordingCheckerboard())) {
   1.647 +                // Calculate the incompletely rendered area of the page
   1.648 +                float checkerboard =  1.0f - GeckoAppShell.computeRenderIntegrity();
   1.649 +
   1.650 +                PanningPerfAPI.recordCheckerboard(checkerboard);
   1.651 +                if (checkerboard < 0.0f || checkerboard > 1.0f) {
   1.652 +                    Log.e(LOGTAG, "Checkerboard value out of bounds: " + checkerboard);
   1.653 +                }
   1.654 +
   1.655 +                mCompleteFramesRendered += 1.0f - checkerboard;
   1.656 +                mFramesRendered ++;
   1.657 +
   1.658 +                if (mFrameStartTime - mProfileOutputTime > NANOS_PER_SECOND) {
   1.659 +                    mProfileOutputTime = mFrameStartTime;
   1.660 +                    printCheckerboardStats();
   1.661 +                }
   1.662 +            }
   1.663 +
   1.664 +            runRenderTasks(mTasks, true, mFrameStartTime);
   1.665 +
   1.666 +            /* Draw the FPS. */
   1.667 +            if (mFrameRateLayer != null) {
   1.668 +                updateDroppedFrames(mFrameStartTime);
   1.669 +
   1.670 +                GLES20.glEnable(GLES20.GL_BLEND);
   1.671 +                GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
   1.672 +                mFrameRateLayer.draw(mScreenContext);
   1.673 +            }
   1.674 +        }
   1.675 +
   1.676 +        /** This function is invoked via JNI; be careful when modifying signature. */
   1.677 +        @JNITarget
   1.678 +        public void endDrawing() {
   1.679 +            // If a layer update requires further work, schedule another redraw
   1.680 +            if (!mUpdated)
   1.681 +                mView.requestRender();
   1.682 +
   1.683 +            PanningPerfAPI.recordFrameTime();
   1.684 +
   1.685 +            /* Used by robocop for testing purposes */
   1.686 +            IntBuffer pixelBuffer = mPixelBuffer;
   1.687 +            if (mUpdated && pixelBuffer != null) {
   1.688 +                synchronized (pixelBuffer) {
   1.689 +                    pixelBuffer.position(0);
   1.690 +                    GLES20.glReadPixels(0, 0, (int)mScreenContext.viewport.width(),
   1.691 +                                        (int)mScreenContext.viewport.height(), GLES20.GL_RGBA,
   1.692 +                                        GLES20.GL_UNSIGNED_BYTE, pixelBuffer);
   1.693 +                    pixelBuffer.notify();
   1.694 +                }
   1.695 +            }
   1.696 +
   1.697 +            // Remove background color once we've painted. GeckoLayerClient is
   1.698 +            // responsible for setting this flag before current document is
   1.699 +            // composited.
   1.700 +            if (mView.getPaintState() == LayerView.PAINT_BEFORE_FIRST) {
   1.701 +                mView.post(new Runnable() {
   1.702 +                    @Override
   1.703 +                    public void run() {
   1.704 +                        mView.getChildAt(0).setBackgroundColor(Color.TRANSPARENT);
   1.705 +                    }
   1.706 +                });
   1.707 +                mView.setPaintState(LayerView.PAINT_AFTER_FIRST);
   1.708 +            }
   1.709 +            mLastFrameTime = mFrameStartTime;
   1.710 +        }
   1.711 +    }
   1.712 +
   1.713 +    @Override
   1.714 +    public void onTabChanged(final Tab tab, Tabs.TabEvents msg, Object data) {
   1.715 +        // Sets the background of the newly selected tab. This background color
   1.716 +        // gets cleared in endDrawing(). This function runs on the UI thread,
   1.717 +        // but other code that touches the paint state is run on the compositor
   1.718 +        // thread, so this may need to be changed if any problems appear.
   1.719 +        if (msg == Tabs.TabEvents.SELECTED) {
   1.720 +            if (mView != null) {
   1.721 +                if (mView.getChildAt(0) != null) {
   1.722 +                    mView.getChildAt(0).setBackgroundColor(tab.getBackgroundColor());
   1.723 +                }
   1.724 +                mView.setPaintState(LayerView.PAINT_START);
   1.725 +            }
   1.726 +        }
   1.727 +    }
   1.728 +}

mercurial