プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

JOGL,JOCLのEclipse環境を整える

 2年ぶりぐらいにJOCL書くかーって思って環境整えようとしたら結構手惑いました。私が利用していたのはJogAmpだったので、今回もJogAmpのJOCLを使おうと思ったんですが、なんか、JOGLが結構2年前と変わってますね。櫻庭さんのJOGLの記事とか、今とインターフェースで定義されてるメソッド違うみたいだし、でもあまり日本語出てこないしで、一応記事にしておきます。



ダウンロード

 まずは、必要なファイルをダウンロードしてきます。JogAmpの「Builds / Downloads」の項目にあるCurrentのzipへ進みます。URLが変わっていなかったらこのリンクです。





 そこからjogamp-all-platforms.7zをダウンロードします。同様にURLが変わっていなかったらこのリンクです。




 ダウンロードしたファイルを適当なディレクトリに展開しておきましょう。私はJavaのライブラリは全てC:\javapathの下に置くようにしています。必須ではないですが、GlueGen,JOGL,JOCLのそれぞれのJavaDocもダウンロードしておくと良いでしょう。あって困ることはありません。
 展開すると、全プラットフォーム入っているのでずらっと並びますね。


 必要でないのが並ぶのが嫌で、必要不要が分からない場合は、ここから必要な物を個別にダウンロードするといいでしょう。必要なのはglugen,jogl,joclのビルドです。


JOGLをEclipseで使えるようにする

 まずは、EclipseのユーザーライブラリーにGlueGenを登録しましょう。GlueGenはCライブラリーから自動でJNIを生成する為の物です。ユーザーライブラリー「GlueGen」を作成して、外部Jar追加からgluegen-rt.jarを追加します。必要ならば、gluegen-rt.jarにネイティブ・ライブラリーの設定と、JavaDocの設定をしてください。私の環境だとネイティブ・ライブラリーの設定はしなくても普通に動くんですが、Pathとかの影響がるのかどうかよく分からないので。基本的には自動でgluegen-rt-native-〜.jarを検索してくれるので必要ないと思う。

 なお、このとき、gluegen.jarは絶対に入れないで下さい。あるとgluegen-natives-〜探しに行ってしまうので、動作しなくなります。私はここで大いに詰まった。





 次にJOGLのユーザーライブラリーを登録します。必要なのは、jogl-all.jar,jogl-all-noawt.jarです。最後のは一応newtが使いたいときの為に入れておくと良いでしょう。必要ならば、jogl-all.jarにネイティブ・ライブラリーの設定と、JavaDocの設定をしてください。




 なお、上の画像でアクセスルールが設定してあるのは、swt系のクラスが真っ先に補間候補に出やがるのがうっとうしかったので、消す為に入れてあります。「**/swt/**」です。



テストコードを動かしてみる

 JOGLが動くかどうかのテストをしてみましょう。適当なプロジェクトを作り、先ほど作成したGlueGenとJOGLのユーザーライブラリをインポートしておきます。

 動かすコードは櫻庭さんの記事を2013年のバージョンで動くようにした物です。test.TestGL.javaに以下の内容を記述して実行してみてください。

package test;

//面倒くさいので、GL2とGLはstatic importしておきましょう。
//*GL2必須
import static javax.media.opengl.GL.*;
import static javax.media.opengl.GL2.*;

import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.awt.GLCanvas;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import com.jogamp.opengl.util.gl2.GLUT;

/**
 * 櫻庭さんの2006年の記事
 * http://itpro.nikkeibp.co.jp/article/COLUMN/20060718/243509/?ST=develop
 * を2013年で動くようにした物です。
 * @author nodamushi
 */
public class TestGL implements GLEventListener{
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        new TestGL();
      }
    });
  }

  //バージョンはGL2以降を利用しないとメソッドが定義されていない
  private GL2 gl;
  private GLUT glut;

  public TestGL() {
    JFrame frame = new JFrame("Simple Cube");

    //3Dを描画するコンポーネント
    //com.jogamp.opengl.swt.GLCanvasってのもあるので
    //インポートを間違えないように。
    //使わないし、swt以下にはアクセス禁止設定した方が楽かも
    GLCanvas canvas = new GLCanvas();
    canvas.addGLEventListener(this);

    frame.add(canvas);
    frame.setSize(300, 300);
    frame.setDefaultCloseOperation(
        JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
  }

  @Override
  public void display(GLAutoDrawable drawable) {
    gl.glClear(GL_COLOR_BUFFER_BIT);
    // 大きさ 2 の線画の立方体を描画
    glut.glutWireCube(2.0f);
  }

  @Override
  public void dispose(GLAutoDrawable drawable) {

  }

  @Override
  public void init(GLAutoDrawable drawable) {
    gl=drawable.getGL().getGL2();
    glut = new GLUT();
  }
  @Override
  public void reshape(GLAutoDrawable drawable,
      int x, int y, int width,int height) {
    float ratio = (float)height / (float)width;
    gl.glViewport(0, 0, width, height);

    //定数はGLではなく、GL2にあります。
    //(正確にはjavax.media.opengl.fixedfunc.GLMatrixFuncみたい)
    gl.glMatrixMode(GL_PROJECTION);
    gl.glLoadIdentity();
    gl.glFrustum(-1.0f, 1.0f, -ratio, ratio,
        5.0f, 40.0f);

    gl.glMatrixMode(GL_MODELVIEW);
    gl.glLoadIdentity();
    gl.glTranslatef(0.0f, 0.0f, -20.0f);
  }
}




 実行結果はこんな感じです。




