【iOS】——渲染原理与离屏渲染

news2024/11/12 18:50:23

图像渲染流水线(图像渲染流程)

图像渲染流程大致分为四个部分:

  • Application 应用处理阶段得到图元
  • Geometry 几何处理阶段:处理图元
  • Rasterization 光栅化阶段:图元转换为像素
  • Pixel 像素处理阶段:处理像素,得到位图

除了Application阶段由 CPU 负责,后续都是由GPU负责

Application 应用处理阶段:得到图元

这个阶段具体指的就是图像在应用中被处理的阶段,此时还处于 CPU 负责的时期。在这个阶段应用可能会对图像进行一系列的操作或者改变,最终将新的图像信息传给下一阶段。这部分信息被叫做图元(primitives)

图元就是描述几何形状的基本元素,通常是三角形、线段、顶点等。

Geometry 几何处理阶段:处理图元

进入这个阶段之后,以及之后的阶段,就都主要由 GPU 负责了。此时 GPU 可以拿到上一个阶段传递下来的图元信息,GPU 会对这部分图元通过顶点着色器、形状装配、几何着色器进行处理之后输出新的图元。这一系列阶段包括:

  • 顶点着色器(Vertex Shader):这个阶段中会将图元中的顶点信息进行视角转换、添加光照信息、增加纹理等操作。
  • 形状装配(Shape Assembly):图元中的三角形、线段、点分别对应三个 Vertex、两个 Vertex、一个 Vertex。这个阶段会将 Vertex 连接成相对应的形状。
  • 几何着色器(Geometry Shader):额外添加额外的Vertex,将原始图元转换成新图元,以构建一个不一样的模型。简单来说就是基于通过三角形、线段和点构建更复杂的几何图形。

Rasterization 光栅化阶段:图元转换为像素

光栅化的主要目的是将几何渲染之后的图元信息,转换为一系列的像素,以便后续显示在屏幕上

工作原理就是会根据图元信息,计算出每个图元所覆盖的像素信息等,从而将像素划分成不同的部分

图片

一种简单的划分就是根据中心点,如果像素的中心点在图元内部,那么这个像素就属于这个图元。

Pixel 像素处理阶段:处理像素,得到位图

经过上述光栅化阶段,我们得到了图元所对应的像素,之后我们需要通过片段着色器(给每一个像素 Pixel 赋予正确的颜色)和测试与混合(处理片段的前后位置以及透明度)给这些像素填充正确的颜色和效果得到位图(bitmap)以便最终显示到屏幕上。

这些经过处理、蕴含大量信息的像素点集合,被称作位图

  • 片段着色器(Fragment Shader):也叫做 Pixel Shader,这个阶段的目的是给每一个像素 Pixel 赋予正确的颜色。颜色的来源就是之前得到的顶点、纹理、光照等信息。由于需要处理纹理、光照等复杂信息,所以这通常是整个系统的性能瓶颈。
  • 测试与混合(Tests and Blending):也叫做 Merging 阶段,这个阶段主要处理片段的前后位置以及透明度。这个阶段会检测各个着色片段的深度值 z 坐标,从而判断片段的前后位置,以及是否应该被舍弃。同时也会计算相应的透明度 alpha 值,从而进行片段的混合,得到最终的颜色。

屏幕成像原理

屏幕成像就是将先前图像渲染结束后得到的像素信息也就是位图显示在屏幕上。

  • 首先GPU将渲染结束后得到的位图存到帧缓存区(Framebuffer)中
  • 之后视频控制器(Video Controller)会读取帧缓冲器中的信息,经过数模转换传递给显示器(Monitor)
  • 显示器的电子束会从屏幕的左上角开始逐行扫描,屏幕上的每个点的图像信息都从帧缓冲器中的位图进行读取,在屏幕上对应地显示。
  • 电子束扫描的过程中,屏幕就能呈现出对应的结果,每次整个屏幕被扫描完一次后,就相当于呈现了一帧完整的图像。屏幕不断地刷新,不停呈现新的帧,就能呈现出连续的影像。(而这个屏幕刷新的频率,就是帧率)

图片

由于人眼的视觉暂留效应,当屏幕刷新频率足够高时(FPS 通常是 50 到 60 左右),就能让画面看起来是连续而流畅的。对于 iOS 而言,app 应该尽量保证 60 FPS 才是最好的体验。

画面撕裂与卡顿

画面撕裂

