Avoid potential negative array index access to cached text.
[LibreOffice.git] / android / source / src / java / org / mozilla / gecko / gfx / LayerRenderer.java
blob6ea7dd0edc106436cc991953702258d92f629b66
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;
29 /**
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
37 * frame.
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;
52 private CopyOnWriteArrayList<Layer> mExtraLayers = new CopyOnWriteArrayList<Layer>();
54 // Used by GLES 2.0
55 private int mProgram;
56 private int mPositionHandle;
57 private int mTextureHandle;
58 private int mSampleHandle;
59 private int mTMatrixHandle;
61 // column-major matrix applied to each vertex to shift the viewport from
62 // one ranging from (-1, -1),(1,1) to (0,0),(1,1) and to scale all sizes by
63 // a factor of 2 to fill up the screen
64 public static final float[] DEFAULT_TEXTURE_MATRIX = {
65 2.0f, 0.0f, 0.0f, 0.0f,
66 0.0f, 2.0f, 0.0f, 0.0f,
67 0.0f, 0.0f, 2.0f, 0.0f,
68 -1.0f, -1.0f, 0.0f, 1.0f
71 private static final int COORD_BUFFER_SIZE = 20;
73 // The shaders run on the GPU directly, the vertex shader is only applying the
74 // matrix transform detailed above
76 // Note we flip the y-coordinate in the vertex shader from a
77 // coordinate system with (0,0) in the top left to one with (0,0) in
78 // the bottom left.
80 public static final String DEFAULT_VERTEX_SHADER =
81 "uniform mat4 uTMatrix;\n" +
82 "attribute vec4 vPosition;\n" +
83 "attribute vec2 aTexCoord;\n" +
84 "varying vec2 vTexCoord;\n" +
85 "void main() {\n" +
86 " gl_Position = uTMatrix * vPosition;\n" +
87 " vTexCoord.x = aTexCoord.x;\n" +
88 " vTexCoord.y = 1.0 - aTexCoord.y;\n" +
89 "}\n";
91 // We use highp because the screenshot textures
92 // we use are large and we stretch them a lot
93 // so we need all the precision we can get.
94 // Unfortunately, highp is not required by ES 2.0
95 // so on GPU's like Mali we end up getting mediump
96 public static final String DEFAULT_FRAGMENT_SHADER =
97 "precision highp float;\n" +
98 "varying vec2 vTexCoord;\n" +
99 "uniform sampler2D sTexture;\n" +
100 "void main() {\n" +
101 " gl_FragColor = texture2D(sTexture, vTexCoord);\n" +
102 "}\n";
104 public LayerRenderer(LayerView view) {
105 mView = view;
107 CairoImage backgroundImage = new BufferedCairoImage(view.getBackgroundPattern());
108 mBackgroundLayer = new SingleTileLayer(true, backgroundImage);
110 CairoImage shadowImage = new BufferedCairoImage(view.getShadowPattern());
111 mShadowLayer = new NinePatchTileLayer(shadowImage);
113 mHorizScrollLayer = ScrollbarLayer.create(this, false);
114 mVertScrollLayer = ScrollbarLayer.create(this, true);
115 mFadeRunnable = new FadeRunnable();
117 // Initialize the FloatBuffer that will be used to store all vertices and texture
118 // coordinates in draw() commands.
119 mCoordByteBuffer = DirectBufferAllocator.allocate(COORD_BUFFER_SIZE * 4);
120 mCoordByteBuffer.order(ByteOrder.nativeOrder());
121 mCoordBuffer = mCoordByteBuffer.asFloatBuffer();
124 @Override
125 protected void finalize() throws Throwable {
126 try {
127 DirectBufferAllocator.free(mCoordByteBuffer);
128 mCoordByteBuffer = null;
129 mCoordBuffer = null;
130 } finally {
131 super.finalize();
135 public void destroy() {
136 DirectBufferAllocator.free(mCoordByteBuffer);
137 mCoordByteBuffer = null;
138 mCoordBuffer = null;
139 mBackgroundLayer.destroy();
140 mShadowLayer.destroy();
141 mHorizScrollLayer.destroy();
142 mVertScrollLayer.destroy();
145 @Override
146 public void onSurfaceCreated(GL10 gl, EGLConfig config) {
147 createDefaultProgram();
148 activateDefaultProgram();
151 public void createDefaultProgram() {
152 int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DEFAULT_VERTEX_SHADER);
153 int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DEFAULT_FRAGMENT_SHADER);
155 mProgram = GLES20.glCreateProgram();
156 GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
157 GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
158 GLES20.glLinkProgram(mProgram); // creates OpenGL program executables
160 // Get handles to the vertex shader's vPosition, aTexCoord, sTexture, and uTMatrix members.
161 mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
162 mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord");
163 mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture");
164 mTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uTMatrix");
166 int maxTextureSizeResult[] = new int[1];
167 GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0);
168 mMaxTextureSize = maxTextureSizeResult[0];
171 // Activates the shader program.
172 public void activateDefaultProgram() {
173 // Add the program to the OpenGL environment
174 GLES20.glUseProgram(mProgram);
176 // Set the transformation matrix
177 GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false, DEFAULT_TEXTURE_MATRIX, 0);
179 // Enable the arrays from which we get the vertex and texture coordinates
180 GLES20.glEnableVertexAttribArray(mPositionHandle);
181 GLES20.glEnableVertexAttribArray(mTextureHandle);
183 GLES20.glUniform1i(mSampleHandle, 0);
186 // Deactivates the shader program. This must be done to avoid crashes after returning to the
187 // Gecko C++ compositor from Java.
188 public void deactivateDefaultProgram() {
189 GLES20.glDisableVertexAttribArray(mTextureHandle);
190 GLES20.glDisableVertexAttribArray(mPositionHandle);
191 GLES20.glUseProgram(0);
194 public int getMaxTextureSize() {
195 return mMaxTextureSize;
198 public void addLayer(Layer layer) {
199 synchronized (mExtraLayers) {
200 if (mExtraLayers.contains(layer)) {
201 mExtraLayers.remove(layer);
204 mExtraLayers.add(layer);
208 public void removeLayer(Layer layer) {
209 synchronized (mExtraLayers) {
210 mExtraLayers.remove(layer);
215 * Called whenever a new frame is about to be drawn.
217 @Override
218 public void onDrawFrame(GL10 gl) {
219 Frame frame = new Frame(mView.getLayerClient().getViewportMetrics());
220 synchronized (mView.getLayerClient()) {
221 frame.beginDrawing();
222 frame.drawBackground();
223 frame.drawRootLayer();
224 frame.drawForeground();
225 frame.endDrawing();
229 private RenderContext createScreenContext(ImmutableViewportMetrics metrics) {
230 RectF viewport = new RectF(0.0f, 0.0f, metrics.getWidth(), metrics.getHeight());
231 RectF pageRect = new RectF(metrics.getPageRect());
232 return createContext(viewport, pageRect, 1.0f);
235 private RenderContext createPageContext(ImmutableViewportMetrics metrics) {
236 Rect viewport = RectUtils.round(metrics.getViewport());
237 RectF pageRect = metrics.getPageRect();
238 float zoomFactor = metrics.zoomFactor;
239 return createContext(new RectF(viewport), pageRect, zoomFactor);
242 private RenderContext createContext(RectF viewport, RectF pageRect, float zoomFactor) {
243 return new RenderContext(viewport, pageRect, zoomFactor, mPositionHandle, mTextureHandle,
244 mCoordBuffer);
247 @Override
248 public void onSurfaceChanged(GL10 gl, final int width, final int height) {
249 GLES20.glViewport(0, 0, width, height);
253 * create a vertex shader type (GLES20.GL_VERTEX_SHADER)
254 * or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
256 public static int loadShader(int type, String shaderCode) {
257 int shader = GLES20.glCreateShader(type);
258 GLES20.glShaderSource(shader, shaderCode);
259 GLES20.glCompileShader(shader);
260 return shader;
263 class FadeRunnable implements Runnable {
264 private boolean mStarted;
265 private long mRunAt;
267 void scheduleStartFade(long delay) {
268 mRunAt = SystemClock.elapsedRealtime() + delay;
269 if (!mStarted) {
270 mView.postDelayed(this, delay);
271 mStarted = true;
275 void scheduleNextFadeFrame() {
276 if (mStarted) {
277 Log.e(LOGTAG, "scheduleNextFadeFrame() called while scheduled for starting fade");
279 mView.postDelayed(this, 1000L / 60L); // request another frame at 60fps
282 boolean timeToFade() {
283 return !mStarted;
286 public void run() {
287 long timeDelta = mRunAt - SystemClock.elapsedRealtime();
288 if (timeDelta > 0) {
289 // the run-at time was pushed back, so reschedule
290 mView.postDelayed(this, timeDelta);
291 } else {
292 // reached the run-at time, execute
293 mStarted = false;
294 mView.requestRender();
299 public class Frame {
300 // A fixed snapshot of the viewport metrics that this frame is using to render content.
301 private ImmutableViewportMetrics mFrameMetrics;
302 // A rendering context for page-positioned layers, and one for screen-positioned layers.
303 private RenderContext mPageContext, mScreenContext;
304 // Whether a layer was updated.
305 private boolean mUpdated;
306 private final Rect mPageRect;
308 public Frame(ImmutableViewportMetrics metrics) {
309 mFrameMetrics = metrics;
310 mPageContext = createPageContext(metrics);
311 mScreenContext = createScreenContext(metrics);
312 mPageRect = getPageRect();
315 private void setScissorRect() {
316 Rect scissorRect = transformToScissorRect(mPageRect);
317 GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
318 GLES20.glScissor(scissorRect.left, scissorRect.top,
319 scissorRect.width(), scissorRect.height());
322 private Rect transformToScissorRect(Rect rect) {
323 IntSize screenSize = new IntSize(mFrameMetrics.getSize());
325 int left = Math.max(0, rect.left);
326 int top = Math.max(0, rect.top);
327 int right = Math.min(screenSize.width, rect.right);
328 int bottom = Math.min(screenSize.height, rect.bottom);
330 return new Rect(left, screenSize.height - bottom, right,
331 (screenSize.height - bottom) + (bottom - top));
334 private Rect getPageRect() {
335 Point origin = PointUtils.round(mFrameMetrics.getOrigin());
336 Rect pageRect = RectUtils.round(mFrameMetrics.getPageRect());
337 pageRect.offset(-origin.x, -origin.y);
338 return pageRect;
341 public void beginDrawing() {
342 TextureReaper.get().reap();
343 TextureGenerator.get().fill();
345 mUpdated = true;
347 Layer rootLayer = mView.getLayerClient().getRoot();
348 Layer lowResLayer = mView.getLayerClient().getLowResLayer();
350 if (!mPageContext.fuzzyEquals(mLastPageContext)) {
351 // the viewport or page changed, so show the scrollbars again
352 // as per UX decision
353 mVertScrollLayer.unfade();
354 mHorizScrollLayer.unfade();
355 mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY);
356 } else if (mFadeRunnable.timeToFade()) {
357 boolean stillFading = mVertScrollLayer.fade() | mHorizScrollLayer.fade();
358 if (stillFading) {
359 mFadeRunnable.scheduleNextFadeFrame();
362 mLastPageContext = mPageContext;
364 /* Update layers. */
365 if (rootLayer != null) mUpdated &= rootLayer.update(mPageContext); // called on compositor thread
366 if (lowResLayer != null) mUpdated &= lowResLayer.update(mPageContext); // called on compositor thread
367 mUpdated &= mBackgroundLayer.update(mScreenContext); // called on compositor thread
368 mUpdated &= mShadowLayer.update(mPageContext); // called on compositor thread
369 mUpdated &= mVertScrollLayer.update(mPageContext); // called on compositor thread
370 mUpdated &= mHorizScrollLayer.update(mPageContext); // called on compositor thread
372 for (Layer layer : mExtraLayers)
373 mUpdated &= layer.update(mPageContext); // called on compositor thread
376 public void drawBackground() {
377 GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
379 /* Update background color. */
380 final int backgroundColor = Color.WHITE;
382 /* Clear to the page background colour. The bits set here need to
383 * match up with those used in gfx/layers/opengl/LayerManagerOGL.cpp.
385 GLES20.glClearColor(((backgroundColor >> 16) & 0xFF) / 255.0f,
386 ((backgroundColor >> 8) & 0xFF) / 255.0f,
387 (backgroundColor & 0xFF) / 255.0f,
388 0.0f);
389 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT |
390 GLES20.GL_DEPTH_BUFFER_BIT);
392 /* Draw the background. */
393 mBackgroundLayer.setMask(mPageRect);
394 mBackgroundLayer.draw(mScreenContext);
396 /* Draw the drop shadow, if we need to. */
397 RectF untransformedPageRect = new RectF(0.0f, 0.0f, mPageRect.width(),
398 mPageRect.height());
399 if (!untransformedPageRect.contains(mFrameMetrics.getViewport()))
400 mShadowLayer.draw(mPageContext);
402 /* Scissor around the page-rect, in case the page has shrunk
403 * since the screenshot layer was last updated.
405 setScissorRect(); // Calls glEnable(GL_SCISSOR_TEST))
408 // Draws the layer the client added to us.
409 void drawRootLayer() {
410 Layer lowResLayer = mView.getLayerClient().getLowResLayer();
411 if (lowResLayer == null) {
412 return;
414 lowResLayer.draw(mPageContext);
416 Layer rootLayer = mView.getLayerClient().getRoot();
417 if (rootLayer == null) {
418 return;
421 rootLayer.draw(mPageContext);
424 public void drawForeground() {
425 /* Draw any extra layers that were added (likely plugins) */
426 if (mExtraLayers.size() > 0) {
427 for (Layer layer : mExtraLayers) {
428 if (!layer.usesDefaultProgram())
429 deactivateDefaultProgram();
431 layer.draw(mPageContext);
433 if (!layer.usesDefaultProgram())
434 activateDefaultProgram();
438 /* Draw the vertical scrollbar. */
439 if (mPageRect.height() > mFrameMetrics.getHeight())
440 mVertScrollLayer.draw(mPageContext);
442 /* Draw the horizontal scrollbar. */
443 if (mPageRect.width() > mFrameMetrics.getWidth())
444 mHorizScrollLayer.draw(mPageContext);
447 public void endDrawing() {
448 // If a layer update requires further work, schedule another redraw
449 if (!mUpdated)
450 mView.requestRender();