当前位置: 首页 > news >正文

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 里的着色器预热原理》

相关文章:

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

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

第6章>>实验7:PS(ARM)端Linux RT与PL端FPGA之间(通过Memory存储器进行通信和交互)《LabVIEW ZYNQ FPGA宝典》

1、实验内容 上一节实验里面介绍的Reg寄存器通道比较适合在PS端和PL端之间传递标量数据&#xff0c;也就是单个元素&#xff0c;如果要传递多个元素的数组或者连续数据流的话&#xff0c;Reg寄存器通道就不是很合适了。 本节实验我们向大家讲解如何借助Memory存储器通道在PS&am…...

通用前端的学习

通用前端的概念 通用前端的概念是我自创的&#xff0c;也是我多年开发全栈时的个人理解&#xff0c;结合自己对各种语言的比较&#xff0c;发现前端都具有几个特征&#xff0c;而这几个特征&#xff0c;很多人只能用具体的表象来描述&#xff0c;比如用安卓方式来说明&#xf…...

git本地仓库关联多个远程仓库时git pull失败问题

目录 问题描述 原因 解决办法 1.多个远程仓库需有继承关系 2.一句命令实现创建本地分支且与远程分支关联 问题描述 今天操作本地仓库时&#xff0c;关联了两个远程仓库&#xff0c;欲在本地仓库创建一个分支&#xff0c;与第二个远程仓库的某个分支关联&#xff0c;然后将…...

人工智能(AI)、Web 3.0和元宇宙三者联系、应用及未来发展趋势的详细分析

人工智能&#xff08;AI&#xff09;、Web 3.0和元宇宙作为当前科技领域的热门话题&#xff0c;它们之间存在着紧密的联系&#xff0c;并在各自领域内展现出广泛的应用和未来的发展趋势。以下是对这三者联系、应用及未来发展趋势的详细分析&#xff1a; 一、人工智能&#xff…...

【IEEE出版 | 高校主办】第三届人工智能、物联网和云计算技术国际会议(AIoTC 2024)

第三届人工智能、物联网和云计算技术国际会议&#xff08;AIoTC 2024&#xff09; 2024 3rd International Conference on Artificial Intelligence, Internet of Things and Cloud Computing Technology 2024年9月13-15日 | 中国武汉 重要信息 大会官网&#xff1a;www.ic…...

PTA 7-4 BCD解密

7-4 BCD解密&#xff08;10分&#xff09; BCD数是用一个字节来表达两位十进制的数&#xff0c;每四个比特表示一位。所以如果一个BCD数的十六进制是0x12&#xff0c;它表达的就是十进制的12。但是小明没学过BCD&#xff0c;把所有的BCD数都当作二进制数转换成十进制输出了。于…...

计算机网络中拥塞控制的门限值怎么设置

拥塞避免的门限值设置主要涉及到加权随机早期检测&#xff08;‌WRED&#xff09;‌技术&#xff0c;‌这是一种拥塞避免机制&#xff0c;‌通过为每个队列设定一对低门限和高门限值来实现。‌具体来说&#xff0c;‌当队列长度小于低门限时&#xff0c;‌不丢弃报文&#xff0…...

解锁肥胖焦虑的枷锁:拥抱自我,健康前行

在这个颜值经济盛行、信息爆炸的时代&#xff0c;肥胖似乎成了许多人心中难以言说的痛。社交媒体上满屏的“A4腰”、“锁骨养鱼”&#xff0c;无形中给大众套上了一层名为“肥胖焦虑”的沉重枷锁。但请相信&#xff0c;真正的美丽与幸福&#xff0c;从不以体重秤上的数字为衡量…...

WPF学习(7)- Control基类+ContentControl类(内容控件)+ButtonBase基类

前面给大家介绍完了WPF所有的布局控件&#xff0c;属性以及使用案例&#xff0c;从这里咱们就开始学下内容控件。 Control基类 Control是许多控件的基类。比如最常见的按钮&#xff08;Button&#xff09;、单选(RadioButton)、复选&#xff08;CheckBox&#xff09;、文本框…...

moka实习生一面0607

java支持哪些数据类型 byte、short、int、long、boolean、char、float、double int取值范围- 2^31~2^31-1 什么是包装类 基础数据类型相应的对象&#xff0c;用于需要对象的地方使用这些基本数据类型。 Integer inull;int ji;会怎样&#xff0c;会报什么错误 会报java.lang.Nul…...