如果在电子束开始扫描新的一帧时,位图还没有渲染好,而是在扫描到屏幕中间时才渲染完成,被放入帧缓冲器中,那么已扫描的部分就是上一帧的画面,而未扫描的部分则会显示新的一帧图像,这就造成画面撕裂。

垂直同步 Vsync + 双缓冲机制 Double Buffering

iOS中使用垂直同步+双缓存机制解决画面撕裂问题。

垂直同步信号相当于给帧缓冲器加锁:当电子束完成一帧的扫描,将要从头开始扫描时,就会发出一个垂直同步信号。只有当视频控制器接收到垂直同步信号之后,才会将帧缓冲器中的位图更新为下一帧,这样就能保证每次显示的都是同一帧的画面,因而避免了屏幕撕裂。但是这种情况下,视频控制器在接受到 垂直同步信号 之后,就要将下一帧的位图传入,这意味着整个 CPU+GPU 的渲染流程都要在一瞬间完成,这是明显不现实的。于是提出双缓存机制

双缓冲机制会增加一个新的备用缓冲器(back buffer)。位图会预先保存在 back buffer 中,在接收到垂直同步信号的时候,视频控制器会将 back buffer 中的内容置换到 frame buffer 中,此时就能保证置换操作几乎在一瞬间完成(实际上是交换了内存地址)。

掉帧

启用 垂直同步信号以及双缓冲机制会导致掉帧。

如果在接收到垂直同步信号时 CPU 和 GPU 还没有渲染好新的位图存到back buffer中,视频控制器就不会去替换 frame buffer 中的位图。这时屏幕就会重新扫描呈现出上一帧一模一样的画面。相当于两个周期显示了同样的画面,这就是所谓掉帧的情况。

三缓冲 Triple Buffering

在发生掉帧的时候,CPU 和 GPU 有一段时间处于闲置状态(比如当 A 的内容正在被扫描显示在屏幕上,而 B 的内容已经被渲染好,此时 CPU 和 GPU 就处于闲置状态)。那么如果我们增加一个帧缓冲器,就可以利用这段时间进行下一步的渲染,并将渲染结果暂存于新增的帧缓冲器中。由于增加了新的帧缓冲器,可以一定程度上地利用掉帧的空档期,合理利用 CPU 和 GPU 性能,从而减少掉帧的次数。

iOS 中的渲染框架

在这里插入图片描述

iOS 的渲染框架依然符合渲染流水线的基本架构,iOS 中有 Core Graphics、Core Animation、Core Image、OpenGL 等多种软件框架来绘制内容,在 CPU 与 GPU 之间进行了更高层地封装。

  • GPU Driver:直接和CPU交流的代码块,直接与CPU连接。所有框架最终都会通过OpenGL连接到GPU Driver

  • OpenGL:是一个提供了 2D 和 3D 图形渲染的 API,它能和 GPU 密切的配合,最高效地利用 GPU 的能力,实现硬件加速渲染。

  • Metal:Metal 类似于 OpenGL ES,也是一套第三方标准,具体实现由苹果实现。Core Animation、Core Image、SceneKit、SpriteKit 等等渲染框架都是构建于 Metal 之上的。

  • Core Graphics:Core Graphics 是一个强大的二维图像绘制引擎是 iOS 的核心图形库,常用的比如 CGRect 就定义在这个框架下

  • Core Animation可以理解为一个复合引擎,是 app 界面渲染和构建的最基础架构。主要职责包含:渲染、构建和实现动画。在 iOS 上,几乎所有的东西都是通过 Core Animation 绘制出来,它的自由度更高,使用范围也更广。

Core Animation 的职责就是尽可能快地组合屏幕上不同的可视内容,这个内容是被分解成独立的 layer(iOS 中具体而言就是 CALayer),并且被存储为树状层级结构。这个树也形成了 UIKit 以及在 iOS 应用程序当中你所能在屏幕上看见的一切的基础。

  • Core Image:Core Image 是一个高性能的图像处理分析的框架,它拥有一系列现成的图像滤镜,能对已存在的图像进行高效的处理。

内容显示——CALayer

iOS中UIView时app的基本组成结构,UIView本身不具备显示内容的作用,只负责响应用户事件,每创建一个UIView都会自动创建一个CALayer,,并将自身固定设置为 CALayer 的代理。

