网易首页 > 网易号 > 正文 申请入驻

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

0
分享至

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
             
  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;   @override   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(
                
 [       -0.5, -0.5,  //       0, 0.5,  //       0.5, -0.5,  //     ]));     final color = transients.emplace(float32(
                  
 [0, 1, 0, 1]));  // rgba     final 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());   }   @override   bool shouldRepaint(covariant CustomPainter oldDelegate) {     return true;   } } class TrianglePage extends StatefulWidget {   const TrianglePage({super.key});   @override   State  createState() => _TrianglePageState(); } class _TrianglePageState extends State
                    
  {   Ticker? tick;    double time =  0;    double deltaSeconds =  0;    double seedX =  -0.512511498387847167;    double seedY =  0.521295573094847167;    @override    void initState() {     tick = Ticker(       (elapsed) {         setState(() {            double previousTime = time;           time = elapsed.inMilliseconds /  1000.0;           deltaSeconds = previousTime >  0 ? time - previousTime :  0;         });       },     );     tick!.start();      super.initState();   }    @override   Widget build(BuildContext context) {      return Column(       children:  [         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});

  @override
  MyAppState createState() => MyAppState();
}

class MyAppState extends State
             
  with SingleTickerProviderStateMixin {   double elapsedSeconds = 0;   Scene scene = Scene();   @override   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();   }   @override   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;   @override   void paint(Canvas canvas, Size size) {     scene.render(camera, canvas, viewport: Offset.zero & size);   }   @override   bool shouldRepaint(covariant CustomPainter oldDelegate) => true; }
      

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

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


特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
刘国梁重返国乒引热议?领导层或将调整,答案其实早就有了

刘国梁重返国乒引热议?领导层或将调整,答案其实早就有了

锐评利物浦
2026-08-19 15:34:14
事态升级!郁廷栋也被抓了,赵海峰给地产的锅掀了,两大集团出手

事态升级!郁廷栋也被抓了,赵海峰给地产的锅掀了,两大集团出手

社会日日鲜
2026-08-19 09:10:23
别让她跑了?章子怡套现3亿登上热搜不到24小时,恶心的一幕出现

别让她跑了?章子怡套现3亿登上热搜不到24小时,恶心的一幕出现

叨唠
2026-08-19 04:44:15
真狠!连跌4年从12跌到1.9又横盘8年,张素芬却从920万加到2200万

真狠!连跌4年从12跌到1.9又横盘8年,张素芬却从920万加到2200万

长风价值掘金
2026-08-19 20:43:42
马来西亚总理答记者问:台湾属于中国毋庸置疑,你的提问是在纵容分裂一个合法主权独立国家的企图,我对此绝不能接受

马来西亚总理答记者问:台湾属于中国毋庸置疑,你的提问是在纵容分裂一个合法主权独立国家的企图,我对此绝不能接受

政知新媒体
2026-08-19 08:42:07
金·卡戴珊度假穿Gucci比基尼仅几块布售价1300美元

金·卡戴珊度假穿Gucci比基尼仅几块布售价1300美元

影视情报室
2026-08-20 02:02:17
伊朗副总统吐槽中国车又烂又贵,推荐日本车,被美石油封锁致油荒

伊朗副总统吐槽中国车又烂又贵,推荐日本车,被美石油封锁致油荒

流史岁月
2026-08-19 15:00:03
办升学宴5死17伤,村民:他们家是低保户,为庆祝孩子考上大学,喜事变丧事

办升学宴5死17伤,村民:他们家是低保户,为庆祝孩子考上大学,喜事变丧事

汉史趣闻
2026-08-19 14:39:00
办升学宴女儿墙倒塌致5死17伤,主家是低保户条件不好本不想办,邻居、村民劝他,好不容易出了一个学习好的

办升学宴女儿墙倒塌致5死17伤,主家是低保户条件不好本不想办,邻居、村民劝他,好不容易出了一个学习好的

极目新闻
2026-08-19 17:17:06
丑闻!接受韩国足协4300元性款待,中国足协3人身份曝光

丑闻!接受韩国足协4300元性款待,中国足协3人身份曝光

小七说篮球
2026-08-19 15:17:52
杭州酒局事件又有一名参与的关键人物被免职,系建发房产杭州事业部总经理马政纲;受侵害女性为厦门国企建发集团旗下房产板块女员工

杭州酒局事件又有一名参与的关键人物被免职,系建发房产杭州事业部总经理马政纲;受侵害女性为厦门国企建发集团旗下房产板块女员工

大风新闻
2026-08-19 12:15:04
2米14,2米15,2米29,让美国男篮头疼的球队诞生了

2米14,2米15,2米29,让美国男篮头疼的球队诞生了

篮球大视野
2026-08-19 16:55:20
最新!7月年青人失业率升至17.9%!较6月上涨3个百分点。

最新!7月年青人失业率升至17.9%!较6月上涨3个百分点。

叶初七
2026-08-19 17:50:26
救市了,终于干美债

救市了,终于干美债

贩财局
2026-08-19 22:13:58
特种部队俘获30名俄军!俄媒大肆报道,暗示乌军第3军团司令已死

特种部队俘获30名俄军!俄媒大肆报道,暗示乌军第3军团司令已死

鹰眼Defence
2026-08-19 17:41:00
杨乐乐11岁儿子现身,眼神举止异常引热议,网友建议汪涵练小号

杨乐乐11岁儿子现身,眼神举止异常引热议,网友建议汪涵练小号

娱乐小丸子
2026-08-19 11:35:36
知情人曝赵海峰酒局上伤害女性细节!警方介入,一关键人物被漏掉

知情人曝赵海峰酒局上伤害女性细节!警方介入,一关键人物被漏掉

大眼妹妹
2026-08-19 20:54:48
两个小女孩买三张硬座票,无座乘客想坐,商量无果找来列车员调解,列车员:座位是她们的,跟我说没用

两个小女孩买三张硬座票,无座乘客想坐,商量无果找来列车员调解,列车员:座位是她们的,跟我说没用

潇湘晨报
2026-08-19 20:38:28
本硕博全撤!北京户口注销,国企工作丢了,哥哥:被弟弟毁了一生

本硕博全撤!北京户口注销,国企工作丢了,哥哥:被弟弟毁了一生

兵卒史
2026-08-19 10:44:36
美国能源部副部长:现在委内瑞拉一半的石油产量,都被美国消化了

美国能源部副部长:现在委内瑞拉一半的石油产量,都被美国消化了

史樍
2026-08-19 16:46:59
2026-08-20 05:52:49
君伟说
君伟说
分享职场故事
482文章数 48关注度
往期回顾 全部

科技要闻

宇树的悬念还在后面

头条要闻

特朗普:预计今年晚些时候会见金正恩 可能在11月

头条要闻

特朗普:预计今年晚些时候会见金正恩 可能在11月

体育要闻

拥有“儿皇梦”的罗德里,为何选择巴萨?

娱乐要闻

章子怡财路遭到质疑,套现3亿冲上热搜

财经要闻

内部反腐,让大疆错过宇树250亿收益?

汽车要闻

小米澎程N70体验 后排空间夸张亦可旋转对坐

态度原创

教育
家居
房产
艺术
时尚

教育要闻

新初一40个班,全校110个班!南京多所“巨无霸”初中,阵仗有点大

家居要闻

2026建博会(广州) 公装联探展交流活动

房产要闻

涉及3800亩!海口秀英港又有大动作!

艺术要闻

他把中国红画到了极致!这红衣女子,美得让人不敢呼吸!

内娱还在三件套的时候,韩综已经卷到“扛大炮”了

无障碍浏览 进入关怀版