• home > webfront > visualization > rudiment >

    数据可视化性能优化(1):Canvas高性能优之OffscreenCanvas

    Author:zhoulujun Date:

    canvas是web绘制的一种常用优化方式,通常能更快地渲染大型场景或动画(可以利用 GPU 进行绘制操作)。canvas 可以通过OffscreenCanvas来进一步做性能优化

    系列开篇前言

    回顾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):

    我们看最后一个 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