1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.content.browser;
7 import android.content.Context;
8 import android.graphics.Bitmap;
9 import android.graphics.Canvas;
10 import android.graphics.Color;
11 import android.graphics.PixelFormat;
12 import android.graphics.SurfaceTexture;
13 import android.os.Build;
14 import android.os.Handler;
15 import android.view.Surface;
16 import android.view.SurfaceHolder;
17 import android.view.SurfaceView;
18 import android.view.TextureView;
19 import android.view.TextureView.SurfaceTextureListener;
20 import android.widget.FrameLayout;
22 import org.chromium.base.CalledByNative;
23 import org.chromium.base.JNINamespace;
24 import org.chromium.base.ObserverList;
25 import org.chromium.base.ObserverList.RewindableIterator;
26 import org.chromium.base.TraceEvent;
27 import org.chromium.ui.base.WindowAndroid;
30 * This view is used by a ContentView to render its content.
31 * Call {@link #setCurrentContentView(ContentView)} with the contentView that should be displayed.
32 * Note that only one ContentView can be shown at a time.
34 @JNINamespace("content")
35 public class ContentViewRenderView extends FrameLayout {
36 private static final int MAX_SWAP_BUFFER_COUNT = 2;
38 // The native side of this object.
39 private long mNativeContentViewRenderView;
40 private final SurfaceHolder.Callback mSurfaceCallback;
42 private final SurfaceView mSurfaceView;
43 private final VSyncAdapter mVSyncAdapter;
45 // Enum for the type of compositing surface:
46 // SURFACE_VIEW - Use SurfaceView as compositing surface which
47 // has a bit performance advantage
48 // TEXTURE_VIEW - Use TextureView as compositing surface which
49 // supports animation on the View
50 public enum CompositingSurfaceType { SURFACE_VIEW, TEXTURE_VIEW };
52 // The stuff for TextureView usage. It is not a good practice to mix 2 different
53 // implementations into one single class. However, for the sake of reducing the
54 // effort of rebasing maintanence in future, here we avoid heavily changes in
56 private TextureView mTextureView;
57 private Surface mSurface;
58 private CompositingSurfaceType mCompositingSurfaceType;
60 private int mPendingRenders;
61 private int mPendingSwapBuffers;
62 private boolean mNeedToRender;
64 private ContentView mCurrentContentView;
66 // The listener which will be triggered when below two conditions become valid.
67 // 1. The view has been initialized and ready to draw content to the screen.
68 // 2. The compositor finished compositing and the OpenGL buffers has been swapped.
69 // Which means the view has been updated with visually non-empty content.
70 // This listener will be triggered only once after registered.
71 private FirstRenderedFrameListener mFirstRenderedFrameListener;
72 private boolean mFirstFrameReceived;
74 private final Runnable mRenderRunnable = new Runnable() {
81 public interface FirstRenderedFrameListener{
82 public void onFirstFrameReceived();
85 // Initialize the TextureView for rendering ContentView and configure the callback
87 private void initTextureView(Context context) {
88 mTextureView = new TextureView(context);
89 mTextureView.setBackgroundColor(Color.WHITE);
91 mTextureView.setSurfaceTextureListener(new SurfaceTextureListener() {
93 public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
94 int width, int height) {
95 assert mNativeContentViewRenderView != 0;
97 mSurface = new Surface(surfaceTexture);
98 nativeSurfaceCreated(mNativeContentViewRenderView);
99 // Force to trigger the compositor to start working.
100 onSurfaceTextureSizeChanged(surfaceTexture, width, height);
102 mPendingSwapBuffers = 0;
108 public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
109 int width, int height) {
110 assert mNativeContentViewRenderView != 0 && mSurface != null;
111 assert surfaceTexture == mTextureView.getSurfaceTexture();
112 assert mSurface != null;
114 // Here we hard-code the pixel format since the native part requires
115 // the format parameter to decide if the compositing surface should be
116 // replaced with a new one when the format is changed.
118 // If TextureView is used, the surface won't be possible to changed,
119 // so that the format is also not changed. There is no special reason
120 // to use RGBA_8888 value since the native part won't use its real
121 // value to do something for drawing.
123 // TODO(hmin): Figure out how to get pixel format from SurfaceTexture.
124 int format = PixelFormat.RGBA_8888;
125 nativeSurfaceChanged(mNativeContentViewRenderView,
126 format, width, height, mSurface);
127 if (mCurrentContentView != null) {
128 mCurrentContentView.getContentViewCore().onPhysicalBackingSizeChanged(
134 public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
135 assert mNativeContentViewRenderView != 0;
136 nativeSurfaceDestroyed(mNativeContentViewRenderView);
138 // Release the underlying surface to make it invalid.
145 public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
146 // Do nothing since the SurfaceTexture won't be updated via updateTexImage().
151 public ContentViewRenderView(Context context, WindowAndroid rootWindow) {
152 this(context, rootWindow, CompositingSurfaceType.SURFACE_VIEW);
156 * Constructs a new ContentViewRenderView that should be can to a view hierarchy.
157 * Native code should add/remove the layers to be rendered through the ContentViewLayerRenderer.
158 * @param context The context used to create this.
159 * @param useTextureView True if TextureView is used as compositing target surface,
160 * otherwise SurfaceView is used.
162 public ContentViewRenderView(Context context, WindowAndroid rootWindow,
163 CompositingSurfaceType surfaceType) {
165 assert rootWindow != null;
166 mNativeContentViewRenderView = nativeInit(rootWindow.getNativePointer());
167 assert mNativeContentViewRenderView != 0;
169 mCompositingSurfaceType = surfaceType;
170 if (surfaceType == CompositingSurfaceType.TEXTURE_VIEW) {
171 initTextureView(context);
173 mVSyncAdapter = new VSyncAdapter(getContext());
174 addView(mTextureView,
175 new FrameLayout.LayoutParams(
176 FrameLayout.LayoutParams.MATCH_PARENT,
177 FrameLayout.LayoutParams.MATCH_PARENT));
179 // Avoid compiler warning.
181 mSurfaceCallback = null;
185 mSurfaceView = createSurfaceView(getContext());
186 mSurfaceView.setZOrderMediaOverlay(true);
187 mSurfaceCallback = new SurfaceHolder.Callback() {
189 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
190 assert mNativeContentViewRenderView != 0;
191 nativeSurfaceChanged(mNativeContentViewRenderView,
192 format, width, height, holder.getSurface());
193 if (mCurrentContentView != null) {
194 mCurrentContentView.getContentViewCore().onPhysicalBackingSizeChanged(
200 public void surfaceCreated(SurfaceHolder holder) {
201 setSurfaceViewBackgroundColor(Color.WHITE);
203 assert mNativeContentViewRenderView != 0;
204 nativeSurfaceCreated(mNativeContentViewRenderView);
206 mPendingSwapBuffers = 0;
213 public void surfaceDestroyed(SurfaceHolder holder) {
214 assert mNativeContentViewRenderView != 0;
215 nativeSurfaceDestroyed(mNativeContentViewRenderView);
218 mSurfaceView.getHolder().addCallback(mSurfaceCallback);
220 mVSyncAdapter = new VSyncAdapter(getContext());
221 addView(mSurfaceView,
222 new FrameLayout.LayoutParams(
223 FrameLayout.LayoutParams.MATCH_PARENT,
224 FrameLayout.LayoutParams.MATCH_PARENT));
227 private class VSyncAdapter implements VSyncManager.Provider, VSyncMonitor.Listener {
228 private final VSyncMonitor mVSyncMonitor;
229 private boolean mVSyncNotificationEnabled;
230 private VSyncManager.Listener mVSyncListener;
231 private final ObserverList<VSyncManager.Listener> mCurrentVSyncListeners;
232 private final RewindableIterator<VSyncManager.Listener> mCurrentVSyncListenersIterator;
234 // The VSyncMonitor gives the timebase for the actual vsync, but we don't want render until
235 // we have had a chance for input events to propagate to the compositor thread. This takes
236 // 3 ms typically, so we adjust the vsync timestamps forward by a bit to give input events a
238 private static final long INPUT_EVENT_LAG_FROM_VSYNC_MICROSECONDS = 3200;
240 VSyncAdapter(Context context) {
241 mVSyncMonitor = new VSyncMonitor(context, this);
242 mCurrentVSyncListeners = new ObserverList<VSyncManager.Listener>();
243 mCurrentVSyncListenersIterator = mCurrentVSyncListeners.rewindableIterator();
247 public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros) {
249 if (mPendingSwapBuffers + mPendingRenders <= MAX_SWAP_BUFFER_COUNT) {
250 mNeedToRender = false;
254 TraceEvent.instant("ContentViewRenderView:bail");
258 if (mVSyncListener != null) {
259 if (mVSyncNotificationEnabled) {
260 for (mCurrentVSyncListenersIterator.rewind();
261 mCurrentVSyncListenersIterator.hasNext();) {
262 mCurrentVSyncListenersIterator.next().onVSync(vsyncTimeMicros);
264 mVSyncMonitor.requestUpdate();
266 // Compensate for input event lag. Input events are delivered immediately on
267 // pre-JB releases, so this adjustment is only done for later versions.
268 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
269 vsyncTimeMicros += INPUT_EVENT_LAG_FROM_VSYNC_MICROSECONDS;
271 mVSyncListener.updateVSync(vsyncTimeMicros,
272 mVSyncMonitor.getVSyncPeriodInMicroseconds());
278 public void registerVSyncListener(VSyncManager.Listener listener) {
279 if (!mVSyncNotificationEnabled) mVSyncMonitor.requestUpdate();
280 mCurrentVSyncListeners.addObserver(listener);
281 mVSyncNotificationEnabled = true;
285 public void unregisterVSyncListener(VSyncManager.Listener listener) {
286 mCurrentVSyncListeners.removeObserver(listener);
287 if (mCurrentVSyncListeners.isEmpty()) {
288 mVSyncNotificationEnabled = false;
292 void setVSyncListener(VSyncManager.Listener listener) {
293 mVSyncListener = listener;
294 if (mVSyncListener != null) mVSyncMonitor.requestUpdate();
297 void requestUpdate() {
298 mVSyncMonitor.requestUpdate();
303 * Sets the background color of the surface view. This method is necessary because the
304 * background color of ContentViewRenderView itself is covered by the background of
306 * @param color The color of the background.
308 public void setSurfaceViewBackgroundColor(int color) {
309 if (mSurfaceView != null) {
310 mSurfaceView.setBackgroundColor(color);
315 * Should be called when the ContentViewRenderView is not needed anymore so its associated
316 * native resource can be freed.
318 public void destroy() {
319 if (mCompositingSurfaceType == CompositingSurfaceType.TEXTURE_VIEW) {
320 mTextureView.setSurfaceTextureListener(null);
321 if (mSurface != null) {
326 mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
329 nativeDestroy(mNativeContentViewRenderView);
330 mNativeContentViewRenderView = 0;
334 * Makes the passed ContentView the one displayed by this ContentViewRenderView.
336 public void setCurrentContentView(ContentView contentView) {
337 assert mNativeContentViewRenderView != 0;
338 mCurrentContentView = contentView;
340 ContentViewCore contentViewCore =
341 contentView != null ? contentView.getContentViewCore() : null;
343 nativeSetCurrentContentView(mNativeContentViewRenderView,
344 contentViewCore != null ? contentViewCore.getNativeContentViewCore() : 0);
346 if (contentViewCore != null) {
347 contentViewCore.onPhysicalBackingSizeChanged(getWidth(), getHeight());
348 mVSyncAdapter.setVSyncListener(contentViewCore.getVSyncListener(mVSyncAdapter));
352 public void registerFirstRenderedFrameListener(FirstRenderedFrameListener listener) {
353 mFirstRenderedFrameListener = listener;
354 if (mFirstFrameReceived && mFirstRenderedFrameListener != null) {
355 mFirstRenderedFrameListener.onFirstFrameReceived();
360 * This method should be subclassed to provide actions to be performed once the view is ready to
363 protected void onReadyToRender() {
367 * This method could be subclassed optionally to provide a custom SurfaceView object to
368 * this ContentViewRenderView.
369 * @param context The context used to create the SurfaceView object.
370 * @return The created SurfaceView object.
372 protected SurfaceView createSurfaceView(Context context) {
373 return new SurfaceView(context) {
375 public void onDraw(Canvas canvas) {
376 // We only need to draw to software canvases, which are used for taking screenshots.
377 if (canvas.isHardwareAccelerated()) return;
378 Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(),
379 Bitmap.Config.ARGB_8888);
380 if (nativeCompositeToBitmap(mNativeContentViewRenderView, bitmap)) {
381 canvas.drawBitmap(bitmap, 0, 0, null);
388 * @return whether the surface view is initialized and ready to render.
390 public boolean isInitialized() {
391 return mSurfaceView.getHolder().getSurface() != null || mSurface != null;
395 * Enter or leave overlay video mode.
396 * @param enabled Whether overlay mode is enabled.
398 public void setOverlayVideoMode(boolean enabled) {
399 if (mCompositingSurfaceType == CompositingSurfaceType.TEXTURE_VIEW) {
400 nativeSetOverlayVideoMode(mNativeContentViewRenderView, enabled);
404 int format = enabled ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
405 mSurfaceView.getHolder().setFormat(format);
406 nativeSetOverlayVideoMode(mNativeContentViewRenderView, enabled);
410 private void requestRender() {
411 ContentViewCore contentViewCore = mCurrentContentView != null ?
412 mCurrentContentView.getContentViewCore() : null;
414 boolean rendererHasFrame =
415 contentViewCore != null && contentViewCore.consumePendingRendererFrame();
417 if (rendererHasFrame && mPendingSwapBuffers + mPendingRenders < MAX_SWAP_BUFFER_COUNT) {
418 TraceEvent.instant("requestRender:now");
419 mNeedToRender = false;
422 // The handler can be null if we are detached from the window. Calling
423 // {@link View#post(Runnable)} properly handles this case, but we lose the front of
424 // queue behavior. That is okay for this edge case.
425 Handler handler = getHandler();
426 if (handler != null) {
427 handler.postAtFrontOfQueue(mRenderRunnable);
429 post(mRenderRunnable);
431 mVSyncAdapter.requestUpdate();
432 } else if (mPendingRenders <= 0) {
433 assert mPendingRenders == 0;
434 TraceEvent.instant("requestRender:later");
435 mNeedToRender = true;
436 mVSyncAdapter.requestUpdate();
441 private void onSwapBuffersCompleted() {
442 TraceEvent.instant("onSwapBuffersCompleted");
444 if (!mFirstFrameReceived && mCurrentContentView != null &&
445 mCurrentContentView.getContentViewCore().isReady()) {
446 mFirstFrameReceived = true;
447 if (mFirstRenderedFrameListener != null) {
448 mFirstRenderedFrameListener.onFirstFrameReceived();
452 if (mPendingSwapBuffers == MAX_SWAP_BUFFER_COUNT && mNeedToRender) requestRender();
453 if (mPendingSwapBuffers > 0) mPendingSwapBuffers--;
456 private void render() {
457 if (mPendingRenders > 0) mPendingRenders--;
459 // Waiting for the content view contents to be ready avoids compositing
460 // when the surface texture is still empty.
461 if (mCurrentContentView == null) return;
462 ContentViewCore contentViewCore = mCurrentContentView.getContentViewCore();
463 if (contentViewCore == null || !contentViewCore.isReady()) {
467 boolean didDraw = nativeComposite(mNativeContentViewRenderView);
469 mPendingSwapBuffers++;
470 // Ignore if TextureView is used.
471 if (mCompositingSurfaceType == CompositingSurfaceType.TEXTURE_VIEW) return;
472 if (mSurfaceView.getBackground() != null) {
473 post(new Runnable() {
476 mSurfaceView.setBackgroundResource(0);
483 private native long nativeInit(long rootWindowNativePointer);
484 private native void nativeDestroy(long nativeContentViewRenderView);
485 private native void nativeSetCurrentContentView(long nativeContentViewRenderView,
486 long nativeContentView);
487 private native void nativeSurfaceCreated(long nativeContentViewRenderView);
488 private native void nativeSurfaceDestroyed(long nativeContentViewRenderView);
489 private native void nativeSurfaceChanged(long nativeContentViewRenderView,
490 int format, int width, int height, Surface surface);
491 private native boolean nativeComposite(long nativeContentViewRenderView);
492 private native boolean nativeCompositeToBitmap(long nativeContentViewRenderView, Bitmap bitmap);
493 private native void nativeSetOverlayVideoMode(long nativeContentViewRenderView,