文章目录
Android中,绘图的API很多,比如2D的绘图skia;3D的绘图OpenGLES,Vulkan等。Android 开始,的View系统中,多数都是采用2D的模式的View Widget,比如绘制一张Bitmap图片,显示一个按钮等。随着Android系统的更新,和用户对视觉效果的追求,以前的这套2D View系统,不仅不能满足要求,而且渲染非常的慢。所以Android一方面完善对3D的API的支持,另一方面修改原来View Widget的渲染机制。
渲染机制的更新,Android提出了硬件加速的机制,其作用就是将2D的绘图操纵,转换为对应的3D的绘图操纵,这个转换的过程,我们把它叫做录制。需要显示的时候,再用OpenGLES通过GPU去渲染。界面创建时,第一次全部录制,后续的过程中,界面如果只有部分区域的widget更新,只需要重新录制更新的widget。录制好的绘图操纵,保存在一个显示列表DisplayList中,需要真正显示到界面的时候,直接显示DisplayList中的绘图 操纵。这样,一方面利用GPU去渲染,比Skia要快;另一方面,采用DisplayList,值重新录制,有更新区域,最大程度利用上一帧的数据,效率自然就快很多。这就是硬件加速的来源。
roundRectClipState
语言苍白,实践为先,我们结合测试示例,来看看硬件加速是怎么回事~
应用使用硬件(GPU)绘制实例
这个是Android原生的测试硬件绘制的应用:
* frameworks/base/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasSurfaceViewActivity.java
private static class RenderingThread extends Thread {
private final SurfaceHolder mSurface;
private volatile boolean mRunning = true;
private int mWidth, mHeight;
public RenderingThread(SurfaceHolder surface) {
mSurface = surface;
}
void setSize(int width, int height) {
mWidth = width;
mHeight = height;
}
@Override
public void run() {
float x = 0.0f;
float y = 0.0f;
float speedX = 5.0f;
float speedY = 3.0f;
Paint paint = new Paint();
paint.setColor(0xff00ff00);
while (mRunning && !Thread.interrupted()) {
final Canvas canvas = mSurface.lockHardwareCanvas();
try {
canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
canvas.drawRect(x, y, x + 20.0f, y + 20.0f, paint);
} finally {
mSurface.unlockCanvasAndPost(canvas);
}
... ...
try {
Thread.sleep(15);
} catch (InterruptedException e) {
// Interrupted
}
}
}
void stopRendering() {
interrupt();
mRunning = false;
}
}
应用这里拿到一个Surface,然后lock一个HardwareCanvas,用lock的HardwareCanvas进行绘制,我们绘制的就可以使用硬件GPU进行绘制。这里每隔15秒循环一次,绘制一个小方块,在屏幕上不停的运动。而背景,被绘制成0x00000000,黑色。
硬件绘制Java层相关流程
通过前面的代码,关键的是在lockHardwareCanvas。
lockHardwareCanvas的代码如下:
* frameworks/base/core/java/android/view/SurfaceView.java
@Override
public Canvas lockHardwareCanvas() {
return internalLockCanvas(null, true);
}
private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
mSurfaceLock.lock();
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
+ mDrawingStopped + ", surfaceControl=" + mSurfaceControl);
Canvas c = null;
if (!mDrawingStopped && mSurfaceControl != null) {
try {
if (hardware) {
c = mSurface.lockHardwareCanvas();
} else {
c = mSurface.lockCanvas(dirty);
}
} catch (Exception e) {
Log.e(LOG_TAG, "Exception locking surface", e);
}
}
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
if (c != null) {
mLastLockTime = SystemClock.uptimeMillis();
return c;
}
... ...
return null;
}
这里Canvas是通过mSurface来申请的。
* frameworks/base/core/java/android/view/Surface.java
public Canvas lockHardwareCanvas() {
synchronized (mLock) {
checkNotReleasedLocked();
if (mHwuiContext == null) {
mHwuiContext = new HwuiContext();
}
return mHwuiContext.lockCanvas(
nativeGetWidth(mNativeObject),
nativeGetHeight(mNativeObject));
}
}
Surface中封装了一个 HwuiContext ,其构造函数如下:
HwuiContext() {
mRenderNode = RenderNode.create("HwuiCanvas", null);
mRenderNode.setClipToBounds(false);
mHwuiRenderer = nHwuiCreate(mRenderNode.mNativeRenderNode, mNativeObject);
}
在HwuiContext的构造函数中,创建了一个RenderNode,创建了一个HwuiRenderer。nHwuiCreate创建一个native的HwuiRender。
这里的HwuiContext,就是和HWUI打交道了。
HwuiContext的lockCanvas实现如下:
Canvas lockCanvas(int width, int height) {
if (mCanvas != null) {
throw new IllegalStateException("Surface was already locked!");
}
mCanvas = mRenderNode.start(width, height);
return mCanvas;
}
RenderNode的start函数:
public DisplayListCanvas start(int width, int height) {
return DisplayListCanvas.obtain(this, width, height);
}
static DisplayListCanvas obtain(@NonNull RenderNode node, int width, int height) {
if (node == null) throw new IllegalArgumentException("node cannot be null");
DisplayListCanvas canvas = sPool.acquire();
if (canvas == null) {
canvas = new DisplayListCanvas(node, width, height);
} else {
nResetDisplayListCanvas(canvas.mNativeCanvasWrapper, node.mNativeRenderNode,
width, height);
}
canvas.mNode = node;
canvas.mWidth = width;
canvas.mHeight = height;
return canvas;
}
RenderNode,start时,将创建一个DisplayListCanvas。DisplayListCanvas是显示列表的Canvas。DisplayListCanvas 构建时,将通过nCreateDisplayListCanvas创建一个native的DisplayListCanvas。
private DisplayListCanvas(@NonNull RenderNode node, int width, int height) {
super(nCreateDisplayListCanvas(node.mNativeRenderNode, width, height));
mDensity = 0; // disable bitmap density scaling
}
DisplayListCanvas和RecordingCanvas的构造函数都比较简单,但是留意一下Canvas的构造函数:
public Canvas(long nativeCanvas) {
if (nativeCanvas == 0) {
throw new IllegalStateException();
}
mNativeCanvasWrapper = nativeCanvas;
mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
this, mNativeCanvasWrapper);
mDensity = Bitmap.getDefaultDensity();
}
这里的mNativeCanvasWrapper,就是nCreateDisplayListCanvas时,创建的native对应的Canvas。后续,JNI中都是通过mNativeCanvasWrapper去找到对应的nativ的Canvas的。
我们先来看这些相关的类之间的关系~
其中,RenderNode,DisplayListCanvas,HwuiRenderer构成了硬件绘制的重要元素。
再回到我们的测试代码,我们这里有两个绘制操纵:
- drawColor
- drawRect
drawColor是在DisplayListCanvas的父类RecordingCanvas中实现的:
public final void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
nDrawColor(mNativeCanvasWrapper, color, mode.nativeInt);
}
这里调用native的nDrawColor方法。
drawRect也是在DisplayListCanvas的父类RecordingCanvas中实现的:
@Override
public final void drawRect(float left, float top, float right, float bottom,
@NonNull Paint paint) {
nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
}
调用native的nDrawRect方法。
native处理流程
###native的Canvas创建
DisplayListCanvas的JNI实现如下:
* frameworks/base/core/jni/android_view_DisplayListCanvas.cpp
const char* const kClassPathName = "android/view/DisplayListCanvas";
static JNINativeMethod gMethods[] = {
// ------------ @FastNative ------------------
{ "nCallDrawGLFunction", "(JJLjava/lang/Runnable;)V",
(void*) android_view_DisplayListCanvas_callDrawGLFunction },
// ------------ @CriticalNative --------------
{ "nCreateDisplayListCanvas", "(JII)J", (void*) android_view_DisplayListCanvas_createDisplayListCanvas },
{ "nResetDisplayListCanvas", "(JJII)V", (void*) android_view_DisplayListCanvas_resetDisplayListCanvas },
{ "nGetMaximumTextureWidth", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureWidth },
{ "nGetMaximumTextureHeight", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureHeight },
{ "nInsertReorderBarrier", "(JZ)V", (void*) android_view_DisplayListCanvas_insertReorderBarrier },
{ "nFinishRecording", "(J)J", (void*) android_view_DisplayListCanvas_finishRecording },
{ "nDrawRenderNode", "(JJ)V", (void*) android_view_DisplayListCanvas_drawRenderNode },
{ "nDrawLayer", "(JJ)V", (void*) android_view_DisplayListCanvas_drawLayer },
{ "nDrawCircle", "(JJJJJ)V", (void*) android_view_DisplayListCanvas_drawCircleProps },
{ "nDrawRoundRect", "(JJJJJJJJ)V",(void*) android_view_DisplayListCanvas_drawRoundRectProps },
};
nCreateDisplayListCanvas对应的实现为android_view_DisplayListCanvas_createDisplayListCanvas。
static jlong android_view_DisplayListCanvas_createDisplayListCanvas(jlong renderNodePtr,
jint width, jint height) {
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
return reinterpret_cast<jlong>(Canvas::create_recording_canvas(width, height, renderNode));
}
注意我们这里的renderNodePtr。这个是RenderNode在native层的对象(地址)。
Canvas的create_recording_canvas函数如下:
Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::RenderNode* renderNode) {
if (uirenderer::Properties::isSkiaEnabled()) {
return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height);
}
return new uirenderer::RecordingCanvas(width, height);
}
isSkiaEnabled没有被enable的,所以创建的是native的RecordingCanvas。Android 8.0开始,对HWUI进行了重构,增加了RenderPipeline的概念。目前有三种类型的pipeline,分别对应不同的渲染。
enum class RenderPipelineType {
OpenGL = 0,
SkiaGL,
SkiaVulkan,
NotInitialized = 128
};
默认还是OpenGL类型。
native的RecordingCanvas如下:
* frameworks/base/libs/hwui/RecordingCanvas.cpp
RecordingCanvas::RecordingCanvas(size_t width, size_t height)
: mState(*this), mResourceCache(ResourceCache::getInstance()) {
resetRecording(width, height);
}
RecordingCanvas创建时,创建了对应的CanvasState,和ResourceCache。CanvasState是Canvas的状态,管理Snapshot的栈,实现matrix,save/restore,clipping等Renderer的接口。ResourceCache主要是做资源cache,cache为点九类型。
在resetRecording函数中,又做了很多初始化。
void RecordingCanvas::resetRecording(int width, int height, RenderNode* node) {
LOG_ALWAYS_FATAL_IF(mDisplayList, "prepareDirty called a second time during a recording!");
mDisplayList = new DisplayList();
mState.initializeRecordingSaveStack(width, height);
mDeferredBarrierType = DeferredBarrierType::InOrder;
}
- 创建了显示列表mDisplayList,这个很重要,稍后我们再介绍。它主要用来保存显示列表的绘制命令。
- 初始化CanvasState
到此,native的Canvas创建完成。
Draw操纵的录制
测试代码中,一共两个绘制操纵,我们以这两个绘制操纵为例,来说明绘制的操纵的录制。
nDrawColor nDrawRect
* frameworks/base/core/jni/android_graphics_Canvas.cpp
static const JNINativeMethod gDrawMethods[] = {
{"nDrawColor","(JII)V", (void*) CanvasJNI::drawColor},
{"nDrawPaint","(JJ)V", (void*) CanvasJNI::drawPaint},
{"nDrawPoint", "(JFFJ)V", (void*) CanvasJNI::drawPoint},
{"nDrawPoints", "(J[FIIJ)V", (void*) CanvasJNI::drawPoints},
{"nDrawLine", "(JFFFFJ)V", (void*) CanvasJNI::drawLine},
{"nDrawLines", "(J[FIIJ)V", (void*) CanvasJNI::drawLines},
{"nDrawRect","(JFFFFJ)V", (void*) CanvasJNI::drawRect},
drawColor函数
static void drawColor(JNIEnv* env,