flutter渲染_渲染“外部纹理”:Flutter优化案例

flutter渲染

优化开源SDK Flutter处理Android和iOS应用程序外部纹理的方式

本文是阿里巴巴的“ 利用颤振” 系列的一部分。

在计算中,就像在日常生活中一样,任何给定的方法都可以在其潜在的缺陷达到决定性的僵局之前看到大量的用途。 对于阿里巴巴来说,发现软件开发工具包Flutter中的一个缺陷意味着该小组最近为其闲鱼二手交易平台开发的移动应用程序的成功与失败之间的区别。

现在,阿里巴巴团队已经针对先玉市场独特的新用途成功地对Flutter进行了优化,为整个UI渲染过程实施了OpenGL流程,以减少CPU和GPU资源的开销。

在今天的文章中,我们将研究小组的优化工作,并详细介绍Flutter的内部工作原理和“外部纹理”,技术读者可以在自己的工作中进行探索。

Flutter渲染框架

Flutter渲染框架被组织为一系列的层,每个层都在前一层之上。 Flutter渲染框架的体系结构设计如下所示:

flutter渲染_渲染“外部纹理”:Flutter优化案例_第1张图片
Flutter渲染框架,逐层

1. 层树 :渲染管道是Dart API在运行时输出的树状结构。 树上的每个叶节点都代表一个界面元素,例如按钮,图像等。

2. Skia :由Google赞助和管理的跨平台渲染框架。 它充当iOS / Android应用程序的图形引擎。 Skia的底层称为OpenGL绘图。 Vulkan支持非常有限,Metal不提供支持。

3. Shell :一种平台功能,包括iOS / Android平台实现,EAGLContext管理,在屏幕上返回数据以及外部纹理实现。

布局过程在Dart运行时执行,并输出一个Layer树。 在管道中遍历“层”树的每个叶节点,以调用Skia引擎来完成界面元素的绘制。

完成上述过程后,请执行以下操作:

1. iOS: 运行glPresentRenderBuffer命令以在屏幕上显示渲染缓冲区的内容。
Android:运行glSwapBuffer命令在当前窗口使用的图层上执行缓冲区交换。

2.单击“完整的屏幕显示”链接。

基于此原理,Flutter可以在本机和Flutter Engine上实现UI隔离。 它还捕获UI代码,而无需分析跨平台解决方案上的平台实现。

实施问题

此实现有其优缺点。 Flutter与Native隔离,有时会使人感觉好像Flutter Engine和Native分开了。 当Flutter想要捕获Native端的高内存图像时,例如照相机框架,视频框架,相册图像等,这会带来问题。

传统应用程序(RN,Weex等)可以通过桥接NativeAPI直接获取此数据。 同时,Flutter确定是否可以直接捕获数据,并基于定义的通道机制发送通知消息,这不可避免地导致传输数据时占用大量内存和CPU。

用外部纹理弥合差距

Flutter提供了一种特殊的机制,称为外部纹理。 请注意,纹理是可以应用于Flutter视图区域的图像。 使用特定于平台的纹理注册表创建,管理和更新它们。 通常,这是通过与主机平台视频播放器,摄像机或OpenGL API或类似图像源集成的插件来完成的。

LayerTree的体系结构图如下所示:

flutter渲染_渲染“外部纹理”:Flutter优化案例_第2张图片
LayerTree体系结构

每个叶节点代表一个Dart代码布局控件。 最后的TextureLayer节点对应于Flutter中的纹理控件。 在Flutter中创建纹理控件时,它表示此控件上显示的需要Native提供的数据。 请注意,此纹理不同于GPU的纹理。 这是Flutter的控制权。

以下是iOS平台上TextureLayer节点的最终绘制代码。 Android平台的代码相似,但是获取纹理的方法略有不同。

建议按照以下三个步骤运行代码:

1.调用外部纹理的copyPixelBuffer函数以获取CVPixelBuffer

2.创建OpenGL ES纹理— CVOpenGLESTextureCacheCreateTextureFromImage

3.将OpenGL ES纹理捕获到SKImage中,并调用Skia的DrawImage函数完成绘制。

