|
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
|
2 * This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 package org.mozilla.gecko.gfx; |
|
7 |
|
8 import org.mozilla.gecko.GeckoAppShell; |
|
9 import org.mozilla.gecko.R; |
|
10 import org.mozilla.gecko.Tab; |
|
11 import org.mozilla.gecko.Tabs; |
|
12 import org.mozilla.gecko.gfx.Layer.RenderContext; |
|
13 import org.mozilla.gecko.gfx.RenderTask; |
|
14 import org.mozilla.gecko.mozglue.DirectBufferAllocator; |
|
15 |
|
16 import android.content.Context; |
|
17 import android.content.SharedPreferences; |
|
18 import android.content.res.Resources; |
|
19 import android.graphics.Bitmap; |
|
20 import android.graphics.Canvas; |
|
21 import android.graphics.Color; |
|
22 import android.graphics.Matrix; |
|
23 import android.graphics.PointF; |
|
24 import android.graphics.Rect; |
|
25 import android.graphics.RectF; |
|
26 import android.opengl.GLES20; |
|
27 import android.os.SystemClock; |
|
28 import android.util.Log; |
|
29 import org.mozilla.gecko.mozglue.JNITarget; |
|
30 |
|
31 import java.nio.ByteBuffer; |
|
32 import java.nio.ByteOrder; |
|
33 import java.nio.FloatBuffer; |
|
34 import java.nio.IntBuffer; |
|
35 import java.util.concurrent.CopyOnWriteArrayList; |
|
36 |
|
37 import javax.microedition.khronos.egl.EGLConfig; |
|
38 |
|
39 /** |
|
40 * The layer renderer implements the rendering logic for a layer view. |
|
41 */ |
|
42 public class LayerRenderer implements Tabs.OnTabsChangedListener { |
|
43 private static final String LOGTAG = "GeckoLayerRenderer"; |
|
44 private static final String PROFTAG = "GeckoLayerRendererProf"; |
|
45 |
|
46 /* |
|
47 * The amount of time a frame is allowed to take to render before we declare it a dropped |
|
48 * frame. |
|
49 */ |
|
50 private static final int MAX_FRAME_TIME = 16; /* 1000 ms / 60 FPS */ |
|
51 |
|
52 private static final int FRAME_RATE_METER_WIDTH = 128; |
|
53 private static final int FRAME_RATE_METER_HEIGHT = 32; |
|
54 |
|
55 private static final long NANOS_PER_MS = 1000000; |
|
56 private static final int NANOS_PER_SECOND = 1000000000; |
|
57 |
|
58 private final LayerView mView; |
|
59 private TextLayer mFrameRateLayer; |
|
60 private final ScrollbarLayer mHorizScrollLayer; |
|
61 private final ScrollbarLayer mVertScrollLayer; |
|
62 private final FadeRunnable mFadeRunnable; |
|
63 private ByteBuffer mCoordByteBuffer; |
|
64 private FloatBuffer mCoordBuffer; |
|
65 private RenderContext mLastPageContext; |
|
66 private int mMaxTextureSize; |
|
67 private int mBackgroundColor; |
|
68 private int mOverscrollColor; |
|
69 |
|
70 private long mLastFrameTime; |
|
71 private final CopyOnWriteArrayList<RenderTask> mTasks; |
|
72 |
|
73 private CopyOnWriteArrayList<Layer> mExtraLayers = new CopyOnWriteArrayList<Layer>(); |
|
74 |
|
75 // Dropped frames display |
|
76 private int[] mFrameTimings; |
|
77 private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames; |
|
78 |
|
79 // Render profiling output |
|
80 private int mFramesRendered; |
|
81 private float mCompleteFramesRendered; |
|
82 private boolean mProfileRender; |
|
83 private long mProfileOutputTime; |
|
84 |
|
85 private IntBuffer mPixelBuffer; |
|
86 |
|
87 // Used by GLES 2.0 |
|
88 private int mProgram; |
|
89 private int mPositionHandle; |
|
90 private int mTextureHandle; |
|
91 private int mSampleHandle; |
|
92 private int mTMatrixHandle; |
|
93 |
|
94 // column-major matrix applied to each vertex to shift the viewport from |
|
95 // one ranging from (-1, -1),(1,1) to (0,0),(1,1) and to scale all sizes by |
|
96 // a factor of 2 to fill up the screen |
|
97 public static final float[] DEFAULT_TEXTURE_MATRIX = { |
|
98 2.0f, 0.0f, 0.0f, 0.0f, |
|
99 0.0f, 2.0f, 0.0f, 0.0f, |
|
100 0.0f, 0.0f, 2.0f, 0.0f, |
|
101 -1.0f, -1.0f, 0.0f, 1.0f |
|
102 }; |
|
103 |
|
104 private static final int COORD_BUFFER_SIZE = 20; |
|
105 |
|
106 // The shaders run on the GPU directly, the vertex shader is only applying the |
|
107 // matrix transform detailed above |
|
108 |
|
109 // Note we flip the y-coordinate in the vertex shader from a |
|
110 // coordinate system with (0,0) in the top left to one with (0,0) in |
|
111 // the bottom left. |
|
112 |
|
113 public static final String DEFAULT_VERTEX_SHADER = |
|
114 "uniform mat4 uTMatrix;\n" + |
|
115 "attribute vec4 vPosition;\n" + |
|
116 "attribute vec2 aTexCoord;\n" + |
|
117 "varying vec2 vTexCoord;\n" + |
|
118 "void main() {\n" + |
|
119 " gl_Position = uTMatrix * vPosition;\n" + |
|
120 " vTexCoord.x = aTexCoord.x;\n" + |
|
121 " vTexCoord.y = 1.0 - aTexCoord.y;\n" + |
|
122 "}\n"; |
|
123 |
|
124 // We use highp because the screenshot textures |
|
125 // we use are large and we stretch them alot |
|
126 // so we need all the precision we can get. |
|
127 // Unfortunately, highp is not required by ES 2.0 |
|
128 // so on GPU's like Mali we end up getting mediump |
|
129 public static final String DEFAULT_FRAGMENT_SHADER = |
|
130 "precision highp float;\n" + |
|
131 "varying vec2 vTexCoord;\n" + |
|
132 "uniform sampler2D sTexture;\n" + |
|
133 "void main() {\n" + |
|
134 " gl_FragColor = texture2D(sTexture, vTexCoord);\n" + |
|
135 "}\n"; |
|
136 |
|
137 public LayerRenderer(LayerView view) { |
|
138 mView = view; |
|
139 try { |
|
140 mOverscrollColor = view.getContext().getResources().getColor(R.color.background_normal); |
|
141 } catch (Resources.NotFoundException nfe) { mOverscrollColor = Color.BLACK; } |
|
142 |
|
143 Bitmap scrollbarImage = view.getScrollbarImage(); |
|
144 IntSize size = new IntSize(scrollbarImage.getWidth(), scrollbarImage.getHeight()); |
|
145 scrollbarImage = expandCanvasToPowerOfTwo(scrollbarImage, size); |
|
146 |
|
147 mTasks = new CopyOnWriteArrayList<RenderTask>(); |
|
148 mLastFrameTime = System.nanoTime(); |
|
149 |
|
150 mVertScrollLayer = new ScrollbarLayer(this, scrollbarImage, size, true); |
|
151 mHorizScrollLayer = new ScrollbarLayer(this, diagonalFlip(scrollbarImage), new IntSize(size.height, size.width), false); |
|
152 mFadeRunnable = new FadeRunnable(); |
|
153 |
|
154 mFrameTimings = new int[60]; |
|
155 mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0; |
|
156 |
|
157 // Initialize the FloatBuffer that will be used to store all vertices and texture |
|
158 // coordinates in draw() commands. |
|
159 mCoordByteBuffer = DirectBufferAllocator.allocate(COORD_BUFFER_SIZE * 4); |
|
160 mCoordByteBuffer.order(ByteOrder.nativeOrder()); |
|
161 mCoordBuffer = mCoordByteBuffer.asFloatBuffer(); |
|
162 |
|
163 Tabs.registerOnTabsChangedListener(this); |
|
164 } |
|
165 |
|
166 private Bitmap expandCanvasToPowerOfTwo(Bitmap image, IntSize size) { |
|
167 IntSize potSize = size.nextPowerOfTwo(); |
|
168 if (size.equals(potSize)) { |
|
169 return image; |
|
170 } |
|
171 // make the bitmap size a power-of-two in both dimensions if it's not already. |
|
172 Bitmap potImage = Bitmap.createBitmap(potSize.width, potSize.height, image.getConfig()); |
|
173 new Canvas(potImage).drawBitmap(image, new Matrix(), null); |
|
174 return potImage; |
|
175 } |
|
176 |
|
177 private Bitmap diagonalFlip(Bitmap image) { |
|
178 Matrix rotation = new Matrix(); |
|
179 rotation.setValues(new float[] { 0, 1, 0, 1, 0, 0, 0, 0, 1 }); // transform (x,y) into (y,x) |
|
180 Bitmap rotated = Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), rotation, true); |
|
181 return rotated; |
|
182 } |
|
183 |
|
184 public void destroy() { |
|
185 DirectBufferAllocator.free(mCoordByteBuffer); |
|
186 mCoordByteBuffer = null; |
|
187 mCoordBuffer = null; |
|
188 mHorizScrollLayer.destroy(); |
|
189 mVertScrollLayer.destroy(); |
|
190 if (mFrameRateLayer != null) { |
|
191 mFrameRateLayer.destroy(); |
|
192 } |
|
193 Tabs.unregisterOnTabsChangedListener(this); |
|
194 } |
|
195 |
|
196 void onSurfaceCreated(EGLConfig config) { |
|
197 checkMonitoringEnabled(); |
|
198 createDefaultProgram(); |
|
199 activateDefaultProgram(); |
|
200 } |
|
201 |
|
202 public void createDefaultProgram() { |
|
203 int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DEFAULT_VERTEX_SHADER); |
|
204 int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DEFAULT_FRAGMENT_SHADER); |
|
205 |
|
206 mProgram = GLES20.glCreateProgram(); |
|
207 GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program |
|
208 GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program |
|
209 GLES20.glLinkProgram(mProgram); // creates OpenGL program executables |
|
210 |
|
211 // Get handles to the vertex shader's vPosition, aTexCoord, sTexture, and uTMatrix members. |
|
212 mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition"); |
|
213 mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord"); |
|
214 mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture"); |
|
215 mTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uTMatrix"); |
|
216 |
|
217 int maxTextureSizeResult[] = new int[1]; |
|
218 GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0); |
|
219 mMaxTextureSize = maxTextureSizeResult[0]; |
|
220 } |
|
221 |
|
222 // Activates the shader program. |
|
223 public void activateDefaultProgram() { |
|
224 // Add the program to the OpenGL environment |
|
225 GLES20.glUseProgram(mProgram); |
|
226 |
|
227 // Set the transformation matrix |
|
228 GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false, DEFAULT_TEXTURE_MATRIX, 0); |
|
229 |
|
230 // Enable the arrays from which we get the vertex and texture coordinates |
|
231 GLES20.glEnableVertexAttribArray(mPositionHandle); |
|
232 GLES20.glEnableVertexAttribArray(mTextureHandle); |
|
233 |
|
234 GLES20.glUniform1i(mSampleHandle, 0); |
|
235 |
|
236 // TODO: Move these calls into a separate deactivate() call that is called after the |
|
237 // underlay and overlay are rendered. |
|
238 } |
|
239 |
|
240 // Deactivates the shader program. This must be done to avoid crashes after returning to the |
|
241 // Gecko C++ compositor from Java. |
|
242 public void deactivateDefaultProgram() { |
|
243 GLES20.glDisableVertexAttribArray(mTextureHandle); |
|
244 GLES20.glDisableVertexAttribArray(mPositionHandle); |
|
245 GLES20.glUseProgram(0); |
|
246 } |
|
247 |
|
248 public int getMaxTextureSize() { |
|
249 return mMaxTextureSize; |
|
250 } |
|
251 |
|
252 public void postRenderTask(RenderTask aTask) { |
|
253 mTasks.add(aTask); |
|
254 mView.requestRender(); |
|
255 } |
|
256 |
|
257 public void removeRenderTask(RenderTask aTask) { |
|
258 mTasks.remove(aTask); |
|
259 } |
|
260 |
|
261 private void runRenderTasks(CopyOnWriteArrayList<RenderTask> tasks, boolean after, long frameStartTime) { |
|
262 for (RenderTask task : tasks) { |
|
263 if (task.runAfter != after) { |
|
264 continue; |
|
265 } |
|
266 |
|
267 boolean stillRunning = task.run(frameStartTime - mLastFrameTime, frameStartTime); |
|
268 |
|
269 // Remove the task from the list if its finished |
|
270 if (!stillRunning) { |
|
271 tasks.remove(task); |
|
272 } |
|
273 } |
|
274 } |
|
275 |
|
276 public void addLayer(Layer layer) { |
|
277 synchronized (mExtraLayers) { |
|
278 if (mExtraLayers.contains(layer)) { |
|
279 mExtraLayers.remove(layer); |
|
280 } |
|
281 |
|
282 mExtraLayers.add(layer); |
|
283 } |
|
284 } |
|
285 |
|
286 public void removeLayer(Layer layer) { |
|
287 synchronized (mExtraLayers) { |
|
288 mExtraLayers.remove(layer); |
|
289 } |
|
290 } |
|
291 |
|
292 private void printCheckerboardStats() { |
|
293 Log.d(PROFTAG, "Frames rendered over last 1000ms: " + mCompleteFramesRendered + "/" + mFramesRendered); |
|
294 mFramesRendered = 0; |
|
295 mCompleteFramesRendered = 0; |
|
296 } |
|
297 |
|
298 /** Used by robocop for testing purposes. Not for production use! */ |
|
299 IntBuffer getPixels() { |
|
300 IntBuffer pixelBuffer = IntBuffer.allocate(mView.getWidth() * mView.getHeight()); |
|
301 synchronized (pixelBuffer) { |
|
302 mPixelBuffer = pixelBuffer; |
|
303 mView.requestRender(); |
|
304 try { |
|
305 pixelBuffer.wait(); |
|
306 } catch (InterruptedException ie) { |
|
307 } |
|
308 mPixelBuffer = null; |
|
309 } |
|
310 return pixelBuffer; |
|
311 } |
|
312 |
|
313 private RenderContext createScreenContext(ImmutableViewportMetrics metrics, PointF offset) { |
|
314 RectF viewport = new RectF(0.0f, 0.0f, metrics.getWidth(), metrics.getHeight()); |
|
315 RectF pageRect = metrics.getPageRect(); |
|
316 |
|
317 return createContext(viewport, pageRect, 1.0f, offset); |
|
318 } |
|
319 |
|
320 private RenderContext createPageContext(ImmutableViewportMetrics metrics, PointF offset) { |
|
321 RectF viewport = metrics.getViewport(); |
|
322 RectF pageRect = metrics.getPageRect(); |
|
323 float zoomFactor = metrics.zoomFactor; |
|
324 |
|
325 return createContext(new RectF(RectUtils.round(viewport)), pageRect, zoomFactor, offset); |
|
326 } |
|
327 |
|
328 private RenderContext createContext(RectF viewport, RectF pageRect, float zoomFactor, PointF offset) { |
|
329 if (mCoordBuffer == null) { |
|
330 throw new IllegalStateException(); |
|
331 } |
|
332 return new RenderContext(viewport, pageRect, zoomFactor, offset, mPositionHandle, mTextureHandle, |
|
333 mCoordBuffer); |
|
334 } |
|
335 |
|
336 private void updateDroppedFrames(long frameStartTime) { |
|
337 int frameElapsedTime = (int)((System.nanoTime() - frameStartTime) / NANOS_PER_MS); |
|
338 |
|
339 /* Update the running statistics. */ |
|
340 mFrameTimingsSum -= mFrameTimings[mCurrentFrame]; |
|
341 mFrameTimingsSum += frameElapsedTime; |
|
342 mDroppedFrames -= (mFrameTimings[mCurrentFrame] + 1) / MAX_FRAME_TIME; |
|
343 mDroppedFrames += (frameElapsedTime + 1) / MAX_FRAME_TIME; |
|
344 |
|
345 mFrameTimings[mCurrentFrame] = frameElapsedTime; |
|
346 mCurrentFrame = (mCurrentFrame + 1) % mFrameTimings.length; |
|
347 |
|
348 int averageTime = mFrameTimingsSum / mFrameTimings.length; |
|
349 mFrameRateLayer.beginTransaction(); // called on compositor thread |
|
350 try { |
|
351 mFrameRateLayer.setText(averageTime + " ms/" + mDroppedFrames); |
|
352 } finally { |
|
353 mFrameRateLayer.endTransaction(); |
|
354 } |
|
355 } |
|
356 |
|
357 /* Given the new dimensions for the surface, moves the frame rate layer appropriately. */ |
|
358 private void moveFrameRateLayer(int width, int height) { |
|
359 mFrameRateLayer.beginTransaction(); // called on compositor thread |
|
360 try { |
|
361 Rect position = new Rect(width - FRAME_RATE_METER_WIDTH - 8, |
|
362 height - FRAME_RATE_METER_HEIGHT + 8, |
|
363 width - 8, |
|
364 height + 8); |
|
365 mFrameRateLayer.setPosition(position); |
|
366 } finally { |
|
367 mFrameRateLayer.endTransaction(); |
|
368 } |
|
369 } |
|
370 |
|
371 void checkMonitoringEnabled() { |
|
372 /* Do this I/O off the main thread to minimize its impact on startup time. */ |
|
373 new Thread(new Runnable() { |
|
374 @Override |
|
375 public void run() { |
|
376 Context context = mView.getContext(); |
|
377 SharedPreferences preferences = context.getSharedPreferences("GeckoApp", 0); |
|
378 if (preferences.getBoolean("showFrameRate", false)) { |
|
379 IntSize frameRateLayerSize = new IntSize(FRAME_RATE_METER_WIDTH, FRAME_RATE_METER_HEIGHT); |
|
380 mFrameRateLayer = TextLayer.create(frameRateLayerSize, "-- ms/--"); |
|
381 moveFrameRateLayer(mView.getWidth(), mView.getHeight()); |
|
382 } |
|
383 mProfileRender = Log.isLoggable(PROFTAG, Log.DEBUG); |
|
384 } |
|
385 }).start(); |
|
386 } |
|
387 |
|
388 /* |
|
389 * create a vertex shader type (GLES20.GL_VERTEX_SHADER) |
|
390 * or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) |
|
391 */ |
|
392 public static int loadShader(int type, String shaderCode) { |
|
393 int shader = GLES20.glCreateShader(type); |
|
394 GLES20.glShaderSource(shader, shaderCode); |
|
395 GLES20.glCompileShader(shader); |
|
396 return shader; |
|
397 } |
|
398 |
|
399 public Frame createFrame(ImmutableViewportMetrics metrics) { |
|
400 return new Frame(metrics); |
|
401 } |
|
402 |
|
403 class FadeRunnable implements Runnable { |
|
404 private boolean mStarted; |
|
405 private long mRunAt; |
|
406 |
|
407 void scheduleStartFade(long delay) { |
|
408 mRunAt = SystemClock.elapsedRealtime() + delay; |
|
409 if (!mStarted) { |
|
410 mView.postDelayed(this, delay); |
|
411 mStarted = true; |
|
412 } |
|
413 } |
|
414 |
|
415 void scheduleNextFadeFrame() { |
|
416 if (mStarted) { |
|
417 Log.e(LOGTAG, "scheduleNextFadeFrame() called while scheduled for starting fade"); |
|
418 } |
|
419 mView.postDelayed(this, 1000L / 60L); // request another frame at 60fps |
|
420 } |
|
421 |
|
422 boolean timeToFade() { |
|
423 return !mStarted; |
|
424 } |
|
425 |
|
426 @Override |
|
427 public void run() { |
|
428 long timeDelta = mRunAt - SystemClock.elapsedRealtime(); |
|
429 if (timeDelta > 0) { |
|
430 // the run-at time was pushed back, so reschedule |
|
431 mView.postDelayed(this, timeDelta); |
|
432 } else { |
|
433 // reached the run-at time, execute |
|
434 mStarted = false; |
|
435 mView.requestRender(); |
|
436 } |
|
437 } |
|
438 } |
|
439 |
|
440 public class Frame { |
|
441 // The timestamp recording the start of this frame. |
|
442 private long mFrameStartTime; |
|
443 // A fixed snapshot of the viewport metrics that this frame is using to render content. |
|
444 private ImmutableViewportMetrics mFrameMetrics; |
|
445 // A rendering context for page-positioned layers, and one for screen-positioned layers. |
|
446 private RenderContext mPageContext, mScreenContext; |
|
447 // Whether a layer was updated. |
|
448 private boolean mUpdated; |
|
449 private final Rect mPageRect; |
|
450 private final Rect mAbsolutePageRect; |
|
451 private final PointF mRenderOffset; |
|
452 |
|
453 public Frame(ImmutableViewportMetrics metrics) { |
|
454 mFrameMetrics = metrics; |
|
455 |
|
456 // Work out the offset due to margins |
|
457 Layer rootLayer = mView.getLayerClient().getRoot(); |
|
458 mRenderOffset = mFrameMetrics.getMarginOffset(); |
|
459 mPageContext = createPageContext(metrics, mRenderOffset); |
|
460 mScreenContext = createScreenContext(metrics, mRenderOffset); |
|
461 |
|
462 RectF pageRect = mFrameMetrics.getPageRect(); |
|
463 mAbsolutePageRect = RectUtils.round(pageRect); |
|
464 |
|
465 PointF origin = mFrameMetrics.getOrigin(); |
|
466 pageRect.offset(-origin.x, -origin.y); |
|
467 mPageRect = RectUtils.round(pageRect); |
|
468 } |
|
469 |
|
470 private void setScissorRect() { |
|
471 Rect scissorRect = transformToScissorRect(mPageRect); |
|
472 GLES20.glEnable(GLES20.GL_SCISSOR_TEST); |
|
473 GLES20.glScissor(scissorRect.left, scissorRect.top, |
|
474 scissorRect.width(), scissorRect.height()); |
|
475 } |
|
476 |
|
477 private Rect transformToScissorRect(Rect rect) { |
|
478 IntSize screenSize = new IntSize(mFrameMetrics.getSize()); |
|
479 |
|
480 int left = Math.max(0, rect.left); |
|
481 int top = Math.max(0, rect.top); |
|
482 int right = Math.min(screenSize.width, rect.right); |
|
483 int bottom = Math.min(screenSize.height, rect.bottom); |
|
484 |
|
485 Rect scissorRect = new Rect(left, screenSize.height - bottom, right, |
|
486 (screenSize.height - bottom) + (bottom - top)); |
|
487 scissorRect.offset(Math.round(-mRenderOffset.x), Math.round(-mRenderOffset.y)); |
|
488 |
|
489 return scissorRect; |
|
490 } |
|
491 |
|
492 /** This function is invoked via JNI; be careful when modifying signature. */ |
|
493 @JNITarget |
|
494 public void beginDrawing() { |
|
495 mFrameStartTime = System.nanoTime(); |
|
496 |
|
497 TextureReaper.get().reap(); |
|
498 TextureGenerator.get().fill(); |
|
499 |
|
500 mUpdated = true; |
|
501 |
|
502 Layer rootLayer = mView.getLayerClient().getRoot(); |
|
503 |
|
504 // Run through pre-render tasks |
|
505 runRenderTasks(mTasks, false, mFrameStartTime); |
|
506 |
|
507 if (!mPageContext.fuzzyEquals(mLastPageContext) && !mView.isFullScreen()) { |
|
508 // The viewport or page changed, so show the scrollbars again |
|
509 // as per UX decision. Don't do this if we're in full-screen mode though. |
|
510 mVertScrollLayer.unfade(); |
|
511 mHorizScrollLayer.unfade(); |
|
512 mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY); |
|
513 } else if (mFadeRunnable.timeToFade()) { |
|
514 boolean stillFading = mVertScrollLayer.fade() | mHorizScrollLayer.fade(); |
|
515 if (stillFading) { |
|
516 mFadeRunnable.scheduleNextFadeFrame(); |
|
517 } |
|
518 } |
|
519 mLastPageContext = mPageContext; |
|
520 |
|
521 /* Update layers. */ |
|
522 if (rootLayer != null) { |
|
523 // Called on compositor thread. |
|
524 mUpdated &= rootLayer.update(mPageContext); |
|
525 } |
|
526 |
|
527 if (mFrameRateLayer != null) { |
|
528 // Called on compositor thread. |
|
529 mUpdated &= mFrameRateLayer.update(mScreenContext); |
|
530 } |
|
531 |
|
532 mUpdated &= mVertScrollLayer.update(mPageContext); // called on compositor thread |
|
533 mUpdated &= mHorizScrollLayer.update(mPageContext); // called on compositor thread |
|
534 |
|
535 for (Layer layer : mExtraLayers) { |
|
536 mUpdated &= layer.update(mPageContext); // called on compositor thread |
|
537 } |
|
538 } |
|
539 |
|
540 /** Retrieves the bounds for the layer, rounded in such a way that it |
|
541 * can be used as a mask for something that will render underneath it. |
|
542 * This will round the bounds inwards, but stretch the mask towards any |
|
543 * near page edge, where near is considered to be 'within 2 pixels'. |
|
544 * Returns null if the given layer is null. |
|
545 */ |
|
546 private Rect getMaskForLayer(Layer layer) { |
|
547 if (layer == null) { |
|
548 return null; |
|
549 } |
|
550 |
|
551 RectF bounds = RectUtils.contract(layer.getBounds(mPageContext), 1.0f, 1.0f); |
|
552 Rect mask = RectUtils.roundIn(bounds); |
|
553 |
|
554 // If the mask is within two pixels of any page edge, stretch it over |
|
555 // that edge. This is to avoid drawing thin slivers when masking |
|
556 // layers. |
|
557 if (mask.top <= 2) { |
|
558 mask.top = -1; |
|
559 } |
|
560 if (mask.left <= 2) { |
|
561 mask.left = -1; |
|
562 } |
|
563 |
|
564 // Because we're drawing relative to the page-rect, we only need to |
|
565 // take into account its width and height (and not its origin) |
|
566 int pageRight = mPageRect.width(); |
|
567 int pageBottom = mPageRect.height(); |
|
568 |
|
569 if (mask.right >= pageRight - 2) { |
|
570 mask.right = pageRight + 1; |
|
571 } |
|
572 if (mask.bottom >= pageBottom - 2) { |
|
573 mask.bottom = pageBottom + 1; |
|
574 } |
|
575 |
|
576 return mask; |
|
577 } |
|
578 |
|
579 private void clear(int color) { |
|
580 GLES20.glClearColor(((color >> 16) & 0xFF) / 255.0f, |
|
581 ((color >> 8) & 0xFF) / 255.0f, |
|
582 (color & 0xFF) / 255.0f, |
|
583 0.0f); |
|
584 // The bits set here need to match up with those used |
|
585 // in gfx/layers/opengl/LayerManagerOGL.cpp. |
|
586 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | |
|
587 GLES20.GL_DEPTH_BUFFER_BIT); |
|
588 } |
|
589 |
|
590 /** This function is invoked via JNI; be careful when modifying signature. */ |
|
591 @JNITarget |
|
592 public void drawBackground() { |
|
593 // Any GL state which is changed here must be restored in |
|
594 // CompositorOGL::RestoreState |
|
595 |
|
596 GLES20.glDisable(GLES20.GL_SCISSOR_TEST); |
|
597 |
|
598 // Draw the overscroll background area as a solid color |
|
599 clear(mOverscrollColor); |
|
600 |
|
601 // Update background color. |
|
602 mBackgroundColor = mView.getBackgroundColor(); |
|
603 |
|
604 // Clear the page area to the page background colour. |
|
605 setScissorRect(); |
|
606 clear(mBackgroundColor); |
|
607 GLES20.glDisable(GLES20.GL_SCISSOR_TEST); |
|
608 } |
|
609 |
|
610 // Draws the layer the client added to us. |
|
611 void drawRootLayer() { |
|
612 Layer rootLayer = mView.getLayerClient().getRoot(); |
|
613 if (rootLayer == null) { |
|
614 return; |
|
615 } |
|
616 |
|
617 rootLayer.draw(mPageContext); |
|
618 } |
|
619 |
|
620 @JNITarget |
|
621 public void drawForeground() { |
|
622 // Any GL state which is changed here must be restored in |
|
623 // CompositorOGL::RestoreState |
|
624 |
|
625 /* Draw any extra layers that were added (likely plugins) */ |
|
626 if (mExtraLayers.size() > 0) { |
|
627 for (Layer layer : mExtraLayers) { |
|
628 layer.draw(mPageContext); |
|
629 } |
|
630 } |
|
631 |
|
632 /* Draw the vertical scrollbar. */ |
|
633 if (mPageRect.height() > mFrameMetrics.getHeight()) |
|
634 mVertScrollLayer.draw(mPageContext); |
|
635 |
|
636 /* Draw the horizontal scrollbar. */ |
|
637 if (mPageRect.width() > mFrameMetrics.getWidth()) |
|
638 mHorizScrollLayer.draw(mPageContext); |
|
639 |
|
640 /* Measure how much of the screen is checkerboarding */ |
|
641 Layer rootLayer = mView.getLayerClient().getRoot(); |
|
642 if ((rootLayer != null) && |
|
643 (mProfileRender || PanningPerfAPI.isRecordingCheckerboard())) { |
|
644 // Calculate the incompletely rendered area of the page |
|
645 float checkerboard = 1.0f - GeckoAppShell.computeRenderIntegrity(); |
|
646 |
|
647 PanningPerfAPI.recordCheckerboard(checkerboard); |
|
648 if (checkerboard < 0.0f || checkerboard > 1.0f) { |
|
649 Log.e(LOGTAG, "Checkerboard value out of bounds: " + checkerboard); |
|
650 } |
|
651 |
|
652 mCompleteFramesRendered += 1.0f - checkerboard; |
|
653 mFramesRendered ++; |
|
654 |
|
655 if (mFrameStartTime - mProfileOutputTime > NANOS_PER_SECOND) { |
|
656 mProfileOutputTime = mFrameStartTime; |
|
657 printCheckerboardStats(); |
|
658 } |
|
659 } |
|
660 |
|
661 runRenderTasks(mTasks, true, mFrameStartTime); |
|
662 |
|
663 /* Draw the FPS. */ |
|
664 if (mFrameRateLayer != null) { |
|
665 updateDroppedFrames(mFrameStartTime); |
|
666 |
|
667 GLES20.glEnable(GLES20.GL_BLEND); |
|
668 GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); |
|
669 mFrameRateLayer.draw(mScreenContext); |
|
670 } |
|
671 } |
|
672 |
|
673 /** This function is invoked via JNI; be careful when modifying signature. */ |
|
674 @JNITarget |
|
675 public void endDrawing() { |
|
676 // If a layer update requires further work, schedule another redraw |
|
677 if (!mUpdated) |
|
678 mView.requestRender(); |
|
679 |
|
680 PanningPerfAPI.recordFrameTime(); |
|
681 |
|
682 /* Used by robocop for testing purposes */ |
|
683 IntBuffer pixelBuffer = mPixelBuffer; |
|
684 if (mUpdated && pixelBuffer != null) { |
|
685 synchronized (pixelBuffer) { |
|
686 pixelBuffer.position(0); |
|
687 GLES20.glReadPixels(0, 0, (int)mScreenContext.viewport.width(), |
|
688 (int)mScreenContext.viewport.height(), GLES20.GL_RGBA, |
|
689 GLES20.GL_UNSIGNED_BYTE, pixelBuffer); |
|
690 pixelBuffer.notify(); |
|
691 } |
|
692 } |
|
693 |
|
694 // Remove background color once we've painted. GeckoLayerClient is |
|
695 // responsible for setting this flag before current document is |
|
696 // composited. |
|
697 if (mView.getPaintState() == LayerView.PAINT_BEFORE_FIRST) { |
|
698 mView.post(new Runnable() { |
|
699 @Override |
|
700 public void run() { |
|
701 mView.getChildAt(0).setBackgroundColor(Color.TRANSPARENT); |
|
702 } |
|
703 }); |
|
704 mView.setPaintState(LayerView.PAINT_AFTER_FIRST); |
|
705 } |
|
706 mLastFrameTime = mFrameStartTime; |
|
707 } |
|
708 } |
|
709 |
|
710 @Override |
|
711 public void onTabChanged(final Tab tab, Tabs.TabEvents msg, Object data) { |
|
712 // Sets the background of the newly selected tab. This background color |
|
713 // gets cleared in endDrawing(). This function runs on the UI thread, |
|
714 // but other code that touches the paint state is run on the compositor |
|
715 // thread, so this may need to be changed if any problems appear. |
|
716 if (msg == Tabs.TabEvents.SELECTED) { |
|
717 if (mView != null) { |
|
718 if (mView.getChildAt(0) != null) { |
|
719 mView.getChildAt(0).setBackgroundColor(tab.getBackgroundColor()); |
|
720 } |
|
721 mView.setPaintState(LayerView.PAINT_START); |
|
722 } |
|
723 } |
|
724 } |
|
725 } |