Flutter纹理渲染插件texture_rgba_renderer的使用
Flutter纹理渲染插件texture_rgba_renderer的使用
texture_rgba_renderer
是一个通用的纹理助手,它为Flutter带来了处理BGRA数据的高低级别API。此插件仅需一次帧复制,旨在实现完全的硬件加速。最初为RustDesk开发,现在处于积极开发中,旨在提高内存使用率和性能。
平台支持
- ✅ Linux
- ✅ Windows
- ✅ MacOS
注意:请在所有平台上使用 BGRA 格式。
快速开始
创建纹理
通过调用createTexture
创建一个纹理:
_textureRgbaRendererPlugin.createTexture(key).then((textureId) {
if (textureId != -1) {
debugPrint("Texture register success, textureId=$textureId");
setState(() {
this.textureId = textureId;
});
} else {
return;
}
});
传输RGBA数据
通过调用onRgba
传输RGBA数据:
data = mockPicture(width, height);
final res = await _textureRgbaRendererPlugin.onRgba(key, data!, height, width);
if (!res) {
debugPrint("WARN: render failed");
}
关闭纹理
通过调用closeTexture
关闭一个纹理:
if (key != -1) {
_textureRgbaRendererPlugin.closeTexture(key);
}
更多细节请参考示例代码。
API
为了提供更方便的接口给Dart和原生开发者,此插件提供了两种API来渲染帧。
性能基准测试
API类型 | FPS(平均) |
---|---|
Method Channel (Dart) API | ~45 |
Native API | ~670 |
结论:为了获得更好的性能,请使用此插件提供的Native
API。
Dart API
你可以通过提供的Dart API创建/关闭纹理,并通过onRgba
函数向纹理提供BGRA数据。
class TextureRgbaRenderer {
/// Create a texture with unique identifier [key].
Future<int> createTexture(int key) {
return TextureRgbaRendererPlatform.instance.createTexture(key);
}
/// Close a texture with unique identifier [key].
Future<bool> closeTexture(int key) {
return TextureRgbaRendererPlatform.instance.closeTexture(key);
}
/// Provide the rgba data to the texture.
Future<bool> onRgba(int key, Uint8List data, int height, int width) {
return TextureRgbaRendererPlatform.instance.onRgba(key, data, height, width);
}
/// For native API.
Future<int> getTexturePtr(int key) async {
final ptr =
await methodChannel.invokeMethod('getTexturePtr', {"key": key}) ??
false;
return ptr;
}
}
Native API
请注意,onRgba
函数不是立即调用的。它依赖于Flutter的执行调度器。为了解决这个问题,该插件在所有3个PC平台上提供了低级别的函数,可以立即执行。
void FlutterRgbaRendererPluginOnRgba(void* texture_rgba_ptr, const uint8_t* buffer, int len, int width, int height, int stride_align)
你可以在其他编程语言中使用原生API。下面是一个Dart的快速示例:
typedef F1 = Void Function(Pointer<Void> ptr, Pointer<Uint8> buffer, Int len,
Int32 width, Int32 height, Int stride_align);
typedef F1Dart = void Function(Pointer<Void> ptr, Pointer<Uint8> buffer,
int len, int width, int height, int stride_align);
class Native {
Native._();
static Native get _instance => Native._()..init();
static Native get instance => _instance;
late F1Dart onRgba;
init() {
final lib = DynamicLibrary.process();
onRgba = lib.lookupFunction<F1, F1Dart>("FlutterRgbaRendererPluginOnRgba");
}
}
// Call
Native.instance.onRgba(ptr_from_method_getTexturePtr, bgra_data, width, height);
请注意,buffer
不由插件管理,你需要自己处理buffer
的生命周期。
示例Demo
以下是完整的示例代码,展示了如何使用texture_rgba_renderer
插件进行纹理渲染。
import 'dart:io';
import 'dart:ffi';
import 'dart:math';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:texture_rgba_renderer/texture_rgba_renderer.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _textureRgbaRendererPlugin = TextureRgbaRenderer();
int textureId = -1;
int height = 768;
int width = 1377;
int cnt = 0;
var key = 0;
int texturePtr = 0;
final random = Random();
Uint8List? data;
Timer? _timer;
int time = 0;
int method = 0;
final strideAlign = Platform.isMacOS ? 64 : 1;
@override
void initState() {
super.initState();
_textureRgbaRendererPlugin.createTexture(key).then((textureId) {
if (textureId != -1) {
debugPrint("Texture register success, textureId=$textureId");
_textureRgbaRendererPlugin.getTexturePtr(key).then((value) {
debugPrint("texture ptr: ${value.toRadixString(16)}");
setState(() {
texturePtr = value;
});
});
setState(() {
this.textureId = textureId;
});
} else {
return;
}
});
}
void start(int methodId) {
debugPrint("start mockPic");
method = methodId;
final rowBytes = (width * 4 + strideAlign - 1) & (~(strideAlign - 1));
final picDataLength = rowBytes * height;
debugPrint('REMOVE ME =============================== rowBytes $rowBytes');
_timer?.cancel();
// 60 fps
_timer = Timer.periodic(const Duration(milliseconds: 1000 ~/ 60), (timer) async {
if (methodId == 0) {
// Method.1: with MethodChannel
data = mockPicture(width, height, rowBytes, picDataLength);
final t1 = DateTime.now().microsecondsSinceEpoch;
final res = await _textureRgbaRendererPlugin.onRgba(
key, data!, height, width, strideAlign);
final t2 = DateTime.now().microsecondsSinceEpoch;
setState(() {
time = t2 - t1;
});
if (!res) {
debugPrint("WARN: render failed");
}
} else {
final dataPtr = mockPicturePtr(width, height, rowBytes, picDataLength);
// Method.2: with native ffi
final t1 = DateTime.now().microsecondsSinceEpoch;
Native.instance.onRgba(Pointer.fromAddress(texturePtr).cast<Void>(),
dataPtr, picDataLength, width, height, strideAlign);
final t2 = DateTime.now().microsecondsSinceEpoch;
setState(() {
time = t2 - t1;
});
malloc.free(dataPtr);
}
});
}
@override
void dispose() {
_timer?.cancel();
if (key != -1) {
_textureRgbaRendererPlugin.closeTexture(key);
}
super.dispose();
}
Uint8List mockPicture(int width, int height, int rowBytes, int length) {
final pic = List.generate(length, (index) {
final r = index / rowBytes;
final c = (index % rowBytes) / 4;
final p = index & 0x03;
if (c > 20 && c < 30) {
if (r > 20 && r < 25) {
if (p == 0 || p == 3) {
return 255;
} else {
return 0;
}
}
if (r > 40 && r < 45) {
if (p == 1 || p == 3) {
return 255;
} else {
return 0;
}
}
if (r > 60 && r < 65) {
if (p == 2 || p == 3) {
return 255;
} else {
return 0;
}
}
}
return 255;
});
return Uint8List.fromList(pic);
}
Pointer<Uint8> mockPicturePtr(
int width, int height, int rowBytes, int length) {
final pic = List.generate(length, (index) {
final r = index / rowBytes;
final c = (index % rowBytes) / 4;
final p = index & 0x03;
final edgeH = (c >= 0 && c < 10) || ((c >= width - 10) && c < width);
final edgeW = (r >= 0 && r < 10) || ((r >= height - 10) && r < height);
if (edgeH || edgeW) {
if (p == 0 || p == 3) {
return 255;
} else {
return 0;
}
}
return 255;
});
final picAddr = malloc.allocate(pic.length).cast<Uint8>();
final list = picAddr.asTypedList(pic.length);
list.setRange(0, pic.length, pic);
return picAddr;
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: textureId == -1
? const Offstage()
: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
alignment: Alignment.center,
decoration: const BoxDecoration(color: Colors.blue),
child: Texture(textureId: textureId)),
),
),
Text(
"texture id: $textureId, texture memory address: ${texturePtr.toRadixString(16)}"),
TextButton.icon(
label: const Text("play with texture (method channel API)"),
icon: const Icon(Icons.play_arrow),
onPressed: () => start(0),
),
TextButton.icon(
label: const Text("play with texture (native API, faster)"),
icon: const Icon(Icons.play_arrow),
onPressed: () => start(1),
),
Text(
"Current mode: ${method == 0 ? 'Method Channel API' : 'Native API'}"),
time != 0 ? Text("FPS: ${1000000 ~/ time} fps") : const Offstage()
],
),
),
);
}
}
以上代码展示了如何使用texture_rgba_renderer
插件进行纹理渲染,包括创建纹理、传输RGBA数据以及关闭纹理等操作。希望这些信息对你有所帮助!
更多关于Flutter纹理渲染插件texture_rgba_renderer的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter纹理渲染插件texture_rgba_renderer的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何使用Flutter的texture_rgba_renderer
插件进行纹理渲染的示例代码。请注意,texture_rgba_renderer
可能是一个假设的插件名称,因为Flutter官方并没有直接提供名为texture_rgba_renderer
的插件。然而,我会基于Flutter中纹理渲染的一般概念,展示如何使用Texture
widget来渲染自定义纹理数据。
在实际应用中,你可能需要找到一个合适的插件或自行实现纹理数据的传递和渲染。这里我们假设你已经有一个插件或方法可以将RGBA数据传递给Flutter进行渲染。
1. 添加依赖
首先,你需要在pubspec.yaml
文件中添加你的纹理渲染插件(如果它是一个公开的Flutter插件)。由于texture_rgba_renderer
是假设的,这里我们用一个通用的插件依赖作为示例:
dependencies:
flutter:
sdk: flutter
# 假设的纹理渲染插件
custom_texture_renderer: ^0.1.0 # 请替换为实际的插件名和版本号
2. 创建Flutter项目结构
确保你的Flutter项目结构如下:
your_flutter_project/
├── android/
├── ios/
├── lib/
│ ├── main.dart
│ └── ... (其他文件)
├── pubspec.yaml
└── ... (其他文件)
3. 使用Texture Widget渲染纹理
下面是一个示例代码,展示了如何在Flutter中使用Texture
widget来渲染自定义的RGBA纹理数据。请注意,这里的_uploadTexture
函数是一个假设的函数,你需要根据你的插件或自定义实现来替换它。
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'dart:typed_data';
import 'package:custom_texture_renderer/custom_texture_renderer.dart'; // 假设的插件导入
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Texture RGBA Renderer Example'),
),
body: Center(
child: CustomTextureWidget(),
),
),
);
}
}
class CustomTextureWidget extends StatefulWidget {
@override
_CustomTextureWidgetState createState() => _CustomTextureWidgetState();
}
class _CustomTextureWidgetState extends State<CustomTextureWidget> {
late int _textureId;
@override
void initState() {
super.initState();
_uploadTexture();
}
@override
Widget build(BuildContext context) {
return Texture(
id: _textureId,
);
}
Future<void> _uploadTexture() async {
// 假设的RGBA数据,这里使用一个简单的红色图像作为示例
final Uint8List rgbaData = Uint8List.fromList(
List.generate(64 * 64 * 4, (index) => index % 4 == 3 ? 255 : (index % 256)),
);
// 创建一个纹理并上传数据(这里是一个假设的函数调用)
// 你需要根据你的插件或自定义实现来替换这部分代码
final ui.Image? image = await CustomTextureRenderer.uploadTexture(rgbaData, 64, 64);
if (image != null && mounted) {
// 获取纹理ID并更新状态
_textureId = image.textureId;
setState(() {});
}
}
}
// 假设的插件实现(你需要根据你的实际情况来实现这个部分)
class CustomTextureRenderer {
static Future<ui.Image?> uploadTexture(Uint8List rgbaData, int width, int height) async {
// 这里应该是与原生平台交互的代码,用于上传纹理数据
// 由于这是一个示例,我们直接返回一个null
return null; // 请替换为实际的纹理上传逻辑
}
}
注意事项
-
插件实现:上面的
CustomTextureRenderer.uploadTexture
函数是一个占位符,你需要根据你的插件或自定义实现来替换它。通常,这需要与原生平台(Android和iOS)进行交互,以将纹理数据上传到GPU。 -
线程安全:确保在UI线程上调用Flutter的
setState
方法,以避免潜在的线程安全问题。 -
性能考虑:纹理渲染可能会涉及大量的数据传输和GPU操作,因此请注意性能优化,特别是在处理高分辨率纹理时。
-
错误处理:添加适当的错误处理逻辑,以处理纹理上传失败或渲染问题。
希望这个示例能帮助你理解如何在Flutter中使用纹理渲染插件进行自定义纹理的渲染。如果你有一个具体的插件或需要更详细的实现指导,请提供更多信息。