欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

如何通过Android渲染管道提高渲染效率

程序员文章站 2024-02-26 21:27:52
...

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