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