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 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;
16 
17 import org.libreoffice.kit.DirectBufferAllocator;
18 import org.mozilla.gecko.gfx.Layer.RenderContext;
19 
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;
25 
26 import javax.microedition.khronos.egl.EGLConfig;
27 import javax.microedition.khronos.opengles.GL10;
28 
29 /**
30  * The layer renderer implements the rendering logic for a layer view.
31  */
32 public class LayerRenderer implements GLSurfaceView.Renderer {
33     private static final String LOGTAG = "GeckoLayerRenderer";
34 
35     /*
36      * The amount of time a frame is allowed to take to render before we declare it a dropped
37      * frame.
38      */
39     private static final int MAX_FRAME_TIME = 16;   /* 1000 ms / 60 FPS */
40 
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;
52 
53     private CopyOnWriteArrayList<Layer> mExtraLayers = new CopyOnWriteArrayList<Layer>();
54 
55     /* Used by robocop for testing purposes */
56     private IntBuffer mPixelBuffer;
57 
58     // Used by GLES 2.0
59     private int mProgram;
60     private int mPositionHandle;
61     private int mTextureHandle;
62     private int mSampleHandle;
63     private int mTMatrixHandle;
64 
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
73     };
74 
75     private static final int COORD_BUFFER_SIZE = 20;
76 
77     // The shaders run on the GPU directly, the vertex shader is only applying the
78     // matrix transform detailed above
79 
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
82     // the bottom left.
83 
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" +
89         "void main() {\n" +
90         "    gl_Position = uTMatrix * vPosition;\n" +
91         "    vTexCoord.x = aTexCoord.x;\n" +
92         "    vTexCoord.y = 1.0 - aTexCoord.y;\n" +
93         "}\n";
94 
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" +
104         "void main() {\n" +
105         "    gl_FragColor = texture2D(sTexture, vTexCoord);\n" +
106         "}\n";
107 
LayerRenderer(LayerView view)108     public LayerRenderer(LayerView view) {
109         mView = view;
110 
111         CairoImage backgroundImage = new BufferedCairoImage(view.getBackgroundPattern());
112         mBackgroundLayer = new SingleTileLayer(true, backgroundImage);
113 
114         CairoImage shadowImage = new BufferedCairoImage(view.getShadowPattern());
115         mShadowLayer = new NinePatchTileLayer(shadowImage);
116 
117         mHorizScrollLayer = ScrollbarLayer.create(this, false);
118         mVertScrollLayer = ScrollbarLayer.create(this, true);
119         mFadeRunnable = new FadeRunnable();
120 
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();
126     }
127 
128     @Override
finalize()129     protected void finalize() throws Throwable {
130         try {
131             DirectBufferAllocator.free(mCoordByteBuffer);
132             mCoordByteBuffer = null;
133             mCoordBuffer = null;
134         } finally {
135             super.finalize();
136         }
137     }
138 
destroy()139     public void destroy() {
140         DirectBufferAllocator.free(mCoordByteBuffer);
141         mCoordByteBuffer = null;
142         mCoordBuffer = null;
143         mBackgroundLayer.destroy();
144         mShadowLayer.destroy();
145         mHorizScrollLayer.destroy();
146         mVertScrollLayer.destroy();
147     }
148 
onSurfaceCreated(GL10 gl, EGLConfig config)149     public void onSurfaceCreated(GL10 gl, EGLConfig config) {
150         createDefaultProgram();
151         activateDefaultProgram();
152     }
153 
createDefaultProgram()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);
157 
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
162 
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");
168 
169         int maxTextureSizeResult[] = new int[1];
170         GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0);
171         mMaxTextureSize = maxTextureSizeResult[0];
172     }
173 
174     // Activates the shader program.
activateDefaultProgram()175     public void activateDefaultProgram() {
176         // Add the program to the OpenGL environment
177         GLES20.glUseProgram(mProgram);
178 
179         // Set the transformation matrix
180         GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false, DEFAULT_TEXTURE_MATRIX, 0);
181 
182         // Enable the arrays from which we get the vertex and texture coordinates
183         GLES20.glEnableVertexAttribArray(mPositionHandle);
184         GLES20.glEnableVertexAttribArray(mTextureHandle);
185 
186         GLES20.glUniform1i(mSampleHandle, 0);
187 
188         // TODO: Move these calls into a separate deactivate() call that is called after the
189         // underlay and overlay are rendered.
190     }
191 
192     // Deactivates the shader program. This must be done to avoid crashes after returning to the
193     // Gecko C++ compositor from Java.
deactivateDefaultProgram()194     public void deactivateDefaultProgram() {
195         GLES20.glDisableVertexAttribArray(mTextureHandle);
196         GLES20.glDisableVertexAttribArray(mPositionHandle);
197         GLES20.glUseProgram(0);
198     }
199 
getMaxTextureSize()200     public int getMaxTextureSize() {
201         return mMaxTextureSize;
202     }
203 
addLayer(Layer layer)204     public void addLayer(Layer layer) {
205         synchronized (mExtraLayers) {
206             if (mExtraLayers.contains(layer)) {
207                 mExtraLayers.remove(layer);
208             }
209 
210             mExtraLayers.add(layer);
211         }
212     }
213 
removeLayer(Layer layer)214     public void removeLayer(Layer layer) {
215         synchronized (mExtraLayers) {
216             mExtraLayers.remove(layer);
217         }
218     }
219 
220     /**
221      * Called whenever a new frame is about to be drawn.
222      */
onDrawFrame(GL10 gl)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();
230             frame.endDrawing();
231         }
232     }
233 
createScreenContext(ImmutableViewportMetrics metrics)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);
238     }
239 
createPageContext(ImmutableViewportMetrics metrics)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);
245     }
246 
createContext(RectF viewport, RectF pageRect, float zoomFactor)247     private RenderContext createContext(RectF viewport, RectF pageRect, float zoomFactor) {
248         return new RenderContext(viewport, pageRect, zoomFactor, mPositionHandle, mTextureHandle,
249                                  mCoordBuffer);
250     }
251 
onSurfaceChanged(GL10 gl, final int width, final int height)252     public void onSurfaceChanged(GL10 gl, final int width, final int height) {
253         GLES20.glViewport(0, 0, width, height);
254     }
255 
256     /*
257      * create a vertex shader type (GLES20.GL_VERTEX_SHADER)
258      * or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
259      */
loadShader(int type, String shaderCode)260     public static int loadShader(int type, String shaderCode) {
261         int shader = GLES20.glCreateShader(type);
262         GLES20.glShaderSource(shader, shaderCode);
263         GLES20.glCompileShader(shader);
264         return shader;
265     }
266 
createFrame(ImmutableViewportMetrics metrics)267     public Frame createFrame(ImmutableViewportMetrics metrics) {
268         return new Frame(metrics);
269     }
270 
271     class FadeRunnable implements Runnable {
272         private boolean mStarted;
273         private long mRunAt;
274 
scheduleStartFade(long delay)275         void scheduleStartFade(long delay) {
276             mRunAt = SystemClock.elapsedRealtime() + delay;
277             if (!mStarted) {
278                 mView.postDelayed(this, delay);
279                 mStarted = true;
280             }
281         }
282 
scheduleNextFadeFrame()283         void scheduleNextFadeFrame() {
284             if (mStarted) {
285                 Log.e(LOGTAG, "scheduleNextFadeFrame() called while scheduled for starting fade");
286             }
287             mView.postDelayed(this, 1000L / 60L); // request another frame at 60fps
288         }
289 
timeToFade()290         boolean timeToFade() {
291             return !mStarted;
292         }
293 
run()294         public void run() {
295             long timeDelta = mRunAt - SystemClock.elapsedRealtime();
296             if (timeDelta > 0) {
297                 // the run-at time was pushed back, so reschedule
298                 mView.postDelayed(this, timeDelta);
299             } else {
300                 // reached the run-at time, execute
301                 mStarted = false;
302                 mView.requestRender();
303             }
304         }
305     }
306 
307     public class Frame {
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;
317 
Frame(ImmutableViewportMetrics metrics)318         public Frame(ImmutableViewportMetrics metrics) {
319             mFrameMetrics = metrics;
320             mPageContext = createPageContext(metrics);
321             mScreenContext = createScreenContext(metrics);
322             mPageRect = getPageRect();
323         }
324 
setScissorRect()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());
330         }
331 
transformToScissorRect(Rect rect)332         private Rect transformToScissorRect(Rect rect) {
333             IntSize screenSize = new IntSize(mFrameMetrics.getSize());
334 
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);
339 
340             return new Rect(left, screenSize.height - bottom, right,
341                             (screenSize.height - bottom) + (bottom - top));
342         }
343 
getPageRect()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);
348             return pageRect;
349         }
350 
351         /** This function is invoked via JNI; be careful when modifying signature. */
beginDrawing()352         public void beginDrawing() {
353             mFrameStartTime = SystemClock.uptimeMillis();
354 
355             TextureReaper.get().reap();
356             TextureGenerator.get().fill();
357 
358             mUpdated = true;
359 
360             Layer rootLayer = mView.getLayerClient().getRoot();
361             Layer lowResLayer = mView.getLayerClient().getLowResLayer();
362 
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();
371                 if (stillFading) {
372                     mFadeRunnable.scheduleNextFadeFrame();
373                 }
374             }
375             mLastPageContext = mPageContext;
376 
377             /* Update layers. */
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
384 
385             for (Layer layer : mExtraLayers)
386                 mUpdated &= layer.update(mPageContext); // called on compositor thread
387         }
388 
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.
394          */
getMaskForLayer(Layer layer)395         private Rect getMaskForLayer(Layer layer) {
396             if (layer == null) {
397                 return null;
398             }
399 
400             RectF bounds = RectUtils.contract(layer.getBounds(mPageContext), 1.0f, 1.0f);
401             Rect mask = RectUtils.roundIn(bounds);
402 
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
405             // layers.
406             if (mask.top <= 2) {
407                 mask.top = -1;
408             }
409             if (mask.left <= 2) {
410                 mask.left = -1;
411             }
412 
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();
417 
418             if (mask.right >= pageRight - 2) {
419                 mask.right = pageRight + 1;
420             }
421             if (mask.bottom >= pageBottom - 2) {
422                 mask.bottom = pageBottom + 1;
423             }
424 
425             return mask;
426         }
427 
428         /** This function is invoked via JNI; be careful when modifying signature. */
drawBackground()429         public void drawBackground() {
430             GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
431 
432             /* Update background color. */
433             mBackgroundColor = Color.WHITE;
434 
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.
437              */
438             GLES20.glClearColor(((mBackgroundColor>>16)&0xFF) / 255.0f,
439                                 ((mBackgroundColor>>8)&0xFF) / 255.0f,
440                                 (mBackgroundColor&0xFF) / 255.0f,
441                                 0.0f);
442             GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT |
443                            GLES20.GL_DEPTH_BUFFER_BIT);
444 
445             /* Draw the background. */
446             mBackgroundLayer.setMask(mPageRect);
447             mBackgroundLayer.draw(mScreenContext);
448 
449             /* Draw the drop shadow, if we need to. */
450             RectF untransformedPageRect = new RectF(0.0f, 0.0f, mPageRect.width(),
451                                                     mPageRect.height());
452             if (!untransformedPageRect.contains(mFrameMetrics.getViewport()))
453                 mShadowLayer.draw(mPageContext);
454 
455             /* Scissor around the page-rect, in case the page has shrunk
456              * since the screenshot layer was last updated.
457              */
458             setScissorRect(); // Calls glEnable(GL_SCISSOR_TEST))
459         }
460 
461         // Draws the layer the client added to us.
drawRootLayer()462         void drawRootLayer() {
463             Layer lowResLayer = mView.getLayerClient().getLowResLayer();
464             if (lowResLayer == null) {
465                 return;
466             }
467             lowResLayer.draw(mPageContext);
468 
469             Layer rootLayer = mView.getLayerClient().getRoot();
470             if (rootLayer == null) {
471                 return;
472             }
473 
474             rootLayer.draw(mPageContext);
475         }
476 
477         /** This function is invoked via JNI; be careful when modifying signature. */
drawForeground()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();
484 
485                     layer.draw(mPageContext);
486 
487                     if (!layer.usesDefaultProgram())
488                         activateDefaultProgram();
489                 }
490             }
491 
492             /* Draw the vertical scrollbar. */
493             if (mPageRect.height() > mFrameMetrics.getHeight())
494                 mVertScrollLayer.draw(mPageContext);
495 
496             /* Draw the horizontal scrollbar. */
497             if (mPageRect.width() > mFrameMetrics.getWidth())
498                 mHorizScrollLayer.draw(mPageContext);
499         }
500 
501         /** This function is invoked via JNI; be careful when modifying signature. */
endDrawing()502         public void endDrawing() {
503             // If a layer update requires further work, schedule another redraw
504             if (!mUpdated)
505                 mView.requestRender();
506 
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();
516                 }
517             }
518         }
519     }
520 }
521