Instantly share code, notes, and snippets.

Embed
What would you like to do?
OffscreenCanvas, image.decode(), createImageBitmap(), WebWorker, transferToImageBitmap(), transferFromImageBitmap(), zero copy, Shape Detection API

個人的に 2012年頃(?)から渇望していた OffscreenCanvas と一連のAPIがついに Chrome に実装されました。

このエントリでは、UIの応答性を改善する OffscreenCanvas の仕組みと、 OffscreenCanvas を有効活用するための周辺APIについて、概要とサンプルコードを紹介していきます。

ネタ元はこちらです https://www.youtube.com/watch?time_continue=159&v=wkDd-x0EkFU

既存の <canvas> は DOM と強く結びついている事から UI Thread(= ブラウザにおける Main Thread) の影響をうけますし、反対に影響を与えてしまいます。

影響: UI Thread で重い処理を走らせてしまうと、アニメーションのフレームスキップが発生し、なめらかにスクロールしなくなり、UIの応答性が悪くなるなどの弊害が発生してしまいます。

New browser API

以下の新しいブラウザAPIを使用することで、これまで発生していた応答性に関する課題を改善することが可能になります。

  • const offcan = new OffscreenCanvas(width, height);
  • image.decode().then(() => {...});
  • const image = createImageBitmap(blob);
  • const offcan = canvas.transferControlToOffscreen(); worker.postMessage({ offcan }, [offcan]);
  • offcan.transferToImageBitmap()

image.decode

image.decode()image.onload に代わるAPIです。

従来から多用されてきた image.src = url; image.onload = () => {} は、 イメージのデコード処理を UI Thread で行うため、 大きな画像をデコードした場合に UI がギクシャクしてしまいます。

const image = new Image();
image.src = url;
image.onload = () => {
  // use
};

この課題を解決するため Chrome 64image.decode() が追加されています。

image.decode() はイメージのデコードを別スレッドで行い Promise を返します。

これで画像の大きさに応じてUIの応答性が悪化する可能性は無視できるほど小さくなるはずです。

const image = new Image();
image.src = url;
image.decode().then(() => { // decode by other thread
  // use
});

画像の非同期読み込み用に、 <image lazyload="auto/on/off"> という属性も最近追加されています。 https://www.bleepingcomputer.com/news/google/built-in-lazy-loading-lands-in-google-chrome-canary/

ImageBitmap

WebGL や 2D Canvas で使用する画像を効率的に作成するため Chrome 50createImageBitmap() が追加されています。

(async() {
  const image = createImageBitmap( await ( await fetch(url) ).blob() );
  // use image
})();
(async() {
  const resp = await fetch(url);
  if (resp.ok) {
    const blob = await resp.blob();
    const image = createImageBitmap(blob);
    // use image
  }
})();

ImageBitmap は canvas に ctx.drawImage(bitmap) として貼り付ける事ができます。

OffscreenCanvas

新しいAPI new OffscreenCanvas(width, height) を使うと DOM から切り離された Canvas を作成することができます。

DOM から切り離すという点が重要でする。DOM から切り離された事により以下のメリットが得られます。

  • すべてが Canvas で構成されている場合は DOM Tree と Canvas を合成するための時間が不要になります。ゲームでは特に有効でしょう
  • 専用の Worker でレンダリングが可能になりました。時間がかかる処理を別スレッドで実行できる事になり、UI Thread が混雑していても影響を受けません
  • UI Thread と Worker のやり取りはポインタのコピーの形で行われます。ゼロコピーとして実装されており、とても効率的です

以下の例では、DOM Tree 上に存在する <canvas class="surface"> を OffscreenCanvas でレンダリングできるように Workerにポインタをシェアしています。

// main.js
const canvas = document.querySelector(`.surface`); // <canvas class="surface"></canvas>
const offcan = canvas.transferControlToOffscreen(); // pointer
const worker = new Worker(`worker.js`);
worker.postMessage({ offcan: offcan }, [ offcan ]);
// worker.js
const ctx;
self.onmessage = (msg) => {
  ctx = msg.data.offcan.getContext(`2d`);
  update();
};
const update = () => {
  ctx.commit().then(update);
};

ImageBitmap on Worker

Worker上でbitmapを作成し、UI Thread や WebGL 側で bitmapを使用することができます。こちらもゼロコピーです

// worker.js
const offcan = new OffscreenCanvas(width, height);
const offctx = offcan.getContext(`2d`);

// rendering to offctx

const image = offcan.transferToImageBitmap();
self.postMessage({ image: image }, [ image ]); // 移譲(imageの所有権は受け取った側に移ります)

bitmaprenderer context

canvas.getContext("bitmaprenderer") が追加されており、bitmap イメージの送受信が可能になっています。

const snapshot = backgroundCanvas.transferToImageBitmap();
const ctx = viewCanvas.getContext(`bitmaprenderer`);
ctx.transferFromImageBitmap(snapshot);

Shape Detection API

OffscreenCanvas は Chrome 70 で実験的に追加された Shape Detection API と組み合わせて使用するとさらに強力になるでしょう。

//TODO: Sample code

Link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment