Edited at

GPUを利用して高速演算を実行する

More than 1 year has passed since last update.

(Processing Advent Calendar 2016 25日目)


概要

ProcessingでGPUを利用した高速演算を行う。


背景

Processingを利用してスケッチを作ったものの、あまりにも速度が遅くて実用に耐えられなかったときありませんか。

Processingの標準機能を利用するのであれば、GLSLを利用してShaderを書くことで描画演算を高速に行うことができると思います。

しかし、Shader言語は難しくすでに作ってしまったスケッチを移植するのは難易度が高すぎるわけで。

そこでGPUの並列演算を利用しつつも、Processingらしさをなるべく崩さずにできないかとおもい、

JavaからGPGPU(OpenCL)を呼び出すライブラリであるaparapiを利用することにしました。


参考リンク

Aparapi

aparapi GitHub レポジトリ


実行方法


ライブラリのインストール

aparapiのライブラリをダウンロードします

aparapip5

スクリーンショット 2016-12-24 22.53.52.png

ダウンロードしたaparapip5-master.zipを解凍すると次のような階層になると思います。

スクリーンショット 2016-12-24 22.58.38.png

processingにライブラリをインストールします。

processingのユーザディレクトリを開き、次の階層になるように配置します。

スクリーンショット 2016-12-24 23.03.05.png

processingを起動し直すとロードされます。

サンプルにaparapiが表示されるようになります。

スクリーンショット 2016-12-24 23.07.39.png

ライブラリにもaparapiが登録されるようになります。

スクリーンショット 2016-12-24 23.11.09.png


プログラム


内容

スクリーンショット 2016-12-24 23.20.42.png

初期状態では利用可能であればOpenCL(GPUを利用した実装)で動きます、

クリックするたびにProcessing標準の動きとOpenCLを利用した動きが切り替わります。

動かした動画はvimeoにて公開しております


ソースコード


p5advent2016.pde

import com.amd.aparapi.*;

KernelP5 kernel;

long generations = 0;
long start = System.currentTimeMillis();

public void setup() {
size(1280, 768);
frameRate(240);
background(0);

fill(255);
loadPixels();

kernel = new KernelP5(width, height, pixels);
//lifeKernel.setExecutionMode(Kernel.EXECUTION_MODE.CPU);
updatePixels();
}
public void draw() {
//background(0);
kernel.nextGeneration();
updatePixels();

fill(0);
rect(0, 0, 100, 20);
fill(255);
text(frameRate, 10, 10);
println(this.kernel.getExecutionMode());
}

public void mouseMoved() {
kernel.mx = mouseX;
kernel.my = mouseY;
}

boolean GPUUsageflag = true;
public void mousePressed() {
if (GPUUsageflag==true) {
kernel.setExecutionMode(Kernel.EXECUTION_MODE.JTP);
} else {
kernel.setExecutionMode(Kernel.EXECUTION_MODE.GPU);
}
GPUUsageflag = !GPUUsageflag;
println(GPUUsageflag);
}



KernelP5.pde

public class KernelP5 extends Kernel {

private int[] imageData;
private final Range range;
private final int width;
private final int height;
private float time;
private float timed;

public int mx = 0;
public int my = 0;

public KernelP5(int _width, int _height, int[] pixels) {
width = _width;
height = _height;
imageData = pixels;
range = Range.create(width * height);
println(getExecutionMode());

setExplicit(true);
clear();
}

public void setImage(int[] pixels) {
imageData = pixels;
}

@Override
public void run() {
int gid = getGlobalId();

int tx = gid % width;
int ty = gid / width;
int red = 0;
int green = 0;
int blue = 0;
for (int i = 0; i < 12; i++) {
float xx = cos(toRadians(i * 30) + (time)) * my + width / 2;
float yy = sin(toRadians(i * 30) + (time)) * my + height / 2;
float dx = tx - xx;
float dy = ty - yy;

float dx2 = dx * dx;
float dy2 = dy * dy;

float dist = sqrt(dx2 + dy2);

if (dist > 512) {
continue;
} else {
dist = (512 - dist) / 2;
}
float r = 0;
r = abs(sin((mx / 500.0) * toRadians(dist * 5) + -time * 3) * dist);
if (i % 3 == 0) {
red += r;
} else if (i % 3 == 1) {
green += r;
} else {
blue += r;
}
}

red = min(red, 255);
green = min(green, 255);
blue = min(blue, 255);
imageData[gid] = 0xEE000000 + (red << 16) + (green << 8) + (blue);
}

public void nextGeneration() {
time -= abs(sin(toRadians(timed))) * 0.01;
timed += 1;
execute(range);
}

public int[] getImageData() {
return imageData;
}
}



解説

aparapiを利用した実装はKernelの無名クラスを作る方法と


test.pde

        final float inA[] = new float[]{1.0f, 2.0f, 3.0f, 4.0f};

final float inB[] = new float[]{0.1f, 0.2f, 0.3f, 0.4f};
final float result[] = new float[inA.length];

Kernel kernel = new Kernel() {
@Override
public void run() {
int i = getGlobalId();
result[i] = inA[i] + inB[i];
}
};

Range range = Range.create(result.length);
kernel.execute(range);


Kernelクラスを継承して作る方法があります。


test2.pde

public class FFTKernel extends Kernel {

final float inA[] = new float[]{1.0f, 2.0f, 3.0f, 4.0f};
final float inB[] = new float[]{0.1f, 0.2f, 0.3f, 0.4f};
final float result[] = new float[inA.length];

@Override
public void run() {
int i = getGlobalId();
result[i] = inA[i] + inB[i];
}
}


run()の中の実装が実行時にGPU(OpenCL)を利用するコードに変換されます。