diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 420f7a174d533..dcef14267b7fa 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -108,6 +108,11 @@ public class ThreadedRenderer extends HardwareRenderer { private Choreographer mChoreographer; private boolean mRootNodeNeedsUpdate; + // In case of multi threaded render nodes, these bounds indicate the content bounds against + // which the backdrop needs to be cropped against. + private final Rect mCurrentContentBounds = new Rect(); + private final Rect mStagedContentBounds = new Rect(); + ThreadedRenderer(Context context, boolean translucent) { final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0); mLightY = a.getDimension(R.styleable.Lighting_lightY, 0); @@ -307,6 +312,47 @@ public class ThreadedRenderer extends HardwareRenderer { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } + /** + * Adds a rendernode to the renderer which can be drawn and changed asynchronously to the + * rendernode of the UI thread. + * @param node The node to add. + * @param placeFront If true, the render node will be placed in front of the content node, + * otherwise behind the content node. + */ + public void addRenderNode(RenderNode node, boolean placeFront) { + nAddRenderNode(mNativeProxy, node.mNativeRenderNode, placeFront); + } + + /** + * Only especially added render nodes can be removed. + * @param node The node which was added via addRenderNode which should get removed again. + */ + public void removeRenderNode(RenderNode node) { + nRemoveRenderNode(mNativeProxy, node.mNativeRenderNode); + } + + /** + * Draws a particular render node. If the node is not the content node, only the additional + * nodes will get drawn and the content remains untouched. + * @param node The node to be drawn. + */ + public void drawRenderNode(RenderNode node) { + nDrawRenderNode(mNativeProxy, node.mNativeRenderNode); + } + + /** + * To avoid unnecessary overdrawing of the main content all additionally passed render nodes + * will be prevented to overdraw this area. It will be synchronized with the draw call. + * This should be updated in the content view's draw call. + * @param left The left side of the protected bounds. + * @param top The top side of the protected bounds. + * @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) { + mStagedContentBounds.set(left, top, right, bottom); + } + @Override void invalidateRoot() { mRootNodeNeedsUpdate = true; @@ -320,6 +366,14 @@ public class ThreadedRenderer extends HardwareRenderer { choreographer.mFrameInfo.markDrawStart(); updateRootDisplayList(view, callbacks); + // The main content view was updating the content bounds and we transfer them to the + // renderer. + if (!mCurrentContentBounds.equals(mStagedContentBounds)) { + mCurrentContentBounds.set(mStagedContentBounds); + nSetContentOverdrawProtectionBounds(mNativeProxy, mCurrentContentBounds.left, + mCurrentContentBounds.top, mCurrentContentBounds.right, + mCurrentContentBounds.bottom); + } attachInfo.mIgnoreDirtyState = false; @@ -541,4 +595,11 @@ public class ThreadedRenderer extends HardwareRenderer { private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd, @DumpFlags int dumpFlags); private static native void nDumpProfileData(byte[] data, FileDescriptor fd); + + private static native void nAddRenderNode(long nativeProxy, long rootRenderNode, + 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, + int top, int right, int bottom); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 98c0494b66f46..24d0bfea17187 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -7372,6 +7372,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + /** + * Compute the view's coordinate within the surface. + * + *

Computes the coordinates of this view in its surface. The argument + * must be an array of two integers. After the method returns, the array + * contains the x and y location in that order.

+ * @hide + * @param location an array of two integers in which to hold the coordinates + */ + public void getLocationInSurface(@Size(2) int[] location) { + getLocationInWindow(location); + if (mAttachInfo != null && mAttachInfo.mViewRootImpl != null) { + location[0] += mAttachInfo.mViewRootImpl.mWindowAttributes.surfaceInsets.left; + location[1] += mAttachInfo.mViewRootImpl.mWindowAttributes.surfaceInsets.top; + } + } + /** * Provide original WindowInsets that are dispatched to the view hierarchy. The insets are * only available if the view is attached. diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 6c3676b5ef05e..9a2703ba2ed30 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -440,6 +440,32 @@ static void android_view_ThreadedRenderer_dumpProfileData(JNIEnv* env, jobject c } } +static void android_view_ThreadedRenderer_addRenderNode(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlong renderNodePtr, jboolean placeFront) { + RenderProxy* proxy = reinterpret_cast(proxyPtr); + RenderNode* renderNode = reinterpret_cast(renderNodePtr); + proxy->addRenderNode(renderNode, placeFront); +} + +static void android_view_ThreadedRenderer_removeRenderNode(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlong renderNodePtr) { + RenderProxy* proxy = reinterpret_cast(proxyPtr); + RenderNode* renderNode = reinterpret_cast(renderNodePtr); + proxy->removeRenderNode(renderNode); +} + +static void android_view_ThreadedRendererd_drawRenderNode(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlong renderNodePtr) { + RenderProxy* proxy = reinterpret_cast(proxyPtr); + RenderNode* renderNode = reinterpret_cast(renderNodePtr); + proxy->drawRenderNode(renderNode); +} + +static void android_view_ThreadedRenderer_setContentOverdrawProtectionBounds(JNIEnv* env, + jobject clazz, jlong proxyPtr, jint left, jint top, jint right, jint bottom) { + RenderProxy* proxy = reinterpret_cast(proxyPtr); + proxy->setContentOverdrawProtectionBounds(left, top, right, bottom); +} // ---------------------------------------------------------------------------- // Shaders @@ -447,7 +473,6 @@ static void android_view_ThreadedRenderer_dumpProfileData(JNIEnv* env, jobject c static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz, jstring diskCachePath) { - const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL); egl_cache_t::get()->setCacheFilename(cacheArray); env->ReleaseStringUTFChars(diskCachePath, cacheArray); @@ -494,6 +519,11 @@ static JNINativeMethod gMethods[] = { { "nDumpProfileData", "([BLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileData }, { "setupShadersDiskCache", "(Ljava/lang/String;)V", (void*) android_view_ThreadedRenderer_setupShadersDiskCache }, + { "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}, }; int register_android_view_ThreadedRenderer(JNIEnv* env) { diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h index ed853f72539d6..98e61468ea70c 100644 --- a/libs/hwui/TreeInfo.h +++ b/libs/hwui/TreeInfo.h @@ -77,7 +77,7 @@ public: , canvasContext(clone.canvasContext) {} - const TraversalMode mode; + TraversalMode mode; // TODO: Remove this? Currently this is used to signal to stop preparing // textures if we run out of cache space. bool prepareTextures; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index b74b5088c14fb..9dc5b45a7738f 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -60,9 +60,10 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, , mEglManager(thread.eglManager()) , mOpaque(!translucent) , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord())) - , mRootRenderNode(rootRenderNode) , mJankTracker(thread.timeLord().frameIntervalNanos()) - , mProfiler(mFrames) { + , mProfiler(mFrames) + , mContentOverdrawProtectionBounds(0, 0, 0, 0) { + mRenderNodes.emplace_back(rootRenderNode); mRenderThread.renderState().registerCanvasContext(this); mProfiler.setDensity(mRenderThread.mainDisplayInfo().density); } @@ -172,7 +173,8 @@ static bool wasSkipped(FrameInfo* info) { return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame); } -void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued) { +void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, + int64_t syncQueued, RenderNode* target) { mRenderThread.removeFrameCallback(this); // If the previous frame was dropped we don't need to hold onto it, so @@ -189,7 +191,13 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy info.canvasContext = this; mAnimationContext->startFrame(info.mode); - mRootRenderNode->prepareTree(info); + for (const sp& node : mRenderNodes) { + // Only the primary target node will be drawn full - all other nodes would get drawn in + // real time mode. In case of a window, the primary node is the window content and the other + // node(s) are non client / filler nodes. + info.mode = (node.get() == target ? TreeInfo::MODE_FULL : TreeInfo::MODE_RT_ONLY); + node->prepareTree(info); + } mAnimationContext->runRemainingAnimations(info); freePrefetechedLayers(); @@ -299,7 +307,95 @@ void CanvasContext::draw() { dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom, mOpaque); Rect outBounds; - mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds); + // It there are multiple render nodes, they are as follows: + // #0 - backdrop + // #1 - content (with - and clipped to - bounds mContentOverdrawProtectionBounds) + // #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 + // backdrop against the content and draw the remaining part of it. It will then crop the content + // 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; + // If there is no content bounds we ignore the layering as stated above and start with 2. + int layer = mContentOverdrawProtectionBounds.isEmpty() ? 2 : 0; + // Draw all render nodes. Note that + for (const sp& node : mRenderNodes) { + if (layer == 0) { // Backdrop. + // Draw the backdrop clipped to the inverse content bounds. + const RenderProperties& properties = node->properties(); + Rect targetBounds(properties.getLeft(), properties.getTop(), + properties.getRight(), properties.getBottom()); + // Remember the intersection of the target bounds and the intersection bounds against + // which we have to crop the content. + backdropBounds.intersect(targetBounds); + // Check if we have to draw something on the left side ... + if (targetBounds.left < mContentOverdrawProtectionBounds.left) { + mCanvas->save(SkCanvas::kClip_SaveFlag); + if (mCanvas->clipRect(targetBounds.left, targetBounds.top, + mContentOverdrawProtectionBounds.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); + mCanvas->restore(); + } + // ... or on the right side ... + if (targetBounds.right > mContentOverdrawProtectionBounds.right && + !targetBounds.isEmpty()) { + mCanvas->save(SkCanvas::kClip_SaveFlag); + if (mCanvas->clipRect(mContentOverdrawProtectionBounds.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); + mCanvas->restore(); + } + // ... or at the top ... + if (targetBounds.top < mContentOverdrawProtectionBounds.top && + !targetBounds.isEmpty()) { + mCanvas->save(SkCanvas::kClip_SaveFlag); + if (mCanvas->clipRect(targetBounds.left, targetBounds.top, targetBounds.right, + mContentOverdrawProtectionBounds.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); + mCanvas->restore(); + } + // ... or at the bottom. + if (targetBounds.bottom > mContentOverdrawProtectionBounds.bottom && + !targetBounds.isEmpty()) { + mCanvas->save(SkCanvas::kClip_SaveFlag); + if (mCanvas->clipRect(targetBounds.left, + mContentOverdrawProtectionBounds.bottom, targetBounds.right, + targetBounds.bottom, SkRegion::kIntersect_Op)) { + mCanvas->drawRenderNode(node.get(), outBounds); + } + mCanvas->restore(); + } + } 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->drawRenderNode(node.get(), outBounds); + } + mCanvas->restore(); + } else { // draw the rest on top at will! + mCanvas->drawRenderNode(node.get(), outBounds); + } + layer++; + } profiler().draw(mCanvas); @@ -343,7 +439,10 @@ void CanvasContext::doFrame() { if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) { return; } + prepareAndDraw(nullptr); +} +void CanvasContext::prepareAndDraw(RenderNode* node) { ATRACE_CALL(); int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE]; @@ -353,7 +452,7 @@ void CanvasContext::doFrame() { mRenderThread.timeLord().latestVsync()); TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState()); - prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC)); + prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC), node); if (info.out.canDrawThisFrame) { draw(); } @@ -423,7 +522,9 @@ void CanvasContext::destroyHardwareResources() { stopDrawing(); if (mEglManager.hasEglContext()) { freePrefetechedLayers(); - mRootRenderNode->destroyHardwareResources(); + for (const sp& node : mRenderNodes) { + node->destroyHardwareResources(); + } Caches& caches = Caches::getInstance(); // Make sure to release all the textures we were owning as there won't // be another draw diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 6a793203e2906..1c3845cac5043 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -34,6 +34,7 @@ #include #include +#include namespace android { namespace uirenderer { @@ -77,12 +78,14 @@ public: void setOpaque(bool opaque); void makeCurrent(); void processLayerUpdate(DeferredLayerUpdater* layerUpdater); - void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued); + void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, + int64_t syncQueued, RenderNode* target); void draw(); void destroy(); // IFrameCallback, Chroreographer-driven frame callback entry point virtual void doFrame() override; + void prepareAndDraw(RenderNode* node); void buildLayer(RenderNode* node); bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap); @@ -113,6 +116,20 @@ public: void serializeDisplayListTree(); + void addRenderNode(RenderNode* node, bool placeFront) { + int pos = placeFront ? 0 : static_cast(mRenderNodes.size()); + mRenderNodes.emplace( mRenderNodes.begin() + pos, node); + } + + void removeRenderNode(RenderNode* node) { + mRenderNodes.erase(std::remove(mRenderNodes.begin(), mRenderNodes.end(), node), + mRenderNodes.end()); + } + + void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) { + mContentOverdrawProtectionBounds.set(left, top, right, bottom); + } + private: friend class RegisterFrameCallbackTask; // TODO: Replace with something better for layer & other GL object @@ -138,7 +155,7 @@ private: DamageAccumulator mDamageAccumulator; std::unique_ptr mAnimationContext; - const sp mRootRenderNode; + std::vector< sp > mRenderNodes; FrameInfo* mCurrentFrameInfo = nullptr; // Ring buffer large enough for 2 seconds worth of frames @@ -148,6 +165,9 @@ private: FrameInfoVisualizer mProfiler; std::set mPrefetechedLayers; + + // Stores the bounds of the main content. + Rect mContentOverdrawProtectionBounds; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index 198906ca845e7..a47c9ecf8b317 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -38,9 +38,11 @@ DrawFrameTask::DrawFrameTask() DrawFrameTask::~DrawFrameTask() { } -void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context) { +void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context, + RenderNode* targetNode) { mRenderThread = thread; mContext = context; + mTargetNode = targetNode; } void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) { @@ -118,7 +120,7 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) { mContext->processLayerUpdate(mLayers[i].get()); } mLayers.clear(); - mContext->prepareTree(info, mFrameInfo, mSyncQueued); + mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode); // This is after the prepareTree so that any pending operations // (RenderNode tree state, prefetched layers, etc...) will be flushed. diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index ebefcba9f6a5c..68ee897e3b8e6 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -57,7 +57,7 @@ public: DrawFrameTask(); virtual ~DrawFrameTask(); - void setContext(RenderThread* thread, CanvasContext* context); + void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode); void pushLayerUpdate(DeferredLayerUpdater* layer); void removeLayerUpdate(DeferredLayerUpdater* layer); @@ -78,6 +78,7 @@ private: RenderThread* mRenderThread; CanvasContext* mContext; + RenderNode* mTargetNode = nullptr; /********************************************* * Single frame data diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index b8388116ff80d..f43a769890a4f 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -74,7 +74,7 @@ RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextF args->thread = &mRenderThread; args->contextFactory = contextFactory; mContext = (CanvasContext*) postAndWait(task); - mDrawFrameTask.setContext(&mRenderThread, mContext); + mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode); } RenderProxy::~RenderProxy() { @@ -91,7 +91,7 @@ void RenderProxy::destroyContext() { SETUP_TASK(destroyContext); args->context = mContext; mContext = nullptr; - mDrawFrameTask.setContext(nullptr, nullptr); + mDrawFrameTask.setContext(nullptr, nullptr, nullptr); // This is also a fence as we need to be certain that there are no // outstanding mDrawFrame tasks posted before it is destroyed postAndWait(task); @@ -461,7 +461,8 @@ void RenderProxy::dumpGraphicsMemory(int fd) { staticPostAndWait(task); } -CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map, size_t size) { +CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map, + size_t size) { CanvasContext::setTextureAtlas(*args->thread, args->buffer, args->map, args->size); args->buffer->decStrong(nullptr); return nullptr; @@ -490,6 +491,61 @@ void RenderProxy::setProcessStatsBuffer(int fd) { post(task); } +CREATE_BRIDGE3(addRenderNode, CanvasContext* context, RenderNode* node, bool placeFront) { + args->context->addRenderNode(args->node, args->placeFront); + return nullptr; +} + +void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) { + SETUP_TASK(addRenderNode); + args->context = mContext; + args->node = node; + args->placeFront = placeFront; + post(task); +} + +CREATE_BRIDGE2(removeRenderNode, CanvasContext* context, RenderNode* node) { + args->context->removeRenderNode(args->node); + return nullptr; +} + +void RenderProxy::removeRenderNode(RenderNode* node) { + SETUP_TASK(removeRenderNode); + args->context = mContext; + args->node = node; + post(task); +} + +CREATE_BRIDGE2(drawRenderNode, CanvasContext* context, RenderNode* node) { + args->context->prepareAndDraw(args->node); + return nullptr; +} + +void RenderProxy::drawRenderNode(RenderNode* node) { + SETUP_TASK(drawRenderNode); + args->context = mContext; + args->node = node; + // Be pseudo-thread-safe and don't use any member variables + staticPostAndWait(task); +} + +CREATE_BRIDGE5(setContentOverdrawProtectionBounds, CanvasContext* context, int left, int top, + int right, int bottom) { + args->context->setContentOverdrawProtectionBounds(args->left, args->top, args->right, + args->bottom); + return nullptr; +} + +void RenderProxy::setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) { + SETUP_TASK(setContentOverdrawProtectionBounds); + args->context = mContext; + args->left = left; + args->top = top; + args->right = right; + args->bottom = bottom; + staticPostAndWait(task); +} + CREATE_BRIDGE1(serializeDisplayListTree, CanvasContext* context) { args->context->serializeDisplayListTree(); return nullptr; diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index e7356dbb0373f..046f24ac3f81b 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -106,6 +106,11 @@ public: ANDROID_API void serializeDisplayListTree(); + 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); + private: RenderThread& mRenderThread; CanvasContext* mContext; diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 10cf5c1b2f8bc..b028ce61f8213 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -944,5 +944,14 @@ + + + + + + + diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java new file mode 100644 index 0000000000000..b458d9b140961 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MultiProducerActivity.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2014 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 com.android.test.hwui; + +import android.app.Activity; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.DisplayListCanvas; +import android.view.HardwareRenderer; +import android.view.RenderNode; +import android.view.ThreadedRenderer; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AbsoluteLayout; +import android.widget.AbsoluteLayout.LayoutParams; + +public class MultiProducerActivity extends Activity implements OnClickListener { + private static final int DURATION = 800; + private View mBackgroundTarget = null; + private View mFrameTarget = null; + private View mContent = null; + // The width & height of our "output drawing". + private final int WIDTH = 900; + private final int HEIGHT = 600; + // A border width around the drawing. + private static final int BORDER_WIDTH = 20; + // The Gap between the content and the frame which should get filled on the right and bottom + // side by the backdrop. + final int CONTENT_GAP = 100; + + // For debug purposes - disable drawing of frame / background. + private final boolean USE_FRAME = true; + private final boolean USE_BACK = true; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // To make things simple - we do a quick and dirty absolute layout. + final AbsoluteLayout layout = new AbsoluteLayout(this); + + // Create the outer frame + if (USE_FRAME) { + mFrameTarget = new View(this); + LayoutParams frameLP = new LayoutParams(WIDTH, HEIGHT, 0, 0); + layout.addView(mFrameTarget, frameLP); + } + + // Create the background which fills the gap between content and frame. + if (USE_BACK) { + mBackgroundTarget = new View(this); + LayoutParams backgroundLP = new LayoutParams( + WIDTH - 2 * BORDER_WIDTH, HEIGHT - 2 * BORDER_WIDTH, + BORDER_WIDTH, BORDER_WIDTH); + layout.addView(mBackgroundTarget, backgroundLP); + } + + // Create the content + // Note: We reduce the size by CONTENT_GAP pixels on right and bottom, so that they get + // drawn by the backdrop. + mContent = new View(this); + mContent.setBackground(new ColorPulse(0xFFF44336, 0xFF9C27B0, null)); + mContent.setOnClickListener(this); + LayoutParams contentLP = new LayoutParams(WIDTH - 2 * BORDER_WIDTH - CONTENT_GAP, + HEIGHT - 2 * BORDER_WIDTH - CONTENT_GAP, BORDER_WIDTH, BORDER_WIDTH); + layout.addView(mContent, contentLP); + + setContentView(layout); + } + + @Override + protected void onStart() { + super.onStart(); + View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget; + if (view != null) { + view.post(mSetup); + } + } + + @Override + protected void onStop() { + super.onStop(); + View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget; + if (view != null) { + view.removeCallbacks(mSetup); + } + if (mBgRenderer != null) { + mBgRenderer.destroy(); + mBgRenderer = null; + } + } + + @Override + public void onClick(View view) { + sBlockThread.run(); + } + + private Runnable mSetup = new Runnable() { + @Override + public void run() { + View view = mBackgroundTarget != null ? mBackgroundTarget : mFrameTarget; + if (view == null) { + view.postDelayed(mSetup, 50); + } + HardwareRenderer renderer = view.getHardwareRenderer(); + if (renderer == null || view.getWidth() == 0) { + view.postDelayed(mSetup, 50); + } + ThreadedRenderer threaded = (ThreadedRenderer) renderer; + + mBgRenderer = new FakeFrame(threaded,mFrameTarget, mBackgroundTarget); + mBgRenderer.start(); + } + }; + + private FakeFrame mBgRenderer; + private class FakeFrame extends Thread { + ThreadedRenderer mRenderer; + volatile boolean mRunning = true; + View mTargetFrame; + View mTargetBack; + Drawable mFrameContent; + Drawable mBackContent; + // The Z value where to place this. + int mZFrame; + int mZBack; + String mRenderNodeName; + + FakeFrame(ThreadedRenderer renderer, View targetFrame, View targetBack) { + mRenderer = renderer; + mTargetFrame = targetFrame; + + mTargetBack = targetBack; + mFrameContent = new ColorPulse(0xFF101010, 0xFF707070, new Rect(0, 0, WIDTH, HEIGHT)); + mBackContent = new ColorPulse(0xFF909090, 0xFFe0e0e0, null); + } + + @Override + public void run() { + Rect currentFrameBounds = new Rect(); + Rect currentBackBounds = new Rect(); + Rect newBounds = new Rect(); + int[] surfaceOrigin = new int[2]; + RenderNode nodeFrame = null; + RenderNode nodeBack = null; + + // Since we are overriding the window painting logic we need to at least fill the + // surface with some window content (otherwise the world will go black). + try { + Thread.sleep(200); + } catch (InterruptedException e) { + } + + if (mTargetBack != null) { + nodeBack = RenderNode.create("FakeBackdrop", null); + nodeBack.setClipToBounds(true); + mRenderer.addRenderNode(nodeBack, true); + } + + if (mTargetFrame != null) { + nodeFrame = RenderNode.create("FakeFrame", null); + nodeFrame.setClipToBounds(true); + mRenderer.addRenderNode(nodeFrame, false); + } + + while (mRunning) { + // Get the surface position to draw to within our surface. + surfaceOrigin[0] = 0; + surfaceOrigin[1] = 0; + // 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], + surfaceOrigin[0] + mContent.getWidth(), + surfaceOrigin[1] + mContent.getHeight()); + // Determine new position for frame. + if (nodeFrame != null) { + surfaceOrigin[0] = 0; + surfaceOrigin[1] = 0; + mTargetFrame.getLocationInSurface(surfaceOrigin); + newBounds.set(surfaceOrigin[0], surfaceOrigin[1], + surfaceOrigin[0] + mTargetFrame.getWidth(), + surfaceOrigin[1] + mTargetFrame.getHeight()); + if (!currentFrameBounds.equals(newBounds)) { + currentFrameBounds.set(newBounds); + nodeFrame.setLeftTopRightBottom(currentFrameBounds.left, + currentFrameBounds.top, + currentFrameBounds.right, currentFrameBounds.bottom); + } + + // Draw frame + DisplayListCanvas canvas = nodeFrame.start(currentFrameBounds.width(), + currentFrameBounds.height()); + mFrameContent.draw(canvas); + nodeFrame.end(canvas); + } + + // Determine new position for backdrop + if (nodeBack != null) { + surfaceOrigin[0] = 0; + surfaceOrigin[1] = 0; + mTargetBack.getLocationInSurface(surfaceOrigin); + newBounds.set(surfaceOrigin[0], surfaceOrigin[1], + surfaceOrigin[0] + mTargetBack.getWidth(), + surfaceOrigin[1] + mTargetBack.getHeight()); + if (!currentBackBounds.equals(newBounds)) { + currentBackBounds.set(newBounds); + nodeBack.setLeftTopRightBottom(currentBackBounds.left, + currentBackBounds.top, + currentBackBounds.right, currentBackBounds.bottom); + } + + // Draw Backdrop + DisplayListCanvas canvas = nodeBack.start(currentBackBounds.width(), + currentBackBounds.height()); + mBackContent.draw(canvas); + nodeBack.end(canvas); + } + + // we need to only render one guy - the rest will happen automatically (I think). + if (nodeFrame != null) { + mRenderer.drawRenderNode(nodeFrame); + } + if (nodeBack != null) { + mRenderer.drawRenderNode(nodeBack); + } + try { + Thread.sleep(5); + } catch (InterruptedException e) {} + } + if (nodeFrame != null) { + mRenderer.removeRenderNode(nodeFrame); + } + if (nodeBack != null) { + mRenderer.removeRenderNode(nodeBack); + } + } + + public void destroy() { + mRunning = false; + try { + join(); + } catch (InterruptedException e) {} + } + } + + private final static Runnable sBlockThread = new Runnable() { + @Override + public void run() { + try { + Thread.sleep(DURATION); + } catch (InterruptedException e) { + } + } + }; + + static class ColorPulse extends Drawable { + + private int mColorStart; + private int mColorEnd; + private int mStep; + private Rect mRect; + private Paint mPaint = new Paint(); + + public ColorPulse(int color1, int color2, Rect rect) { + mColorStart = color1; + mColorEnd = color2; + if (rect != null) { + mRect = new Rect(rect.left + BORDER_WIDTH / 2, rect.top + BORDER_WIDTH / 2, + rect.right - BORDER_WIDTH / 2, rect.bottom - BORDER_WIDTH / 2); + } + } + + static int evaluate(float fraction, int startInt, int endInt) { + int startA = (startInt >> 24) & 0xff; + int startR = (startInt >> 16) & 0xff; + int startG = (startInt >> 8) & 0xff; + int startB = startInt & 0xff; + + int endA = (endInt >> 24) & 0xff; + int endR = (endInt >> 16) & 0xff; + int endG = (endInt >> 8) & 0xff; + int endB = endInt & 0xff; + + return (int)((startA + (int)(fraction * (endA - startA))) << 24) | + (int)((startR + (int)(fraction * (endR - startR))) << 16) | + (int)((startG + (int)(fraction * (endG - startG))) << 8) | + (int)((startB + (int)(fraction * (endB - startB)))); + } + + @Override + public void draw(Canvas canvas) { + float frac = mStep / 50.0f; + int color = evaluate(frac, mColorStart, mColorEnd); + if (mRect != null && !mRect.isEmpty()) { + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeWidth(BORDER_WIDTH); + mPaint.setColor(color); + canvas.drawRect(mRect, mPaint); + } else { + canvas.drawColor(color); + } + + mStep++; + if (mStep >= 50) { + mStep = 0; + int tmp = mColorStart; + mColorStart = mColorEnd; + mColorEnd = tmp; + } + invalidateSelf(); + } + + @Override + public void setAlpha(int alpha) { + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + } + + @Override + public int getOpacity() { + return mRect == null || mRect.isEmpty() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; + } + + } +} +