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.os.Build;
13 import android.os.Handler;
14 import android.view.Surface;
15 import android.view.SurfaceHolder;
16 import android.view.SurfaceView;
17 import android.widget.FrameLayout;
19 import org.chromium.base.CalledByNative;
20 import org.chromium.base.JNINamespace;
21 import org.chromium.base.TraceEvent;
22 import org.chromium.ui.base.WindowAndroid;
25 * This view is used by a ContentView to render its content.
26 * Call {@link #setCurrentContentView(ContentView)} with the contentView that should be displayed.
27 * Note that only one ContentView can be shown at a time.
29 @JNINamespace("content")
30 public class ContentViewRenderView extends FrameLayout {
31 private static final int MAX_SWAP_BUFFER_COUNT = 2;
33 // The native side of this object.
34 private long mNativeContentViewRenderView;
35 private final SurfaceHolder.Callback mSurfaceCallback;
37 private final SurfaceView mSurfaceView;
38 private final VSyncAdapter mVSyncAdapter;
40 private int mPendingRenders;
41 private int mPendingSwapBuffers;
42 private boolean mNeedToRender;
44 private ContentView mCurrentContentView;
46 // The listener which will be triggered when below two conditions become valid.
47 // 1. The view has been initialized and ready to draw content to the screen.
48 // 2. The compositor finished compositing and the OpenGL buffers has been swapped.
49 // Which means the view has been updated with visually non-empty content.
50 // This listener will be triggered only once after registered.
51 private FirstRenderedFrameListener mFirstRenderedFrameListener;
52 private boolean mFirstFrameReceived;
54 private final Runnable mRenderRunnable = new Runnable() {
61 public interface FirstRenderedFrameListener{
62 public void onFirstFrameReceived();
66 * Constructs a new ContentViewRenderView that should be can to a view hierarchy.
67 * Native code should add/remove the layers to be rendered through the ContentViewLayerRenderer.
68 * @param context The context used to create this.
70 public ContentViewRenderView(Context context, WindowAndroid rootWindow) {
72 assert rootWindow != null;
73 mNativeContentViewRenderView = nativeInit(rootWindow.getNativePointer());
74 assert mNativeContentViewRenderView != 0;
76 mSurfaceView = createSurfaceView(getContext());
77 mSurfaceView.setZOrderMediaOverlay(true);
78 mSurfaceCallback = new SurfaceHolder.Callback() {
80 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
81 assert mNativeContentViewRenderView != 0;
82 nativeSurfaceChanged(mNativeContentViewRenderView,
83 format, width, height, holder.getSurface());
84 if (mCurrentContentView != null) {
85 mCurrentContentView.getContentViewCore().onPhysicalBackingSizeChanged(
91 public void surfaceCreated(SurfaceHolder holder) {
92 assert mNativeContentViewRenderView != 0;
93 nativeSurfaceCreated(mNativeContentViewRenderView);
95 mPendingSwapBuffers = 0;
102 public void surfaceDestroyed(SurfaceHolder holder) {
103 assert mNativeContentViewRenderView != 0;
104 nativeSurfaceDestroyed(mNativeContentViewRenderView);
107 mSurfaceView.getHolder().addCallback(mSurfaceCallback);
108 setSurfaceViewBackgroundColor(Color.WHITE);
110 mVSyncAdapter = new VSyncAdapter(getContext());
111 addView(mSurfaceView,
112 new FrameLayout.LayoutParams(
113 FrameLayout.LayoutParams.MATCH_PARENT,
114 FrameLayout.LayoutParams.MATCH_PARENT));
117 private class VSyncAdapter implements VSyncManager.Provider, VSyncMonitor.Listener {
118 private final VSyncMonitor mVSyncMonitor;
119 private boolean mVSyncNotificationEnabled;
120 private VSyncManager.Listener mVSyncListener;
122 // The VSyncMonitor gives the timebase for the actual vsync, but we don't want render until
123 // we have had a chance for input events to propagate to the compositor thread. This takes
124 // 3 ms typically, so we adjust the vsync timestamps forward by a bit to give input events a
126 private static final long INPUT_EVENT_LAG_FROM_VSYNC_MICROSECONDS = 3200;
128 VSyncAdapter(Context context) {
129 mVSyncMonitor = new VSyncMonitor(context, this);
133 public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros) {
135 if (mPendingSwapBuffers + mPendingRenders <= MAX_SWAP_BUFFER_COUNT) {
136 mNeedToRender = false;
140 TraceEvent.instant("ContentViewRenderView:bail");
144 if (mVSyncListener != null) {
145 if (mVSyncNotificationEnabled) {
146 mVSyncListener.onVSync(vsyncTimeMicros);
147 mVSyncMonitor.requestUpdate();
149 // Compensate for input event lag. Input events are delivered immediately on
150 // pre-JB releases, so this adjustment is only done for later versions.
151 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
152 vsyncTimeMicros += INPUT_EVENT_LAG_FROM_VSYNC_MICROSECONDS;
154 mVSyncListener.updateVSync(vsyncTimeMicros,
155 mVSyncMonitor.getVSyncPeriodInMicroseconds());
161 public void registerVSyncListener(VSyncManager.Listener listener) {
162 if (!mVSyncNotificationEnabled) mVSyncMonitor.requestUpdate();
163 mVSyncNotificationEnabled = true;
167 public void unregisterVSyncListener(VSyncManager.Listener listener) {
168 mVSyncNotificationEnabled = false;
171 void setVSyncListener(VSyncManager.Listener listener) {
172 mVSyncListener = listener;
173 if (mVSyncListener != null) mVSyncMonitor.requestUpdate();
176 void requestUpdate() {
177 mVSyncMonitor.requestUpdate();
182 * Sets the background color of the surface view. This method is necessary because the
183 * background color of ContentViewRenderView itself is covered by the background of
185 * @param color The color of the background.
187 public void setSurfaceViewBackgroundColor(int color) {
188 if (mSurfaceView != null) {
189 mSurfaceView.setBackgroundColor(color);
194 * Should be called when the ContentViewRenderView is not needed anymore so its associated
195 * native resource can be freed.
197 public void destroy() {
198 mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
199 nativeDestroy(mNativeContentViewRenderView);
200 mNativeContentViewRenderView = 0;
204 * Makes the passed ContentView the one displayed by this ContentViewRenderView.
206 public void setCurrentContentView(ContentView contentView) {
207 assert mNativeContentViewRenderView != 0;
208 mCurrentContentView = contentView;
210 ContentViewCore contentViewCore =
211 contentView != null ? contentView.getContentViewCore() : null;
213 nativeSetCurrentContentView(mNativeContentViewRenderView,
214 contentViewCore != null ? contentViewCore.getNativeContentViewCore() : 0);
216 if (contentViewCore != null) {
217 contentViewCore.onPhysicalBackingSizeChanged(getWidth(), getHeight());
218 mVSyncAdapter.setVSyncListener(contentViewCore.getVSyncListener(mVSyncAdapter));
222 public void registerFirstRenderedFrameListener(FirstRenderedFrameListener listener) {
223 mFirstRenderedFrameListener = listener;
224 if (mFirstFrameReceived && mFirstRenderedFrameListener != null) {
225 mFirstRenderedFrameListener.onFirstFrameReceived();
230 * This method should be subclassed to provide actions to be performed once the view is ready to
233 protected void onReadyToRender() {
237 * This method could be subclassed optionally to provide a custom SurfaceView object to
238 * this ContentViewRenderView.
239 * @param context The context used to create the SurfaceView object.
240 * @return The created SurfaceView object.
242 protected SurfaceView createSurfaceView(Context context) {
243 return new SurfaceView(context) {
245 public void onDraw(Canvas canvas) {
246 // We only need to draw to software canvases, which are used for taking screenshots.
247 if (canvas.isHardwareAccelerated()) return;
248 Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(),
249 Bitmap.Config.ARGB_8888);
250 if (nativeCompositeToBitmap(mNativeContentViewRenderView, bitmap)) {
251 canvas.drawBitmap(bitmap, 0, 0, null);
258 * @return whether the surface view is initialized and ready to render.
260 public boolean isInitialized() {
261 return mSurfaceView.getHolder().getSurface() != null;
265 * Enter or leave overlay video mode.
266 * @param enabled Whether overlay mode is enabled.
268 public void setOverlayVideoMode(boolean enabled) {
269 int format = enabled ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
270 mSurfaceView.getHolder().setFormat(format);
271 nativeSetOverlayVideoMode(mNativeContentViewRenderView, enabled);
275 private void requestRender() {
276 ContentViewCore contentViewCore = mCurrentContentView != null ?
277 mCurrentContentView.getContentViewCore() : null;
279 boolean rendererHasFrame =
280 contentViewCore != null && contentViewCore.consumePendingRendererFrame();
282 if (rendererHasFrame && mPendingSwapBuffers + mPendingRenders < MAX_SWAP_BUFFER_COUNT) {
283 TraceEvent.instant("requestRender:now");
284 mNeedToRender = false;
287 // The handler can be null if we are detached from the window. Calling
288 // {@link View#post(Runnable)} properly handles this case, but we lose the front of
289 // queue behavior. That is okay for this edge case.
290 Handler handler = getHandler();
291 if (handler != null) {
292 handler.postAtFrontOfQueue(mRenderRunnable);
294 post(mRenderRunnable);
296 mVSyncAdapter.requestUpdate();
297 } else if (mPendingRenders <= 0) {
298 assert mPendingRenders == 0;
299 TraceEvent.instant("requestRender:later");
300 mNeedToRender = true;
301 mVSyncAdapter.requestUpdate();
306 private void onSwapBuffersCompleted() {
307 TraceEvent.instant("onSwapBuffersCompleted");
309 if (!mFirstFrameReceived && mCurrentContentView != null &&
310 mCurrentContentView.isReadyForSnapshot()) {
311 mFirstFrameReceived = true;
312 if (mFirstRenderedFrameListener != null) {
313 mFirstRenderedFrameListener.onFirstFrameReceived();
317 if (mPendingSwapBuffers == MAX_SWAP_BUFFER_COUNT && mNeedToRender) requestRender();
318 if (mPendingSwapBuffers > 0) mPendingSwapBuffers--;
321 private void render() {
322 if (mPendingRenders > 0) mPendingRenders--;
324 // Waiting for the content view contents to be ready avoids compositing
325 // when the surface texture is still empty.
326 if (mCurrentContentView == null) return;
327 ContentViewCore contentViewCore = mCurrentContentView.getContentViewCore();
328 if (contentViewCore == null || !contentViewCore.isReady()) {
332 boolean didDraw = nativeComposite(mNativeContentViewRenderView);
334 mPendingSwapBuffers++;
335 if (mSurfaceView.getBackground() != null) {
336 post(new Runnable() {
339 mSurfaceView.setBackgroundResource(0);
346 private native long nativeInit(long rootWindowNativePointer);
347 private native void nativeDestroy(long nativeContentViewRenderView);
348 private native void nativeSetCurrentContentView(long nativeContentViewRenderView,
349 long nativeContentView);
350 private native void nativeSurfaceCreated(long nativeContentViewRenderView);
351 private native void nativeSurfaceDestroyed(long nativeContentViewRenderView);
352 private native void nativeSurfaceChanged(long nativeContentViewRenderView,
353 int format, int width, int height, Surface surface);
354 private native boolean nativeComposite(long nativeContentViewRenderView);
355 private native boolean nativeCompositeToBitmap(long nativeContentViewRenderView, Bitmap bitmap);
356 private native void nativeSetOverlayVideoMode(long nativeContentViewRenderView,