离屏渲染

1,290 阅读3分钟

介绍:

离屏渲染(Offscreen Rendering)是一种常见的性能优化策略,用于减少图形渲染的时间和复杂性。在此模式下,所有的渲染操作都会在当前屏幕视图之外的内存区域进行。只有当所有的渲染操作完成后,才会将最终的图像传输到屏幕上。

这种方式的一个重要好处是可以避免屏幕闪烁或者不必要的重绘,因为所有的渲染工作都是在后台完成的,用户不会看到半成品的渲染结果。

HTML5 的 OffscreenCanvas 就是离屏渲染技术的一个实例。它允许开发者在 Web Worker 线程中执行渲染操作,然后再将结果显示到屏幕上。这样可以把渲染过程从主线程中剥离出去,避免阻塞 UI 的运行。

另外,在OpenGL或WebGL等3D渲染环境中,离屏渲染也被广泛使用。例如,开发者可能会创建一个离屏缓冲区(Framebuffer Object, FBO),在这个缓冲区里绘制3D场景,然后再将渲染结果呈现到屏幕上。这对于实现各种效果(如反射、阴影、模糊等)非常有用。

总的来说,离屏渲染是一个强大且灵活的工具,能提供更优雅和高效的渲染解决方案。但是,它也需要更多的内存和处理能力,所以在使用时需要权衡其利弊。

OffscreenCanvas 的基本使用示例如下:

首先,我们需要在主线程上创建 OffscreenCanvas,并传送给 worker:

var offscreen = document.querySelector('canvas').transferControlToOffscreen();
var worker = new Worker('worker.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);

接下来,在 worker 内部,我们可以像通常一样操作 Canvas:

// worker.js
self.onmessage = function(event) {
  var offscreen = event.data.canvas;
  var ctx = offscreen.getContext('2d');

  ctx.fillStyle = 'red';
  ctx.fillRect(0, 0, offscreen.width, offscreen.height);
};

企业使用案例:

  • 初始化需要绘制的canvas实例

    ```js
    initCanvas() {
    this.canvas = this.$refs[`canvas${this.cameraIndex}`]
    this.ctx = this.canvas.getContext("2d", { alpha: false })
    
    // to here
    // Globally init once 离屏画布
    this.image = new Image()
    
    this.offscreen_img_canvas = document.createElement("canvas")
    this.offscreenCtx = this.offscreen_img_canvas.getContext("2d", {
      alpha: false,
    }}
    
  • 离屏绘制具体步骤案例

drawImages: async function (info) {
     const begin = window.performance.now(),
       image = new Image(),
       { ctx, offscreen_img_canvas, offscreenCtx } = this
     if (this.current_concurrency >= this.concurrency_threshold) {
       return
     }
     this.current_concurrency++
     const t0 = performance.now()
     let blob = new Blob([info.matData], { type: "image/jpeg" })
     const t1 = performance.now()
     this.draw_duration_counter.register("getBlob", t1 - t0)
     image.src = (window.URL || window.webkitURL).createObjectURL(blob)
     image.onload = () => {
       this.current_concurrency--
       const t2 = performance.now()
       this.draw_duration_counter.register("imgLoad", t2 - t1)

       if (!this.draw_counter) {
         this.draw_counter = new GeneralCounter("draw")
       }
       this.draw_counter.count()

       // console.info(">>> onload")

       if (image.src) {
         window.URL.revokeObjectURL(image.src)
       }
       this.clock(begin, "Image Loaded Success!")
       offscreen_img_canvas.width = info.cols
       offscreen_img_canvas.height = info.rows

       // FIXME:自适应
       offscreenCtx.drawImage(image, 0, 0, info.cols, info.rows)
       const t3 = performance.now()
       this.draw_duration_counter.register("offCanvasImgDraw", t3 - t2)

       this.changeoffscreenCtx(info)
       this.drawGazes(offscreenCtx)
       this.drawHand && this.drawHand(offscreenCtx)
       this.drawFace && this.drawFace(offscreenCtx)
       const t4 = performance.now()
       this.draw_duration_counter.register("offCanvasMetaDraw", t4 - t3)

       ctx.clearRect(0, 0, info.cols, info.rows)
       ctx.drawImage(offscreen_img_canvas, 0, 0)

       const t5 = performance.now()
       this.draw_duration_counter.register("canvasDraw", t5 - t4)

       this.clock(begin, "Draw Success!")
       this.draw_duration_counter.register("total", t5 - t0)
     }
   },
   
  1. 使用来自外部信息 info 的尺寸设置离屏 canvas 的大小:
offscreen_img_canvas.width = info.cols;
offscreen_img_canvas.height = info.rows;
  1. 利用 drawImage() 方法在离屏 canvas 上绘制图像:
offscreenCtx.drawImage(image, 0, 0, info.cols, info.rows);
  1. 在离屏 canvas 上进行一些额外的绘制操作,如绘制注视点 (this.drawGazes(offscreenCtx))、手势 (this.drawHand && this.drawHand(offscreenCtx)) 和面部(this.drawFace && this.drawFace(offscreenCtx)):
this.changeoffscreenCtx(info);
this.drawGazes(offscreenCtx);
this.drawHand && this.drawHand(offscreenCtx);
this.drawFace && this.drawFace(offscreenCtx);
  1. 清除主 canvas,然后将离屏 canvas 的内容绘制到主 canvas 上:
javascript复制代码
ctx.clearRect(0, 0, info.cols, info.rows);
ctx.drawImage(offscreen_img_canvas, 0, 0);

以上这整个流程就是典型的离屏渲染。首先在内存中的 canvas (即离屏 canvas)上进行所有的绘制操作,这包括绘制图像和一些额外的元素(如注视点、手势、面部等)。然后只有当所有的绘制操作都完成后,才会把最终的图像渲染到页面上的 canvas 元素上。这种做法可以有效地减少浏览器重绘的次数,从而提高性能。