如何通过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。
这些结论从以下源码得出:

  1. Android 8.0版本Properties.cpp
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;
}
  1. Android 9.0系统Properties.cpp
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;
}
  1. Android 10.0 Properties.cpp
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

结论:

  1. skialg和opengl比opengl支持更多绘制原语,比如在opengl渲染管道下,Cavas scale后Path会模糊,通过把渲染管道设置为skialg或skiavk,这样Cavas scale后Path就不会模糊(Android 8.0之前渲染管道只有opengl,想要Path不模糊只能设置View LayoutType设置为LAYER_TYPE_SOFTWARE,这会导致View的渲染效率降低)
  2. 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 衡量界面渲染效率

Gfxinfo Android官方说明文档链接

  1. 聚合帧统计信息

adb shell dumpsys gfxinfo <PACKAGE_NAME>

  1. 精确的帧时间信息

adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats

  1. 控制统计信息收集的时段

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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章