void IOSExternalTextureGL::Paint(SkCanvas& canvas, const SkRect& bounds) {
if (!cache_ref_) {
CVOpenGLESTextureCacheRef cache;
CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL,
[EAGLContext currentContext], NULL, &cache);
if (err == noErr) {
cache_ref_.Reset(cache);
} else {
FXL_LOG(WARNING) << "Failed to create GLES texture cache: " << err;
return;
}
}
fml::CFRef bufferRef;
bufferRef.Reset([external_texture_ copyPixelBuffer]);
if (bufferRef != nullptr) {
CVOpenGLESTextureRef texture;
CVReturn err = CVOpenGLESTextureCacheCreateTextureFromImage(
kCFAllocatorDefault, cache_ref_, bufferRef, nullptr, GL_TEXTURE_2D, GL_RGBA,
static_cast(CVPixelBufferGetWidth(bufferRef)),
static_cast(CVPixelBufferGetHeight(bufferRef)), GL_BGRA, GL_UNSIGNED_BYTE, 0,
&texture);
texture_ref_.Reset(texture);
if (err != noErr) {
FXL_LOG(WARNING) << "Could not create texture from pixel buffer: " << err;
return;
}
}
if (!texture_ref_) {
return;
}
GrGLTextureInfo textureInfo = {CVOpenGLESTextureGetTarget(texture_ref_),
CVOpenGLESTextureGetName(texture_ref_), GL_RGBA8_OES};
GrBackendTexture backendTexture(bounds.width(), bounds.height(), GrMipMapped::kNo, textureInfo);
sk_sp image =
SkImage::MakeFromTexture(canvas.getGrContext(), backendTexture, kTopLeft_GrSurfaceOrigin,
kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr);
if (image) {
canvas.drawImage(image, bounds.x(), bounds.y());
}
}

external_texture_object来自哪里? 在Native端调用RegisterExternalTexture之前,创建一个用于实现FlutterTexture协议的对象,该协议已分配给external_texture对象。 外部纹理是Flutter和Native之间的桥梁,用于连续获取要显示的图像数据。

flutter渲染_渲染“外部纹理”:Flutter优化案例_第3张图片

如图所示,PixelBuffer是Flutter和Native在使用外部纹理时传输的数据的载体。 本机端的数据源(相机,播放器等)将数据传输到PixelBuffer中。 Flutter使用PixelBuffer并将其转换为Skia的OpenGL ES纹理以完成绘图。

此时,Flutter可以轻松绘制本机端要绘制的所有数据。 除了动态图像数据(相机播放器)外,图像的显示还提供了Image控件之外的另一种可能性,尤其是对于存在SDWebImage等大型图像加载库的本机端。 当需要在Flutter一侧用Dart书写副本时,此过程非常耗时且费力。

优化处理速度

上面描述的整个过程似乎可以解决Flutter显示本机端大数据的问题,但是它具有以下局限性:

flutter渲染_渲染“外部纹理”:Flutter优化案例_第4张图片

如上图所示,视频图像数据处理通常在本机端使用GPU处理以提高性能。 copyPixelBuffer接口由Flutter端定义。 整个数据流通过GPU> CPU> GPU进程进行。 请注意,在所有操作中,CPU和GPU之间的内存交换是最耗时的。 通常,一次往返执行所需的时间要比整个管道的处理时间长。

Skia渲染引擎需要GPU Texture,而本机数据处理输出是GPU Texture。 可以使用EAGLContext的共享资源直接使用此GPU纹理。 EAGLContext对象管理OpenGL ES渲染上下文-使用OpenGL ES绘制所需的状态信息,命令和资源。

Flutter的线程结构介绍如下:

Flutter通常会创建4位奔跑者。 TaskRunner就像iOS的Grand Central Dispatch(GCD)。 它是一种在队列中执行任务的机制。 通常,TaskRunner与线程通信,而Platform Runner在主线程上运行。

以下3个TaskRunner与本文相关:

1. GPU TaskRunner :负责GPU渲染相关的操作。

2. IO TaskRunner :负责资源的加载。

3. Platform TaskRunner :负责本机引擎与Flutter Engine之间的所有交换并在主线程上运行。

通常,使用OpenGL的应用程序线程设计将有两个线程-一个线程用于加载资源(从图像到纹理),另一个线程用于渲染。 但是,通常情况下,为了使加载线程创建的纹理能够在渲染线程中使用,两个线程共享一个EAGLContext。 这不是标准做法,因为这是不安全的。 使用锁对同一对象进行多线程访问将不可避免地影响性能。 如果代码处理不当,甚至会导致死锁。

为避免此问题,Flutter提供了一种使用EAGLContext的新机制-每个线程使用其自己的EAGLContext并通过iOS应用程序的ShareGroup和Android应用程序的shareContext共享纹理数据。

