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.Handler;
14 import android.view.Surface;
15 import android.view.SurfaceHolder;
16 import android.view.SurfaceView;
17 import android.view.TextureView;
18 import android.view.TextureView.SurfaceTextureListener;
19 import android.widget.FrameLayout;
21 import org.chromium.base.CalledByNative;
22 import org.chromium.base.JNINamespace;
23 import org.chromium.base.TraceEvent;
24 import org.chromium.ui.base.WindowAndroid;
27 * This view is used by a ContentView to render its content.
28 * Call {@link #setCurrentContentViewCore(ContentViewCore)} with the contentViewCore that should be
30 * Note that only one ContentViewCore can be shown at a time.
32 @JNINamespace("content")
33 public class ContentViewRenderView extends FrameLayout implements WindowAndroid.VSyncClient {
34 private static final int MAX_SWAP_BUFFER_COUNT = 2;
36 // The native side of this object.
37 private long mNativeContentViewRenderView;
38 private final SurfaceHolder.Callback mSurfaceCallback;
40 private final SurfaceView mSurfaceView;
41 private final WindowAndroid mRootWindow;
43 // Enum for the type of compositing surface:
44 // SURFACE_VIEW - Use SurfaceView as compositing surface which
45 // has a bit performance advantage
46 // TEXTURE_VIEW - Use TextureView as compositing surface which
47 // supports animation on the View
48 public enum CompositingSurfaceType { SURFACE_VIEW, TEXTURE_VIEW };
50 // The stuff for TextureView usage. It is not a good practice to mix 2 different
51 // implementations into one single class. However, for the sake of reducing the
52 // effort of rebasing maintanence in future, here we avoid heavily changes in
54 private TextureView mTextureView;
55 private Surface mSurface;
56 private CompositingSurfaceType mCompositingSurfaceType;
58 private int mPendingRenders;
59 private int mPendingSwapBuffers;
60 private boolean mNeedToRender;
62 protected ContentViewCore mContentViewCore;
64 private ContentReadbackHandler mContentReadbackHandler;
65 // The listener which will be triggered when below two conditions become valid.
66 // 1. The view has been initialized and ready to draw content to the screen.
67 // 2. The compositor finished compositing and the OpenGL buffers has been swapped.
68 // Which means the view has been updated with visually non-empty content.
69 // This listener will be triggered only once after registered.
70 private FirstRenderedFrameListener mFirstRenderedFrameListener;
71 private boolean mFirstFrameReceived;
73 private final Runnable mRenderRunnable = new Runnable() {
80 public interface FirstRenderedFrameListener{
81 public void onFirstFrameReceived();
84 // Initialize the TextureView for rendering ContentView and configure the callback
86 private void initTextureView(Context context) {
87 mTextureView = new TextureView(context);
88 mTextureView.setBackgroundColor(Color.WHITE);
90 mTextureView.setSurfaceTextureListener(new SurfaceTextureListener() {
92 public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
93 int width, int height) {
94 assert mNativeContentViewRenderView != 0;
96 mSurface = new Surface(surfaceTexture);
97 nativeSurfaceCreated(mNativeContentViewRenderView);
98 // Force to trigger the compositor to start working.
99 onSurfaceTextureSizeChanged(surfaceTexture, width, height);
101 mPendingSwapBuffers = 0;
107 public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
108 int width, int height) {
109 assert mNativeContentViewRenderView != 0 && mSurface != null;
110 assert surfaceTexture == mTextureView.getSurfaceTexture();
111 assert mSurface != null;
113 // Here we hard-code the pixel format since the native part requires
114 // the format parameter to decide if the compositing surface should be
115 // replaced with a new one when the format is changed.
117 // If TextureView is used, the surface won't be possible to changed,
118 // so that the format is also not changed. There is no special reason
119 // to use RGBA_8888 value since the native part won't use its real
120 // value to do something for drawing.
122 // TODO(hmin): Figure out how to get pixel format from SurfaceTexture.
123 int format = PixelFormat.RGBA_8888;
124 nativeSurfaceChanged(mNativeContentViewRenderView,
125 format, width, height, mSurface);
126 if (mContentViewCore != null) {
127 mContentViewCore.onPhysicalBackingSizeChanged(
133 public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
134 assert mNativeContentViewRenderView != 0;
135 nativeSurfaceDestroyed(mNativeContentViewRenderView);
137 // Release the underlying surface to make it invalid.
144 public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
145 // Do nothing since the SurfaceTexture won't be updated via updateTexImage().
150 public ContentViewRenderView(Context context, WindowAndroid rootWindow) {
151 this(context, rootWindow, CompositingSurfaceType.SURFACE_VIEW);
155 * Constructs a new ContentViewRenderView that should be can to a view hierarchy.
156 * Native code should add/remove the layers to be rendered through the ContentViewLayerRenderer.
157 * @param context The context used to create this.
158 * @param useTextureView True if TextureView is used as compositing target surface,
159 * otherwise SurfaceView is used.
161 public ContentViewRenderView(Context context, WindowAndroid rootWindow,
162 CompositingSurfaceType surfaceType) {
164 assert rootWindow != null;
165 mNativeContentViewRenderView = nativeInit(rootWindow.getNativePointer());
166 assert mNativeContentViewRenderView != 0;
168 mRootWindow = rootWindow;
169 rootWindow.setVSyncClient(this);
170 initContentReadbackHandler();
172 mCompositingSurfaceType = surfaceType;
173 if (surfaceType == CompositingSurfaceType.TEXTURE_VIEW) {
174 initTextureView(context);
176 addView(mTextureView,
177 new FrameLayout.LayoutParams(
178 FrameLayout.LayoutParams.MATCH_PARENT,
179 FrameLayout.LayoutParams.MATCH_PARENT));
181 // Avoid compiler warning.
183 mSurfaceCallback = null;
187 mSurfaceView = createSurfaceView(getContext());
188 mSurfaceView.setZOrderMediaOverlay(true);
189 mSurfaceCallback = new SurfaceHolder.Callback() {
191 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
192 assert mNativeContentViewRenderView != 0;
193 nativeSurfaceChanged(mNativeContentViewRenderView,
194 format, width, height, holder.getSurface());
195 if (mContentViewCore != null) {
196 mContentViewCore.onPhysicalBackingSizeChanged(
202 public void surfaceCreated(SurfaceHolder holder) {
203 setSurfaceViewBackgroundColor(Color.WHITE);
205 assert mNativeContentViewRenderView != 0;
206 nativeSurfaceCreated(mNativeContentViewRenderView);
208 mPendingSwapBuffers = 0;
215 public void surfaceDestroyed(SurfaceHolder holder) {
216 assert mNativeContentViewRenderView != 0;
217 nativeSurfaceDestroyed(mNativeContentViewRenderView);
220 mSurfaceView.getHolder().addCallback(mSurfaceCallback);
222 addView(mSurfaceView,
223 new FrameLayout.LayoutParams(
224 FrameLayout.LayoutParams.MATCH_PARENT,
225 FrameLayout.LayoutParams.MATCH_PARENT));
229 private void initContentReadbackHandler() {
230 mContentReadbackHandler = new ContentReadbackHandler() {
232 protected boolean readyForReadback() {
233 return mNativeContentViewRenderView != 0 && mContentViewCore != null;
236 mContentReadbackHandler.initNativeContentReadbackHandler();
240 public void onVSync(long vsyncTimeMicros) {
242 if (mPendingSwapBuffers + mPendingRenders <= MAX_SWAP_BUFFER_COUNT) {
243 mNeedToRender = false;
247 TraceEvent.instant("ContentViewRenderView:bail");
253 * @return The content readback handler.
255 public ContentReadbackHandler getContentReadbackHandler() {
256 return mContentReadbackHandler;
260 * Sets the background color of the surface view. This method is necessary because the
261 * background color of ContentViewRenderView itself is covered by the background of
263 * @param color The color of the background.
265 public void setSurfaceViewBackgroundColor(int color) {
266 if (mSurfaceView != null) {
267 mSurfaceView.setBackgroundColor(color);
272 * Should be called when the ContentViewRenderView is not needed anymore so its associated
273 * native resource can be freed.
275 public void destroy() {
276 mContentReadbackHandler.destroy();
277 mContentReadbackHandler = null;
278 mRootWindow.setVSyncClient(null);
279 if (mCompositingSurfaceType == CompositingSurfaceType.TEXTURE_VIEW) {
280 mTextureView.setSurfaceTextureListener(null);
281 if (mSurface != null) {
286 mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
289 nativeDestroy(mNativeContentViewRenderView);
290 mNativeContentViewRenderView = 0;
293 public void setCurrentContentViewCore(ContentViewCore contentViewCore) {
294 assert mNativeContentViewRenderView != 0;
295 mContentViewCore = contentViewCore;
297 if (mContentViewCore != null) {
298 mContentViewCore.onPhysicalBackingSizeChanged(getWidth(), getHeight());
299 nativeSetCurrentContentViewCore(mNativeContentViewRenderView,
300 mContentViewCore.getNativeContentViewCore());
302 nativeSetCurrentContentViewCore(mNativeContentViewRenderView, 0);
306 public void registerFirstRenderedFrameListener(FirstRenderedFrameListener listener) {
307 mFirstRenderedFrameListener = listener;
308 if (mFirstFrameReceived && mFirstRenderedFrameListener != null) {
309 mFirstRenderedFrameListener.onFirstFrameReceived();
314 * This method should be subclassed to provide actions to be performed once the view is ready to
317 protected void onReadyToRender() {
321 * This method could be subclassed optionally to provide a custom SurfaceView object to
322 * this ContentViewRenderView.
323 * @param context The context used to create the SurfaceView object.
324 * @return The created SurfaceView object.
326 protected SurfaceView createSurfaceView(Context context) {
327 return new SurfaceView(context);
331 * @return whether the surface view is initialized and ready to render.
333 public boolean isInitialized() {
334 return mSurfaceView.getHolder().getSurface() != null || mSurface != null;
338 * Enter or leave overlay video mode.
339 * @param enabled Whether overlay mode is enabled.
341 public void setOverlayVideoMode(boolean enabled) {
342 if (mCompositingSurfaceType == CompositingSurfaceType.TEXTURE_VIEW) {
343 nativeSetOverlayVideoMode(mNativeContentViewRenderView, enabled);
347 int format = enabled ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
348 mSurfaceView.getHolder().setFormat(format);
349 nativeSetOverlayVideoMode(mNativeContentViewRenderView, enabled);
353 * Set the native layer tree helper for this {@link ContentViewRenderView}.
354 * @param layerTreeBuildHelperNativePtr Native pointer to the layer tree build helper.
356 public void setLayerTreeBuildHelper(long layerTreeBuildHelperNativePtr) {
357 nativeSetLayerTreeBuildHelper(mNativeContentViewRenderView, layerTreeBuildHelperNativePtr);
361 private void requestRender() {
362 boolean rendererHasFrame =
363 mContentViewCore != null && mContentViewCore.consumePendingRendererFrame();
365 if (rendererHasFrame && mPendingSwapBuffers + mPendingRenders < MAX_SWAP_BUFFER_COUNT) {
366 TraceEvent.instant("requestRender:now");
367 mNeedToRender = false;
370 // The handler can be null if we are detached from the window. Calling
371 // {@link View#post(Runnable)} properly handles this case, but we lose the front of
372 // queue behavior. That is okay for this edge case.
373 Handler handler = getHandler();
374 if (handler != null) {
375 handler.postAtFrontOfQueue(mRenderRunnable);
377 post(mRenderRunnable);
379 } else if (mPendingRenders <= 0) {
380 assert mPendingRenders == 0;
381 TraceEvent.instant("requestRender:later");
382 mNeedToRender = true;
383 mRootWindow.requestVSyncUpdate();
388 private void onSwapBuffersCompleted() {
389 TraceEvent.instant("onSwapBuffersCompleted");
391 if (!mFirstFrameReceived && mContentViewCore != null &&
392 mContentViewCore.isReady()) {
393 mFirstFrameReceived = true;
394 if (mFirstRenderedFrameListener != null) {
395 mFirstRenderedFrameListener.onFirstFrameReceived();
399 if (mPendingSwapBuffers == MAX_SWAP_BUFFER_COUNT && mNeedToRender) requestRender();
400 if (mPendingSwapBuffers > 0) mPendingSwapBuffers--;
403 protected void render() {
404 if (mPendingRenders > 0) mPendingRenders--;
406 boolean didDraw = nativeComposite(mNativeContentViewRenderView);
408 mPendingSwapBuffers++;
409 // Ignore if TextureView is used.
410 if (mCompositingSurfaceType == CompositingSurfaceType.TEXTURE_VIEW) return;
411 if (mSurfaceView.getBackground() != null) {
412 post(new Runnable() {
415 mSurfaceView.setBackgroundResource(0);
422 private native long nativeInit(long rootWindowNativePointer);
423 private native void nativeDestroy(long nativeContentViewRenderView);
424 private native void nativeSetCurrentContentViewCore(long nativeContentViewRenderView,
425 long nativeContentViewCore);
426 private native void nativeSetLayerTreeBuildHelper(long nativeContentViewRenderView,
427 long buildHelperNativePtr);
428 private native void nativeSurfaceCreated(long nativeContentViewRenderView);
429 private native void nativeSurfaceDestroyed(long nativeContentViewRenderView);
430 private native void nativeSurfaceChanged(long nativeContentViewRenderView,
431 int format, int width, int height, Surface surface);
432 private native boolean nativeComposite(long nativeContentViewRenderView);
433 private native void nativeSetOverlayVideoMode(long nativeContentViewRenderView,