centos开启samba服务

centos开启samba服务 一、安装二、配置 一、安装 1.检查是否已有samba组件&#xff0c;如已经安装则会显示对应信息 rpm -qa|grep samba2.执行安装命令 yum -y install samba二、配置 1.创建samba用户 pdbedit -a centos_smb11.编辑配置文件/etc/samba/smb.conf,替换为如下…...

2024年8月一区SCI-海市蜃楼优化算法Fata morgana algorithm-附Matlab免费代码

引言 本期介绍了一种基于地球物理的高效优化方法名为海市蜃楼优化算法Fata morgana algorithm&#xff0c;FATA的元启发式算法。通过模拟海市蜃楼的形成过程&#xff0c;FATA分别设计了海市蜃楼滤光原理(MLF)和光传播策略(LPS)。该成果于2024年8月最新上线在JCR 1区&#xff0…...

【编程笔记】解决移动硬盘无法访问文件或目录损坏且无法读取

解决移动硬盘无法访问文件或目录损坏且无法读取 只解决&#xff1a;移动硬盘无法访问文件或目录损坏且无法读取 问题 由于频繁下载数据&#xff0c;多次安装虚拟机导致磁盘无法被系统识别。磁盘本身是好的&#xff0c;只是不能被识别&#xff0c;如果将磁盘格式化&#xff0c…...

行为型模式(一)策略模式

目录 1. 介绍 2. 目的 3. 实践 1. 介绍 策略模式&#xff08;Strategy Pattern&#xff09;是一种行为设计模式&#xff0c;它定义了一系列算法&#xff0c;将每种算法封装成具体的策略类&#xff0c;并使它们可以互相替换。策略模式使得算法可以独立于客户端而变化&#x…...

JAVA中的Wrapper类

ava中的Wrapper类&#xff0c;也称为包装类&#xff0c;是Java提供的一组特殊类&#xff0c;它们将基本数据类型&#xff08;如int、char、double等&#xff09;封装为对象。这种封装提供了基本数据类型所不具备的功能&#xff0c;如类型转换、序列化、以及在集合&#xff08;如…...

在没有硬盘的情况下进行电脑数据迁移

电脑数据迁移方式 在更换电脑的时候需要进行文件的传输&#xff0c;但是没有硬盘可以选择使用网线直连或者无线文件共享。通用配置 1.将旧电脑的文件夹或者磁盘设置文件共享 找到指定的文件夹右键属》属性&#xff0c;点击共享》点击高级共享 选择共享文件夹以及修改共享用户…...

C++转Java基础知识

目录 1. Java基础知识 1.1 JDK和JVM 1.2 Java基础代码演示 1.3 Java中的注释 1.4 Java中的关键字 1.5 Java中的println和print的区别 2. Java中的数据类型 2.1 常量 2.2 标识符 2.3 变量 2.4 类型转换 2.5 自动类型转换 2.6 强制类型转换 3. 运算符 3.1 自增运算…...

搭建jenkins一键部署java项目

一、搭建jenkins 链接: https://pan.baidu.com/s/1jzx15PiyI8EhLd_vg7q8bw 提取码: ydhl 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 直接使用docker导入镜像&#xff0c;运行就好 docker run -di --name jenkins -p 8080:8080 -v /home/jenkins_home:/var/je…...

从零到一打造自己的大模型(一)模型实现

前言 最近看了很多大模型&#xff0c;也使用了很多大模型。对于大模型理论似乎很了解&#xff0c;但是好像又缺点什么&#xff0c;思来想去决定自己动手实现一个 toy 级别的模型&#xff0c;在实践中加深对大语言模型的理解。 在这个系列的文章中&#xff0c;我将通过亲手实践…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

华为OD机试-食堂供餐-二分法

import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目&#xff0c;大多数同学都会有相同的感受&#xff0c;那就是麻烦。 核心体现在三点&#xff1a; 命令太多了&#xff0c;记不住 软件安装包名字复杂&…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

uniapp 小程序 学习(一)

利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 &#xff1a;开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置&#xff0c;将微信开发者工具放入到Hbuilder中&#xff0c; 打开后出现 如下 bug 解…...