Flutter GPU 是什么?为什么它对 Flutter 有跨时代的意义?

Flutter 3.24 版本引入了 Flutter GPU 概念的新底层图形 API flutter_gpu ,还有 flutter_scene 的 3D 渲染支持库,它们目前都是预览阶段,只能在 main channel 上体验,并且依赖 Impeller 的实现。

Flutter GPU 是 Flutter 内置的底层图形 API,它可以通过编写 Dart 代码和 GLSL 着色器在 Flutter 中构建和集成自定义渲染器,而无需 Native 平台代码。

目前 Flutter GPU 处于早期预览阶段并只提供基本的光栅化 API,但随着 API 接近稳定,会继续添加和完善更多功能。

详细说,Flutter GPU 是 Impeller 对于 HAL 的一层很轻的包装,并搭配了关于着色器和管道编排的自动化能力,也通过 Flutter GPU 就可以使用 Dart 直接构建自定义渲染器。

Flutter GPU 和 Impeller 一样,它的着色器也是使用 impellerc 提前编译,所以 Flutter GPU 也只支持 Impeller 的平台上可用。

Impeller 的 HAL 和 Flutter GPU 都没打算成为类似 WebGPU 这样的正式标准,相反,Flutter GPU 主要是由 Flutter 社区开发和发展,专职为了 Flutter 服务,所以不需要考虑「公有化」的兼容问题。

在 Flutter GPU 上,可直接从 Dart 与 Impeller 的 HAL 对话,甚至 Impeller Scene API(3D)也将作为重写的一部分出现。

说人话就是,可以用 Dart 通过 Flutter GPU 直接构建自定义渲染效果,未来直接支持 3D

可能有的人对于 Impeller 的整体结构和 HAL 还很模式无法理解,那么这里我们简单过一下:

  • 在 Framework 上层,我们知道 Widget -> Element -> RenderObject -> Layer 这样的过程,而后其实流程就来到了 Flutter 自定义抽象的 DisplayList

  • DisplayList 帮助 Flutter 在 Engine 做了接耦,从而让 Flutter 可以在 skia 和 Impeller 之间进行的切换

  • 之后 Impeller 架构的顶层是 Aiks,这一层主要作为绘图操作的高级接口,它接受来自 Flutter 框架的命令,例如绘制路径或图像,并将这些命令转换为一组更精细的 “Entities”,然后转给下一层。

  • Entities Framework,它是 Impeller 架构的核心组件,当 Aiks 处理完命令时生成 Entities 后,每一个 Entity 其实就是渲染指令的独立单元,其中包含绘制特定元素的所有必要信息(编码位置、旋转、缩放、content object),此时还不能直接作用于 GPU

  • HAL(Hardware Abstraction Layer) 则为底层图形硬件提供了统一的接口,抽象了不同图形 API 的细节,该层确保了 Impeller 的跨平台能力,它将高级渲染命令转换为低级 GPU 指令,充当 Impeller 渲染逻辑和设备图形硬件之间的桥梁。

所以 HAL 它包装了各种图形 API,以提供通用的设备作业调度接口、一致的资源管理语义和统一的着色器创作体验,而对于 Impeller , Entities (2D renderer) 和 Scene (3D renderer) 都是直接通过 HAL 对接,甚至可以认为,Impeller 的 HAL 抽象并统一了 Metal 和 Vulkan 的常见用法和相似结构。

Unity 现在也有在 C# 直接向用户公开其 HAL 版本,称为 “Scriptable Render Pipeline” ,并提供了两个基于该 API 构建的默认渲染器 “Universal RP” / “High Definition RP” 用于服务不同的场景,所以 Unity 开发可以从使用这些渲染器去进行修改或扩展一些特定渲染需求。

而在 Flutter 的设计上,Flutter GPU 会作为 Flutter SDK 的一部分,并以 flutter_gpu 的 Dart 包的形式提供使用。

