After reading xianyu’s article “I never expected that A Flutter can have external textures”, we learned that a flutter provides a mechanism for sharing native textures with a flutter for rendering. However, because the data type of Flutter to obtain native texture is CVPixelBuffer, the native texture needs to go through the GPU->CPU->GPU conversion process and consume extra performance, which is unacceptable for real-time audio and video rendering.
Our solution is to modify the code of the Flutter engine to connect the GL environment of the Flutter and the native GL environment through ShareGroup, avoiding the need to go around the CPU memory for texture transfer between the two environments. This solution solves the memory copy performance problem, but exposing the GL environment of the flutter is a risky operation, which also adds complexity to future flutter rendering problem location. So, is there a perfect, easy solution? The answer is to take advantage of CVPixelBuffer’s shared memory mechanism.
Principle of flutter external texture
Let’s review the pre-knowledge and see how the official external texture mechanism works.
In the figure, the red block is the native code we wrote, and the yellow block is the internal code logic of the Flutter Engine. The whole process is divided into registering the texture, and the overall texture rendering logic.
Registered texture
- Create an object and implement
FlutterTexture
Protocol, which is used to manage specific texture data - through
FlutterTextureRegistry
To sign up for step oneFlutterTexture
Gets a Flutter texture ID - If the ID is sent to the DART side through the channel mechanism, the DART side can pass the id
Texture
The widget is used to use the texture, and the parameter is id
Texture rendering
- Dart side declares one
Texture
Widget, indicating that the widget actually renders a texture provided by Native - Engine side get layerTree, layerTree
TextureLayer
The node is responsible for rendering the surrounding texture - First go through the ID passed by the DART side to find the first registered one
FlutterTexture
, the flutterTexture is realized by our own native code, and its core is realizedcopyPixelBuffer
methods - Flutter engine calls
copyPixelBuffer
Get the specific texture data and hand it over to the underlying GPU rendering
CVPixelBuffer format analysis
The source of all the problems is here: CVPixelBuffer. According to the rendering process of the upper and external flutter textures, the data interaction between native textures and flutter textures is transmitted through copyPixelBuffer, whose parameter is CVPixelBuffer. The performance problems mentioned in the previous salty fish article come from the conversion between textures and CVPixelBuffer.
If CVPixelBuffer and OpenGL textures can share the same memory copy, will the GPU -> CPU -> GPU performance bottleneck be solved? CVPixelBuffer can be used to create textures in the Flutter Engine.
|
|
Flutter engine is to use CVOpenGLESTextureCacheCreateTextureFromImage this interface to from CVPixelBuffer object creation of OpenGL texture, then the interface actually do? Let’s take a look at the official documentation
This function either creates a new or returns a cached
CVOpenGLESTextureRef
texture object mapped to theCVImageBufferRef
and associated parameters. This operation creates a live binding between the image buffer and the underlying texture object. The EAGLContext associated with the cache may be modified to create, delete, or bind textures. When used as a source texture orGL_COLOR_ATTACHMENT
, the image buffer must be unlocked before rendering. The source or render buffer texture should not be re-used until the rendering has completed. This can be guaranteed by callingglFlush()
.
From the document, we learn several key points:
- The returned texture object is mapped directly into the memory of the CVPixelBufferRef object
- The buffer memory can be accessed by CPU and GPU at the same time. We just need to follow the following rules:
- This parameter is used when the GPU is accessed
CVPixelBuffer
Cannot be in lock state.
Pixelbuffer if you have used pixelBuffer before, the CPU must lock the pixelBuffer object first and then unlock it. So it’s easy to understand that when a GPU uses a texture, it must not be manipulated by the CPU at the same time. - CPU access, to ensure that the GPU has completed rendering, usually refers to in
glFlush()
After the call.
When the CPU reads or writes the buffer, it must ensure that the associated texture is not being rendered by OpenGL.
- This parameter is used when the GPU is accessed
We use instrument allocation to verify that:
The results of instrument can also validate the conclusions in the document. Memory is allocated only when pixelBuffer is created, and no new memory is allocated when textures are mapped.
This also confirms our conclusion that pixelBuffer is allocated when it is created, and no new memory is allocated when it is mapped to the texture.
Shared memory scheme
Now that we know that CVPixelBuffer objects can actually bridge an OpenGL texture, our overall solution comes naturally, as shown in the figure below
The key point is that you first need to create the pixelBuffer object and allocate memory. Then map a texture object to both the Native GL environment and the Flutter GL environment. Thus, in two separate GL environments, we each have our own texture object, but its memory is actually mapped to the same CVPixelBuffer. In the actual rendering process of each frame, the Native environment renders to the texture, while the Flutter environment reads data from the texture.
The Demo presentation
Here I wrote a small demo to verify the effect in action. The main logic of the demo is to render a rotated triangle onto a pixelBuffer mapped texture at 60FPS. Then, after each frame is drawn, inform the flutter side to read the pixelBuffer object for rendering.
The core code is shown below:
|
|
The key code is commented, so I won’t analyze it here
We can see from the GIF above that the rendering process is very smooth, and finally the displayLink frame rate can reach 60FPS. This demo can be applied to other scenarios that require CPU and GPU shared memory.
Complete demo code:
flutter_texture
Original link:
www.luoyibu.cn/posts/9703/