用户能看到的屏幕上的内容都由 CALayer 进行管理,CALayer中有contents属性保存了由设备渲染流水线渲染好的位图 bitmap(通常也被称为 backing store),而当设备屏幕进行刷新时,会从 CALayer 中读取生成好的 bitmap,进而呈现到屏幕上。

也正因为每次要被渲染的内容是被静态的存储起来的,所以每次渲染时,Core Animation 会触发调用 drawRect: 方法,使用存储好的 bitmap 进行新一轮的展示。

Core Animation 渲染全内容

Core Animation Pipeline 渲染流水线

图片

整个流水线一共有下面几个步骤:

  • Handle Events:这个过程中会先处理点击事件,这个过程中有可能会需要改变页面的布局和界面层次。
  • Commit Transaction:此时 app 会通过 CPU 处理显示内容的前置计算,比如布局计算、图片解码等任务。之后将计算好的图层进行打包发给 Render Server
  • Decode:打包好的图层被传输到 Render Server 之后,首先会进行解码。注意完成解码之后需要等待下一个 RunLoop 才会执行下一步 Draw Calls
  • Draw Calls:解码完成后,Core Animation 会调用下层渲染框架(比如 OpenGL 或者 Metal)的方法进行绘制,进而调用到 GPU。
  • Render:这一阶段主要由 GPU 进行渲染。
  • Display:显示阶段,需要等 render 结束的下一个 RunLoop 触发显示。

Commit Transaction 发生了什么

一般开发当中能影响到的就是 Handle EventsCommit Transaction 这两个阶段。Commit Transaction 这部分中主要进行的是:Layout(处理视图的构建和布局,由CPU负责)、Display( Core Graphics 进行视图的绘制得到图元数据)、Prepare(图片的解码和转换)、Commit(图层打包发送到Rander Server) 等四个具体的操作。

Layout:构建视图

这个阶段主要处理视图的构建和布局,具体步骤包括:

  1. 调用重载的 layoutSubviews 方法
  2. 创建视图,并通过 addSubview 方法添加子视图
  3. 计算视图布局,即所有的 Layout Constraint
Display:绘制视图

这个阶段主要是交给 Core Graphics 进行视图的绘制(注意不是真正的显示)得到图元数据:

  1. 根据上一阶段 Layout 的结果创建得到图元信息。
  2. 如果重写了 drawRect: 方法,那么会调用重载的 drawRect: 方法,在 drawRect: 方法中手动绘制得到 bitmap 数据,从而自定义视图的绘制。

正常情况下 Display 阶段只会得到图元信息,但是如果重写了 drawRect: 方法,这个方法会直接调用 Core Graphics 绘制方法得到 bitmap 数据,同时系统会额外申请一块内存,用于暂存绘制好的 bitmap。

由于重写了 drawRect: 方法,导致绘制过程从 GPU 转移到了 CPU,这就导致了一定的效率损失。与此同时,这个过程会额外使用 CPU 和内存,因此需要高效绘制,否则容易造成 CPU 卡顿或者内存爆炸。

Prepare:Core Animation 额外的工作

主要进行图片解码和转换

Commit:打包并发送

这一步主要是:图层打包并发送到 Render Server。

注意 commit 操作是依赖图层树递归执行的,所以如果图层树过于复杂,commit 的开销就会很大。这也是我们希望减少视图层级,从而降低图层树复杂度的原因。

Rendering Pass:Render Server 的具体操作

图片

Render Server 通常是 OpenGL 或者是 Metal。

如果是OpenGL的话,么上图主要是 GPU 中执行的操作,具体主要包括:

  1. GPU收到包含图元信息的Command Buffer
  2. Tiler 开始工作:先通过顶点着色器 Vertex Shader 对顶点进行处理,更新图元信息
  3. 平铺过程:平铺生成 tile bucket 的几何图形,这一步会将图元信息转化为像素,之后将结果写入 Parameter Buffer 中
  4. Tiler 更新完所有的图元信息,或者 Parameter Buffer 已满,则会开始下一步
  5. Renderer 工作:将像素信息进行处理得到 bitmap,之后存入 Render Buffer
  6. Render Buffer 中存储有渲染好的 bitmap,供之后的 Display 操作使用

离屏渲染

通常的渲染流程是:CPU和GPU负责内容渲染流程,接着将得到的位图放入帧缓存区(frame buffer)中,而显示屏幕不断地从 Framebuffer 中获取内容,显示实时的内容

图片

