数据可视化性能优化(1):Canvas高性能优之OffscreenCanvas
Author:zhoulujun Date:
系列开篇前言
回顾GEM重构,之前写《从版式与交互设计的角度提升前端页面》、《再谈JavaScript垃圾回收机制:分析与排查JS内存泄露情形》、《图表列表性能优化:可视化区域内最小资源消耗》,但是关于项目的核心(数据可视化)如何优化一直没空写,这方面要写的东西太多,准备出一个专题,叫数据可视化性能优化系列,但是规划起来,发现这个有太多技巧性东西,比如:避免浮点数的坐标点、不要在用drawImage时缩放图像、 关闭透明度、canvas分层渲染……这里抽一些重点来讲
探索canvas渲染瓶颈
canvas是web绘制的一种常用优化方式,通常能更快地渲染大型场景或动画(可以利用 GPU 进行绘制操作)。比如小程序在某些场合就使用canvas
对于需要高性能的图形渲染,例如游戏或者复杂的动画,它可以直接调用图形API,绕过DOM的复杂性,减少渲染负担。
对于交互性强、需要频繁更新的界面,Canvas可能更合适,它可以更灵活地处理像素级的更新。
其实对于频繁的大面积的更新,canvas也是扛不住的,但有了 OffscreenCanvas 离屏(处理I/O操作瓶颈,并发/异步) ,就能更好地解决这个问题。
https://developer.mozilla.org/zh-CN/docs/Web/API/OffscreenCanvas
OffscreenCanvas 是能够离屏渲染的 canvas,它也是 canvas。也就是说,canvas 支持的 API, OffscreenCanvas 也支持。其次,OffscreenCanvas 同时支持在 window 环境和 worker 环境中使用。
其是就是利用并发,启用多个worker并发渲染(OffscreenCanvas 渲染绘制的内容会实时同步到主线程中的 canvas )
只要是主线程中涉及 canvas 绘制复杂计算逻辑与绘制( OffscreenCanvas ),都可以迁移到 worker 中执行(原来只有计算能)。
如何并发渲染
在 worker 中创建 OffscreenCanvas,执行计算绘制逻辑结束后,再将内容传递回主线程。
内容如何回传主线程
先看下:https://developer.mozilla.org/zh-CN/docs/Web/API/ImageBitmap
我们回顾下canvas.getContext()参数(大部分同学只知道2d):
"2d", 建立一个 CanvasRenderingContext2D 二维渲染上下文。
"webgl" (或"experimental-webgl") 这将创建一个 WebGLRenderingContext 三维渲染上下文对象。只在实现WebGL 版本 1(OpenGL ES 2.0) 的浏览器上可用。
"webgl2" (或 "experimental-webgl2") 这将创建一个 WebGL2RenderingContext 三维渲染上下文对象。只在实现 WebGL 版本 2 (OpenGL ES 3.0) 的浏览器上可用。实验性
"bitmaprenderer" 这将创建一个只提供将 canvas 内容替换为指定ImageBitmap功能的ImageBitmapRenderingContext 。
我们看最后一个 bitmaprenderer,了解一下 ImageBitmapRenderingContext
https://developer.mozilla.org/zh-CN/docs/Web/API/ImageBitmapRenderingContext
ImageBitmapRenderingContext 接口是 canvas 的渲染上下文,它只提供了使用给定 ImageBitmap 替换 canvas 的功能。它的上下文 ID (HTMLCanvasElement.getContext() 或 OffscreenCanvas.getContext() (en-US) 的第一个参数) 是 "bitmaprenderer"。
这个接口可用于 window context 和 worker context.
我们再来看一下 ImageBitmap
https://developer.mozilla.org/zh-CN/docs/Web/API/ImageBitmap
ImageBitmap 接口表示能够被绘制到 <canvas> 上的位图图像,具有低延迟的特性。运用 createImageBitmap() (en-US) 工厂方法模式,它可以从多种源中生成。 ImageBitmap提供了一种异步且高资源利用率的方式来为 WebGL 的渲染准备基础结构。
目前Three.js、Babylon.js等3D引擎以及pixi.js这样的2D引擎,均已使用ImageBitmap来提高性能,下面给一个参考例子:
// 假设我们有一个 canvas 元素和 WebGL 上下文 const canvas = document.getElementById('myCanvas'); const gl = canvas.getContext('webgl'); // 创建一个纹理 const texture = gl.createTexture(); // 加载图像 fetch('path/to/image.jpg') .then(response => response.blob()) .then(blob => createImageBitmap(blob)) .then(imageBitmap => {// 普通渲染,直接: ctx.drawImage(imgBitmap, 0, 0); // 将 ImageBitmap 绑定到纹理 gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imageBitmap); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.bindTexture(gl.TEXTURE_2D, null); // 现在可以使用这个纹理进行渲染了 // 释放资源,降低内存消耗 imgBitmap.close(); }) .catch(error => { console.error('Error loading image:', error); });
注意,使用 ImageBitmap 绘制完后,需要调用 close 方法用来释放资源,降低内存消耗。
为什么不用ImageData ?
ImageBitmap:这是一个高级接口,代表了一个硬件加速的二维图像——是一个位图图像,它表示一个矩形区域的像素数据,可以直接用于绘制。所以通常占用更少的内存,绘制更快!但是兼容性需要仔细考量!
ImageData: 这是一个更低级的接口,它直接与原始像素数据一起工作——一个包含像素级数据的对象,它包含一个 Uint8ClampedArray 类型的 data 属性,用于存储 RGBA 像素值。
如果你需要对图像进行详细的像素级操作,比如图像分析(比如滤镜、分析或者其他图像操作)或者应用复杂的图像处理算法,那么 ImageData 更适合。但是,的像素数据可能会有较大的性能开销,特别是在处理大图像时,因为所有操作都在主线程上进行。其次,ImageData 对象可能会使用更多的内存,因为它包含了未经压缩的像素数据。
而ImageBitmap 提供了一种异步且高效的方式来处理图像。它允许在 Web Workers 中进行图像的解码,不会阻塞主线程。其次,ImageBitmap 对象是经过浏览器优化的(占用内存更少),直接用于 <canvas> 元素或 WebGL 的纹理时渲染时非常高效。
这里可以参考下:《技术解码 | Web端AR美颜特效性能优化》
Worker是给Web提供多线程运行的一种简单的解决方案,Worker在后台独立执行,不会干扰主界面。但worker不能直接访问和操作DOM元素,就需要主线程在每帧渲染的时候把当前帧手动发送给Worker。直接传输文本数据给Worker时,是将文本数据复制一份发送到Worker,在数据量大的场景里效率很低,因此不能通过传输画面buffer的方式。可以通过createImageBitmap方法将常见的HTMLMediaElement转成ImageBitmap。
OffscreenCanvas/Worker 能做哪些优化?
这里抛砖引玉哈,比如柱线图的 对比分析、监控极值、阈值等各类动态警告标注,如果渲染压力大情况,可以先绘制基础数据,然后再绘制后面的数据。
具体参看:https://g-next.antv.vision/api/canvas/offscreen-canvas-ssr
第二类,比如h5游戏、视频播放各类 动态字母、动画 也可以使用这个方案。
这里可以参考下:《技术解码 | Web端AR美颜特效性能优化》
Worker是给Web提供多线程运行的一种简单的解决方案,Worker在后台独立执行,不会干扰主界面。但worker不能直接访问和操作DOM元素,就需要主线程在每帧渲染的时候把当前帧手动发送给Worker。直接传输文本数据给Worker时,是将文本数据复制一份发送到Worker,在数据量大的场景里效率很低,因此不能通过传输画面buffer的方式。可以通过createImageBitmap方法将常见的HTMLMediaElement转成ImageBitmap。
第三类,比如表格(其是也属于图表类),典型的antv s2,其是可以用 离屏渲染来优化的。
虚拟滚动:主要解决了 DOM 元素“量”的问题,并没有解决“频繁”操作的问题。快速滚动时,涉及大量计算和频繁 DOM 操作,只能通过“节流”来妥协,一方面会带来滚动过程中的白屏问题,另一方面资源占用波动较大,影响体验。
Canvas:只能工作在主线程,且绘制之前需要进行大量的计算。更主要的问题是画布的交互体验远不及 DOM。
Web Worker:问题主要在通信性能上。跨线程传输前后,浏览器会自动对数据进行序列化和反序列化,当数据量较大时,性能开销不容小觑。同时,这也意味着,不能被序列化的数据类型无法跨线程传输。另一方面,在 Web Worker 线程中不能直接操作 DOM,因此涉及 DOM 操作时又无法避免与主线程的频繁通信。
通过 OffscreenCanvas、Canvas、Web Worker、MessageChannel、Shadow DOM、DOM、Fetch、requestAnimationFrame 等系列技术,是非常好的 图表渲染优化方案
转载本站文章《数据可视化性能优化(1):Canvas高性能优之OffscreenCanvas》,
请注明出处:https://www.zhoulujun.cn/html/webfront/visualization/rudiment/9082.html