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:
@@ -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