离屏渲染的流程是:先额外创建离屏渲染缓冲区 Offscreen Buffer,将提前渲染好的内容放入其中,等到合适的时机再将 Offscreen Buffer 中的内容进一步叠加、渲染,完成后将结果切换到 Framebuffer 中

图片

离屏渲染的效率问题

离屏渲染的耗时操作主要体现在需要提前对部分内容进行额外的渲染并保存到 Offscreen Buffer,以及需要在必要时刻对 Offscreen Buffer 和 Framebuffer 进行内容切换

并且 Offscreen Buffer 本身就需要额外的空间,大量的离屏渲染可能早能内存的过大压力。与此同时,Offscreen Buffer 的总大小也有限,不能超过屏幕总像素的 2.5 倍。

一旦需要离屏渲染的内容过多,很容易造成掉帧的问题

离屏渲染的优点

  • 一些特殊效果比如阴影和圆角等,需要使用额外的 Offscreen Buffer 来保存渲染的中间状态,所以不得不使用离屏渲染。
  • 可以将内容提前渲染保存在 Offscreen Buffer 中,达到复用的目的。

对于第一种情况,一般都是系统自动触发的。比如使用了mask蒙版利用额外的内存空间保存中间的渲染结果来实现将两层渲染结果叠加。

而第二种情况,为了复用提高效率而使用离屏渲染一般是主动的行为,是通过 CALayer 的 shouldRasterize 光栅化操作实现的。

shouldRasterize 光栅化

开启光栅化后,会触发离屏渲染,Render Server 会强制将 CALayer 的渲染位图结果 bitmap 保存下来,这样下次再需要渲染时就可以直接复用,从而提高效率。

而保存的 bitmap 包含 layer 的 subLayer、圆角、阴影、组透明度 group opacity 等,所以如果 layer 的构成包含上述几种元素,结构复杂且需要反复利用,那么就可以考虑打开光栅化。这样可以节约第二次以后的渲染时间。

不过使用光栅化的时候需要注意以下几点:

  1. 如果 layer 不能被复用,则没有必要打开光栅化
  2. 如果 layer 不是静态,需要被频繁修改,比如处于动画之中,那么开启离屏渲染反而影响效率
  3. 离屏渲染缓存内容有时间限制,缓存内容 100ms 内如果没有被使用,那么就会被丢弃,无法进行复用
  4. 离屏渲染缓存空间有限,超过 2.5 倍屏幕像素大小的话也会失效,无法复用

圆角的离屏渲染

如果只是设置了 layer的cornerRadius 而没有设置 masksToBounds,由于不需要叠加裁剪,此时是并不会触发离屏渲染的。而当设置了裁剪属性的时候,由于 masksToBounds 会对 layer 以及所有 subLayer 的 content 都进行裁剪,所以不得不触发离屏渲染。

离屏渲染的具体逻辑

图层的叠加绘制大概遵循“画家算法”,在这种算法下会按层绘制,首先绘制距离较远的场景,然后用绘制距离较近的场景覆盖较远的部分。

图片

在普通的layer绘制中,上层的 sublayer 会覆盖下层的 sublayer,下层 sublayer 绘制完之后就可以抛弃了。所有 sublayer 依次绘制完毕之后,整个绘制过程完成,就可以进行后续的呈现了。
在这里插入图片描述

如设置了 cornerRadius 以及 masksToBounds 进行圆角 + 裁剪时,masksToBounds 裁剪属性会应用到所有的 sublayer 上,也就是说所有的sublayer在第一次被绘制之后不能被立刻丢弃还需要被保存在 Offscreen buffer 中等待下一轮圆角+裁剪,这也就诱发了离屏渲染。
在这里插入图片描述

实际上不只是圆角+裁剪,如果设置了透明度+组透明(layer.allowsGroupOpacity+layer.opacity),阴影属性(shadowOffset 等)都会产生类似的效果,因为组透明度、阴影都是和裁剪类似的,会作用与 layer 以及其所有 sublayer 上,这就导致必然会引起离屏渲染。

离屏渲染的本质原因就是裁剪的叠加导致了对layer和所有的sublayer进行了二次处理。

下面这些情况都会触发离屏渲染:

  1. 使用了 mask 的 layer (layer.mask)
  2. 需要进行裁剪的 layer (layer.masksToBounds / view.clipsToBounds)
  3. 设置了组透明度为 YES,并且透明度不为 1 的 layer (layer.allowsGroupOpacity/layer.opacity)
  4. 添加了投影的 layer (layer.shadow*)
  5. 采用了光栅化的 layer (layer.shouldRasterize)
  6. 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)

