Flutter纹理渲染插件texture_rgba_renderer的使用

发布于 1周前 作者 itying888 来自 Flutter

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

1 回复

更多关于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; // 请替换为实际的纹理上传逻辑
  }
}

注意事项

  1. 插件实现:上面的CustomTextureRenderer.uploadTexture函数是一个占位符,你需要根据你的插件或自定义实现来替换它。通常,这需要与原生平台(Android和iOS)进行交互,以将纹理数据上传到GPU。

  2. 线程安全:确保在UI线程上调用Flutter的setState方法,以避免潜在的线程安全问题。

  3. 性能考虑:纹理渲染可能会涉及大量的数据传输和GPU操作,因此请注意性能优化,特别是在处理高分辨率纹理时。

  4. 错误处理:添加适当的错误处理逻辑,以处理纹理上传失败或渲染问题。

希望这个示例能帮助你理解如何在Flutter中使用纹理渲染插件进行自定义纹理的渲染。如果你有一个具体的插件或需要更详细的实现指导,请提供更多信息。

回到顶部