JOCLを使えるようにする

 JOCLのユーザーライブラリーも同様に定義します。必要なのは、jocl.jarです。
必要ならばjocl.jarにネイティブ・ライブラリーの設定と、JavaDocの設定をしてください。






JOCLのテストをする

 適当なプロジェクトを作成し、GlueGenとJOCLのユーザーライブラリーをインポートしておきます。
 

 以下のソースコードを貼り付け、実行できればテストは成功です。ソースコードはJOCL Tutorialを多少改変した物です。


package test;

import static com.jogamp.opencl.CLMemory.Mem.*;

import java.nio.FloatBuffer;
import java.util.Random;

import com.jogamp.opencl.CLBuffer;
import com.jogamp.opencl.CLCommandQueue;
import com.jogamp.opencl.CLContext;
import com.jogamp.opencl.CLDevice;
import com.jogamp.opencl.CLKernel;
import com.jogamp.opencl.CLProgram;
/**
 * JOCL Tutorial
 * http://jogamp.org/wiki/index.php/JOCL_Tutorial
 * を日本語にしただけ。
 * @author nodamushi
 */
public class TestCL {
  
  //二つの配列を足し算するだけのカーネルプログラム
  public static final String KERNEL_CODE=
        "kernel void add(global const float* inputA,\n"
      + "                global const float* inputB,\n"
      + "                global float* output,\n"
      + "                uint numElements){\n"
      + "  size_t gid = get_global_id(0);\n"
      + "  if(gid>=numElements){return;}\n"
      + "  output[gid] = inputA[gid]+inputB[gid];\n"
      + "}";
  
  
  public static void main(String[] args) {
    
    CLContext context = CLContext.create();
    
    try{
      //デバイスの取得
      CLDevice device = context.getMaxFlopsDevice();
      System.out.println("use device:"+device);
      
      //デバイス上のコマンドキューを作成
      CLCommandQueue queue = device.createCommandQueue();
      
      // 上のソースコードからプログラムを作成します
      CLProgram program = context.createProgram(KERNEL_CODE).build();

      
      //---データの作成処理----
      
      //有効なデータ数
      int elementCount = 1444477;
      //ローカルワークグループの数を決めます。
      int localWorkSize = Math.min(device.getMaxWorkGroupSize(), 256);
      // localWorkSizeで割り切れる、elementCount以上の最小の値をグローバルワークサイズとします
      int globalWorkSize = roundUp(localWorkSize, elementCount);


      //global const float* inputAになります。
      CLBuffer<FloatBuffer> clBufferA = context.createFloatBuffer(globalWorkSize, READ_ONLY);
      //global const float* inputBになります。
      CLBuffer<FloatBuffer> clBufferB = context.createFloatBuffer(globalWorkSize, READ_ONLY);
      //global float* outputになります
      CLBuffer<FloatBuffer> clBufferC = context.createFloatBuffer(globalWorkSize, WRITE_ONLY);

      System.out.println("デバイスメモリー使用量: "
          + (clBufferA.getCLSize()+clBufferB.getCLSize()+clBufferC.getCLSize())/1000000 +"MB");

      // ランダムな値でデータを埋めます
      fillBuffer(clBufferA.getBuffer());
      fillBuffer(clBufferB.getBuffer());

      //---データ作成終了-----
      
      // CLProgramからadd関数のカーネルを作成
      CLKernel kernel = program.createCLKernel("add");
      //渡す引数の設定をします
      kernel.putArgs(clBufferA, clBufferB, clBufferC).putArg(elementCount);

      // 実際に実行します
      long time = System.nanoTime();
      queue.putWriteBuffer(clBufferA, false)//まずはCPU側のメモリにあるデータをデバイスに書き込みます
           .putWriteBuffer(clBufferB, false)//falseは非同期という意味です
           //カーネルプログラムを走らせます
           .put1DRangeKernel(kernel, 0, globalWorkSize, localWorkSize)
           .putReadBuffer(clBufferC, true);//結果をCPU側のメモリに読み込みます
      time = System.nanoTime() - time;

      // 結果を適当に出力
      System.out.println("a+b=c results snapshot: ");
      for(int i = 0; i < 10; i++)
        System.out.print(clBufferC.getBuffer().get() + ", ");
      System.out.println("...; " + clBufferC.getBuffer().remaining() + " more");

      System.out.println("計測時間: "+(time/1000000)+"ms");
    }finally{
      context.release();
    }
  }

  private static void fillBuffer(FloatBuffer buffer) {
    Random rnd = new Random(System.nanoTime());
    while(buffer.remaining() != 0)
      buffer.put(rnd.nextFloat()*100);
    buffer.rewind();
  }

  private static int roundUp(int groupSize, int globalSize) {
    int r = globalSize % groupSize;
    if (r == 0) {
      return globalSize;
    } else {
      return globalSize + groupSize - r;
    }
  }
}