Android?drawFunctor?原理及應(yīng)用詳情
一. 背景
螞蟻 NativeCanvas 項(xiàng)目 Android 平臺(tái)中使用了基于 TextureView 環(huán)境實(shí)現(xiàn) GL 渲染的技術(shù)方案,而 TextureView 需使用與 Activity Window 獨(dú)立的 GraphicBuffer,RenderThread 在上屏 TextureView 內(nèi)容時(shí)需要將 GraphicBuffer 封裝為 EGLImage 上傳為紋理再渲染,內(nèi)存占用較高。為降低內(nèi)存占用,經(jīng)仔細(xì)調(diào)研 Android 源碼,發(fā)現(xiàn)其中存在一種稱為 drawFunctor 的技術(shù),用來(lái)將 WebView 合成后的內(nèi)容同步到 Activity Window 內(nèi)上屏。經(jīng)過(guò)一番探索成功實(shí)現(xiàn)了基于 drawFunctor 實(shí)現(xiàn) GL 注入 RenderThread 的功能,本文將介紹這是如何實(shí)現(xiàn)的。
二. drawFunctor 原理介紹
drawFunctor 是 Android 提供的一種在 RenderThread 渲染流程中插入執(zhí)行代碼機(jī)制,Android 框架是通過(guò)以下三步來(lái)實(shí)現(xiàn)這個(gè)機(jī)制的:
- 在 UI 線程 View 繪制流程 onDraw 方法中,通過(guò) RecordingCanvas.invoke 接口,將 functor 插入 DisplayList 中
- 在 RenderThread 渲染 frame 時(shí)執(zhí)行 DisplayList,判斷如果是 functor 類型的 op,則保存當(dāng)前部分 gl 狀態(tài)
- 在 RenderThread 中真正執(zhí)行 functor 邏輯,執(zhí)行完成后恢復(fù) gl 狀態(tài)并繼續(xù)
目前只能通過(guò) View.OnDraw 來(lái)注入 functor,因此對(duì)于非 attached 的 view 是無(wú)法實(shí)現(xiàn)注入的。Functor 對(duì)具體要執(zhí)行的代碼并未限制,理論上可以插入任何代碼的,比如插入一些統(tǒng)計(jì)、性能檢測(cè)之類代碼。系統(tǒng)為了 functor 不影響當(dāng)前 gl context,執(zhí)行 functor 前后進(jìn)行了基本的狀態(tài)保存和恢復(fù)工作。
另外,如果 View 設(shè)置了使用 HardwareLayer, 則 RenderThread 會(huì)單獨(dú)渲染此 View,具體做法是為 Layer 生成一塊 FBO,View 的內(nèi)容渲染到此 FBO 上,然后再將 FBO 以 View 在 hierachy 上的變換繪制 Activity Window Buffer 上。 對(duì) drawFunctor 影響的是, 會(huì)切換到 View 對(duì)應(yīng)的 FBO 下執(zhí)行 functor, 即 functor 執(zhí)行的結(jié)果是寫(xiě)入到 FBO 而不是 Window Buffer。
三. 利用 drawFunctor 注入 GL 渲染
根據(jù)上文介紹,通過(guò) drawFunctor 可以在 RenderThread 中注入任何代碼,那么也一定可以注入 OpenGL API 來(lái)進(jìn)行渲染。我們知道 OpenGL API 需要執(zhí)行 EGL Context 上,所以就有兩種策略:一種是利用 RenderThread 默認(rèn)的 EGL Context 環(huán)境,一種是創(chuàng)建與 RenderThread EGL Context share 的 EGL Context。本文重點(diǎn)介紹第一種,第二種方法大同小異。
Android Functor 定義
首先找到 Android 源碼中 Functor 的頭文件定義并引入項(xiàng)目:
namespace android { class Functor { public: Functor() {} virtual ~Functor() {} virtual int operator()(int /*what*/, void * /*data*/) { return 0; } }; }
RenderThread 執(zhí)行 Functor 時(shí)將調(diào)用 operator()方法,what 表示 functor 的操作類型,常見(jiàn)的有同步和繪制, 而 data 是 RenderThread 執(zhí)行 functor 時(shí)傳入的參數(shù),根據(jù)源碼發(fā)現(xiàn)是 data 是 android::uirenderer::DrawGlInfo 類型指針,包含當(dāng)前裁剪區(qū)域、變換矩陣、dirty 區(qū)域等等。
DrawGlInfo 頭文件定義如下:
namespace android { namespace uirenderer { /** * Structure used by OpenGLRenderer::callDrawGLFunction() to pass and * receive data from OpenGL functors. */ struct DrawGlInfo { // Input: current clip rect int clipLeft; int clipTop; int clipRight; int clipBottom; // Input: current width/height of destination surface int width; int height; // Input: is the render target an FBO bool isLayer; // Input: current transform matrix, in OpenGL format float transform[16]; // Input: Color space. // const SkColorSpace* color_space_ptr; const void* color_space_ptr; // Output: dirty region to redraw float dirtyLeft; float dirtyTop; float dirtyRight; float dirtyBottom; /** * Values used as the "what" parameter of the functor. */ enum Mode { // Indicates that the functor is called to perform a draw kModeDraw, // Indicates the the functor is called only to perform // processing and that no draw should be attempted kModeProcess, // Same as kModeProcess, however there is no GL context because it was // lost or destroyed kModeProcessNoContext, // Invoked every time the UI thread pushes over a frame to the render thread // *and the owning view has a dirty display list*. This is a signal to sync // any data that needs to be shared between the UI thread and the render thread. // During this time the UI thread is blocked. kModeSync }; /** * Values used by OpenGL functors to tell the framework * what to do next. */ enum Status { // The functor is done kStatusDone = 0x0, // DisplayList actually issued GL drawing commands. // This is used to signal the HardwareRenderer that the // buffers should be flipped - otherwise, there were no // changes to the buffer, so no need to flip. Some hardware // has issues with stale buffer contents when no GL // commands are issued. kStatusDrew = 0x4 }; }; // struct DrawGlInfo } // namespace uirenderer } // namespace android
Functor 設(shè)計(jì)
operator()調(diào)用時(shí)傳入的 what 參數(shù)為 Mode 枚舉, 對(duì)于注入 GL 的場(chǎng)景只需處理 kModeDraw 即可,c++ 側(cè)類設(shè)計(jì)如下:
// MyFunctor定義 namespace android { class MyFunctor : Functor { public: MyFunctor(); virtual ~MyFunctor() {} virtual void onExec(int what, android::uirenderer::DrawGlInfo* info); virtual std::string getFunctorName() = 0; int operator()(int /*what*/, void * /*data*/) override; private: }; } // MyFunctor實(shí)現(xiàn) int MyFunctor::operator() (int what, void *data) { if (what == android::uirenderer::DrawGlInfo::Mode::kModeDraw) { auto info = (android::uirenderer::DrawGlInfo*)data; onExec(what, info); } return android::uirenderer::DrawGlInfo::Status::kStatusDone; } void MyFunctor::onExec(int what, android::uirenderer::DrawGlInfo* info) { // 渲染實(shí)現(xiàn) }
因?yàn)?functor 是 Java 層調(diào)度的,而真正實(shí)現(xiàn)是在 c++ 的,因此需要設(shè)計(jì) java 側(cè)類并做 JNI 橋接:
// java MyFunctor定義 class MyFunctor { private long nativeHandle; public MyFunctor() { nativeHandle = createNativeHandle(); } public long getNativeHandle() { return nativeHanlde; } private native long createNativeHandle(); } // jni 方法: extern "C" JNIEXPORT jlong JNICALL Java_com_test_MyFunctor_createNativeHandle(JNIEnv *env, jobject thiz) { auto p = new MyFunctor(); return (jlong)p; }
在 View.onDraw () 中調(diào)度 functor
框架在 java Canvas 類上提供了 API,可以在 onDraw () 時(shí)將 functor 記錄到 Canvas 的 DisplayList 中。不過(guò)由于版本迭代的原因 API 在各版本上稍有不同,經(jīng)總結(jié)可采用如下代碼調(diào)用,兼容各版本區(qū)別:
public class FunctorView extends View { ... private static Method sDrawGLFunction; private MyFunctor myFunctor = new MyFunctor(); @Override public void onDraw(Canvas cvs) { super.onDraw(cvs); getDrawFunctorMethodIfNot(); invokeFunctor(cvs, myFunctor); } private void invokeFunctor(Canvas canvas, MyFunctor functor) { if (functor.getNativeHandle() != 0 && sDrawGLFunction != null) { try { sDrawGLFunction.invoke(canvas, functor.getNativeHandle()); } catch (Throwable t) { // log } } } public synchronized static Method getDrawFunctorMethodIfNot() { if (sDrawGLFunction != null) { return sDrawGLFunction; } hasReflect = true; String className; String methodName; Class<?> paramClass = long.class; try { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { className = "android.graphics.RecordingCanvas"; methodName = "callDrawGLFunction2"; } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { className = "android.view.DisplayListCanvas"; methodName = "callDrawGLFunction2"; } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) { className = "android.view.HardwareCanvas"; methodName = "callDrawGLFunction"; } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1) { className = "android.view.HardwareCanvas"; methodName = "callDrawGLFunction2"; } else { className = "android.view.HardwareCanvas"; methodName = "callDrawGLFunction"; paramClass = int.class; } Class<?> canvasClazz = Class.forName(className); sDrawGLFunction = SystemApiReflector.getInstance(). getDeclaredMethod(SystemApiReflector.KEY_GL_FUNCTOR, canvasClazz, methodName, paramClass); } catch (Throwable t) { // 異常 } if (sDrawGLFunction != null) { sDrawGLFunction.setAccessible(true); } else { // (異常) } return sDrawGLFunction; } }
注意上述代碼反射系統(tǒng)內(nèi)部 API,Android 10 之后做了 Hidden API 保護(hù),直接反射會(huì)失敗,此部分可網(wǎng)上搜索解決方案,此處不展開(kāi)。
四. 實(shí)踐中遇到的問(wèn)題
GL 狀態(tài)保存&恢復(fù)
Android RenderThread 在執(zhí)行 drawFunctor 前會(huì)保存部分 GL 狀態(tài),如下源碼:
// Android 9.0 code // 保存狀態(tài) void RenderState::interruptForFunctorInvoke() { mCaches->setProgram(nullptr); mCaches->textureState().resetActiveTexture(); meshState().unbindMeshBuffer(); meshState().unbindIndicesBuffer(); meshState().resetVertexPointers(); meshState().disableTexCoordsVertexArray(); debugOverdraw(false, false); // TODO: We need a way to know whether the functor is sRGB aware (b/32072673) if (mCaches->extensions().hasLinearBlending() && mCaches->extensions().hasSRGBWriteControl()) { glDisable(GL_FRAMEBUFFER_SRGB_EXT); } } // 恢復(fù)狀態(tài) void RenderState::resumeFromFunctorInvoke() { if (mCaches->extensions().hasLinearBlending() && mCaches->extensions().hasSRGBWriteControl()) { glEnable(GL_FRAMEBUFFER_SRGB_EXT); } glViewport(0, 0, mViewportWidth, mViewportHeight); glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); debugOverdraw(false, false); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); scissor().invalidate(); blend().invalidate(); mCaches->textureState().activateTexture(0); mCaches->textureState().resetBoundTextures(); }
可以看出并沒(méi)有保存所有 GL 狀態(tài),可以增加保存和恢復(fù)所有其他 GL 狀態(tài)的邏輯,也可以針對(duì)實(shí)際 functor 中改變的狀態(tài)進(jìn)行保存和恢復(fù);特別注意 functor 執(zhí)行時(shí)的 GL 狀態(tài)是非初始狀態(tài),例如 stencil、blend 等都可能被系統(tǒng) RenderThread 修改,因此很多狀態(tài)需要重置到默認(rèn)。
View變換處理
當(dāng)承載 functor 的 View 外部套 ScrollView、ViewPager,或者 View 執(zhí)行動(dòng)畫(huà)時(shí),渲染結(jié)果異?;蛘卟徽_。例如水平滾動(dòng)條中 View 使用 functor 渲染,內(nèi)容不會(huì)隨著滾動(dòng)條移動(dòng)調(diào)整位置。進(jìn)一步研究源碼 Android 發(fā)現(xiàn),此類問(wèn)題原因都是 Android 在渲染 View 時(shí)加入了變換,變換采用標(biāo)準(zhǔn) 4x4 變換列矩陣描述,其值可以從 DrawGlInfo::transform 字段中獲取, 因此渲染時(shí)需要處理 transform,例如將 transform 作為模型變換矩陣傳入 shader。
ContextLost
Android framework 在 trimMemory 時(shí)在 RenderThread 中會(huì)銷毀當(dāng)前 GL Context 并創(chuàng)建一個(gè)新 Context, 這樣會(huì)導(dǎo)致 functor 的 program、shader、紋理等 GL 資源都不可用,再去渲染的話可能會(huì)導(dǎo)致閃退、渲染異常等問(wèn)題,因此這種情況必須處理。
首先,需要響應(yīng) lowMemory 事件,可以通過(guò)監(jiān)聽(tīng) Application 的 trimMemory 回調(diào)實(shí)現(xiàn):
activity.getApplicationContext().registerComponentCallbacks( new ComponentCallbacks2() { @Override public void onTrimMemory(int level) { if (level == 15) { // 觸發(fā)functor重建 } } @Override public void onConfigurationChanged(Configuration newConfig) { } @Override public void onLowMemory() { } });
然后,保存 & 恢復(fù) functor 的 GL 資源和執(zhí)行狀態(tài),例如 shader、program、fbo 等需要重新初始化,紋理、buffer、uniform 數(shù)據(jù)需要重新上傳。注意由于無(wú)法事前知道 onTrimMemory 發(fā)生,上一幀內(nèi)容是無(wú)法恢復(fù)的,當(dāng)然知道完整的狀態(tài)是可以重新渲染出來(lái)的。
鑒于存在無(wú)法提前感知的 ContextLost 情況,建議采用基于 commandbuffer 的模式來(lái)實(shí)現(xiàn) functor 渲染邏輯。
五. 效果
我們用一個(gè) OpenGL 渲染的簡(jiǎn)單 case (分辨率1080x1920),對(duì)使用 TextureView 渲染和使用 drawFunctor 渲染的方式進(jìn)行了比較,
結(jié)果如下:
Simple Case | 內(nèi)存 | CPU 占用 |
---|---|---|
基于 TextureView | 100 M ( Graphics 38 M ) | 6% |
基于 GLFunctor | 84 M ( Graphics 26 M ) | 4% |
從上述結(jié)果可得出結(jié)論,使用 drawFunctor 方式在內(nèi)存、CPU 占用上具有優(yōu)勢(shì), 可應(yīng)用于局部頁(yè)面的互動(dòng)渲染、視頻渲染等場(chǎng)景。
到此這篇關(guān)于Android drawFunctor 原理及應(yīng)用詳情的文章就介紹到這了,更多相關(guān)Android drawFunctor 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android保存的文件顯示到文件管理的最近文件和下載列表中的方法
這篇記錄的是Android中如何把我們往存儲(chǔ)中寫(xiě)入的文件,如何顯示到文件管理的下載列表、最近文件列表中,需要的朋友可以參考下2020-01-01Android開(kāi)發(fā)中日期工具類DateUtil完整實(shí)例
這篇文章主要介紹了Android開(kāi)發(fā)中日期工具類DateUtil,結(jié)合完整實(shí)例形式分析了Android針對(duì)日期與時(shí)間的計(jì)算、轉(zhuǎn)換、格式化、獲取等相關(guān)操作技巧,需要的朋友可以參考下2017-11-11Android關(guān)于Glide的使用(高斯模糊、加載監(jiān)聽(tīng)、圓角圖片)
這篇文章主要為大家詳細(xì)介紹了Android關(guān)于Glide的使用,內(nèi)容豐富,高斯模糊、加載監(jiān)聽(tīng)、圓角圖片希望大家可以掌握,感興趣的小伙伴們可以參考一下2016-11-11Android?雙屏異顯自適應(yīng)Dialog的實(shí)現(xiàn)
Android 多屏互聯(lián)的時(shí)代,必然會(huì)出現(xiàn)多屏連接的問(wèn)題,本文主要介紹了Android?雙屏異顯自適應(yīng)Dialog的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12Android使用acoco統(tǒng)計(jì)代碼行覆蓋率介紹
大家好,本篇文章主要講的是Android使用acoco統(tǒng)計(jì)代碼行覆蓋率介紹,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12Android控件gridview實(shí)現(xiàn)單行多列橫向滾動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了Android控件gridview實(shí)現(xiàn)單行多列橫向滾動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12