工作原理其实比较简单,一句话就可以说明白。首先封装图形API(OpenGL、Vulkan、Metal…)以支持WebGL和Canvas 2D矢量图渲染能力,对下桥接到不同操作系统和容器之上,对上通过language binding将渲染能力以标准化接口透出到业务容器的JS上下文。
举个例子,以下是淘宝小程序容器Canvas组件的渲染流程,省略了「亿」点点细节。
Canvas在Android上其实是一个SurfaceView/TextureView,通过同层渲染的方式嵌入到UCWebView中。开发者调用Canvas JS接口,最终会生成一系列的渲染指令送到GPU,渲染结果写入图形缓冲区,在合适时机通过SwapBuffer交换缓冲区,然后操作系统进行图层合成和送显。
▐ 分层架构
从业务形态上看,不管是小程序、小游戏还是其他容器,实现上都是相似的,如下图所示,通过JSBinding实现标准Canvas接口,开发者可以通过适配在上面跑web游戏引擎(laya、egret、threejs…),下边是JS引擎,这一层可以有不同的技术选型,如老牌的V8、JSC,后起之秀quickjs、hermes等等,在这之下就是Canvas核心实现了,这一层需要分别提供WebGL、Canvas2D的能力。WebGL较为简单,基本与OpenGLES接口一一对应,简单封装即可。
Canvas 2D如果要从零开始实现的话相对来说会复杂一些(特别是文字、图片、路径的渲染等),不过技术选型上仍然有很多选择比如cairo、skia、nanovg等等,不管使用哪种方案,只要是硬件渲染,其backend只有vulkan/OpenGLES/metal/Direct3D等几种选择。
目前OpenGL使用最为广泛,还可以通过google的Angle项目适配到vulkan/directx等不同backend上。Canvas实现层之下是WAL窗体抽象层,这一层的职责就是为渲染提供宿主环境,通过EGL/EAGL等方式绑定GL上下文与平台窗体系统。下文将对相关模块的实现分别进行介绍。考虑到性能、可移植性等因素,除了与平台/容器桥接的部分需要使用OC/Java等语言实现之外,其余部分基本采用C++实现。
JS Binding机制
JS引擎通常会抽象出VM、JSContext、JSValue、GlobalObject等概念,VM代表一个JS虚拟机实例,拥有独立的堆栈空间,有点类似进程的概念,不同的VM相互是隔离的(因此在v8中以v8::Isolate命名),一个VM中可以有多个JSContext,JSContext代表一个JS的执行上下文,可以执行JS代码,JSValue代表一个JS值类型,可以是基础数据类型也可以是Object类型,每个JSContext中都会拥有一个GlobalObject对象,GlobalObject在JSContext整个生命周期内,都可以直接进行访问,它默认是可读可写的,因此可以在GlobalObject上绑定属性或者函数等,这样就可以在JSContext执行上下文中访问它们了。
要想在JS环境中使用Canvas,需要将Canvas相关接口注入到JS环境,正如Java JNI、Python Binding、Lua Binding等类似,JS引擎也提供了Extension机制,称之为JS Binding,它允许开发者使用c++等语言向JS上下文中注入变量、函数、对象等。
// V8函数绑定示例
static void LogCallback(const v8::FunctionCallbackInfov8::Value& args){…}
…
// Create a template for the global object and set the
// built-in global functions.
v8::Localv8::ObjectTemplate global = v8::ObjectTemplate::New(isolate);
global->Set(v8::String::NewFromUtf8(isolate, “log”),
v8::FunctionTemplate::New(isolate, LogCallback));
// Each processor gets its own context so different processors
// do not affect each other.
v8::Persistentv8::Context context =
v8::Context::New(isolate, nullptr, global);
以小程序环境为例,小程序容器初始化时,会分别创建Render和Worker,Render负责界面渲染,Worker负责执行业务逻辑,拥有独立JSContext,Canvas提