如何通过Android渲染管道提高渲染效率
一、Android 硬件加速发展过程
从Android 3.0开始支持硬件加速渲染(就是通过GPU来渲染2D UI),优点是比软件渲染速度更快,缺点是更耗内存。
从Android 8.0开始就支持对硬件加速渲染设置不同的渲染管道,目前有三种opengl、skiagl、skiavk,Android 8.0系统默认使用是opengl渲染管道,Android 9.0系统默认使用skiagl渲染管道,
从Android 10.0开始不支持opengl渲染管道,只支持skiagl、skiavk,根据系统属性ro.hwui.use_vulkan值决定默认使用skiavk或是skiagl。
这些结论从以下源码得出:
RenderPipelineType Properties::getRenderPipelineType() {
if (RenderPipelineType::NotInitialized != sRenderPipelineType) {
return sRenderPipelineType;
}
char prop[PROPERTY_VALUE_MAX];
// 默认是opengl
property_get(PROPERTY_RENDERER, prop, "opengl");
if (!strcmp(prop, "skiagl") ) {
ALOGD("Skia GL Pipeline");
sRenderPipelineType = RenderPipelineType::SkiaGL;
} else if (!strcmp(prop, "skiavk") ) {
ALOGD("Skia Vulkan Pipeline");
sRenderPipelineType = RenderPipelineType::SkiaVulkan;
} else { //"opengl"
ALOGD("HWUI GL Pipeline");
sRenderPipelineType = RenderPipelineType::OpenGL;
}
return sRenderPipelineType;
}
RenderPipelineType Properties::getRenderPipelineType() {
if (sRenderPipelineType != RenderPipelineType::NotInitialized) {
return sRenderPipelineType;
}
char prop[PROPERTY_VALUE_MAX];
// 默认是skiagl
property_get(PROPERTY_RENDERER, prop, "skiagl");
if (!strcmp(prop, "skiagl")) {
ALOGD("Skia GL Pipeline");
sRenderPipelineType = RenderPipelineType::SkiaGL;
} else if (!strcmp(prop, "skiavk")) {
ALOGD("Skia Vulkan Pipeline");
sRenderPipelineType = RenderPipelineType::SkiaVulkan;
} else { //"opengl"
ALOGD("HWUI GL Pipeline");
sRenderPipelineType = RenderPipelineType::OpenGL;
}
return sRenderPipelineType;
}
RenderPipelineType Properties::peekRenderPipelineType() {
// If sRenderPipelineType has been locked, just return the locked type immediately.
if (sRenderPipelineType != RenderPipelineType::NotInitialized) {
return sRenderPipelineType;
}
bool useVulkan = use_vulkan().value_or(false);
char prop[PROPERTY_VALUE_MAX];
// 默认根据useVulkan决定skiavk还是skiagl
property_get(PROPERTY_RENDERER, prop, useVulkan ? "skiavk" : "skiagl");
if (!strcmp(prop, "skiavk")) {
return RenderPipelineType::SkiaVulkan;
}
return RenderPipelineType::SkiaGL;
}
二、三种渲染管道的区别
渲染管道 | 底层图形API | 渲染效率 | 绘制原语(Canvas API) |
---|---|---|---|
opengl | OpenGL | 渲染效率高,但占用内存大 | 不支持部分Canvas API |
skiagl | OpenGL | 渲染效率高,但占用内存大 | 支持全部Canvas API |
skiavk | Vulkan | Vulkan全面优于OpenGL | 支持全部Canvas API |
结论:
- skialg和opengl比opengl支持更多绘制原语,比如在opengl渲染管道下,Cavas scale后Path会模糊,通过把渲染管道设置为skialg或skiavk,这样Cavas scale后Path就不会模糊(Android 8.0之前渲染管道只有opengl,想要Path不模糊只能设置View LayoutType设置为LAYER_TYPE_SOFTWARE,这会导致View的渲染效率降低)
- skiavk底层使用Vulkan API效率高于skiagl和opengl,所以我们在对渲染效率要求高的场景可以把渲染管道设置为skiavk,设置方式如下。
SystemProperties.set("debug.hwui.renderer", "skiavk")
三、渲染管道初始化在什么时机?
问题:我们在什么时候设置渲染管道,保证后面的View使用我们设置的渲染管道?
我们通过Window addView流程进行分析,代码比较简单,直接列出所有源码。
frameworks/base/core/java/android/view/WindowManagerImpl.java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
frameworks/base/core/java/android/view/WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);
}
frameworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
enableHardwareAcceleration(attrs);
}
private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
mAttachInfo.mHardwareAccelerated = false;
mAttachInfo.mHardwareAccelerationRequested = false;
final boolean hardwareAccelerated =
(attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
if (hardwareAccelerated) {
mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
attrs.getTitle().toString());
}
}
frameworks/base/core/java/android/view/ThreadedRenderer.java
public static ThreadedRenderer create(Context context, boolean translucent, String name) {
ThreadedRenderer renderer = null;
if (isAvailable()) {
renderer = new ThreadedRenderer(context, translucent, name);
}
return renderer;
}
ThreadedRenderer(Context context, boolean translucent, String name) {
super();
}
frameworks/base/graphics/java/android/graphics/HardwareRenderer.java
public HardwareRenderer() {
mRootNode = RenderNode.adopt(nCreateRootRenderNode());
mRootNode.setClipToBounds(false);
mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode);
if (mNativeProxy == 0) {
throw new OutOfMemoryError("Unable to create hardware renderer");
}
Cleaner.create(this, new DestroyContextRunnable(mNativeProxy));
ProcessInitializer.sInstance.init(mNativeProxy);
}
frameworks/base/core/jni/android_view_ThreadedRenderer.cpp
static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject clazz,
jboolean translucent, jlong rootRenderNodePtr) {
RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr);
ContextFactoryImpl factory(rootRenderNode);
return (jlong) new RenderProxy(translucent, rootRenderNode, &factory);
}
frameworks/base/libs/hwui/renderthread/RenderProxy.cpp
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory)
: mRenderThread(RenderThread::getInstance()), mContext(nullptr) {
mContext = mRenderThread.queue().runSync([&]() -> CanvasContext* {
return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory);
});
mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
}
CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent,
RenderNode* rootRenderNode, IContextFactory* contextFactory) {
auto renderType = Properties::getRenderPipelineType();
switch (renderType) {
case RenderPipelineType::SkiaGL:
return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread));
case RenderPipelineType::SkiaVulkan:
return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread));
default:
LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType);
break;
}
return nullptr;
}
frameworks/base/libs/hwui/Properties.cpp
// 允许将渲染管线模式设置为OpenGL(默认),Skia OpenGL或Vulkan。
#define PROPERTY_RENDERER "debug.hwui.renderer"
RenderPipelineType Properties::getRenderPipelineType() {
if (sRenderPipelineType != RenderPipelineType::NotInitialized) {
return sRenderPipelineType;
}
char prop[PROPERTY_VALUE_MAX];
property_get(PROPERTY_RENDERER, prop, "skiagl");
if (!strcmp(prop, "skiagl")) {
ALOGD("Skia GL Pipeline");
sRenderPipelineType = RenderPipelineType::SkiaGL;
} else if (!strcmp(prop, "skiavk")) {
ALOGD("Skia Vulkan Pipeline");
sRenderPipelineType = RenderPipelineType::SkiaVulkan;
} else { //"opengl"
ALOGD("HWUI GL Pipeline");
sRenderPipelineType = RenderPipelineType::OpenGL;
}
return sRenderPipelineType;
}
**结论:**意味着我们要在WindowManager.addView 之前设置渲染管道PROPERTY_RENDERER,这样WindowManager.addView的View会使用我们设置的作为渲染管道。
四、 验证不同渲染管道渲染效率
4.1 使用 Gfxinfo 衡量界面渲染效率
- 聚合帧统计信息
adb shell dumpsys gfxinfo <PACKAGE_NAME>
- 精确的帧时间信息
adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats
- 控制统计信息收集的时段
adb shell dumpsys gfxinfo <PACKAGE_NAME> reset
4.2 使用Demo验证不同渲染管道渲染效率
test应用Canvas画上1000条Path后,设置不同渲染管道后,在5s内对Canvas进行scale 0.3 - 3,gfxinfo获取这段时间的渲染数据。
adb shell dumpsys gfxinfo com.test > /Users/cvter/Downloads/opengl.txt
50th percentile: 150ms
90th percentile: 200ms
95th percentile: 200ms
99th percentile: 250ms
adb shell dumpsys gfxinfo com.test > /Users/cvter/Downloads/skiagl.txt
50th percentile: 800ms
90th percentile: 850ms
95th percentile: 900ms
99th percentile: 900ms
adb shell dumpsys gfxinfo com.test > /Users/cvter/Downloads/skiavk.txt
50th percentile: 450ms
90th percentile: 450ms
95th percentile: 450ms
99th percentile: 450ms
结论 渲染效率 opengl > skiavk > skiagl ,opengl效率大于skiavk是因为opengl 对Canvas scale的Path没有进行矢量缩放,Canvas放大后Path模糊,虽然效率高,但是渲染效果差,所以选择skiavk对于渲染效果和渲染效率是最优选择。
五、参考资料
https://developer.android.com/training/testing/performance
https://cs.android.com/android/platform/superproject/+/android-9.0.0_r1:frameworks/
下一篇: Day35:航班管理系统