WebCLImageの話


# はじめに

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

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

* [PyOpenCLでアプリケーション(PIL & PyOpenGL)](http://qiita.com/_likr/items/f0f99c08f42731ab9eeb)

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

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

* [WebCLのデモを動かす](http://qiita.com/_likr/items/bf135a7d0f523ecf6944)
* [WebCLプログラミング入門](http://qiita.com/_likr/items/a4c34a906e0e3d2d2843)

# カーネル関数

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

```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オブジェクトを作成します。

```javascript
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つの属性を持ち、`width`と`height`はそのまま画像の幅と高さ、`channelOrder`はピクセルの表現形式、`channelType`はピクセルのバイナリ形式になります。
`rowPitch`は第三引数を与えない場合は0である必要があります。

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

## WebCLImageのデータ転送

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

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

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

# Canvasとの連携

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

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

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

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

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

# デモ

[ソースコード全体](http://github.com/likr/webcl20141208)と[実行できる環境](http://likr.github.io/webcl20141208/)を公開しています。

動かすとこんな感じになるはずです。
![kobito.1418034344.432105.png](https://qiita-image-store.s3.amazonaws.com/0/4249/261b56b6-eac2-1264-6943-206d6e47a21d.png "kobito.1418034344.432105.png")


# おわりに

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

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