LoginSignup
3
3

More than 5 years have passed since last update.

SIMD.jsで画像処理してみたよ

Last updated at Posted at 2016-03-06

やったこと

SIMD.jsでどのくらい高速化できるのか、簡単な画像処理で試してみました。
やることは単純で、Canvasから取得したRGBAをYUVに変換した後にガンマ補正を掛け、YUVからRGBAに戻すだけです。ガンマ補正はLUTを当てるだけでSIMDに乗せられないので、RGBA/YUV変換がSIMD化の対象となりますが、とりあえずRGBAからYUVへの変換だけSIMD化しました。

Windows 10 + Firefox Nightly 47.0a1で動作確認しています。Edgeでも設定を変えるとSIMDが使えるのですが、ビットシフトに対応していないようなので動かないです。

ソース: https://github.com/arenahito/simdjs-example
デモ: http://arenahito.github.io/simdjs-example/

SIMD化前

まずは、SIMDなしで作ります。
RGBAからYUVに変換するだけですが、YUVを加工しやすいようにPlaner形式としています。RGBA→RGBA→……からYYY……→UUU……→VVV……となります。
あと、Alphaはいらないので捨てています。YUVからRGBAに戻すときは固定値を設定する感じで。

function convertRgbaToYuv(rgba, width, height, output) {
  var imageSize = width * height;

  for (var y = 0; y < height; y++) {
    var rgbaOffsetY = y * width * 4;
    var yuvOffsetBaseY = y * width;

    for (var x = 0; x < width; x++) {
      var rgbaOffset = rgbaOffsetY + x * 4;
      var yuvOffsetBase = yuvOffsetBaseY + x;

      var r = rgba[rgbaOffset + 0];
      var g = rgba[rgbaOffset + 1];
      var b = rgba[rgbaOffset + 2];
      output[yuvOffsetBase + imageSize * 0] = (306 * r + 601 * g + 117 * b) >> 10;
      output[yuvOffsetBase + imageSize * 1] = (-173 * r - 339 * g + 512 * b + 131072) >> 10;
      output[yuvOffsetBase + imageSize * 2] = (512 * r - 429 * g - 83 * b + 131072) >> 10;
    }
  }
}

SIMD化後

SIMD化してみます。
Int32x4にRGBの3バイトを乗せてYUVに変換します。1バイト分余っている上に、3並列だと性能に期待できないんじゃ・・・とか言ってはいけません。
RGB→Y、RGB→U、RGB→Vと計算するのでは無く、RGBそれぞれについて計算した後に足し込みます。こうしないと加算処理にSIMDを使えないので。

function convertRgbaToYuvSimd(rgba, width, height, output) {
  var imageSize = width * height;
  var arrayYUV = new Int32Array(4);

  var vR0 = SIMD.Int32x4(306, -173, 512, 0);
  var vG0 = SIMD.Int32x4(601, -339, -429, 0);
  var vB0 = SIMD.Int32x4(117, 512, -83, 0);
  var vUVOffset = SIMD.Int32x4(0, 131072, 131072, 0);

  for (var y = 0; y < height; y++) {
    var rgbaOffsetY = y * width * 4;
    var yuvOffsetBaseY = y * width;

    for (var x = 0; x < width; x++) {
      var rgbaOffset = rgbaOffsetY + x * 4;
      var yuvOffsetBase = yuvOffsetBaseY + x;

      var r = rgba[rgbaOffset + 0];
      var g = rgba[rgbaOffset + 1];
      var b = rgba[rgbaOffset + 2];

      var vR1 = SIMD.Int32x4.mul(SIMD.Int32x4.splat(r), vR0);
      var vG1 = SIMD.Int32x4.mul(SIMD.Int32x4.splat(g), vG0);
      var vB1 = SIMD.Int32x4.mul(SIMD.Int32x4.splat(b), vB0);

      var vRGB1 = SIMD.Int32x4.add(vR1, vG1);
      var vRGB2 = SIMD.Int32x4.add(vRGB1, vB1);
      var vRGB3 = SIMD.Int32x4.add(vRGB2, vUVOffset);
      var vRGB4 = SIMD.Int32x4.shiftRightByScalar(vRGB3, 10);

      SIMD.Int32x4.store(arrayYUV, 0, vRGB4);
      output[yuvOffsetBase + imageSize * 0] = arrayYUV[0];
      output[yuvOffsetBase + imageSize * 1] = arrayYUV[1];
      output[yuvOffsetBase + imageSize * 2] = arrayYUV[2];
    }
  }
}

試してみて

RGBAからYUVに10回変換するのに掛かった時間を測ってみました。SIMDなし145ms、SIMDあり170ms。まあ、あまり効率的にSIMD化出来てないですし・・・。

処理性能はおいておくとして、そもそもサポートされている機能が貧弱なので使いづらいです。SSEとかのIntel系のは使ったことが無いのでわかりませんが、Neonと同じくらいの機能は欲しいなと。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3