在Web技术领域中,canvas是一个使用非常广泛的功能,可以支持开发者在原有的HTML能力之外,拓展矢量图形绘制能力,常用于实现矢量动画、粒子特效、图表、游戏等等场景。而canvas在HTML里面只是一个画布,本身并不具备绘图能力,需要依托JS脚本来绘制图形。
canvas是W3C(万维网联盟)标准集中的其中一项标准,该功能目前主要实现在浏览器中。对于大部分IoT终端来说,浏览器是一个太复杂的系统,包括它的性能、资源占用等都是大部分IoT终端无法承受的。而目前,越来越多IoT领域的GUI框架,开始引入前端技术,支持使用JS来写UI,那么canvas也成为一个非常棒的高级功能,包括鸿蒙的JS应用、RTT的柿饼UI以及阿里云IoT的HaaS UI都部分或完整的支持了canvas功能。
所以本文主要探讨在IoT带屏领域里面,使用quickjs引擎以及Skia图形引擎来拓展2dcanvas场景的基本方法。
QuickJS
QuickJS(官网地址:QuickJS Javascript Engine)是一个小型并且可嵌入的JS引擎,由Bellard大神(Qemu、FFmpeg的作者)于2019年推出的开源方案。相比较其他一些嵌入式的JS引擎,QuickJS在性能、标准支持等方面,都表现优异。在运行性能上,几乎可以媲美jitless的V8引擎,而启动性能又碾压之;并且QuickJS支持最新的ES2020规范,包括模块、异步等等。所以像鸿蒙JS应用,阿里云IoT的HaaS轻应用等框架,都开始拥抱并使用QuickJS作为其JS的运行引擎。所以这里也选择使用QuickJS来进行canvas2d的接口拓展。
下图是QuickJS作者放出的比较不同JS引擎的benchmark测试,分数越高代表性能越好:
引擎启动
QuickJS的运行环境主要有两个关键信息:
- JSRuntime:JS运行时,可以同时存在多个Runtime,互相独立,在Runtime维度,不支持多线程,所以QuickJS不支持跨线程调用与回调
- JSContext:JS上下文环境,每个JSContext都有自己的全局(global)对象,一个JSRuntime可以创建多个JSContext,它们之间可以通过引用方式共享JS对象(一般JS扩展,包括cfunction以及cmodule的扩展是以context为载体的)
以下为一个QuickJS实例启动的代码示例:
// 创建Runtime
JSRuntime *rt = JS_NewRuntime();
// 创建Context
JSContext *ctx = JS_NewContext(rt);
// 在这里可以添加各种cfunction和cmodule的扩展,提供给JS调用
// eval js
JSValue val = JS_Eval(ctx, buf, buf_len, filename, eval_flags);
JS_FreeValue(ctx, val); // 引用计数,需要使用者注意管理js对象引用
// 或者eval binary(quickjs支持导出字节码,加速执行)
// js_std_eval_binary,参考quickjs-libc.c
// 这里是线程loop(用于接收执行timer,Promise等pending任务,以及其他线程callback的消息)
// 在Linux系统,可以使用quickjs-libc.c里自带的js_std_loop
// 对于一些RTOS系统,可以参考后进行适配
// 退出loop
// 销毁Context
JS_FreeContext(ctx);
// 销毁Runtime
JS_FreeRuntime(rt);
C函数/属性扩展
空引擎初始化之后,只会包含在ES规范中定义的JS语法。而对于大部分的真正使用场景,都需要拓展业务场景所必须的扩展功能,所有的JS引擎都会支持这样的一些扩展能力。扩展一般用于往global对象上binding一些C函数与属性(也可以往其他对象上):
// 需binding的C函数
JSValue wrap_Foo(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) {
// argv为参数列表
printf("run global.foo(arg).\n");
int a = 0;
if (argc > 0) {
JS_ToInt32(ctx, &a, argv[0]);
}
// return a+1
return JS_NewInt32(ctx, a + 1);
}
// 获取当前context的global对象
JSValue global_obj = JS_GetGlobalObject(ctx);
// 往global对象上绑定env属性
JSValue environment = JS_NewObject(ctx);
// globalThis.$env.platform
JS_SetPropertyStr(ctx, environment, "platform",
JS_NewString(ctx, "AliOS Things"));
// glob