当然,Flutter GPU 由 Impeller 支持,但重要的是要记住它不是 Impeller ,Impeller 的 HAL 是私有内部代码与 Flutter GPU 的要求非常不同, Impeller 的私有 HAL 和 Flutter GPU 的公共 API 设计之间是存在一定差异化实现,而前面的流程,如 Scene (3D renderer) ,也可以被调整为基于 Flutter GPU 的全新模式实现。

而通过 Flutter GPU,如曾经的 Scene (3D renderer) 支持,也可以被调整为基于 Flutter GPU 的全新模式实现,因为 Flutter GPU 的 API 允许完全控制渲染通道附件、顶点阶段和数据上传到 GPU。这种灵活性对于创建复杂的渲染解决方案(从 2D 角色动画到复杂的 3D 场景)至关重要。

Flutter GPU 支持的自定义 2D 渲染器的一个很好的用例:依赖于骨骼网格变形的 2D 角色动画格式。

Spine 2D 就是一个很好的例子,骨骼网格解决方案通常具有动画剪辑,可以按层次结构操纵骨骼的平移、旋转和缩放属性,并且每个顶点都有几个相关的“bone weights”,这些权重决定了哪些骨骼应该影响顶点以及影响程度如何。

使用像 drawVertices 这样的 Canvas 解决方案,需要在 CPU 上对每个顶点应用骨骼权重变换,而 使用 Flutter GPU,骨骼变换可以用统一数组或纹理采样器的形式发送到顶点着色器,从而允许根据骨架状态和每个顶点的 “bone weights” 在 GPU 上并行计算每个顶点的最终位置。

使用 Flutter GPU

首先你需要在最新的 main channel 分支,然后通过 flutter pub add flutter_gpu --sdk=flutter 将 flutter_gpu SDK 包添加到你的 pubspec。

为了使用 Flutter GPU 渲染内容,你会需要编写一些 GLSL 着色器,Flutter GPU 的着色器与 Flutter 的 fragment shader 功能所使用的着色器具有不同的语义,特别是在统一绑定方面,还需要定义一个顶点(vertex)着色器来与 fragment shader 一起使用,然后配合 gpu.ShaderLibrary 等 API 就可以直接实现 Flutter GPU 渲染。

当然,本篇不会介绍详细的 API 使用 ,这里只是单纯做一个简单的介绍,目前 Flutter GPU 进行光栅化的简单流程如下:

  • 获取 GPUContext。

  • GpuContext.createCommandBuffer 创建一个 CommandBuffer

  • CommandBuffer.createRenderPass 创建一个 RenderPass

  • 使用各种方法设置状态/管道并绑定资源 RenderPass

  • 附加绘图命令 RenderPass.draw

  • CommandBuffer 使用 CommandBuffer.submit (异步)提交绘制,所有 RenderPass 会按照其创建顺序进行编码

