diff options
| -rw-r--r-- | core/java/android/view/ThreadedRenderer.java | 10 | ||||
| -rw-r--r-- | core/java/android/view/ViewRootImpl.java | 89 | ||||
| -rw-r--r-- | core/java/android/view/WindowCallbacks.java | 48 | ||||
| -rw-r--r-- | core/java/com/android/internal/policy/PhoneWindow.java | 2 | ||||
| -rw-r--r-- | core/java/com/android/internal/widget/NonClientDecorView.java | 297 | ||||
| -rw-r--r-- | core/jni/android_view_ThreadedRenderer.cpp | 7 | ||||
| -rw-r--r-- | libs/hwui/renderthread/CanvasContext.cpp | 61 | ||||
| -rw-r--r-- | libs/hwui/renderthread/CanvasContext.h | 6 | ||||
| -rw-r--r-- | libs/hwui/renderthread/RenderProxy.cpp | 9 | ||||
| -rw-r--r-- | libs/hwui/renderthread/RenderProxy.h | 2 | ||||
| -rw-r--r-- | tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java | 2 |
11 files changed, 477 insertions, 56 deletions
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index dcef14267b7f..304e9c048b60 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -349,7 +349,7 @@ public class ThreadedRenderer extends HardwareRenderer { * @param right The right side of the protected bounds. * @param bottom The bottom side of the protected bounds. */ - public void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) { + public void setContentDrawBounds(int left, int top, int right, int bottom) { mStagedContentBounds.set(left, top, right, bottom); } @@ -370,9 +370,9 @@ public class ThreadedRenderer extends HardwareRenderer { // renderer. if (!mCurrentContentBounds.equals(mStagedContentBounds)) { mCurrentContentBounds.set(mStagedContentBounds); - nSetContentOverdrawProtectionBounds(mNativeProxy, mCurrentContentBounds.left, - mCurrentContentBounds.top, mCurrentContentBounds.right, - mCurrentContentBounds.bottom); + nSetContentDrawBounds(mNativeProxy, mCurrentContentBounds.left, + mCurrentContentBounds.top, mCurrentContentBounds.right, + mCurrentContentBounds.bottom); } attachInfo.mIgnoreDirtyState = false; @@ -600,6 +600,6 @@ public class ThreadedRenderer extends HardwareRenderer { boolean placeFront); private static native void nRemoveRenderNode(long nativeProxy, long rootRenderNode); private static native void nDrawRenderNode(long nativeProxy, long rootRenderNode); - private static native void nSetContentOverdrawProtectionBounds(long nativeProxy, int left, + private static native void nSetContentDrawBounds(long nativeProxy, int left, int top, int right, int bottom); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f6c60ed688a5..7cf23e717f6e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -73,6 +73,7 @@ import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.view.WindowCallbacks; import android.widget.Scroller; import com.android.internal.R; @@ -115,6 +116,12 @@ public final class ViewRootImpl implements ViewParent, private static final boolean DEBUG_INPUT_STAGES = false || LOCAL_LOGV; /** + * Set to false if we do not want to use the multi threaded renderer. Note that by disabling + * this, WindowCallbacks will not fire. + */ + private static final boolean USE_MT_RENDERER = true; + + /** * Set this system property to true to force the view hierarchy to render * at 60 Hz. This can be used to measure the potential framerate. */ @@ -132,11 +139,11 @@ public final class ViewRootImpl implements ViewParent, static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>(); - static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<Runnable>(); + static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList(); static boolean sFirstDrawComplete = false; + static final ArrayList<WindowCallbacks> sWindowCallbacks = new ArrayList(); - static final ArrayList<ComponentCallbacks> sConfigCallbacks - = new ArrayList<ComponentCallbacks>(); + static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList(); final Context mContext; final IWindowSession mWindowSession; @@ -417,6 +424,22 @@ public final class ViewRootImpl implements ViewParent, } } + public static void addWindowCallbacks(WindowCallbacks callback) { + if (USE_MT_RENDERER) { + synchronized (sWindowCallbacks) { + sWindowCallbacks.add(callback); + } + } + } + + public static void removeWindowCallbacks(WindowCallbacks callback) { + if (USE_MT_RENDERER) { + synchronized (sWindowCallbacks) { + sWindowCallbacks.remove(callback); + } + } + } + // FIXME for perf testing only private boolean mProfile = false; @@ -1378,6 +1401,7 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mWindowVisibility = viewVisibility; host.dispatchWindowVisibilityChanged(viewVisibility); if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) { + endDragResizing(); destroyHardwareResources(); } if (viewVisibility == View.GONE) { @@ -1701,14 +1725,20 @@ public final class ViewRootImpl implements ViewParent, final boolean dragResizing = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING) != 0; if (mDragResizing != dragResizing) { - mDragResizing = dragResizing; - mFullRedrawNeeded = true; + if (dragResizing) { + startDragResizing(frame); + } else { + // We shouldn't come here, but if we come we should end the resize. + endDragResizing(); + } } - if (dragResizing) { - mCanvasOffsetX = mWinFrame.left; - mCanvasOffsetY = mWinFrame.top; - } else { - mCanvasOffsetX = mCanvasOffsetY = 0; + if (!USE_MT_RENDERER) { + if (dragResizing) { + mCanvasOffsetX = mWinFrame.left; + mCanvasOffsetY = mWinFrame.top; + } else { + mCanvasOffsetX = mCanvasOffsetY = 0; + } } } catch (RemoteException e) { } @@ -6635,6 +6665,15 @@ public final class ViewRootImpl implements ViewParent, Configuration newConfig) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { + // Tell all listeners that we are resizing the window so that the chrome can get + // updated as fast as possible on a separate thread, + if (mViewAncestor.get().mDragResizing) { + synchronized (sWindowCallbacks) { + for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) { + sWindowCallbacks.get(i).onWindowSizeIsChanging(frame); + } + } + } viewAncestor.dispatchResized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets, reportDraw, newConfig); } @@ -6804,6 +6843,36 @@ public final class ViewRootImpl implements ViewParent, } /** + * Start a drag resizing which will inform all listeners that a window resize is taking place. + */ + private void startDragResizing(Rect initialBounds) { + if (!mDragResizing) { + mDragResizing = true; + synchronized (sWindowCallbacks) { + for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) { + sWindowCallbacks.get(i).onWindowDragResizeStart(initialBounds); + } + } + mFullRedrawNeeded = true; + } + } + + /** + * End a drag resize which will inform all listeners that a window resize has ended. + */ + private void endDragResizing() { + if (mDragResizing) { + mDragResizing = false; + synchronized (sWindowCallbacks) { + for (int i = sWindowCallbacks.size() - 1; i >= 0; i--) { + sWindowCallbacks.get(i).onWindowDragResizeEnd(); + } + } + mFullRedrawNeeded = true; + } + } + + /** * Class for managing the accessibility interaction connection * based on the global accessibility state. */ diff --git a/core/java/android/view/WindowCallbacks.java b/core/java/android/view/WindowCallbacks.java new file mode 100644 index 000000000000..cb6e98353a59 --- /dev/null +++ b/core/java/android/view/WindowCallbacks.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.graphics.Rect; + +/** + * These callbacks are used to communicate window configuration changes while the user is performing + * window changes. + * @hide + */ +public interface WindowCallbacks { + /** + * Called by the system when the window got changed by the user, before the layouter got called. + * It can be used to perform a "quick and dirty" resize which should never take more then 4ms to + * complete. + * + * <p>At the time the layouting has not happened yet. + * + * @param newBounds The new window frame bounds. + */ + void onWindowSizeIsChanging(Rect newBounds); + + /** + * Called when a drag resize starts. + * @param initialBounds The initial bounds where the window will be. + */ + void onWindowDragResizeStart(Rect initialBounds); + + /** + * Called when a drag resize ends. + */ + void onWindowDragResizeEnd(); +} diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index b6d736465e17..c9b81190ce38 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -2615,7 +2615,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (action == MotionEvent.ACTION_DOWN) { int y = (int)event.getY(); if (y >= (getHeight()-5) && !mWindow.hasChildren()) { - Log.i(TAG, "Watchiing!"); + Log.i(TAG, "Watching!"); mWatchingForMenu = true; } return false; diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java index 56cf9216d0c9..6960a3b29fad 100644 --- a/core/java/com/android/internal/widget/NonClientDecorView.java +++ b/core/java/com/android/internal/widget/NonClientDecorView.java @@ -17,15 +17,23 @@ package com.android.internal.widget; import android.content.Context; +import android.graphics.Color; import android.graphics.Rect; +import android.os.Looper; import android.os.RemoteException; import android.util.AttributeSet; +import android.view.Choreographer; +import android.view.DisplayListCanvas; import android.view.MotionEvent; +import android.view.RenderNode; +import android.view.ThreadedRenderer; import android.view.View; +import android.view.ViewRootImpl; import android.widget.LinearLayout; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.Window; +import android.view.WindowCallbacks; import android.util.Log; import android.util.TypedValue; @@ -58,7 +66,7 @@ import com.android.internal.policy.PhoneWindow; * This will be mitigated once b/22527834 will be addressed. */ public class NonClientDecorView extends LinearLayout - implements View.OnClickListener, View.OnTouchListener { + implements View.OnClickListener, View.OnTouchListener, WindowCallbacks { private final static String TAG = "NonClientDecorView"; // The height of a window which has focus in DIP. private final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20; @@ -67,6 +75,8 @@ public class NonClientDecorView extends LinearLayout private PhoneWindow mOwner = null; private boolean mWindowHasShadow = false; private boolean mShowDecor = false; + // True when this object is listening for window size changes. + private boolean mAttachedCallbacksToRootViewImpl = false; // True if the window is being dragged. private boolean mDragging = false; @@ -85,6 +95,9 @@ public class NonClientDecorView extends LinearLayout // to max until the first layout command has been executed. private boolean mAllowUpdateElevation = false; + // The resize frame renderer. + private ResizeFrameThread mFrameRendererThread = null; + public NonClientDecorView(Context context) { super(context); } @@ -108,6 +121,18 @@ public class NonClientDecorView extends LinearLayout // By changing the outline provider to BOUNDS, the window can remove its // background without removing the shadow. mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS); + + if (!mAttachedCallbacksToRootViewImpl) { + // If there is no window callback installed there was no window set before. Set it now. + // Note that our ViewRootImpl object will not change. + getViewRootImpl().addWindowCallbacks(this); + mAttachedCallbacksToRootViewImpl = true; + } else if (mFrameRendererThread != null) { + // We are resizing and this call happened due to a configuration change. Tell the + // renderer about it. + mFrameRendererThread.onConfigurationChange(); + } + findViewById(R.id.maximize_window).setOnClickListener(this); findViewById(R.id.close_window).setOnClickListener(this); } @@ -251,7 +276,9 @@ public class NonClientDecorView extends LinearLayout **/ private void updateElevation() { float elevation = 0; - if (mWindowHasShadow) { + // Do not use a shadow when we are in resizing mode (mRenderer not null) since the shadow + // is bound to the content size and not the target size. + if (mWindowHasShadow && mFrameRendererThread == null) { boolean fill = isFillingScreen(); elevation = fill ? 0 : (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : @@ -293,4 +320,270 @@ public class NonClientDecorView extends LinearLayout } } } + + @Override + public void onWindowDragResizeStart(Rect initialBounds) { + if (mOwner.isDestroyed()) { + // If the owner's window is gone, we should not be able to come here anymore. + releaseResources(); + return; + } + if (mFrameRendererThread != null) { + return; + } + final ThreadedRenderer renderer = + (ThreadedRenderer) mOwner.getDecorView().getHardwareRenderer(); + if (renderer != null) { + mFrameRendererThread = new ResizeFrameThread(renderer, initialBounds); + // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time. + // If we want to get the shadow shown while resizing, we would need to elevate a new + // element which owns the caption and has the elevation. + updateElevation(); + } + } + + @Override + public void onWindowDragResizeEnd() { + releaseThreadedRenderer(); + } + + @Override + public void onWindowSizeIsChanging(Rect newBounds) { + if (mFrameRendererThread != null) { + mFrameRendererThread.setTargetRect(newBounds); + } + } + + /** + * Release the renderer thread which is usually done when the user stops resizing. + */ + private void releaseThreadedRenderer() { + if (mFrameRendererThread != null) { + mFrameRendererThread.releaseRenderer(); + mFrameRendererThread = null; + // Bring the shadow back. + updateElevation(); + } + } + + /** + * Called when the parent window is destroyed to release all resources. Note that this will also + * destroy the renderer thread. + */ + private void releaseResources() { + releaseThreadedRenderer(); + if (mAttachedCallbacksToRootViewImpl) { + ViewRootImpl.removeWindowCallbacks(this); + mAttachedCallbacksToRootViewImpl = false; + } + } + + /** + * The thread which draws the chrome while we are resizing. + * It starts with the creation and it ends once someone calls destroy(). + * Any size changes can be passed by a call to setTargetRect will passed to the thread and + * executed via the Choreographer. + */ + private class ResizeFrameThread extends Thread implements Choreographer.FrameCallback { + // This is containing the last requested size by a resize command. Note that this size might + // or might not have been applied to the output already. + private final Rect mTargetRect = new Rect(); + + // The render nodes for the multi threaded renderer. + private ThreadedRenderer mRenderer; + private RenderNode mFrameNode; + private RenderNode mBackdropNode; + + private final Rect mOldTargetRect = new Rect(); + private final Rect mNewTargetRect = new Rect(); + private Choreographer mChoreographer; + + // Cached size values from the last render for the case that the view hierarchy is gone + // during a configuration change. + private int mLastContentWidth; + private int mLastContentHeight; + private int mLastCaptionHeight; + private int mLastXOffset; + private int mLastYOffset; + + ResizeFrameThread(ThreadedRenderer renderer, Rect initialBounds) { + mRenderer = renderer; + + // Create the render nodes for our frame and backdrop which can be resized independently + // from the content. + mFrameNode = RenderNode.create("FrameNode", null); + mBackdropNode = RenderNode.create("BackdropNode", null); + + mRenderer.addRenderNode(mFrameNode, false); + mRenderer.addRenderNode(mBackdropNode, true); + + // Set the initial bounds and draw once so that we do not get a broken frame. + mTargetRect.set(initialBounds); + changeWindowSize(initialBounds); + + // Kick off our draw thread. + start(); + } + + /** + * Call this function asynchronously when the window size has been changed. The change will + * be picked up once per frame and the frame will be re-rendered accordingly. + * @param newTargetBounds The new target bounds. + */ + public void setTargetRect(Rect newTargetBounds) { + synchronized (this) { + mTargetRect.set(newTargetBounds); + // Notify of a bounds change. + pingRenderLocked(); + } + } + + /** + * The window got replaced due to a configuration change. + */ + public void onConfigurationChange() { + if (mRenderer != null) { + // Enforce a window redraw. + mOldTargetRect.set(0, 0, 0, 0); + pingRenderLocked(); + } + } + + /** + * All resources of the renderer will be released. This function can be called from the + * the UI thread as well as the renderer thread. + */ + public void releaseRenderer() { + synchronized (this) { + if (mRenderer != null) { + // Invalidate the current content bounds. + mRenderer.setContentDrawBounds(0, 0, 0, 0); + + // Remove the render nodes again (see comment above - better to do that only once). + mRenderer.removeRenderNode(mFrameNode); + mRenderer.removeRenderNode(mBackdropNode); + + mRenderer = null; + + // Exit the renderer loop. + pingRenderLocked(); + } + } + } + + @Override + public void run() { + try { + Looper.prepare(); + mChoreographer = Choreographer.getInstance(); + Looper.loop(); + } finally { + releaseRenderer(); + } + synchronized (this) { + // Make sure no more messages are being sent. + mChoreographer = null; + } + } + + /** + * The implementation of the FrameCallback. + * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, + * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000} + */ + @Override + public void doFrame(long frameTimeNanos) { + if (mRenderer == null) { + // Tell the looper to stop. We are done. + Looper.myLooper().quit(); + return; + } + // Prevent someone from changing this while we are copying. + synchronized (this) { + mNewTargetRect.set(mTargetRect); + } + if (!mNewTargetRect.equals(mOldTargetRect)) { + mOldTargetRect.set(mNewTargetRect); + changeWindowSize(mNewTargetRect); + } + } + + /** + * Resizing the frame to fit the new window size. + * @param newBounds The window bounds which needs to be drawn. + */ + private void changeWindowSize(Rect newBounds) { + long startTime = System.currentTimeMillis(); + + // While a configuration change is taking place the view hierarchy might become + // inaccessible. For that case we remember the previous metrics to avoid flashes. + View caption = getChildAt(0); + View content = getChildAt(1); + if (content != null && caption != null) { + mLastContentWidth = content.getWidth(); + mLastContentHeight = content.getHeight(); + mLastCaptionHeight = caption.getHeight(); + + // Get the draw position within our surface. + int[] surfaceOrigin = new int[2]; + surfaceOrigin[0] = 0; + surfaceOrigin[1] = 0; + + // Get the shadow offsets. + getLocationInSurface(surfaceOrigin); + mLastXOffset = surfaceOrigin[0]; + mLastYOffset = surfaceOrigin[1]; + } + + // Since the surface is spanning the entire screen, we have to add the start offset of + // the bounds to get to the surface location. + final int left = mLastXOffset + newBounds.left; + final int top = mLastYOffset + newBounds.top; + final int width = newBounds.width(); + final int height = newBounds.height(); + + // Produce the draw calls. + // TODO(skuhne): Create a separate caption view which draws this. If the shadow should + // be resized while the window resizes, this hierarchy needs to have the elevation. + // That said - it is probably no good idea to draw the shadow every time since it costs + // a considerable time which we should rather spend for resizing the content and it does + // barely show while the entire screen is moving. + mFrameNode.setLeftTopRightBottom(left, top, left + width, top + mLastCaptionHeight); + DisplayListCanvas canvas = mFrameNode.start(width, height); + canvas.drawColor(Color.BLACK); + mFrameNode.end(canvas); + + mBackdropNode.setLeftTopRightBottom(left, top + mLastCaptionHeight, left + width, + top + height); + + // The backdrop: clear everything with the background. Clipping is done elsewhere. + canvas = mBackdropNode.start(width, height - mLastCaptionHeight); + // TODO(skuhne): mOwner.getDecorView().mBackgroundFallback.draw(..) - or similar. + // Note: This might not work (calculator for example uses a transparent background). + canvas.drawColor(0xff808080); + mBackdropNode.end(canvas); + + // The current content buffer is drawn here. + mRenderer.setContentDrawBounds( + mLastXOffset, + mLastYOffset + mLastCaptionHeight, + mLastXOffset + mLastContentWidth, + mLastYOffset + mLastCaptionHeight + mLastContentHeight); + + // We need to render both rendered nodes explicitly. + mRenderer.drawRenderNode(mFrameNode); + mRenderer.drawRenderNode(mBackdropNode); + } + + /** + * Sends a message to the renderer to wake up and perform the next action which can be + * either the next rendering or the self destruction if mRenderer is null. + * Note: This call must be synchronized. + */ + private void pingRenderLocked() { + if (mChoreographer != null) { + mChoreographer.postFrameCallback(this); + } + } + } } diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index c79f833b54a9..17eb876aebf3 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -461,10 +461,10 @@ static void android_view_ThreadedRendererd_drawRenderNode(JNIEnv* env, jobject c proxy->drawRenderNode(renderNode); } -static void android_view_ThreadedRenderer_setContentOverdrawProtectionBounds(JNIEnv* env, +static void android_view_ThreadedRenderer_setContentDrawBounds(JNIEnv* env, jobject clazz, jlong proxyPtr, jint left, jint top, jint right, jint bottom) { RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); - proxy->setContentOverdrawProtectionBounds(left, top, right, bottom); + proxy->setContentDrawBounds(left, top, right, bottom); } // ---------------------------------------------------------------------------- @@ -522,8 +522,7 @@ static const JNINativeMethod gMethods[] = { { "nAddRenderNode", "(JJZ)V", (void*) android_view_ThreadedRenderer_addRenderNode}, { "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode}, { "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode}, - { "nSetContentOverdrawProtectionBounds", "(JIIII)V", - (void*)android_view_ThreadedRenderer_setContentOverdrawProtectionBounds}, + { "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds}, }; int register_android_view_ThreadedRenderer(JNIEnv* env) { diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 9dc5b45a7738..ddfd62141f5d 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -62,7 +62,7 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord())) , mJankTracker(thread.timeLord().frameIntervalNanos()) , mProfiler(mFrames) - , mContentOverdrawProtectionBounds(0, 0, 0, 0) { + , mContentDrawBounds(0, 0, 0, 0) { mRenderNodes.emplace_back(rootRenderNode); mRenderThread.renderState().registerCanvasContext(this); mProfiler.setDensity(mRenderThread.mainDisplayInfo().density); @@ -309,7 +309,7 @@ void CanvasContext::draw() { Rect outBounds; // It there are multiple render nodes, they are as follows: // #0 - backdrop - // #1 - content (with - and clipped to - bounds mContentOverdrawProtectionBounds) + // #1 - content (positioned at (0,0) and clipped to - its bounds mContentDrawBounds) // #2 - frame // Usually the backdrop cannot be seen since it will be entirely covered by the content. While // resizing however it might become partially visible. The following render loop will crop the @@ -317,66 +317,72 @@ void CanvasContext::draw() { // against the backdrop (since that indicates a shrinking of the window) and then the frame // around everything. // The bounds of the backdrop against which the content should be clipped. - Rect backdropBounds = mContentOverdrawProtectionBounds; + Rect backdropBounds = mContentDrawBounds; + // Usually the contents bounds should be mContentDrawBounds - however - we will + // move it towards the fixed edge to give it a more stable appearance (for the moment). + Rect contentBounds; // If there is no content bounds we ignore the layering as stated above and start with 2. - int layer = mContentOverdrawProtectionBounds.isEmpty() ? 2 : 0; + int layer = (mContentDrawBounds.isEmpty() || mRenderNodes.size() <= 2) ? 2 : 0; // Draw all render nodes. Note that for (const sp<RenderNode>& node : mRenderNodes) { if (layer == 0) { // Backdrop. - // Draw the backdrop clipped to the inverse content bounds. + // Draw the backdrop clipped to the inverse content bounds, but assume that the content + // was moved to the upper left corner. const RenderProperties& properties = node->properties(); Rect targetBounds(properties.getLeft(), properties.getTop(), properties.getRight(), properties.getBottom()); + // Move the content bounds towards the fixed corner of the backdrop. + const int x = targetBounds.left; + const int y = targetBounds.top; + contentBounds.set(x, y, x + mContentDrawBounds.getWidth(), + y + mContentDrawBounds.getHeight()); // Remember the intersection of the target bounds and the intersection bounds against // which we have to crop the content. + backdropBounds.set(x, y, x + backdropBounds.getWidth(), y + backdropBounds.getHeight()); backdropBounds.intersect(targetBounds); // Check if we have to draw something on the left side ... - if (targetBounds.left < mContentOverdrawProtectionBounds.left) { + if (targetBounds.left < contentBounds.left) { mCanvas->save(SkCanvas::kClip_SaveFlag); if (mCanvas->clipRect(targetBounds.left, targetBounds.top, - mContentOverdrawProtectionBounds.left, targetBounds.bottom, + contentBounds.left, targetBounds.bottom, SkRegion::kIntersect_Op)) { mCanvas->drawRenderNode(node.get(), outBounds); } // Reduce the target area by the area we have just painted. - targetBounds.left = std::min(mContentOverdrawProtectionBounds.left, - targetBounds.right); + targetBounds.left = std::min(contentBounds.left, targetBounds.right); mCanvas->restore(); } // ... or on the right side ... - if (targetBounds.right > mContentOverdrawProtectionBounds.right && + if (targetBounds.right > contentBounds.right && !targetBounds.isEmpty()) { mCanvas->save(SkCanvas::kClip_SaveFlag); - if (mCanvas->clipRect(mContentOverdrawProtectionBounds.right, targetBounds.top, + if (mCanvas->clipRect(contentBounds.right, targetBounds.top, targetBounds.right, targetBounds.bottom, SkRegion::kIntersect_Op)) { mCanvas->drawRenderNode(node.get(), outBounds); } // Reduce the target area by the area we have just painted. - targetBounds.right = std::max(targetBounds.left, - mContentOverdrawProtectionBounds.right); + targetBounds.right = std::max(targetBounds.left, contentBounds.right); mCanvas->restore(); } // ... or at the top ... - if (targetBounds.top < mContentOverdrawProtectionBounds.top && + if (targetBounds.top < contentBounds.top && !targetBounds.isEmpty()) { mCanvas->save(SkCanvas::kClip_SaveFlag); if (mCanvas->clipRect(targetBounds.left, targetBounds.top, targetBounds.right, - mContentOverdrawProtectionBounds.top, + contentBounds.top, SkRegion::kIntersect_Op)) { mCanvas->drawRenderNode(node.get(), outBounds); } // Reduce the target area by the area we have just painted. - targetBounds.top = std::min(mContentOverdrawProtectionBounds.top, - targetBounds.bottom); + targetBounds.top = std::min(contentBounds.top, targetBounds.bottom); mCanvas->restore(); } // ... or at the bottom. - if (targetBounds.bottom > mContentOverdrawProtectionBounds.bottom && + if (targetBounds.bottom > contentBounds.bottom && !targetBounds.isEmpty()) { mCanvas->save(SkCanvas::kClip_SaveFlag); - if (mCanvas->clipRect(targetBounds.left, - mContentOverdrawProtectionBounds.bottom, targetBounds.right, + if (mCanvas->clipRect(targetBounds.left, contentBounds.bottom, targetBounds.right, targetBounds.bottom, SkRegion::kIntersect_Op)) { mCanvas->drawRenderNode(node.get(), outBounds); } @@ -384,10 +390,17 @@ void CanvasContext::draw() { } } else if (layer == 1) { // Content // It gets cropped against the bounds of the backdrop to stay inside. - mCanvas->save(SkCanvas::kClip_SaveFlag); - if (mCanvas->clipRect(backdropBounds.left, backdropBounds.top, - backdropBounds.right, backdropBounds.bottom, - SkRegion::kIntersect_Op)) { + mCanvas->save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + + // We shift and clip the content to match its final location in the window. + const float left = mContentDrawBounds.left; + const float top = mContentDrawBounds.top; + const float dx = backdropBounds.left - left; + const float dy = backdropBounds.top - top; + const float width = backdropBounds.getWidth(); + const float height = backdropBounds.getHeight(); + mCanvas->translate(dx, dy); + if (mCanvas->clipRect(left, top, left + width, top + height, SkRegion::kIntersect_Op)) { mCanvas->drawRenderNode(node.get(), outBounds); } mCanvas->restore(); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 1c3845cac504..e0cbabdc933a 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -126,8 +126,8 @@ public: mRenderNodes.end()); } - void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) { - mContentOverdrawProtectionBounds.set(left, top, right, bottom); + void setContentDrawBounds(int left, int top, int right, int bottom) { + mContentDrawBounds.set(left, top, right, bottom); } private: @@ -167,7 +167,7 @@ private: std::set<RenderNode*> mPrefetechedLayers; // Stores the bounds of the main content. - Rect mContentOverdrawProtectionBounds; + Rect mContentDrawBounds; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index f43a769890a4..26aae90d5990 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -529,15 +529,14 @@ void RenderProxy::drawRenderNode(RenderNode* node) { staticPostAndWait(task); } -CREATE_BRIDGE5(setContentOverdrawProtectionBounds, CanvasContext* context, int left, int top, +CREATE_BRIDGE5(setContentDrawBounds, CanvasContext* context, int left, int top, int right, int bottom) { - args->context->setContentOverdrawProtectionBounds(args->left, args->top, args->right, - args->bottom); + args->context->setContentDrawBounds(args->left, args->top, args->right, args->bottom); return nullptr; } -void RenderProxy::setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) { - SETUP_TASK(setContentOverdrawProtectionBounds); +void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) { + SETUP_TASK(setContentDrawBounds); args->context = mContext; args->left = left; args->top = top; diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 046f24ac3f81..d1b62f1f64a6 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -109,7 +109,7 @@ public: ANDROID_API void addRenderNode(RenderNode* node, bool placeFront); ANDROID_API void removeRenderNode(RenderNode* node); ANDROID_API void drawRenderNode(RenderNode* node); - ANDROID_API void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom); + ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom); private: RenderThread& mRenderThread; diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java index b458d9b14096..7628c5c24941 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java @@ -188,7 +188,7 @@ public class MultiProducerActivity extends Activity implements OnClickListener { // This call should be done while the rendernode's displaylist is produced. // For simplicity of this test we do this before we kick off the draw. mContent.getLocationInSurface(surfaceOrigin); - mRenderer.setContentOverdrawProtectionBounds(surfaceOrigin[0], surfaceOrigin[1], + mRenderer.setContentDrawBounds(surfaceOrigin[0], surfaceOrigin[1], surfaceOrigin[0] + mContent.getWidth(), surfaceOrigin[1] + mContent.getHeight()); // Determine new position for frame. |