web上的OffscreenCanvas-离屏canvas使⽤说明(离屏渲染)OffscreenCanvas 是⼀个实验中的新特性(在最新版本的 Chrome 和 Firefox 上都可以通过实验室开关打开,Chrome 的开关是chome://flags -> Experimental Web Platform features(离屏渲染chrome86已默认⽀持,不需要开启),本⽂的例程是在 Chrome 67 Canary 上进⾏验证),主要⽤于提升 Canvas 2D/3D 绘图的渲染性能和使⽤体验。
OffscreenCanvas和canvas都是渲染图形的对象。 不同的是canvas只能在window环境下使⽤,⽽OffscreenCanvas既可以在window 环境下使⽤,也可以在web worker中使⽤,这让不影响浏览器主线程的离屏渲染成为可能。
跟 OffscreenCanvas 关系⽐较紧密的还有另外两个新的 API,ImageBitmap 和 ImageBitmapRenderingContext。
使⽤解析
OffscreenCanvas ⽬前主要⽤于两种不同的使⽤场景:
Transfer 模式:⼀种是在 Worker 线程创建⼀个 OffscreenCanvas 做后台渲染,然后再把渲染好的缓冲区 Transfer 回主线程显⽰;
Commit 模式:⼀种是主线程从当前 DOM 树中的 Canvas 元素产⽣⼀个 OffscreenCanvas,再把这个 O
ffscreenCanvas 发送给Worker 线程进⾏渲染,渲染的结果直接 Commit 到浏览器的 Display Compositor 输出到当前窗⼝,相当于在 Worker 线程直接更新 Canvas 元素的内容;
⼀、Transfer模式
应⽤场景:Transfer 模式主要⽤于后台渲染,避免耗时的渲染任务会阻塞前台线程,导致应⽤⽆法及时响应⽤户的操作,⽐如⼀些 2D/3D 图表,图形可视化应⽤,地图应⽤等。
Transfer Demo 运⾏流程⼤致如下:
1. 主线程启动 Worker 线程,并请求初始化;
canvas动画2. Worker 线程创建 OffscreenCanvas;
3. Worker 线程获取 OffscreenCanvas 的 WebGL Context 并进⾏绘制;
4. Worker 线程获取 OffscreenCanvas 的缓冲区(ImageBitmap),然后 Transfer 回主线程;
5. 主线程将 Worker 线程回传的缓冲区分别绘制在两个不同的 Canvas 上,⼀个 Canvas 使⽤ CanvasRenderingContext2D,⼀个
Canvas 使⽤ ImageBitmapRenderingContext;
6. 3 ~ 5 重复运⾏;
1、渲染到canvas上的两种⽅式:
⽅案⼀:ImageBitmap 可以被当做普通的 Image 绘制在⼀个 2D Canvas 上;
⽅案⼆:可以通过 Transfer 到⼀个 Bitmap Canvas;
2、ImageBitmap介绍
主要是⽤来封装⼀块 GPU 缓冲区,可以被 GPU 读写,并且实现了 Transferable 的接⼝(transferFromImageBitmap和
transferToImageBitmap),可以在不同线程之间 Transfer。
跟 ImageData 不⼀样,ImageBitmap 并没有提供 JavaScipt API 供 CPU 进⾏读写,这是因为使⽤ CPU 读写 GPU 缓冲区的成本⾮常⾼,需要拷贝到临时缓冲区进⾏读写然后再写回。这也是为什么规范的制定者没有扩展 ImageData,⽽是提供了⼀个新的
ImageBitmap 的缘故。
3、渲染的具体流程:
(1) 当我们使⽤ OffscreenCanvas,通过 2D/3D 进⾏绘制时==》我们有⼀块画板,上⾯有⼀些画纸,我们可以在画纸上作画;
(2) 调⽤ ansferToImageBitmap 获取 ImageBitmap 封装的缓冲区==》我们把当前绘画的画纸取下来;
function TransferBuffer() {
let image_bitmap = ansferToImageBitmap();
postMessage({name:"TransferBuffer", buffer:image_bitmap},
[image_bitmap]);
}
(3) 把 ImageBitmap 作为 Image 绘制在⼀个 2D Canvas 上(⽅案⼀)==》我们对已经绘制好的图
画在新的画纸上进⾏临摹;
function init() {
g_bitmap_canvas = helper.GetCanvas("bitmap");
g_2d_canvas = helper.GetCanvas("2d");
g_render_worker = new Worker("./render_worker.js");
g_ssage = function(msg) {
if (msg.data.name === "TransferBuffer") {
GetTransferBuffer(msg.data.buffer);
}
}
}
function GetTransferBuffer(buffer) {
let context_2d = g_Context("2d");
context_2d.clearRect(0, 0, g_2d_canvas.width, g_2d_canvas.height);
context_2d.save();
anslate(g_bitmap_canvas.width / 2, g_bitmap_canvas.height / 2);
ate(g_angle * Math.PI / 180);
context_2d.scale(0.5, 0.5);
anslate(-g_bitmap_canvas.width / 2, -g_bitmap_canvas.height / 2);
context_2d.drawImage(buffer, 0, 0);
store();
g_angle += 15;
if (g_angle > 360)
g_angle = 0;
}
(4)把 ImageBitmap 通过 ansferFromImageBitmap Transfer 给 Bitmap Canvas(⽅案⼆)==》我们把画纸放⼊⼀个画框⾥挂在墙上显⽰;
function GetTransferBuffer(buffer) {
let bitmap_context = g_Context("bitmaprenderer");
ansferFromImageBitmap(buffer);
console.log("end");
}
4、Transfer 总结
ImageBitmap Transfer 语义实现了零拷贝 的所有权转移,不需要对缓冲区进⾏拷贝,性能更⾼,但是也限制了显⽰的⽅式。
临摹意味着我们可以对临摹的副本进⾏旋转,缩放,位移等等,还可以在上⾯再绘制其它内容。
ImageBitmap Transfer 之后所有权发⽣了转移,例如“渲染具体流程中”(3)和(4)交换顺序,即调换⼀下两个 Canvas 的绘制顺序就会报错,因为 Transfer 之后,原来的缓冲区引⽤已经被置空变成⼀个空引⽤。
===》具体使⽤哪种⽅式取决于应⽤的场景,如果只是简单的展现就可以考虑使⽤性能更⾼ImageBitmapRenderingContext,即⽅案⼆。
⼆、Commit模式
Commit 模式主要⽤于 H5 游戏,它允许应⽤/游戏在 Worker 线程直接对 DOM 树⾥⾯的 Canvas 元素进⾏更新,浏览器在这种模式下提供了⼀条最短路径和最佳性能的 Canvas 渲染流⽔线。
1、普通 Canvas 元素更新的渲染流⽔线
普通 Canvas 元素更新的渲染流⽔线,跟其它 DOM 元素⼀样,Canvas 元素的更新也是⾛⾮合成器动
画的渲染流⽔线,主要的缺点是:⾮合成器动画的渲染流⽔线⽐较复杂和冗长,有较多的 Overhead,页⾯的结构越复杂,Overhead 就越⾼;
如果同时有其它 DOM 元素⼀起更新,Canvas 的更新会被其它 DOM 元素的光栅化所阻塞,导致性能下降,性能下降的幅度取决于其它 DOM 元素光栅化的耗时;
如果我们调⽤ Commit,并且 Commit 的 OffscreenCanvas 是跟当前 DOM 树⾥⾯的某个 Canvas 元素相关联,浏览器就会直接将OffscreenCanvas 的当前绘制缓冲区发送给 Display Compositor,然后 Display Compositor 就会合成新的⼀帧输出到当前窗⼝,对浏览器来说这就是最短的渲染路径。
Commit模式相对普通canvas的优点:
避免被主线程的其它任务所阻塞,Worker 线程可以完全专注在 Canvas 动画的运⾏上;
通过 OffscreenCanvas 更新 Canvas 元素,浏览器⾛的是最短的渲染路径,避免了⾮合成器动画的冗长流⽔线和 Overhead;
如果有其它 DOM 元素同时更新,不会阻塞 OffscreenCanvas 的更新,所以通过 OffscreenCanvas,的确实现了 Canvas 更新和其它 DOM 更新的并发运⾏;
如果 DOM 元素需要处理事件,这些事件处理不会被 Worker 线程所阻塞,只是处理的结果数据可能需要发送给 Worker 线程⽤于后续的绘制;
Commit模式的缺点:
OffscreenCanvas 的更新和其它 DOM 元素的更新不再是强制同步的。即使它们是同时更新,甚⾄都在主线程⽽不使⽤ Worker 线程,因为两者已经分别⾛了不同的流⽔线,最后呈现在屏幕的时机也可能不会完全⼀致。如果⼀定要求同步,就只能参考 Transfer Demo 的做法,将绘制后的缓冲区 Transfer 给 Bitmap Canvas 来显⽰,但是这样就⽆法发挥 Commit 模式的性能优势了。
2、commit渲染的具体流程
(1)主线程从当前 DOM 树中的 Canvas 元素⽣成 OffscreenCanvas;
(2)主线程启动 Worker 线程并初始化,OffscreenCanvas 作为初始化的参数被 Transfer;
var g_offscreen_canvas = null;
var g_render_worker = null;
function main() {
g_offscreen_canvas = helper.GetCanvas("offscreen");
g_render_worker = new Worker("./render_worker.js");
let offscreen = g_ansferControlToOffscreen();
g_render_worker.postMessage(
{name:"Init", mode:"commit", canvas:offscreen}, [offscreen]);
}
(3)Worker 线程接收 OffscreenCanvas 后完成初始化;
function Init(mode, data) {
if (mode === "transfer")
canvas = new OffscreenCanvas(data.width, data.height);
else if (mode === "commit")
canvas = data.canvas;
}
(4)Worker 线程使⽤ WebGL 对 OffscreenCanvas 进⾏绘制;
(5)Worker 线程绘制完成后 Commit,然后等待浏览器的回调;
function renderloop() {
render();
glmit().then(renderloop);
}
renderloop();
(6)Worker 线程接收到到浏览器的回调后继续绘制下⼀帧,重复 4 ~ 6;
注意:
canvas对象调⽤了函数transferControlToOffscreen移交控制权之后,不能再获取绘制上下⽂,调⽤Context('2d')会报错。
同样,如果canvas已经获取的绘制上下⽂,调⽤transferControlToOffscreen会报错。
Demo1
1、html主线程
<!doctype html>
<html>
<head>
</head>
<body>
<style>
.items {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
</style>
<html>
<head>
<meta charset="utf-8">
<title>player</title>
</head>
<body>
<canvas id="player" width="480px" height="280px" ></canvas>
</body>
</html>
<div class="items">
<button type="button" onClick="init()">start</button>
</div>
<script>
var worker2 = null;
var canvasBitmap=null;
var canvas2d=null;
var ctxBitmap=null;
function init() {
canvasBitmap = ElementById('player');
worker2 = new Worker('./bitmap_worker.js');
console.log("test.html create worker success");
⽅法⼀:transfer
//worker2.postMessage({name:"Init", mode:"transfer",width:canvasBitmap.width, height: canvasBitmap.height});
⽅法⼆:commit
let offscreen = ansferControlToOffscreen();
worker2.postMessage({name:'Init',mode:"commit",canvas:offscreen},[offscreen]); ssage = function (e) {
if(e.data.name==="TransferBuffer")
{
//⽅法⼀:transfer 2d渲染
// ctxBitmap = Context('2d');
// ctxBitmap.clearRect(0,0,canvasBitmap.width,canvasBitmap.height);
// ctxBitmap.save();
// ctxBitmap.drawImage(e.data.buffer,0,0);
// console.log("2d");
// store();
//⽅法⼆:transfer bitmaprenderer渲染
let bitmap_context = Context("bitmaprenderer");//bitmaprenderer console.log("bitmap_context:"+bitmap_context);
if(bitmap_context)
{
ansferFromImageBitmap(e.data.buffer);
console.log("end");
}
}
}
}
</script>
</body>
</html>
worker:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论