不过,需要注意的是,重写 drawRect: 方法并不会触发离屏渲染。前文中我们提到过,重写 drawRect: 会将 GPU 中的渲染操作转移到 CPU 中完成,并且需要额外开辟内存空间。这和标准意义上的离屏渲染并不一样

避免圆角离屏渲染

只要避免使用 masksToBounds 进行二次处理,而是对所有的 sublayer 进行预处理,就可以只进行“画家算法”,用一次叠加就完成绘制。

那么可行的实现方法大概有下面几种:

  1. 用带圆角的图片或者替换背景色为带圆角的纯色背景图。
  2. 用贝塞尔曲线绘制闭合带圆角的矩形,在上下文中设置只有内部可见,再将不带圆角的 layer 渲染成图片,添加到贝塞尔矩形中
  3. 重写 drawRect:,用 CoreGraphics 相关方法,在需要应用圆角时进行手动绘制

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图像去噪算法性能比较与分析

在数字图像处理领域,去噪是一个重要且常见的任务。本文将介绍一种实验,通过MATLAB实现多种去噪算法,并比较它们的性能。实验中使用了包括中值滤波(MF)、自适应加权中值滤波(ACWMF)、差分同态算法…

Clion不识别C代码或者无法跳转C语言项目怎么办?

如果是中文会显示: 此时只需要右击项目,或者你的源代码目录,将这个项目或者源码目录标记为项目源和头文件即可。 英文如下:

什么是数字化人才?数字化人才画像是怎么样的?(附数字化知识能力框架体系)

什么是数字化人才? 数字化人才是指具备较高信息素养,有效掌握数字化相关能力,并将这种能力不可或缺地应用于工作场景的相关人才。随着数字技术的快速发展和应用,数字化人才的需求日益增加,他们在大数据、“互联网”、…

编程效率飙升的秘密武器:Cursor编辑器的AI革命

有没有想过,写代码这件事其实可以更加轻松、高效?尤其是对于那些需要频繁修正、调试和优化的开发者们,Cursor编辑器带来的AI赋能,简直让人眼前一亮。相信很多人一提到AI,第一反应就是:“这真的靠谱吗?”今天,我就带你来揭开Cursor这款AI编辑器的神秘面纱,看看它是如何…

借助ChatGPT高效撰写优质论文的7大要素

大家好,感谢关注。我是七哥,一个在高校里不务正业,折腾学术科研AI实操的学术人。关于使用ChatGPT等AI学术科研的相关问题可以和作者七哥(yida985)交流,多多交流,相互成就,共同进步,为大家带来最酷最有效的智能AI学术科研写作攻略。 撰写一篇优秀的学术论文不仅需要深入…

onnxruntime——CUDA Execution Provider学习记录

ONNX Runtime(简称 ORT)是一个高性能的推理引擎,支持多种硬件加速器。CUDA Execution Provider 是 ONNX Runtime 提供的一个执行提供者(Execution Provider),专门用于在 NVIDIA GPU 上加速推理。以下是详细…

ComfyUI 基础教程—— 应用 Controlnet 精准控制图像生成

一、前言 你是否有见过下面类似这样的图片: 看起来平平无奇,当你站远点看,或者把眼睛眯成一条缝了看,你会发现,这个图中藏有一些特别的元素。这就是利用了 Ai 绘画中的 ControlNet,实现对图片的相对更精…

实施经济实惠的DFIR 网络防御解决方案

数字取证 事件响应(DFIR)是防御的重要组成部分,它包括发现网络危险,调查它们,并采取措施阻止它们。这对于保护私有数据安全和确保IT系统正常工作非常重要。由于资金和资源有限,小公司往往难以建立有效的DFI…

数据库安全漏洞的克星:SqlMap

SqlMap:一键自动化,精准识别SQL注入漏洞。 - 精选真开源,释放新价值。 概览 sqlmap是一个广受认可的开源工具,专注于自动化SQL注入漏洞的检测和利用。它能够与多种数据库系统交互,包括但不限于MySQL、Oracle、Postgre…

Android源码修改 默认导航方式

1、静态修改 代码路径&#xff1a;frameworks/base/core/res/res/values/config.xml&#xff0c;由于我是用hbuilder打开 我们可以看到代码注释 <!-- Controls the navigation bar interaction mode: 0: 3 button mode (back, home, overview buttons) 1…