·····
///导入 flutter_gpu
import 'package:flutter_gpu/gpu.dart' as gpu;ByteData float32(List<double> values) {return Float32List.fromList(values).buffer.asByteData();
}ByteData float32Mat(Matrix4 matrix) {return Float32List.fromList(matrix.storage).buffer.asByteData();
}class TrianglePainter extends CustomPainter {TrianglePainter(this.time, this.seedX, this.seedY);double time;double seedX;double seedY;void paint(Canvas canvas, Size size) {/// Allocate a new renderable texture.final gpu.Texture? renderTexture = gpu.gpuContext.createTexture(gpu.StorageMode.devicePrivate, 300, 300,enableRenderTargetUsage: true,enableShaderReadUsage: true,coordinateSystem: gpu.TextureCoordinateSystem.renderToTexture);if (renderTexture == null) {return;}final gpu.Texture? depthTexture = gpu.gpuContext.createTexture(gpu.StorageMode.deviceTransient, 300, 300,format: gpu.gpuContext.defaultDepthStencilFormat,enableRenderTargetUsage: true,coordinateSystem: gpu.TextureCoordinateSystem.renderToTexture);if (depthTexture == null) {return;}/// Create the command buffer. This will be used to submit all encoded/// commands at the end.final commandBuffer = gpu.gpuContext.createCommandBuffer();/// Define a render target. This is just a collection of attachments that a/// RenderPass will write to.final renderTarget = gpu.RenderTarget.singleColor(gpu.ColorAttachment(texture: renderTexture),depthStencilAttachment: gpu.DepthStencilAttachment(texture: depthTexture),);/// Add a render pass encoder to the command buffer so that we can start/// encoding commands.final encoder = commandBuffer.createRenderPass(renderTarget);/// Load a shader bundle asset.final library = gpu.ShaderLibrary.fromAsset('assets/TestLibrary.shaderbundle')!;/// Create a RenderPipeline using shaders from the asset.final vertex = library['UnlitVertex']!;final fragment = library['UnlitFragment']!;final pipeline = gpu.gpuContext.createRenderPipeline(vertex, fragment);encoder.bindPipeline(pipeline);/// (Optional) Configure blending for the first color attachment.encoder.setColorBlendEnable(true);encoder.setColorBlendEquation(gpu.ColorBlendEquation(colorBlendOperation: gpu.BlendOperation.add,sourceColorBlendFactor: gpu.BlendFactor.one,destinationColorBlendFactor: gpu.BlendFactor.oneMinusSourceAlpha,alphaBlendOperation: gpu.BlendOperation.add,sourceAlphaBlendFactor: gpu.BlendFactor.one,destinationAlphaBlendFactor: gpu.BlendFactor.oneMinusSourceAlpha));/// Append quick geometry and uniforms to a host buffer that will be/// automatically uploaded to the GPU later on.final transients = gpu.HostBuffer();final vertices = transients.emplace(float32(<double>[-0.5, -0.5, //0, 0.5, //0.5, -0.5, //]));final color = transients.emplace(float32(<double>[0, 1, 0, 1])); // rgbafinal mvp = transients.emplace(float32Mat(Matrix4(1, 0, 0, 0, //0, 1, 0, 0, //0, 0, 1, 0, //0, 0, 0.5, 1, //) *Matrix4.rotationX(time) *Matrix4.rotationY(time * seedX) *Matrix4.rotationZ(time * seedY)));/// Bind the vertex data. In this case, we won't bother binding an index/// buffer.encoder.bindVertexBuffer(vertices, 3);/// Bind the host buffer data we just created to the vertex shader's uniform/// slots. Although the locations are specified in the shader and are/// predictable, we can optionally fetch the uniform slots by name for/// convenience.final mvpSlot = pipeline.vertexShader.getUniformSlot('mvp')!;final colorSlot = pipeline.vertexShader.getUniformSlot('color')!;encoder.bindUniform(mvpSlot, mvp);encoder.bindUniform(colorSlot, color);/// And finally, we append a draw call.encoder.draw();/// Submit all of the previously encoded passes. Passes are encoded in the/// same order they were created in.commandBuffer.submit();/// Wrap the Flutter GPU texture as a ui.Image and draw it like normal!final image = renderTexture.asImage();canvas.drawImage(image, Offset(-renderTexture.width / 2, 0), Paint());}bool shouldRepaint(covariant CustomPainter oldDelegate) {return true;}
}class TrianglePage extends StatefulWidget {const TrianglePage({super.key});State<TrianglePage> createState() => _TrianglePageState();
}class _TrianglePageState extends State<TrianglePage> {Ticker? tick;double time = 0;double deltaSeconds = 0;double seedX = -0.512511498387847167;double seedY = 0.521295573094847167;void initState() {tick = Ticker((elapsed) {setState(() {double previousTime = time;time = elapsed.inMilliseconds / 1000.0;deltaSeconds = previousTime > 0 ? time - previousTime : 0;});},);tick!.start();super.initState();}Widget build(BuildContext context) {return Column(children: <Widget>[Slider(value: seedX,max: 1,min: -1,onChanged: (value) => {setState(() => seedX = value)}),Slider(value: seedY,max: 1,min: -1,onChanged: (value) => {setState(() => seedY = value)}),CustomPaint(painter: TrianglePainter(time, seedX, seedY),),],);}
}

GpuContext 是分配所有 GPU 资源并调度 GPU 的存在,而 GpuContext 仅有启用 Impeller 时才能访问。

DeviceBuffer 和 Texture 就是 GPU 拥有的资源,可以通过 GPUContext 创建获取,如 createDeviceBuffercreateTexture

  • DeviceBuffer 简单理解就是在 GPU 上分配的简单字节串,主要用于存储几何数据(索引和顶点属性)以及统一数据
  • Texture 是一个特殊的设备缓冲区

CommandBuffer 用于对 GPU 上的异步执行进行排队和调度工作。

RenderPass 是 GPU 上渲染工作的顶层单元。

RenderPipeline 提供增量更改绘制所有状态以及附加绘制调用的方法如 RenderPass.draw()

可以想象,通过 Flutter GPU,Flutter 开发者可以更简单地对 GPU 进行更精细的控制,通过与 HAL 直接通信,创建 GPU 资源并记录 GPU 命令,从而最大限度的发挥 Flutter 的渲染能力。

另外,对于 3D 支持的 Flutter Scene , 可以通过使用 native-assets 来设置 Flutter Scene 的 3D 模型自动导入,通过导入编译模型 .model 之后,就可以通过 Dart 实现一些 3D 的渲染。

import 'dart:math';import 'package:flutter/material.dart';
import 'package:flutter_scene/camera.dart';
import 'package:flutter_scene/node.dart';
import 'package:flutter_scene/scene.dart';
import 'package:vector_math/vector_math.dart';void main() {runApp(const MyApp());
}class MyApp extends StatefulWidget {const MyApp({super.key});MyAppState createState() => MyAppState();
}class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {double elapsedSeconds = 0;Scene scene = Scene();void initState() {createTicker((elapsed) {setState(() {elapsedSeconds = elapsed.inMilliseconds.toDouble() / 1000;});}).start();Node.fromAsset('build/models/DamagedHelmet.model').then((model) {model.name = 'Helmet';scene.add(model);});super.initState();}Widget build(BuildContext context) {final painter = ScenePainter(scene: scene,camera: PerspectiveCamera(position: Vector3(sin(elapsedSeconds) * 3, 2, cos(elapsedSeconds) * 3),target: Vector3(0, 0, 0),),);return MaterialApp(title: 'My 3D app',home: CustomPaint(painter: painter),);}
}class ScenePainter extends CustomPainter {ScenePainter({required this.scene, required this.camera});Scene scene;Camera camera;void paint(Canvas canvas, Size size) {scene.render(camera, canvas, viewport: Offset.zero & size);}bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

目前 Flutter GPU 和 Flutter Scene 的支持还十分有限,但是借助 Impeller ,Flutter 开启了新的可能,可以说是,Flutter 团队完全掌控了渲染堆栈,在除了自定义更丰富的 2D 场景之外,也为 Flutter 开启了 3D 游戏的可能,2023 年 Flutter Forward 大会的承诺,目前正在被落地实现

详细 API 使用例子可以参看 :https://medium.com/flutter/getting-started-with-flutter-gpu-f33d497b7c11

如果你对 Flutter Impeller 和其着色器感兴趣,也可以看:

  • 《快速了解 Flutter 的渲染引擎的优势》

  • 《Flutter 里的着色器预热原理》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/394167.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024最新Mysql锁机制与优化实践以及MVCC底层原理剖析

锁机制详解 锁是计算机协调多个进程或线程并发访问某一资源的机制。 在数据库中&#xff0c;除了传统的计算资源&#xff08;如CPU、RAM、I/O等&#xff09;的争用以外&#xff0c;数据也是一种供需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解…

3Done学习笔记

一、基本操作 1、旋转视角 使用左下角立方体选择&#xff1b; 右键可以拖动视角&#xff1b; 中间滑轮按住拖动整个舞台界面。 2、平移和旋转 右键选择移动&#xff0c;有两种方式。 第一种选择起始点&#xff0c;按照起始点位置移动到终止点。第二种直接根据轮盘旋转或…

【docker】docker容器部署常用服务

1、容器部署nginx&#xff0c;并且新增一个页面 docker run -d -p 81:80 --name nginx2 nginx docker exec -it nginx2 /bin/bashcd /usr/share/nginx/html/ echo "hello world">>hello.html2、容器部署redis&#xff0c;成功部署后向redis中添加一条数据 do…

【C/C++笔记】:易错难点3 (二叉树)

选择题 &#x1f308;eg1 一棵有15个节点的完全二叉树和一棵同样有15个节点的普通二叉树&#xff0c;叶子节点的个数最多会差多少个&#xff08;&#xff09;&#xff1f; 正确答案&#xff1a; C A. 3 B. 5 C. 7 D. 9 解析&#xff1a;普通二叉树的叶子节…

WPF学习笔记

WPF WPF&#xff08;Windows Presentation Foundation&#xff0c;Windows呈现基础&#xff09;是微软推出的基于Windows 的用户界面框架&#xff0c;属于.NET Framework 3.0的一部分。它提供了统一的编程模型、语言和框架&#xff0c;真正做到了分离界面设计人员与开发人员的…

C语言----计算开机时间

计算开机时间 实例说明 编程实现计算开机时间&#xff0c;要求在每次开始计算开机时间时都能接着上次记录的结果向下记录。 实现过程&#xff1a; 1. 在TC中创建一个C文件。 2. 引用头文件&#xff0c;代码如下: #include <stdio.h> 3. 定义结构体time&#xff0c;用来…

如何在Chrome、Edge、360、Firefox等浏览器查看网站SSL证书信息?

在如今的网络环境中&#xff0c;保障网络安全、数据安全尤其重要&#xff0c;市面上大部分网站都部署了SSL证书以实现HTTPS加密保护数据传输安全以及验证网站身份&#xff0c;确保网站安全可信。那么如何查看网站的SSL证书信息&#xff1f;接下来&#xff0c;我们将详细介绍如何…

【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式

文章目录 webView使用步骤示例 HttpURLConnection使用步骤示例GET请求POST请求 okHttp使用步骤1. 添加依赖2. 创建OkHttpClient实例3. 创建Request对象构建请求4. 发送请求5. 获取响应 Pull解析方式1. 准备XML数据2. 创建数据类3. 使用Pull解析器解析XML webView WebView 是 An…

【Nacos无压力源码领读】(三) Nacos 配置中心与热更新原理详解 敢说全网最细

本文将从 Nacos 配置中心的基本使用入手, 详细介绍 Nacos 客户端发布配置, 拉取配置, 订阅配置的过程以及服务器对应的处理过程; 配置订阅以及热更新原理相关的部分, 我看了主流的博客网站, 绝对没有比这更详细的讲解; 如果在阅读过程中对文中提到的 SpringBoot 启动过程以及…

Milvus与Zilliz Cloud:向量数据库高可用性的双重飞跃

向量数据库高可用性的重要性及其在现代数据分析中的关键作用 在数据爆炸式增长的今天,企业对于高效、准确地处理和分析大规模数据集的需求日益迫切。尤其是在人工智能、机器学习、图像识别、自然语言处理等领域,向量数据库因其对高维数据的高效存储与检索能力,成为了不可或…

Elasticsearch未授权访问漏洞

步骤一:使用以下Fofa语法进行Elasticsearch产品搜索.. fofa语法"Elasticsearch" && port"9200" 步骤二:存在未授权访问则直接进入到信息页面...不需要输入用户密码登陆. http://localhost:9200/_plugin/head/web管理界面 http://localhost:9200/…

【JavaEE】线程池

目录 前言 什么是线程池 线程池的优点 ThreadPollExecutor中的构造方法 corePoolSize && maximumPoolSize keepAliveTime && unit workQueue threadFactory 如何在java中使用线程池 1.创建线程池对象 2.调用submit添加任务 3.调用shutdown关闭线程池…

【Python】requests的response.text 和 urllib.request 的 response.read()的区别

刚写代码的时候&#xff0c;我经常会把requests 和 urllib下的request 包搞混&#xff0c;这两个请求响应的方法看起来很相似&#xff0c;但是写获取的方法是不一样的。 前者requests 是用response.text 来获取源码&#xff0c;而 urllib.request是用 response.read() 来获取h…

数学建模--智能算法之免疫算法

目录 基本原理 应用实例 代码示例 总结 免疫算法在免疫系统研究中的应用和进展是什么&#xff1f; 如何量化评估免疫算法在不同优化问题中的性能和效率&#xff1f; 免疫算法与其他智能优化算法&#xff08;如遗传算法、粒子群优化&#xff09;相比有哪些独特优势和局限性…

【C++】C++11的新特性 — 线程库 ,原子操作 , 条件变量

勇敢就是接受发生在你身上的事&#xff0c;并把它尽力做到最好。 -- 约翰・欧文 -- C11的新特性 1 线程1.1 线程概念1.2 C中的线程1.3 线程并行1.4 锁 2 原子操作3 条件变量Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&#xff01;下…

编译和汇编的区别

一、编译 编译是将高级语言&#xff08;如C、C、Java等&#xff09;编写的源代码转换成计算机可以直接执行的低级语言&#xff08;通常是机器语言或汇编语言&#xff09;的过程 编译 —— 将人类可读的源代码转换为计算机可执行的指令集 编译过程 通常包括词法分析、语法分…

正点原子imx6ull-mini-Linux驱动之Linux 网络驱动实验

网络驱动是 linux 里面驱动三巨头之一&#xff0c;linux 下的网络功能非常强大&#xff0c;嵌入式 linux 中也常 常用到网络功能。前面我们已经讲过了字符设备驱动和块设备驱动&#xff0c;本章我们就来学习一下 linux 里面的网络设备驱动。 1&#xff1a;嵌入式网络简介 1.1…

如何给源代码加密?这款加密软件教给你操作步骤

给源代码加密是保护软件知识产权和商业机密的重要手段。以这款加密软件安企神为例&#xff0c;给源代码加密的过程可以概述为以下几个方面。 一、安企神软件概述 安企神是一款功能强大的企业安全加密软件&#xff0c;它提供了全面的源代码防泄漏解决方案。该软件通过透明文件加…

扩散模型系列笔记(一)——DDPM

直观理解 扩散模型分为前向过程&#xff08;扩散过程&#xff0c;Data → \to →Noise&#xff09;和后向过程&#xff08;生成过程或逆扩散过程&#xff0c;Noise → \to →Data&#xff09;。在前向过程中&#xff0c;对于每一个观测样本&#xff0c;不断向样本中添加少量噪…

Leetcode每日刷题之字符串中的第一个唯一字符(C++)

在学习的过程中对代码的熟练运用至关重要&#xff0c;练习解决实际问题就可以很好的锻炼自己的编程能力&#xff0c;接下来让我们练习这道 387.字符串中的第一个唯一字符 思路解析 根据题意我们可以知道这个字符串只有小写字母&#xff0c;并且可能包含多个唯一字符&#xff0…