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/. */
6 package org
.mozilla
.gecko
.gfx
;
8 import android
.graphics
.Color
;
9 import android
.graphics
.Point
;
10 import android
.graphics
.Rect
;
11 import android
.graphics
.RectF
;
12 import android
.opengl
.GLES20
;
13 import android
.opengl
.GLSurfaceView
;
14 import android
.os
.SystemClock
;
15 import android
.util
.Log
;
17 import org
.libreoffice
.kit
.DirectBufferAllocator
;
18 import org
.mozilla
.gecko
.gfx
.Layer
.RenderContext
;
20 import java
.nio
.ByteBuffer
;
21 import java
.nio
.ByteOrder
;
22 import java
.nio
.FloatBuffer
;
23 import java
.nio
.IntBuffer
;
24 import java
.util
.concurrent
.CopyOnWriteArrayList
;
26 import javax
.microedition
.khronos
.egl
.EGLConfig
;
27 import javax
.microedition
.khronos
.opengles
.GL10
;
30 * The layer renderer implements the rendering logic for a layer view.
32 public class LayerRenderer
implements GLSurfaceView
.Renderer
{
33 private static final String LOGTAG
= "GeckoLayerRenderer";
36 * The amount of time a frame is allowed to take to render before we declare it a dropped
39 private static final int MAX_FRAME_TIME
= 16; /* 1000 ms / 60 FPS */
41 private final LayerView mView
;
42 private final SingleTileLayer mBackgroundLayer
;
43 private final NinePatchTileLayer mShadowLayer
;
44 private final ScrollbarLayer mHorizScrollLayer
;
45 private final ScrollbarLayer mVertScrollLayer
;
46 private final FadeRunnable mFadeRunnable
;
47 private ByteBuffer mCoordByteBuffer
;
48 private FloatBuffer mCoordBuffer
;
49 private RenderContext mLastPageContext
;
50 private int mMaxTextureSize
;
51 private int mBackgroundColor
;
53 private CopyOnWriteArrayList
<Layer
> mExtraLayers
= new CopyOnWriteArrayList
<Layer
>();
55 /* Used by robocop for testing purposes */
56 private IntBuffer mPixelBuffer
;
60 private int mPositionHandle
;
61 private int mTextureHandle
;
62 private int mSampleHandle
;
63 private int mTMatrixHandle
;
65 // column-major matrix applied to each vertex to shift the viewport from
66 // one ranging from (-1, -1),(1,1) to (0,0),(1,1) and to scale all sizes by
67 // a factor of 2 to fill up the screen
68 public static final float[] DEFAULT_TEXTURE_MATRIX
= {
69 2.0f
, 0.0f
, 0.0f
, 0.0f
,
70 0.0f
, 2.0f
, 0.0f
, 0.0f
,
71 0.0f
, 0.0f
, 2.0f
, 0.0f
,
72 -1.0f
, -1.0f
, 0.0f
, 1.0f
75 private static final int COORD_BUFFER_SIZE
= 20;
77 // The shaders run on the GPU directly, the vertex shader is only applying the
78 // matrix transform detailed above
80 // Note we flip the y-coordinate in the vertex shader from a
81 // coordinate system with (0,0) in the top left to one with (0,0) in
84 public static final String DEFAULT_VERTEX_SHADER
=
85 "uniform mat4 uTMatrix;\n" +
86 "attribute vec4 vPosition;\n" +
87 "attribute vec2 aTexCoord;\n" +
88 "varying vec2 vTexCoord;\n" +
90 " gl_Position = uTMatrix * vPosition;\n" +
91 " vTexCoord.x = aTexCoord.x;\n" +
92 " vTexCoord.y = 1.0 - aTexCoord.y;\n" +
95 // We use highp because the screenshot textures
96 // we use are large and we stretch them a lot
97 // so we need all the precision we can get.
98 // Unfortunately, highp is not required by ES 2.0
99 // so on GPU's like Mali we end up getting mediump
100 public static final String DEFAULT_FRAGMENT_SHADER
=
101 "precision highp float;\n" +
102 "varying vec2 vTexCoord;\n" +
103 "uniform sampler2D sTexture;\n" +
105 " gl_FragColor = texture2D(sTexture, vTexCoord);\n" +
108 public LayerRenderer(LayerView view
) {
111 CairoImage backgroundImage
= new BufferedCairoImage(view
.getBackgroundPattern());
112 mBackgroundLayer
= new SingleTileLayer(true, backgroundImage
);
114 CairoImage shadowImage
= new BufferedCairoImage(view
.getShadowPattern());
115 mShadowLayer
= new NinePatchTileLayer(shadowImage
);
117 mHorizScrollLayer
= ScrollbarLayer
.create(this, false);
118 mVertScrollLayer
= ScrollbarLayer
.create(this, true);
119 mFadeRunnable
= new FadeRunnable();
121 // Initialize the FloatBuffer that will be used to store all vertices and texture
122 // coordinates in draw() commands.
123 mCoordByteBuffer
= DirectBufferAllocator
.allocate(COORD_BUFFER_SIZE
* 4);
124 mCoordByteBuffer
.order(ByteOrder
.nativeOrder());
125 mCoordBuffer
= mCoordByteBuffer
.asFloatBuffer();
129 protected void finalize() throws Throwable
{
131 DirectBufferAllocator
.free(mCoordByteBuffer
);
132 mCoordByteBuffer
= null;
139 public void destroy() {
140 DirectBufferAllocator
.free(mCoordByteBuffer
);
141 mCoordByteBuffer
= null;
143 mBackgroundLayer
.destroy();
144 mShadowLayer
.destroy();
145 mHorizScrollLayer
.destroy();
146 mVertScrollLayer
.destroy();
149 public void onSurfaceCreated(GL10 gl
, EGLConfig config
) {
150 createDefaultProgram();
151 activateDefaultProgram();
154 public void createDefaultProgram() {
155 int vertexShader
= loadShader(GLES20
.GL_VERTEX_SHADER
, DEFAULT_VERTEX_SHADER
);
156 int fragmentShader
= loadShader(GLES20
.GL_FRAGMENT_SHADER
, DEFAULT_FRAGMENT_SHADER
);
158 mProgram
= GLES20
.glCreateProgram();
159 GLES20
.glAttachShader(mProgram
, vertexShader
); // add the vertex shader to program
160 GLES20
.glAttachShader(mProgram
, fragmentShader
); // add the fragment shader to program
161 GLES20
.glLinkProgram(mProgram
); // creates OpenGL program executables
163 // Get handles to the vertex shader's vPosition, aTexCoord, sTexture, and uTMatrix members.
164 mPositionHandle
= GLES20
.glGetAttribLocation(mProgram
, "vPosition");
165 mTextureHandle
= GLES20
.glGetAttribLocation(mProgram
, "aTexCoord");
166 mSampleHandle
= GLES20
.glGetUniformLocation(mProgram
, "sTexture");
167 mTMatrixHandle
= GLES20
.glGetUniformLocation(mProgram
, "uTMatrix");
169 int maxTextureSizeResult
[] = new int[1];
170 GLES20
.glGetIntegerv(GLES20
.GL_MAX_TEXTURE_SIZE
, maxTextureSizeResult
, 0);
171 mMaxTextureSize
= maxTextureSizeResult
[0];
174 // Activates the shader program.
175 public void activateDefaultProgram() {
176 // Add the program to the OpenGL environment
177 GLES20
.glUseProgram(mProgram
);
179 // Set the transformation matrix
180 GLES20
.glUniformMatrix4fv(mTMatrixHandle
, 1, false, DEFAULT_TEXTURE_MATRIX
, 0);
182 // Enable the arrays from which we get the vertex and texture coordinates
183 GLES20
.glEnableVertexAttribArray(mPositionHandle
);
184 GLES20
.glEnableVertexAttribArray(mTextureHandle
);
186 GLES20
.glUniform1i(mSampleHandle
, 0);
188 // TODO: Move these calls into a separate deactivate() call that is called after the
189 // underlay and overlay are rendered.
192 // Deactivates the shader program. This must be done to avoid crashes after returning to the
193 // Gecko C++ compositor from Java.
194 public void deactivateDefaultProgram() {
195 GLES20
.glDisableVertexAttribArray(mTextureHandle
);
196 GLES20
.glDisableVertexAttribArray(mPositionHandle
);
197 GLES20
.glUseProgram(0);
200 public int getMaxTextureSize() {
201 return mMaxTextureSize
;
204 public void addLayer(Layer layer
) {
205 synchronized (mExtraLayers
) {
206 if (mExtraLayers
.contains(layer
)) {
207 mExtraLayers
.remove(layer
);
210 mExtraLayers
.add(layer
);
214 public void removeLayer(Layer layer
) {
215 synchronized (mExtraLayers
) {
216 mExtraLayers
.remove(layer
);
221 * Called whenever a new frame is about to be drawn.
223 public void onDrawFrame(GL10 gl
) {
224 Frame frame
= createFrame(mView
.getLayerClient().getViewportMetrics());
225 synchronized (mView
.getLayerClient()) {
226 frame
.beginDrawing();
227 frame
.drawBackground();
228 frame
.drawRootLayer();
229 frame
.drawForeground();
234 private RenderContext
createScreenContext(ImmutableViewportMetrics metrics
) {
235 RectF viewport
= new RectF(0.0f
, 0.0f
, metrics
.getWidth(), metrics
.getHeight());
236 RectF pageRect
= new RectF(metrics
.getPageRect());
237 return createContext(viewport
, pageRect
, 1.0f
);
240 private RenderContext
createPageContext(ImmutableViewportMetrics metrics
) {
241 Rect viewport
= RectUtils
.round(metrics
.getViewport());
242 RectF pageRect
= metrics
.getPageRect();
243 float zoomFactor
= metrics
.zoomFactor
;
244 return createContext(new RectF(viewport
), pageRect
, zoomFactor
);
247 private RenderContext
createContext(RectF viewport
, RectF pageRect
, float zoomFactor
) {
248 return new RenderContext(viewport
, pageRect
, zoomFactor
, mPositionHandle
, mTextureHandle
,
252 public void onSurfaceChanged(GL10 gl
, final int width
, final int height
) {
253 GLES20
.glViewport(0, 0, width
, height
);
257 * create a vertex shader type (GLES20.GL_VERTEX_SHADER)
258 * or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
260 public static int loadShader(int type
, String shaderCode
) {
261 int shader
= GLES20
.glCreateShader(type
);
262 GLES20
.glShaderSource(shader
, shaderCode
);
263 GLES20
.glCompileShader(shader
);
267 public Frame
createFrame(ImmutableViewportMetrics metrics
) {
268 return new Frame(metrics
);
271 class FadeRunnable
implements Runnable
{
272 private boolean mStarted
;
275 void scheduleStartFade(long delay
) {
276 mRunAt
= SystemClock
.elapsedRealtime() + delay
;
278 mView
.postDelayed(this, delay
);
283 void scheduleNextFadeFrame() {
285 Log
.e(LOGTAG
, "scheduleNextFadeFrame() called while scheduled for starting fade");
287 mView
.postDelayed(this, 1000L / 60L); // request another frame at 60fps
290 boolean timeToFade() {
295 long timeDelta
= mRunAt
- SystemClock
.elapsedRealtime();
297 // the run-at time was pushed back, so reschedule
298 mView
.postDelayed(this, timeDelta
);
300 // reached the run-at time, execute
302 mView
.requestRender();
308 // The timestamp recording the start of this frame.
309 private long mFrameStartTime
;
310 // A fixed snapshot of the viewport metrics that this frame is using to render content.
311 private ImmutableViewportMetrics mFrameMetrics
;
312 // A rendering context for page-positioned layers, and one for screen-positioned layers.
313 private RenderContext mPageContext
, mScreenContext
;
314 // Whether a layer was updated.
315 private boolean mUpdated
;
316 private final Rect mPageRect
;
318 public Frame(ImmutableViewportMetrics metrics
) {
319 mFrameMetrics
= metrics
;
320 mPageContext
= createPageContext(metrics
);
321 mScreenContext
= createScreenContext(metrics
);
322 mPageRect
= getPageRect();
325 private void setScissorRect() {
326 Rect scissorRect
= transformToScissorRect(mPageRect
);
327 GLES20
.glEnable(GLES20
.GL_SCISSOR_TEST
);
328 GLES20
.glScissor(scissorRect
.left
, scissorRect
.top
,
329 scissorRect
.width(), scissorRect
.height());
332 private Rect
transformToScissorRect(Rect rect
) {
333 IntSize screenSize
= new IntSize(mFrameMetrics
.getSize());
335 int left
= Math
.max(0, rect
.left
);
336 int top
= Math
.max(0, rect
.top
);
337 int right
= Math
.min(screenSize
.width
, rect
.right
);
338 int bottom
= Math
.min(screenSize
.height
, rect
.bottom
);
340 return new Rect(left
, screenSize
.height
- bottom
, right
,
341 (screenSize
.height
- bottom
) + (bottom
- top
));
344 private Rect
getPageRect() {
345 Point origin
= PointUtils
.round(mFrameMetrics
.getOrigin());
346 Rect pageRect
= RectUtils
.round(mFrameMetrics
.getPageRect());
347 pageRect
.offset(-origin
.x
, -origin
.y
);
351 /** This function is invoked via JNI; be careful when modifying signature. */
352 public void beginDrawing() {
353 mFrameStartTime
= SystemClock
.uptimeMillis();
355 TextureReaper
.get().reap();
356 TextureGenerator
.get().fill();
360 Layer rootLayer
= mView
.getLayerClient().getRoot();
361 Layer lowResLayer
= mView
.getLayerClient().getLowResLayer();
363 if (!mPageContext
.fuzzyEquals(mLastPageContext
)) {
364 // the viewport or page changed, so show the scrollbars again
365 // as per UX decision
366 mVertScrollLayer
.unfade();
367 mHorizScrollLayer
.unfade();
368 mFadeRunnable
.scheduleStartFade(ScrollbarLayer
.FADE_DELAY
);
369 } else if (mFadeRunnable
.timeToFade()) {
370 boolean stillFading
= mVertScrollLayer
.fade() | mHorizScrollLayer
.fade();
372 mFadeRunnable
.scheduleNextFadeFrame();
375 mLastPageContext
= mPageContext
;
378 if (rootLayer
!= null) mUpdated
&= rootLayer
.update(mPageContext
); // called on compositor thread
379 if (lowResLayer
!= null) mUpdated
&= lowResLayer
.update(mPageContext
); // called on compositor thread
380 mUpdated
&= mBackgroundLayer
.update(mScreenContext
); // called on compositor thread
381 mUpdated
&= mShadowLayer
.update(mPageContext
); // called on compositor thread
382 mUpdated
&= mVertScrollLayer
.update(mPageContext
); // called on compositor thread
383 mUpdated
&= mHorizScrollLayer
.update(mPageContext
); // called on compositor thread
385 for (Layer layer
: mExtraLayers
)
386 mUpdated
&= layer
.update(mPageContext
); // called on compositor thread
389 /** Retrieves the bounds for the layer, rounded in such a way that it
390 * can be used as a mask for something that will render underneath it.
391 * This will round the bounds inwards, but stretch the mask towards any
392 * near page edge, where near is considered to be 'within 2 pixels'.
393 * Returns null if the given layer is null.
395 private Rect
getMaskForLayer(Layer layer
) {
400 RectF bounds
= RectUtils
.contract(layer
.getBounds(mPageContext
), 1.0f
, 1.0f
);
401 Rect mask
= RectUtils
.roundIn(bounds
);
403 // If the mask is within two pixels of any page edge, stretch it over
404 // that edge. This is to avoid drawing thin slivers when masking
409 if (mask
.left
<= 2) {
413 // Because we're drawing relative to the page-rect, we only need to
414 // take into account its width and height (and not its origin)
415 int pageRight
= mPageRect
.width();
416 int pageBottom
= mPageRect
.height();
418 if (mask
.right
>= pageRight
- 2) {
419 mask
.right
= pageRight
+ 1;
421 if (mask
.bottom
>= pageBottom
- 2) {
422 mask
.bottom
= pageBottom
+ 1;
428 /** This function is invoked via JNI; be careful when modifying signature. */
429 public void drawBackground() {
430 GLES20
.glDisable(GLES20
.GL_SCISSOR_TEST
);
432 /* Update background color. */
433 mBackgroundColor
= Color
.WHITE
;
435 /* Clear to the page background colour. The bits set here need to
436 * match up with those used in gfx/layers/opengl/LayerManagerOGL.cpp.
438 GLES20
.glClearColor(((mBackgroundColor
>>16)&0xFF) / 255.0f
,
439 ((mBackgroundColor
>>8)&0xFF) / 255.0f
,
440 (mBackgroundColor
&0xFF) / 255.0f
,
442 GLES20
.glClear(GLES20
.GL_COLOR_BUFFER_BIT
|
443 GLES20
.GL_DEPTH_BUFFER_BIT
);
445 /* Draw the background. */
446 mBackgroundLayer
.setMask(mPageRect
);
447 mBackgroundLayer
.draw(mScreenContext
);
449 /* Draw the drop shadow, if we need to. */
450 RectF untransformedPageRect
= new RectF(0.0f
, 0.0f
, mPageRect
.width(),
452 if (!untransformedPageRect
.contains(mFrameMetrics
.getViewport()))
453 mShadowLayer
.draw(mPageContext
);
455 /* Scissor around the page-rect, in case the page has shrunk
456 * since the screenshot layer was last updated.
458 setScissorRect(); // Calls glEnable(GL_SCISSOR_TEST))
461 // Draws the layer the client added to us.
462 void drawRootLayer() {
463 Layer lowResLayer
= mView
.getLayerClient().getLowResLayer();
464 if (lowResLayer
== null) {
467 lowResLayer
.draw(mPageContext
);
469 Layer rootLayer
= mView
.getLayerClient().getRoot();
470 if (rootLayer
== null) {
474 rootLayer
.draw(mPageContext
);
477 /** This function is invoked via JNI; be careful when modifying signature. */
478 public void drawForeground() {
479 /* Draw any extra layers that were added (likely plugins) */
480 if (mExtraLayers
.size() > 0) {
481 for (Layer layer
: mExtraLayers
) {
482 if (!layer
.usesDefaultProgram())
483 deactivateDefaultProgram();
485 layer
.draw(mPageContext
);
487 if (!layer
.usesDefaultProgram())
488 activateDefaultProgram();
492 /* Draw the vertical scrollbar. */
493 if (mPageRect
.height() > mFrameMetrics
.getHeight())
494 mVertScrollLayer
.draw(mPageContext
);
496 /* Draw the horizontal scrollbar. */
497 if (mPageRect
.width() > mFrameMetrics
.getWidth())
498 mHorizScrollLayer
.draw(mPageContext
);
501 /** This function is invoked via JNI; be careful when modifying signature. */
502 public void endDrawing() {
503 // If a layer update requires further work, schedule another redraw
505 mView
.requestRender();
507 /* Used by robocop for testing purposes */
508 IntBuffer pixelBuffer
= mPixelBuffer
;
509 if (mUpdated
&& pixelBuffer
!= null) {
510 synchronized (pixelBuffer
) {
511 pixelBuffer
.position(0);
512 GLES20
.glReadPixels(0, 0, (int)mScreenContext
.viewport
.width(),
513 (int)mScreenContext
.viewport
.height(), GLES20
.GL_RGBA
,
514 GLES20
.GL_UNSIGNED_BYTE
, pixelBuffer
);
515 pixelBuffer
.notify();