WebCLImageの話


はじめに

WebGL Resource Sharingの話をすると言った気がしますが、実装されてなさそうなのでWebCLImageの話をします。

WebCLImageはOpenCLのImage Objectsに相当する機能です。
Image Objectsを使うとカーネルで画像を扱うのが少し便利になったりすると昔書いたことがある気がします。

本稿ではWebCLImageのAPIやCanvasとの連携方法などを実例を交えて紹介していこうと思います。
実例としては、安易に、マンデルブロ集合でも書いてみましょうか。

WebCLの環境構築や基本的なAPIについては過去の記事も参照してください。

カーネル関数

マンデルブロ集合の計算をするカーネルを適当に用意します。

kernel.cl
__kernel void mandelbrot(
  write_only image2d_t out,
  float xStart,
  float yStart,
  float xStop,
  float yStop,
  int max_iter,
  float threshold
)
{
  const int i = get_global_id(0);
  const int j = get_global_id(1);
  const int width = get_global_size(0);
  const int height = get_global_size(1);
  const float cr = (xStop - xStart) / width * i + xStart;
  const float ci = (yStop - yStart) / height * j + yStart;
  float zr = 0.0f;
  float zi = 0.0f;
  float zrzi, zr2, zi2;
  int k;
  for(k = 0; k < max_iter; k++) {
    zrzi = zr * zi;
    zr2 = zr * zr;
    zi2 = zi * zi;
    zr = zr2 - zi2 + cr;
    zi = zrzi + zrzi + ci;
    if(zi2 + zr2 >= threshold) {
      break;
    }
  }
  const int level = 255 - 255 * k / max_iter;
  const int2 pos = (int2)(i, j);
  const uint4 pixel = (uint4)(level, level, level, 255);
  write_imageui(out, pos, pixel);
}

注目するところは下3行です。
WebCLで画像を扱うには、Uint8Arrayで画像のバイト列を表現してやることでもできます。
しかし、WebCLImageとwrite_imageuiなどの関数を使うことで画像のバイナリ表現から抽象化されたピクセルの書き込みができます(読み込みも同様)。
カーネル関数内部についてはOpenCLと同様なので深く説明はしません。

WebCLImageのAPI

WebCLImageを扱うホスト側のAPIの説明に入ります。
といっても特別なのはWebCLImageの作成と転送ぐらいしかありません。

WebCLImageオブジェクトの作成

WebCL ContextオブジェクトのcreateImage関数でWebCLImageオブジェクトを作成します。

var image = context.createImage(WebCL.MEM_WRITE_ONLY, {
  channelOrder: WebCL.RGBA,
  channelType: WebCL.UNSIGNED_INT8,
  width: width,
  height: height,
  rowPitch: 0
});

第一引数はcreateBufferと同様のmemFlagsで、第二引数がWebCLImageDescriptorと呼ばれるオブジェクトになります。
WebCLImageDescriptorは5つの属性を持ち、widthheightはそのまま画像の幅と高さ、channelOrderはピクセルの表現形式、channelTypeはピクセルのバイナリ形式になります。
rowPitchは第三引数を与えない場合は0である必要があります。

第三引数にホスト側の画像Bufferを与えるとその内容がデバイスにコピーされます。
その場合、descriptorのrowPitchを設定してやる必要があります。
rowPitchは画像の1行のバイト数を表します。

WebCLImageのデータ転送

WebCLImageに関係するデータ転送関数としてenqueueCopyImageenqueueCopyImageToBufferenqueueCopyBufferToImageenqueueReadImageenqueueWriteImageがCommandQueueオブジェクトに用意されています。
大体名前で想像がつくと思いますが、今回使用したenqueueReadImageは以下のように使います。

queue.enqueueReadImage(image, false, [0, 0], [width, height], width * 4, pixels.data);

第一引数が転送元のWebCLImageオブジェクト、第三引数が転送する領域の開始点、第四引数が転送する領域のサイズ、第五引数が前述のrowPitch、第六引数が転送先のBufferです。

Canvasとの連携

WebCLで計算したマンデルブロ集合の画像をHTMLのCanvas要素を使って表示させてみます。
CanvasのImageDataオブジェクトをホスト側Bufferとして使うことができます。
ImageDataオブジェクトは以下のように作成します。

var canvas = document.getElementById('#display');
var canvasContext = canvas.getContext('2d');
var pixels = canvasContext.createImageData(width, height);

enqueueReadImageだけで、デバイス側BufferからImageDataオブジェクトにピクセルを読み込むことができます。
前述のものと同じですが、以下のようなコードです。

queue.enqueueReadImage(image, false, [0, 0], [width, height], width * 4, pixels.data);

ImageDataオブジェクトのdata属性を転送先Bufferとして指定します。

デモ

ソースコード全体実行できる環境を公開しています。

動かすとこんな感じになるはずです。

おわりに

今回は紹介できませんでしたが、WebCLImageを使うことでピクセルの補間計算なども簡単に行えるようになるので画像を扱う上ではなかなか便利だと思います。

Advent Calendar 9日目は@tap4453さんです。