如何通過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/

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