MultiThreaded rendering of different renderNodes
This is adding the renderer side infrastructure to allow rendering multiple render nodes with different threads. This is a pre-step for decoupling a non client decor resize reder from a content resize render. Multiple render nodes can be added to be drawn, and to prevent overdrawing, a content bounds area can be set Bug: 22527834 Change-Id: Ie7271e20895bf38957e5a84aeefc883e282039ad
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -7372,6 +7372,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the view's coordinate within the surface.
|
||||
*
|
||||
* <p>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.</p>
|
||||
* @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.
|
||||
|
||||
@@ -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<RenderProxy*>(proxyPtr);
|
||||
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
|
||||
proxy->addRenderNode(renderNode, placeFront);
|
||||
}
|
||||
|
||||
static void android_view_ThreadedRenderer_removeRenderNode(JNIEnv* env, jobject clazz,
|
||||
jlong proxyPtr, jlong renderNodePtr) {
|
||||
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
|
||||
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
|
||||
proxy->removeRenderNode(renderNode);
|
||||
}
|
||||
|
||||
static void android_view_ThreadedRendererd_drawRenderNode(JNIEnv* env, jobject clazz,
|
||||
jlong proxyPtr, jlong renderNodePtr) {
|
||||
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
|
||||
RenderNode* renderNode = reinterpret_cast<RenderNode*>(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<RenderProxy*>(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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<RenderNode>& 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<RenderNode>& 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<RenderNode>& 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
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<int>(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<AnimationContext> mAnimationContext;
|
||||
|
||||
const sp<RenderNode> mRootRenderNode;
|
||||
std::vector< sp<RenderNode> > mRenderNodes;
|
||||
|
||||
FrameInfo* mCurrentFrameInfo = nullptr;
|
||||
// Ring buffer large enough for 2 seconds worth of frames
|
||||
@@ -148,6 +165,9 @@ private:
|
||||
FrameInfoVisualizer mProfiler;
|
||||
|
||||
std::set<RenderNode*> mPrefetechedLayers;
|
||||
|
||||
// Stores the bounds of the main content.
|
||||
Rect mContentOverdrawProtectionBounds;
|
||||
};
|
||||
|
||||
} /* namespace renderthread */
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -944,5 +944,14 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="MultiProducerActivity"
|
||||
android:label="Threads/Multiple Producers">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="com.android.test.hwui.TEST" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user