(尽管两个上下文的用户分别是GPU和IO Runner,但现有Flutter逻辑的两个上下文是在Platform Runner下创建的。Flutter设计的这一方面相当令人困惑,并会产生各种问题,但是这些问题都属于超出本文的范围。)

对于在本机端使用OpenGL的模块,它还将在其自己的线程下并在其下创建一个Context。 要将在此上下文下创建的纹理交付给Flutter,并将此数据发送到Skia进行完整绘制,请在Flutter中创建两个内部上下文的同时公开iOS应用的ShareGroup,然后将ShareGroup保存在本机端。 本机创建上下文时,它还将使用此ShareGroup创建它。 这样,本机和Flutter可以共享纹理。

以下是使用external_texture带来的两个好处:

1. 减少计算时间
阿里巴巴测试得出的结论是,在Android机型上,一帧720P RGBA格式的视频从读取GPU到CPU大约需要5毫秒,然后从CPU到GPU则需要另外5毫秒。 即使引入PBO,也要消耗约5毫秒的时间,这对于高帧率场景显然是不可接受的。

2. 减少CPU内存消耗
可以理解,数据是通过GPU传递的,尤其是在图片场景中(因为同时要显示很多图片)。

一些注意事项

既然已经介绍了Flutter外部纹理的基本原理和优化策略,本节将介绍这些原理的一些缺点和例外。

Flutter纹理的完整性与Pixelbuffer的关系

许多人此时提出的一个问题是:如果直接将Texture用作外部纹理符合要求,那么Google为什么要使用Pixelbuffer?

如果使用Texture,则必须公开ShareGroup,这意味着Flutter的GL环境已打开。 如果外部OpenGL无法正常工作,则在断点时,OpenGL对象只是一个数字,CPU的Texture或FrameBuffer,对于用户而言则是GLuint(无符号二进制整数)。 如果环境是隔离的,则用户可以根据需要操作deleteTexture和deleteFrameBuffer,而不会影响其他环境中的对象。 否则,这些操作可能会在Flutter上下文中影响对象。 作为框架设计者,最大的优先事项是确保框架是封闭的环境,以保持其完整性。

故障排除:崩溃

在开发过程中,团队遇到了一个奇怪的问题:Flutter在渲染过程中会例行崩溃,但是没人知道原因。

经过大量搜索后,最终确定原因是在主线程没有setCurrentContext时调用了glDeleteFrameBuffer,这意味着Flutter的FrameBuffer被意外删除。 如果确实选择使用此程序,则本机端的相关GL操作应至少遵循以下几点之一:

1.尽量不要在主线程上执行GL操作;

2.始终在使用GL操作调用函数之前添加setCurrentContext。

iOS与Android

本文中的大多数逻辑均基于iOS示例。 总体原理与Android相同,但实现方式略有不同。

Android上Flutter的外部纹理由SurfaceTexture实现。 该机制实际上是从CPU到GPU内存的复制。 Android OpenGL中没有ShareGroup的概念。 相反,它使用shareContext,这意味着Context被直接发送出去。

此外,壳层中Android的GL实现基于C ++,因此Context是一个C ++对象。 要与Android Native端的Java Context对象共享此C ++对象,需要在jni层中进行如下调用:

static jobject GetContext(JNIEnv* env,
jobject jcaller,
jlong shell_holder) {
jclass eglcontextClassLocal = env->FindClass("android/opengl/EGLContext");
jmethodID eglcontextConstructor = env->GetMethodID(eglcontextClassLocal, "", "(J)V");

void * cxt = ANDROID_SHELL_HOLDER->GetPlatformView()->GetContext();

if((EGLContext)cxt == EGL_NO_CONTEXT)
{
return env->NewObject(eglcontextClassLocal, eglcontextConstructor, reinterpret_cast(EGL_NO_CONTEXT));
}

return env->NewObject(eglcontextClassLocal, eglcontextConstructor, reinterpret_cast(cxt));
}

(Chen Lujun陈炉军的原创文章)

阿里巴巴科技

关于阿里巴巴最新技术的第一手和深入信息→Facebook: “阿里巴巴技术” 。 Twitter: “阿里巴巴技术”

翻译自: https://hackernoon.com/rendering-external-texture-an-flutter-optimization-by-alibaba-c5ed143af747

flutter渲染

你可能感兴趣的:(android,python,java,编程语言,人工智能)