michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.gfx; michael@0: michael@0: import org.mozilla.gecko.GeckoAppShell; michael@0: import org.mozilla.gecko.R; michael@0: import org.mozilla.gecko.Tab; michael@0: import org.mozilla.gecko.Tabs; michael@0: import org.mozilla.gecko.gfx.Layer.RenderContext; michael@0: import org.mozilla.gecko.gfx.RenderTask; michael@0: import org.mozilla.gecko.mozglue.DirectBufferAllocator; michael@0: michael@0: import android.content.Context; michael@0: import android.content.SharedPreferences; michael@0: import android.content.res.Resources; michael@0: import android.graphics.Bitmap; michael@0: import android.graphics.Canvas; michael@0: import android.graphics.Color; michael@0: import android.graphics.Matrix; michael@0: import android.graphics.PointF; michael@0: import android.graphics.Rect; michael@0: import android.graphics.RectF; michael@0: import android.opengl.GLES20; michael@0: import android.os.SystemClock; michael@0: import android.util.Log; michael@0: import org.mozilla.gecko.mozglue.JNITarget; michael@0: michael@0: import java.nio.ByteBuffer; michael@0: import java.nio.ByteOrder; michael@0: import java.nio.FloatBuffer; michael@0: import java.nio.IntBuffer; michael@0: import java.util.concurrent.CopyOnWriteArrayList; michael@0: michael@0: import javax.microedition.khronos.egl.EGLConfig; michael@0: michael@0: /** michael@0: * The layer renderer implements the rendering logic for a layer view. michael@0: */ michael@0: public class LayerRenderer implements Tabs.OnTabsChangedListener { michael@0: private static final String LOGTAG = "GeckoLayerRenderer"; michael@0: private static final String PROFTAG = "GeckoLayerRendererProf"; michael@0: michael@0: /* michael@0: * The amount of time a frame is allowed to take to render before we declare it a dropped michael@0: * frame. michael@0: */ michael@0: private static final int MAX_FRAME_TIME = 16; /* 1000 ms / 60 FPS */ michael@0: michael@0: private static final int FRAME_RATE_METER_WIDTH = 128; michael@0: private static final int FRAME_RATE_METER_HEIGHT = 32; michael@0: michael@0: private static final long NANOS_PER_MS = 1000000; michael@0: private static final int NANOS_PER_SECOND = 1000000000; michael@0: michael@0: private final LayerView mView; michael@0: private TextLayer mFrameRateLayer; michael@0: private final ScrollbarLayer mHorizScrollLayer; michael@0: private final ScrollbarLayer mVertScrollLayer; michael@0: private final FadeRunnable mFadeRunnable; michael@0: private ByteBuffer mCoordByteBuffer; michael@0: private FloatBuffer mCoordBuffer; michael@0: private RenderContext mLastPageContext; michael@0: private int mMaxTextureSize; michael@0: private int mBackgroundColor; michael@0: private int mOverscrollColor; michael@0: michael@0: private long mLastFrameTime; michael@0: private final CopyOnWriteArrayList mTasks; michael@0: michael@0: private CopyOnWriteArrayList mExtraLayers = new CopyOnWriteArrayList(); michael@0: michael@0: // Dropped frames display michael@0: private int[] mFrameTimings; michael@0: private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames; michael@0: michael@0: // Render profiling output michael@0: private int mFramesRendered; michael@0: private float mCompleteFramesRendered; michael@0: private boolean mProfileRender; michael@0: private long mProfileOutputTime; michael@0: michael@0: private IntBuffer mPixelBuffer; michael@0: michael@0: // Used by GLES 2.0 michael@0: private int mProgram; michael@0: private int mPositionHandle; michael@0: private int mTextureHandle; michael@0: private int mSampleHandle; michael@0: private int mTMatrixHandle; michael@0: michael@0: // column-major matrix applied to each vertex to shift the viewport from michael@0: // one ranging from (-1, -1),(1,1) to (0,0),(1,1) and to scale all sizes by michael@0: // a factor of 2 to fill up the screen michael@0: public static final float[] DEFAULT_TEXTURE_MATRIX = { michael@0: 2.0f, 0.0f, 0.0f, 0.0f, michael@0: 0.0f, 2.0f, 0.0f, 0.0f, michael@0: 0.0f, 0.0f, 2.0f, 0.0f, michael@0: -1.0f, -1.0f, 0.0f, 1.0f michael@0: }; michael@0: michael@0: private static final int COORD_BUFFER_SIZE = 20; michael@0: michael@0: // The shaders run on the GPU directly, the vertex shader is only applying the michael@0: // matrix transform detailed above michael@0: michael@0: // Note we flip the y-coordinate in the vertex shader from a michael@0: // coordinate system with (0,0) in the top left to one with (0,0) in michael@0: // the bottom left. michael@0: michael@0: public static final String DEFAULT_VERTEX_SHADER = michael@0: "uniform mat4 uTMatrix;\n" + michael@0: "attribute vec4 vPosition;\n" + michael@0: "attribute vec2 aTexCoord;\n" + michael@0: "varying vec2 vTexCoord;\n" + michael@0: "void main() {\n" + michael@0: " gl_Position = uTMatrix * vPosition;\n" + michael@0: " vTexCoord.x = aTexCoord.x;\n" + michael@0: " vTexCoord.y = 1.0 - aTexCoord.y;\n" + michael@0: "}\n"; michael@0: michael@0: // We use highp because the screenshot textures michael@0: // we use are large and we stretch them alot michael@0: // so we need all the precision we can get. michael@0: // Unfortunately, highp is not required by ES 2.0 michael@0: // so on GPU's like Mali we end up getting mediump michael@0: public static final String DEFAULT_FRAGMENT_SHADER = michael@0: "precision highp float;\n" + michael@0: "varying vec2 vTexCoord;\n" + michael@0: "uniform sampler2D sTexture;\n" + michael@0: "void main() {\n" + michael@0: " gl_FragColor = texture2D(sTexture, vTexCoord);\n" + michael@0: "}\n"; michael@0: michael@0: public LayerRenderer(LayerView view) { michael@0: mView = view; michael@0: try { michael@0: mOverscrollColor = view.getContext().getResources().getColor(R.color.background_normal); michael@0: } catch (Resources.NotFoundException nfe) { mOverscrollColor = Color.BLACK; } michael@0: michael@0: Bitmap scrollbarImage = view.getScrollbarImage(); michael@0: IntSize size = new IntSize(scrollbarImage.getWidth(), scrollbarImage.getHeight()); michael@0: scrollbarImage = expandCanvasToPowerOfTwo(scrollbarImage, size); michael@0: michael@0: mTasks = new CopyOnWriteArrayList(); michael@0: mLastFrameTime = System.nanoTime(); michael@0: michael@0: mVertScrollLayer = new ScrollbarLayer(this, scrollbarImage, size, true); michael@0: mHorizScrollLayer = new ScrollbarLayer(this, diagonalFlip(scrollbarImage), new IntSize(size.height, size.width), false); michael@0: mFadeRunnable = new FadeRunnable(); michael@0: michael@0: mFrameTimings = new int[60]; michael@0: mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0; michael@0: michael@0: // Initialize the FloatBuffer that will be used to store all vertices and texture michael@0: // coordinates in draw() commands. michael@0: mCoordByteBuffer = DirectBufferAllocator.allocate(COORD_BUFFER_SIZE * 4); michael@0: mCoordByteBuffer.order(ByteOrder.nativeOrder()); michael@0: mCoordBuffer = mCoordByteBuffer.asFloatBuffer(); michael@0: michael@0: Tabs.registerOnTabsChangedListener(this); michael@0: } michael@0: michael@0: private Bitmap expandCanvasToPowerOfTwo(Bitmap image, IntSize size) { michael@0: IntSize potSize = size.nextPowerOfTwo(); michael@0: if (size.equals(potSize)) { michael@0: return image; michael@0: } michael@0: // make the bitmap size a power-of-two in both dimensions if it's not already. michael@0: Bitmap potImage = Bitmap.createBitmap(potSize.width, potSize.height, image.getConfig()); michael@0: new Canvas(potImage).drawBitmap(image, new Matrix(), null); michael@0: return potImage; michael@0: } michael@0: michael@0: private Bitmap diagonalFlip(Bitmap image) { michael@0: Matrix rotation = new Matrix(); michael@0: rotation.setValues(new float[] { 0, 1, 0, 1, 0, 0, 0, 0, 1 }); // transform (x,y) into (y,x) michael@0: Bitmap rotated = Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), rotation, true); michael@0: return rotated; michael@0: } michael@0: michael@0: public void destroy() { michael@0: DirectBufferAllocator.free(mCoordByteBuffer); michael@0: mCoordByteBuffer = null; michael@0: mCoordBuffer = null; michael@0: mHorizScrollLayer.destroy(); michael@0: mVertScrollLayer.destroy(); michael@0: if (mFrameRateLayer != null) { michael@0: mFrameRateLayer.destroy(); michael@0: } michael@0: Tabs.unregisterOnTabsChangedListener(this); michael@0: } michael@0: michael@0: void onSurfaceCreated(EGLConfig config) { michael@0: checkMonitoringEnabled(); michael@0: createDefaultProgram(); michael@0: activateDefaultProgram(); michael@0: } michael@0: michael@0: public void createDefaultProgram() { michael@0: int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DEFAULT_VERTEX_SHADER); michael@0: int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DEFAULT_FRAGMENT_SHADER); michael@0: michael@0: mProgram = GLES20.glCreateProgram(); michael@0: GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program michael@0: GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program michael@0: GLES20.glLinkProgram(mProgram); // creates OpenGL program executables michael@0: michael@0: // Get handles to the vertex shader's vPosition, aTexCoord, sTexture, and uTMatrix members. michael@0: mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition"); michael@0: mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord"); michael@0: mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture"); michael@0: mTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uTMatrix"); michael@0: michael@0: int maxTextureSizeResult[] = new int[1]; michael@0: GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0); michael@0: mMaxTextureSize = maxTextureSizeResult[0]; michael@0: } michael@0: michael@0: // Activates the shader program. michael@0: public void activateDefaultProgram() { michael@0: // Add the program to the OpenGL environment michael@0: GLES20.glUseProgram(mProgram); michael@0: michael@0: // Set the transformation matrix michael@0: GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false, DEFAULT_TEXTURE_MATRIX, 0); michael@0: michael@0: // Enable the arrays from which we get the vertex and texture coordinates michael@0: GLES20.glEnableVertexAttribArray(mPositionHandle); michael@0: GLES20.glEnableVertexAttribArray(mTextureHandle); michael@0: michael@0: GLES20.glUniform1i(mSampleHandle, 0); michael@0: michael@0: // TODO: Move these calls into a separate deactivate() call that is called after the michael@0: // underlay and overlay are rendered. michael@0: } michael@0: michael@0: // Deactivates the shader program. This must be done to avoid crashes after returning to the michael@0: // Gecko C++ compositor from Java. michael@0: public void deactivateDefaultProgram() { michael@0: GLES20.glDisableVertexAttribArray(mTextureHandle); michael@0: GLES20.glDisableVertexAttribArray(mPositionHandle); michael@0: GLES20.glUseProgram(0); michael@0: } michael@0: michael@0: public int getMaxTextureSize() { michael@0: return mMaxTextureSize; michael@0: } michael@0: michael@0: public void postRenderTask(RenderTask aTask) { michael@0: mTasks.add(aTask); michael@0: mView.requestRender(); michael@0: } michael@0: michael@0: public void removeRenderTask(RenderTask aTask) { michael@0: mTasks.remove(aTask); michael@0: } michael@0: michael@0: private void runRenderTasks(CopyOnWriteArrayList tasks, boolean after, long frameStartTime) { michael@0: for (RenderTask task : tasks) { michael@0: if (task.runAfter != after) { michael@0: continue; michael@0: } michael@0: michael@0: boolean stillRunning = task.run(frameStartTime - mLastFrameTime, frameStartTime); michael@0: michael@0: // Remove the task from the list if its finished michael@0: if (!stillRunning) { michael@0: tasks.remove(task); michael@0: } michael@0: } michael@0: } michael@0: michael@0: public void addLayer(Layer layer) { michael@0: synchronized (mExtraLayers) { michael@0: if (mExtraLayers.contains(layer)) { michael@0: mExtraLayers.remove(layer); michael@0: } michael@0: michael@0: mExtraLayers.add(layer); michael@0: } michael@0: } michael@0: michael@0: public void removeLayer(Layer layer) { michael@0: synchronized (mExtraLayers) { michael@0: mExtraLayers.remove(layer); michael@0: } michael@0: } michael@0: michael@0: private void printCheckerboardStats() { michael@0: Log.d(PROFTAG, "Frames rendered over last 1000ms: " + mCompleteFramesRendered + "/" + mFramesRendered); michael@0: mFramesRendered = 0; michael@0: mCompleteFramesRendered = 0; michael@0: } michael@0: michael@0: /** Used by robocop for testing purposes. Not for production use! */ michael@0: IntBuffer getPixels() { michael@0: IntBuffer pixelBuffer = IntBuffer.allocate(mView.getWidth() * mView.getHeight()); michael@0: synchronized (pixelBuffer) { michael@0: mPixelBuffer = pixelBuffer; michael@0: mView.requestRender(); michael@0: try { michael@0: pixelBuffer.wait(); michael@0: } catch (InterruptedException ie) { michael@0: } michael@0: mPixelBuffer = null; michael@0: } michael@0: return pixelBuffer; michael@0: } michael@0: michael@0: private RenderContext createScreenContext(ImmutableViewportMetrics metrics, PointF offset) { michael@0: RectF viewport = new RectF(0.0f, 0.0f, metrics.getWidth(), metrics.getHeight()); michael@0: RectF pageRect = metrics.getPageRect(); michael@0: michael@0: return createContext(viewport, pageRect, 1.0f, offset); michael@0: } michael@0: michael@0: private RenderContext createPageContext(ImmutableViewportMetrics metrics, PointF offset) { michael@0: RectF viewport = metrics.getViewport(); michael@0: RectF pageRect = metrics.getPageRect(); michael@0: float zoomFactor = metrics.zoomFactor; michael@0: michael@0: return createContext(new RectF(RectUtils.round(viewport)), pageRect, zoomFactor, offset); michael@0: } michael@0: michael@0: private RenderContext createContext(RectF viewport, RectF pageRect, float zoomFactor, PointF offset) { michael@0: if (mCoordBuffer == null) { michael@0: throw new IllegalStateException(); michael@0: } michael@0: return new RenderContext(viewport, pageRect, zoomFactor, offset, mPositionHandle, mTextureHandle, michael@0: mCoordBuffer); michael@0: } michael@0: michael@0: private void updateDroppedFrames(long frameStartTime) { michael@0: int frameElapsedTime = (int)((System.nanoTime() - frameStartTime) / NANOS_PER_MS); michael@0: michael@0: /* Update the running statistics. */ michael@0: mFrameTimingsSum -= mFrameTimings[mCurrentFrame]; michael@0: mFrameTimingsSum += frameElapsedTime; michael@0: mDroppedFrames -= (mFrameTimings[mCurrentFrame] + 1) / MAX_FRAME_TIME; michael@0: mDroppedFrames += (frameElapsedTime + 1) / MAX_FRAME_TIME; michael@0: michael@0: mFrameTimings[mCurrentFrame] = frameElapsedTime; michael@0: mCurrentFrame = (mCurrentFrame + 1) % mFrameTimings.length; michael@0: michael@0: int averageTime = mFrameTimingsSum / mFrameTimings.length; michael@0: mFrameRateLayer.beginTransaction(); // called on compositor thread michael@0: try { michael@0: mFrameRateLayer.setText(averageTime + " ms/" + mDroppedFrames); michael@0: } finally { michael@0: mFrameRateLayer.endTransaction(); michael@0: } michael@0: } michael@0: michael@0: /* Given the new dimensions for the surface, moves the frame rate layer appropriately. */ michael@0: private void moveFrameRateLayer(int width, int height) { michael@0: mFrameRateLayer.beginTransaction(); // called on compositor thread michael@0: try { michael@0: Rect position = new Rect(width - FRAME_RATE_METER_WIDTH - 8, michael@0: height - FRAME_RATE_METER_HEIGHT + 8, michael@0: width - 8, michael@0: height + 8); michael@0: mFrameRateLayer.setPosition(position); michael@0: } finally { michael@0: mFrameRateLayer.endTransaction(); michael@0: } michael@0: } michael@0: michael@0: void checkMonitoringEnabled() { michael@0: /* Do this I/O off the main thread to minimize its impact on startup time. */ michael@0: new Thread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: Context context = mView.getContext(); michael@0: SharedPreferences preferences = context.getSharedPreferences("GeckoApp", 0); michael@0: if (preferences.getBoolean("showFrameRate", false)) { michael@0: IntSize frameRateLayerSize = new IntSize(FRAME_RATE_METER_WIDTH, FRAME_RATE_METER_HEIGHT); michael@0: mFrameRateLayer = TextLayer.create(frameRateLayerSize, "-- ms/--"); michael@0: moveFrameRateLayer(mView.getWidth(), mView.getHeight()); michael@0: } michael@0: mProfileRender = Log.isLoggable(PROFTAG, Log.DEBUG); michael@0: } michael@0: }).start(); michael@0: } michael@0: michael@0: /* michael@0: * create a vertex shader type (GLES20.GL_VERTEX_SHADER) michael@0: * or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) michael@0: */ michael@0: public static int loadShader(int type, String shaderCode) { michael@0: int shader = GLES20.glCreateShader(type); michael@0: GLES20.glShaderSource(shader, shaderCode); michael@0: GLES20.glCompileShader(shader); michael@0: return shader; michael@0: } michael@0: michael@0: public Frame createFrame(ImmutableViewportMetrics metrics) { michael@0: return new Frame(metrics); michael@0: } michael@0: michael@0: class FadeRunnable implements Runnable { michael@0: private boolean mStarted; michael@0: private long mRunAt; michael@0: michael@0: void scheduleStartFade(long delay) { michael@0: mRunAt = SystemClock.elapsedRealtime() + delay; michael@0: if (!mStarted) { michael@0: mView.postDelayed(this, delay); michael@0: mStarted = true; michael@0: } michael@0: } michael@0: michael@0: void scheduleNextFadeFrame() { michael@0: if (mStarted) { michael@0: Log.e(LOGTAG, "scheduleNextFadeFrame() called while scheduled for starting fade"); michael@0: } michael@0: mView.postDelayed(this, 1000L / 60L); // request another frame at 60fps michael@0: } michael@0: michael@0: boolean timeToFade() { michael@0: return !mStarted; michael@0: } michael@0: michael@0: @Override michael@0: public void run() { michael@0: long timeDelta = mRunAt - SystemClock.elapsedRealtime(); michael@0: if (timeDelta > 0) { michael@0: // the run-at time was pushed back, so reschedule michael@0: mView.postDelayed(this, timeDelta); michael@0: } else { michael@0: // reached the run-at time, execute michael@0: mStarted = false; michael@0: mView.requestRender(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: public class Frame { michael@0: // The timestamp recording the start of this frame. michael@0: private long mFrameStartTime; michael@0: // A fixed snapshot of the viewport metrics that this frame is using to render content. michael@0: private ImmutableViewportMetrics mFrameMetrics; michael@0: // A rendering context for page-positioned layers, and one for screen-positioned layers. michael@0: private RenderContext mPageContext, mScreenContext; michael@0: // Whether a layer was updated. michael@0: private boolean mUpdated; michael@0: private final Rect mPageRect; michael@0: private final Rect mAbsolutePageRect; michael@0: private final PointF mRenderOffset; michael@0: michael@0: public Frame(ImmutableViewportMetrics metrics) { michael@0: mFrameMetrics = metrics; michael@0: michael@0: // Work out the offset due to margins michael@0: Layer rootLayer = mView.getLayerClient().getRoot(); michael@0: mRenderOffset = mFrameMetrics.getMarginOffset(); michael@0: mPageContext = createPageContext(metrics, mRenderOffset); michael@0: mScreenContext = createScreenContext(metrics, mRenderOffset); michael@0: michael@0: RectF pageRect = mFrameMetrics.getPageRect(); michael@0: mAbsolutePageRect = RectUtils.round(pageRect); michael@0: michael@0: PointF origin = mFrameMetrics.getOrigin(); michael@0: pageRect.offset(-origin.x, -origin.y); michael@0: mPageRect = RectUtils.round(pageRect); michael@0: } michael@0: michael@0: private void setScissorRect() { michael@0: Rect scissorRect = transformToScissorRect(mPageRect); michael@0: GLES20.glEnable(GLES20.GL_SCISSOR_TEST); michael@0: GLES20.glScissor(scissorRect.left, scissorRect.top, michael@0: scissorRect.width(), scissorRect.height()); michael@0: } michael@0: michael@0: private Rect transformToScissorRect(Rect rect) { michael@0: IntSize screenSize = new IntSize(mFrameMetrics.getSize()); michael@0: michael@0: int left = Math.max(0, rect.left); michael@0: int top = Math.max(0, rect.top); michael@0: int right = Math.min(screenSize.width, rect.right); michael@0: int bottom = Math.min(screenSize.height, rect.bottom); michael@0: michael@0: Rect scissorRect = new Rect(left, screenSize.height - bottom, right, michael@0: (screenSize.height - bottom) + (bottom - top)); michael@0: scissorRect.offset(Math.round(-mRenderOffset.x), Math.round(-mRenderOffset.y)); michael@0: michael@0: return scissorRect; michael@0: } michael@0: michael@0: /** This function is invoked via JNI; be careful when modifying signature. */ michael@0: @JNITarget michael@0: public void beginDrawing() { michael@0: mFrameStartTime = System.nanoTime(); michael@0: michael@0: TextureReaper.get().reap(); michael@0: TextureGenerator.get().fill(); michael@0: michael@0: mUpdated = true; michael@0: michael@0: Layer rootLayer = mView.getLayerClient().getRoot(); michael@0: michael@0: // Run through pre-render tasks michael@0: runRenderTasks(mTasks, false, mFrameStartTime); michael@0: michael@0: if (!mPageContext.fuzzyEquals(mLastPageContext) && !mView.isFullScreen()) { michael@0: // The viewport or page changed, so show the scrollbars again michael@0: // as per UX decision. Don't do this if we're in full-screen mode though. michael@0: mVertScrollLayer.unfade(); michael@0: mHorizScrollLayer.unfade(); michael@0: mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY); michael@0: } else if (mFadeRunnable.timeToFade()) { michael@0: boolean stillFading = mVertScrollLayer.fade() | mHorizScrollLayer.fade(); michael@0: if (stillFading) { michael@0: mFadeRunnable.scheduleNextFadeFrame(); michael@0: } michael@0: } michael@0: mLastPageContext = mPageContext; michael@0: michael@0: /* Update layers. */ michael@0: if (rootLayer != null) { michael@0: // Called on compositor thread. michael@0: mUpdated &= rootLayer.update(mPageContext); michael@0: } michael@0: michael@0: if (mFrameRateLayer != null) { michael@0: // Called on compositor thread. michael@0: mUpdated &= mFrameRateLayer.update(mScreenContext); michael@0: } michael@0: michael@0: mUpdated &= mVertScrollLayer.update(mPageContext); // called on compositor thread michael@0: mUpdated &= mHorizScrollLayer.update(mPageContext); // called on compositor thread michael@0: michael@0: for (Layer layer : mExtraLayers) { michael@0: mUpdated &= layer.update(mPageContext); // called on compositor thread michael@0: } michael@0: } michael@0: michael@0: /** Retrieves the bounds for the layer, rounded in such a way that it michael@0: * can be used as a mask for something that will render underneath it. michael@0: * This will round the bounds inwards, but stretch the mask towards any michael@0: * near page edge, where near is considered to be 'within 2 pixels'. michael@0: * Returns null if the given layer is null. michael@0: */ michael@0: private Rect getMaskForLayer(Layer layer) { michael@0: if (layer == null) { michael@0: return null; michael@0: } michael@0: michael@0: RectF bounds = RectUtils.contract(layer.getBounds(mPageContext), 1.0f, 1.0f); michael@0: Rect mask = RectUtils.roundIn(bounds); michael@0: michael@0: // If the mask is within two pixels of any page edge, stretch it over michael@0: // that edge. This is to avoid drawing thin slivers when masking michael@0: // layers. michael@0: if (mask.top <= 2) { michael@0: mask.top = -1; michael@0: } michael@0: if (mask.left <= 2) { michael@0: mask.left = -1; michael@0: } michael@0: michael@0: // Because we're drawing relative to the page-rect, we only need to michael@0: // take into account its width and height (and not its origin) michael@0: int pageRight = mPageRect.width(); michael@0: int pageBottom = mPageRect.height(); michael@0: michael@0: if (mask.right >= pageRight - 2) { michael@0: mask.right = pageRight + 1; michael@0: } michael@0: if (mask.bottom >= pageBottom - 2) { michael@0: mask.bottom = pageBottom + 1; michael@0: } michael@0: michael@0: return mask; michael@0: } michael@0: michael@0: private void clear(int color) { michael@0: GLES20.glClearColor(((color >> 16) & 0xFF) / 255.0f, michael@0: ((color >> 8) & 0xFF) / 255.0f, michael@0: (color & 0xFF) / 255.0f, michael@0: 0.0f); michael@0: // The bits set here need to match up with those used michael@0: // in gfx/layers/opengl/LayerManagerOGL.cpp. michael@0: GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | michael@0: GLES20.GL_DEPTH_BUFFER_BIT); michael@0: } michael@0: michael@0: /** This function is invoked via JNI; be careful when modifying signature. */ michael@0: @JNITarget michael@0: public void drawBackground() { michael@0: // Any GL state which is changed here must be restored in michael@0: // CompositorOGL::RestoreState michael@0: michael@0: GLES20.glDisable(GLES20.GL_SCISSOR_TEST); michael@0: michael@0: // Draw the overscroll background area as a solid color michael@0: clear(mOverscrollColor); michael@0: michael@0: // Update background color. michael@0: mBackgroundColor = mView.getBackgroundColor(); michael@0: michael@0: // Clear the page area to the page background colour. michael@0: setScissorRect(); michael@0: clear(mBackgroundColor); michael@0: GLES20.glDisable(GLES20.GL_SCISSOR_TEST); michael@0: } michael@0: michael@0: // Draws the layer the client added to us. michael@0: void drawRootLayer() { michael@0: Layer rootLayer = mView.getLayerClient().getRoot(); michael@0: if (rootLayer == null) { michael@0: return; michael@0: } michael@0: michael@0: rootLayer.draw(mPageContext); michael@0: } michael@0: michael@0: @JNITarget michael@0: public void drawForeground() { michael@0: // Any GL state which is changed here must be restored in michael@0: // CompositorOGL::RestoreState michael@0: michael@0: /* Draw any extra layers that were added (likely plugins) */ michael@0: if (mExtraLayers.size() > 0) { michael@0: for (Layer layer : mExtraLayers) { michael@0: layer.draw(mPageContext); michael@0: } michael@0: } michael@0: michael@0: /* Draw the vertical scrollbar. */ michael@0: if (mPageRect.height() > mFrameMetrics.getHeight()) michael@0: mVertScrollLayer.draw(mPageContext); michael@0: michael@0: /* Draw the horizontal scrollbar. */ michael@0: if (mPageRect.width() > mFrameMetrics.getWidth()) michael@0: mHorizScrollLayer.draw(mPageContext); michael@0: michael@0: /* Measure how much of the screen is checkerboarding */ michael@0: Layer rootLayer = mView.getLayerClient().getRoot(); michael@0: if ((rootLayer != null) && michael@0: (mProfileRender || PanningPerfAPI.isRecordingCheckerboard())) { michael@0: // Calculate the incompletely rendered area of the page michael@0: float checkerboard = 1.0f - GeckoAppShell.computeRenderIntegrity(); michael@0: michael@0: PanningPerfAPI.recordCheckerboard(checkerboard); michael@0: if (checkerboard < 0.0f || checkerboard > 1.0f) { michael@0: Log.e(LOGTAG, "Checkerboard value out of bounds: " + checkerboard); michael@0: } michael@0: michael@0: mCompleteFramesRendered += 1.0f - checkerboard; michael@0: mFramesRendered ++; michael@0: michael@0: if (mFrameStartTime - mProfileOutputTime > NANOS_PER_SECOND) { michael@0: mProfileOutputTime = mFrameStartTime; michael@0: printCheckerboardStats(); michael@0: } michael@0: } michael@0: michael@0: runRenderTasks(mTasks, true, mFrameStartTime); michael@0: michael@0: /* Draw the FPS. */ michael@0: if (mFrameRateLayer != null) { michael@0: updateDroppedFrames(mFrameStartTime); michael@0: michael@0: GLES20.glEnable(GLES20.GL_BLEND); michael@0: GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); michael@0: mFrameRateLayer.draw(mScreenContext); michael@0: } michael@0: } michael@0: michael@0: /** This function is invoked via JNI; be careful when modifying signature. */ michael@0: @JNITarget michael@0: public void endDrawing() { michael@0: // If a layer update requires further work, schedule another redraw michael@0: if (!mUpdated) michael@0: mView.requestRender(); michael@0: michael@0: PanningPerfAPI.recordFrameTime(); michael@0: michael@0: /* Used by robocop for testing purposes */ michael@0: IntBuffer pixelBuffer = mPixelBuffer; michael@0: if (mUpdated && pixelBuffer != null) { michael@0: synchronized (pixelBuffer) { michael@0: pixelBuffer.position(0); michael@0: GLES20.glReadPixels(0, 0, (int)mScreenContext.viewport.width(), michael@0: (int)mScreenContext.viewport.height(), GLES20.GL_RGBA, michael@0: GLES20.GL_UNSIGNED_BYTE, pixelBuffer); michael@0: pixelBuffer.notify(); michael@0: } michael@0: } michael@0: michael@0: // Remove background color once we've painted. GeckoLayerClient is michael@0: // responsible for setting this flag before current document is michael@0: // composited. michael@0: if (mView.getPaintState() == LayerView.PAINT_BEFORE_FIRST) { michael@0: mView.post(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: mView.getChildAt(0).setBackgroundColor(Color.TRANSPARENT); michael@0: } michael@0: }); michael@0: mView.setPaintState(LayerView.PAINT_AFTER_FIRST); michael@0: } michael@0: mLastFrameTime = mFrameStartTime; michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void onTabChanged(final Tab tab, Tabs.TabEvents msg, Object data) { michael@0: // Sets the background of the newly selected tab. This background color michael@0: // gets cleared in endDrawing(). This function runs on the UI thread, michael@0: // but other code that touches the paint state is run on the compositor michael@0: // thread, so this may need to be changed if any problems appear. michael@0: if (msg == Tabs.TabEvents.SELECTED) { michael@0: if (mView != null) { michael@0: if (mView.getChildAt(0) != null) { michael@0: mView.getChildAt(0).setBackgroundColor(tab.getBackgroundColor()); michael@0: } michael@0: mView.setPaintState(LayerView.PAINT_START); michael@0: } michael@0: } michael@0: } michael@0: }