homo 渲染引擎的思考

homo 渲染引擎的思考

大家好,我是 132,今天写一篇文章,详细介绍我对渲染引擎以及前端框架的思考,过去我也经常发类似的文章,但随着认知的积累,不同阶段的判断是不一样的

所以,很多东西你可能不认可,但我只管写,你看不看,看不看得懂,认同不认同,与我无关

前端框架彻底凉凉,web 彻底凉凉

很久以前,我说 vue3 将会称为 angular2,还被人喷死,日了哦

我现在已经不想说别的框架的坏话了,因为 fre 也凉了,碰瓷是没有意义的

接受现实,要想活下去,不浪费自己的努力和积累

只能开新坑

大家似乎都意识到 web 的没落,有搞跨端的,又移植 flutter 的,vue 和 preact 团队也搞了 vite 和 wmr……反正除了前端框架,大家啥都搞,尽快抢占市场

我也有我自己的判断,我也有我想搞的东西

渲染引擎 === 浏览器子集

是的你没有看错,下一个坑我会写一个渲染引擎,为此,我有很多思考,这些东西你从其他的技术专家那里是听不到的,我今天就捋顺一一道来

实现一套渲染引擎,重点不在于图形学,也就是不在于【画图】行为本身

它说白了是实现一个简易的浏览器,同样的输入,同样的输出,不同的过程

我们一一道来

html 子集:binary template

关于 jsx 和 template,大家真的是讨论太多了,基本上没什么营养

在 web 世界里,jsx 有一定的优势,尤其是 runtime 的优势,它的灵活性可以概括更多的场景

但如果我们重新实现全部,不是 web 没有 dom,jsx 就很鸡肋了,它不是一个纯静态的字符串,它不可以被编译,而且太过于灵活,业务 case 太多太乱

所以 homo 会选择静态 template,但!

请注意,不是 vue 的 template,这完全不同,我之所以选择使用 template,是为了限制用户的 case,是为了可以将 template 编译为字节码,这些都是 vue 所不认可的

  1. 限制性 > 灵活性

什么意思,简单说就是用户只能写个变量绑定个事件,其他啥都干不了

<view @click={add}>{count}</view> // √

<div @click="count++">{{count * 2}}</div> // ×

字符串,就是字符串,它永远都是字符串,它干的事情就只有标记一下字符串

它不应该具备更多的语义,任何赋予字符串语义的行为,都需要万分谨慎

所以从这个角度来说,此 template 非彼 template

2. 二进制字节码

其实对 template 的编译,早在很多年前就有人研究过

Glimmer&#39;s Optimizing Compiler

比如 glimmer,甚至 inferno 作者也尝试过编译 jsx

trueadm/react-compiler

他们都失败了……为什么会失败,一方面是他们不贴近 web,另一方面是他们的 template 限制不够

我准备在 homo 中尝试这个思路,因为首先我不需要 dom,然后 homo template 也是一个限制为主的 template

反正就是,啥也不让你写,不然我就编译不了了-_-||

以上,关于 dsl 这块,差不多就是这样

排版引擎:css 子集

我们拿到了一个编译好的 template 字节码,是不是就应该开始画图了?

那肯定不是啊,举例画图还有很遥远的距离,我之前说了,图形学在渲染引擎中是最次要的,画图你怎么画都行,没所谓的

在画图之前的工作才是重心所在

布局最好的策略仍然还是 css,但 css 不是所有的东西都是好东西

我们需要实现一套排版引擎,它的核心是 flex 布局算法:

w3.org/TR/css-flexbox-1

就是,本来我的 template 是这样的

<view style={ padding:20 }>
  <text style={ padding: 10 }/>
</view>

经过排版后,则应该是这样的:

<view style={ padding:20 } layout={ top: 0, left: 0, padding: 20 }>
  <text style={ padding: 10 } layout={ top: 0, left: 0, padding: 30 }/>
</view>

这块内容,说白了就是根据 css 样式,计算出坐标和边距,因为我们不在 web 环境了嘛,所以这块一定要自己算

RN 使用 yoga,还是很不错的,只不过我认为这块内容很重要,需要自己复现

github.com/facebook/yog

核心调度:链表

好了,我们现在经过 template 和 layout,终于终于拿到一个可以用于绘制的结构了

现在我们就要安排调度了,如果是放到浏览器中,那就是 dom 渲染和 js 执行的调度了

过去,我们在这方面吃了很大的亏,那是因为浏览器的调度是和 js 的 event loop 相关联的

我准备大胆地引入 fiber,彻底舍弃 event loop

实际上根据我在 fre 中的经验,我对 fiber 还是很有信心的,它实现的主要难点在于链表这个数据结构,我写过一篇文章

一旦默认实现了链表和 fiber,那么 homo 的 roadmap 将和其他渲染引擎完全不同,没错这里说的是 flutter

fiber 架构说白了就是实现这张图,其实有相关经验的一看就知道,这就是两个大循环呀

它的原理实际上比 event loop 要简单得多,只是实现上对数据结构要求严苛一点

这个调度在 fre 中是针对 vdom 的,homo 中完全不需要实现 dom

与此同时,这个思路甚至可以用于 layout 的调度,css 一样可以用链表描述,一样可以用循环打断

csstree/csstree

最小包围盒

好了,经过上面的步骤,我们应该是已经拿到了一棵带有布局信息的链表树了

这棵树呢,和 vdom 的一模一样的,只不过 vdom 是动态生成的,而 homo 是静态生成的

在传统的 web 框架中,vdom 通常是需要 diff 的,因为他们的最终目标是为了操作 dom

但是一开始我们说了,我们没有 dom,我们最终是画图,就像 canvas 一样

所以 diff 我们是不需要 diff 了,diff 算法也不需要了

但,我们需要实现另一个算法:

PCL --最小包围盒_漫长IT路-CSDN博客_最小包围盒

最小包围盒算法,它的基本思路就是将不同的组件,概括为简单的几何体,然后算出来一个最小更新范围

因为我们的排版引擎已经有了信息,所以我们实现这个算法的变体会方便一点

无论如何,是不需要 diff 了

同步行为

到了这一步,就基本啥都做好了,包围盒我们算好了,就开始画图了

这块是最后一步,也是整个流程中,唯二的“同步行为”,另一个同步的过程是 js 执行的过程

因为我们整个渲染引擎架构,异步是一等公民,同步过程都属于次要的

所以,你爱怎么画怎么画,这我就不管了,你画到 canvas 上,skia 上,都无所谓

同理,js 的执行我也不关心,你跑 v8,跑 quickjs,跑 jsc,统统无所谓

同步过程我是无法控制的,不在我的工作范围内

所以其实说白了,homo 做的事情,就是一个异步计算

总结

以上,其实说比较简单,但真的是一个大坑,融合了编译,调度,计算,很多方面的内容

最近 flutter2 挺火的,其实 flutter2 的架构和 homo 有一点类似

但我完全不慌,因为我研究的东西,和 flutter 是不重合的,我希望在异步渲染层面寻找突破,而不是为了服务业务,处理上万个 issue

我说的这些东西呢,很多人都是不理解的,很多人会问,你为什么不使用 RN,为什么不基于 vue?

这种傻叉我经常被问到,我是 fre 作者,可我不用 fre,我就没有脑子吗?

肯定不是

之所以什么框架都不用,是因为 js 和 canvas 这两个同步层,是很次要的,我的研究方向不在于此,我要将更多的精力放到更重要的事情上

以上,继续冥思,等我消息

发布于 2021-03-07 15:22