Excel和Word日常使用记录:

Excel使用总结 表格颜色填充&#xff1a; 合并单元格&#xff1a; 选中你要合并的单元格区域。 按下快捷键 Alt H&#xff0c;然后松开这些键。 再按下 M&#xff0c;接着按 C。 这个组合键执行的操作是&#xff1a;Alt H&#xff1a;打开“主页”选项卡。 M&#xff1a;选…

【Vue】状态管理模式Vuex

数据共享 流程搭建变更状态辅助函数分割模块 流程 Vuex是一个Vue的状态管理工具&#xff0c;状态就是数据&#xff08;某个状态在很多个组件来使用 &#xff0c;多个组件共同维护一份数据&#xff09; 搭建 1&#xff09;Vuex我们在脚手架搭建项目的时候直接搭建好了&#xff0…

面壁带来了小钢炮,MiniCPM3-4B

MiniCPM3-4B 是 MiniCPM 系列的第三代产品。 相比 MiniCPM1.0/MiniCPM2.0&#xff0c;MiniCPM3-4B 拥有更强大、更多用途的技能集&#xff0c;可以实现更广泛的应用。 MiniCPM3-4B 支持函数调用和代码解释器。 有关使用指南&#xff0c;请参阅高级功能。 MiniCPM3-4B 具有 32k …

微擎忘记后台登录用户名和密码怎么办?解决方法

微擎忘记后台登录名和登录密码是很常见的&#xff0c;服务器百科网fwqbk.com告诉你找回后台登录用户名和密码的方法&#xff1a; 一&#xff1a;找回微擎后台用户名 &#xff08;如果只是忘记了后台登录密码&#xff0c;请忽略此步骤&#xff0c;跳转到第二步&#xff09; 通…

博客摘录「 深度学习 | 编码器-解码器网络、seq2seq模型、束搜索算法」2024年9月7日

老师在课上是这样引入的&#xff1a; Sequence to Sequence Learning&#xff1a;两个循环神经网络组成。 红色部分和绿色部分都是RNN。 预测任务就是从一个序列到另一个序列。 第一个序列称之为原序列&#xff0c;第二个序列称为目标序列。两者…

【Windows】解决新版 Edge 浏览器开机自启问题(简单有效)

文章目录 1.前言2.查找资料3.查找方法4.解决办法 参考文章&#xff1a; 解决新版 Edge 浏览器开机自启问题&#xff08;简单有效&#xff09; Edge开机启动如何关闭 1.前言 电脑开机后edge浏览器开机自启动&#xff0c;并且在任务管理器–启动项内可看到edge浏览器&#xff0…

【Python篇】PyQt5 超详细教程——由入门到精通(中篇一)

文章目录 PyQt5入门级超详细教程前言第4部分&#xff1a;事件处理与信号槽机制4.1 什么是信号与槽&#xff1f;4.2 信号与槽的基本用法4.3 信号与槽的基础示例代码详解&#xff1a; 4.4 处理不同的信号代码详解&#xff1a; 4.5 自定义信号与槽代码详解&#xff1a; 4.6 信号槽…

如何部署Vue+Springboot项目

很多同学在项目上线的部署遇到困难&#xff0c;不懂得怎么部署项目&#xff0c;本文将会带大家手把手从前端部署、java部署来教会大家。 如果项目涉及到了docker相关中间件的环境配置&#xff0c;请参看&#xff1a;https://blog.csdn.net/weixin_73195042/article/details/13…

vue3 为组件的 emits 标注类型,defineEmits基于类型的定义的简单理解

1&#xff09;在 <script setup> 中&#xff0c;emit 函数的类型标注也可以通过运行时声明或是类型声明进行。 2&#xff09;基于类型的&#xff1a; const emit defineEmits<{ (e: change, id: number): void (e: update, value: string): void }>() 说明&#x…

STM32 PWM 详解(基于 STM32F429 HAL 库)

目录 前言 一、PWM 简介 二、STM32F429 的 PWM 功能 1.定时器资源 2.PWM 模式 3.PWM原理图 三、使用 HAL 库配置 STM32F429 的 PWM 1.开启时钟 2.配置定时器 3.配置通道 4.启动定时器 5.PWM 占空比的调节 四、应用实例 五、总结 前言 在嵌入式系统开发中&#…