Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / android / source / src / java / org / mozilla / gecko / gfx / LayerRenderer.java
blobb1aea3616d6c353bcd052c6a538f08e6799c74a8
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;
51 private int mBackgroundColor;
53 private CopyOnWriteArrayList<Layer> mExtraLayers = new CopyOnWriteArrayList<Layer>();
55 /* Used by robocop for testing purposes */
56 private IntBuffer mPixelBuffer;
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;
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
82 // the bottom left.
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";
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";
108 public LayerRenderer(LayerView view) {
109 mView = 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();
128 @Override
129 protected void finalize() throws Throwable {
130 try {
131 DirectBufferAllocator.free(mCoordByteBuffer);
132 mCoordByteBuffer = null;
133 mCoordBuffer = null;
134 } finally {
135 super.finalize();
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();
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();
230 frame.endDrawing();
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,
249 mCoordBuffer);
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);
264 return shader;
267 public Frame createFrame(ImmutableViewportMetrics metrics) {
268 return new Frame(metrics);
271 class FadeRunnable implements Runnable {
272 private boolean mStarted;
273 private long mRunAt;
275 void scheduleStartFade(long delay) {
276 mRunAt = SystemClock.elapsedRealtime() + delay;
277 if (!mStarted) {
278 mView.postDelayed(this, delay);
279 mStarted = true;
283 void scheduleNextFadeFrame() {
284 if (mStarted) {
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() {
291 return !mStarted;
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();
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;
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);
348 return pageRect;
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();
358 mUpdated = true;
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();
371 if (stillFading) {
372 mFadeRunnable.scheduleNextFadeFrame();
375 mLastPageContext = mPageContext;
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
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) {
396 if (layer == null) {
397 return null;
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
405 // layers.
406 if (mask.top <= 2) {
407 mask.top = -1;
409 if (mask.left <= 2) {
410 mask.left = -1;
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;
425 return mask;
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,
441 0.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(),
451 mPageRect.height());
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) {
465 return;
467 lowResLayer.draw(mPageContext);
469 Layer rootLayer = mView.getLayerClient().getRoot();
470 if (rootLayer == null) {
471 return;
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
504 if (!mUpdated)
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();