一、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/