RocketBoards.orgのOpenCL Mandelbrot Demo on Alteraを読んで試してみた,という話.
ソースコード
https://github.com/altcrauer/mandelbrot_demo で公開されている.
プログラムはARM上で実行されるものとFPGAで実行されるものから構成される.
ARM上で実行されるもの
- main.cpp
- colorTableInit 色テーブル[0~999]の初期化
- main 初期化と実行
- MandelbrotWindow.cpp マンデルブロー描画の処理全体の統括
- mandelbrotWindowInitialize hardwareInitializeとsoftwareInitializeを呼び出す.また,SDL_CreateRGBSurfaceFromを呼び出してマンデルブロー画像保持用のバッファを2面用意(バッファへのポインタはtheFrames[0], theFrames[1]で保持される)
- mandelbrotWindowRelease
- mandelbrotWindowResetView
- mandelbrotWindowUpdate
- mandelbrotWindowMainLoop マンデルブローの計算と描画処理のループ
- mandelbrotCalculateFrameを呼び出してtheFrames[]に画像を書かせる
- 結果をディスプレイに出力する,ファイルに保存する,など
- mandelbrotWindowRepaint
- mandelbrotDumpFrame
- Mandelbrot.cpp
- mandelbrotSetColorTable
- mandelbrotSwitchCalculationMethod マンデルブロー計算をHWで行うかSWで行うか切り替える.デフォルトはHW.実行中にSDLK_h(SDLのhキー押下イベント)で切り替え.
- mandelbrotCalculateFrame マンデルブロー計算をHWまたはSWで実行
- mandelbrotRelease
- HardwareMandelbrot.cpp
- hardwareInitialize FPGAにマンデルブロー計算カーネルをロード
- hardwareSetColorTable
- hardwareCalculateFrame FPGA上のカーネルでマンデルブロー計算
- hardwareRelease
- cleanup opencl.cppで利用されるクリーンアップ関数.実際はhardwareReleaseを呼んでいる
- SoftwareMandelbrot.cpp
- softwareInitialize HardwareInitializeと違って何もしてない
- softwareSetColorTable
- softwareCalculateFrame ソフトウェアでのマンデルブロー計算
- softwareRelease
- opencl.cpp OpenCLコアを利用するためのユーティリティ
- setCwdToExeDir
- findPlatform
- getPlatformName
- getDeviceName
- getDevices
- createProgramFromBinary
- createProgramFromSource
- loadBinaryFile
- fileExists
- getBoardBinaryFile
- getCurrentTimestamp
- getStartEndTime
- getStartEndTime
- waitMilliseconds
- oclContextCallback
- isAlteraPlatform
- findAnyPlatform
- Keyboard.cpp
- keyboardPressEvent
- Mouse.cpp
- mousePressEvent
- mouseReleaseEvent
- StopWatch.cpp
- startTime
- getElapsedTime
- options.cpp
FPGA上で実行されるもの
- device/mandelbrot_kernel.cl
- hw_mandelbrot_frame グローバルIDにしたがって並列計算
ソフトウェア処理とOpenCL処理の実装
マンデルブロー画像の描画ルーチンはCPUで実行するソフトウェアとOpenCLの両方で実装されている.それぞれの実装をみてみる
ソフトウェアバージョン
あるピクセルの値を決定するmandel_pixelが定義されていて
inline unsigned int mandel_pixel(MANDELBROT_SW_PRECISION x0, MANDELBROT_SW_PRECISION y0, unsigned int maxIterations)
{
// 略
  while (xSqr + ySqr < 4.0 && iterations < maxIterations)
  {
    // perform the current iteration                                                                                                 
    xSqr = x*x;
    ySqr = y*y;
    y = 2*x*y + y0;
    x = xSqr - ySqr + x0;
    // increment iteration count                                                                                                     
    iterations++;
  }
  // return the iteration count                                                                                                      
  return iterations;
}
softwareCalculateFrameの中で,全ピクセルを求めている
  // for each pixel in the y dimension window                                                                                        
  for (j = 0, cur_y = y; j < theHeight; j++, cur_y -= cur_step_size)
  {
    // for each pixel in the x dimension of the window                                                                               
    for (cur_x = x, k = 0; k < theWidth; k++, cur_x += cur_step_size)
    {
      // set the value of the pixel in the window                                                                                    
      pixel = mandel_pixel(cur_x, cur_y, theSoftColorTableSize);
      if (pixel == theSoftColorTableSize)
        *fb_ptr++ = 0x0;
      else
        *fb_ptr++ = theSoftColorTable[pixel];
    }
  }
OpenCLバージョン
OpenCLカーネルとしては,↓の処理がオフロードされる
        const size_t windowPosX = get_global_id(0);
        const size_t windowPosY = get_global_id(1);
        const MANDELBROT_HW_PRECISION stepPosX = x0 + (windowPosX * stepSize);
        const MANDELBROT_HW_PRECISION stepPosY = y0 - (windowPosY * stepSize);
// 略
        while ( xSqr + ySqr < 4.0 &&
                        iterations < maxIterations)
        {
                // Perform the current iteration                                                                                     
                xSqr = x*x;
                ySqr = y*y;
                y = 2*x*y + stepPosY;
                x = xSqr - ySqr + stepPosX;
                // Increment iteration count                                                                                         
                iterations++;
        }
        // Output black if we never finished, and a color from the look up table otherwise                                           
        framebuffer[windowWidth * windowPosY + windowPosX] = (iterations == maxIterations)? BLACK : colorLUT[iterations];
ソフトウェアからは次のように呼び出される(エラーチェック部分を省略している).
int hardwareCalculateFrame(MANDELBROT_HW_PRECISION aStartX, MANDELBROT_HW_PRECISION aStartY, MANDELBROT_HW_PRECISION aScale, unsigned short int* aFrameBuffer)
{
  // Make sure width and height match up                                                                                             
  hardwareSetFrameBufferSize();
  unsigned rowOffset = 0;
  for(unsigned i = 0; i < numDevices; rowOffset += rowsPerDevice[i++])
  {
    // Create ND range size                                                                                                          
    size_t globalSize[2] = {thePixelDataWidth, rowsPerDevice[i]};
    // Set the arguments                                                                                                             
    unsigned argi = 0;
    theStatus = clSetKernelArg(theKernels[i], argi++, sizeof(MANDELBROT_CL_PRECISION), (void*)&aStartX);
    const MANDELBROT_HW_PRECISION offsetedStartY = aStartY - rowOffset * aScale;
    theStatus = clSetKernelArg(theKernels[i], argi++, sizeof(MANDELBROT_CL_PRECISION), (void*)&offsetedStartY);
    theStatus = clSetKernelArg(theKernels[i], argi++, sizeof(MANDELBROT_CL_PRECISION), (void*)&aScale);
    theStatus = clSetKernelArg(theKernels[i], argi++, sizeof(cl_uint), (void*)&theHardColorTableSize);
    theStatus = clSetKernelArg(theKernels[i], argi++, sizeof(cl_mem), (void*)&thePixelData[i]);
    theStatus = clSetKernelArg(theKernels[i], argi++, sizeof(cl_mem), (void*)&theHardColorTable);
    theStatus = clSetKernelArg(theKernels[i], argi++, sizeof(cl_uint), (void*)&theWidth);
    // Launch kernel                                                                                                                 
    theStatus = clEnqueueNDRangeKernel(theQueues[i], theKernels[i], 2, NULL, globalSize, NULL, 0, NULL, NULL);
  }
  rowOffset = 0;
  for(unsigned i = 0; i < numDevices; rowOffset += rowsPerDevice[i++])
  {
    // Read the output                                                                                                               
    theStatus = clEnqueueReadBuffer(theQueues[i], thePixelData[i], CL_TRUE, 0, thePixelDataWidth*rowsPerDevice[i]*sizeof(unsigned short int), &aFrameBuffer[rowOffset * theWidth], 0, NULL, NULL);
   }
簡単にためすには
OpenCL Mandelbrot Demo on Alteraの手順でコンパイルすればいい...けどカーネルのコンパイルは面倒という場合はイメージファイル(mandelbrot_demo.img)をダウンロードして試すことができる.
自分でコンパイルしたアプリケーションを追加したい場合には,イメージファイルをループバックでマウントして書けばいい.mandelbrot_demo.imgには複数のパーティションがあるので,たとえば,
fdisk -l -u mandelbrot_demo.img
で,Linuxシステムのあるパーティションの開始オフセットを確認して
sudo mount -o loop,offset=$((49152*512)) mandelbrot_demo.img /mnt
としてマウントすればいい.
OpenCLソースをLinuxな環境でコンパイルするには,たとえば
export LM_LICENSE_FILE=ライセンスファイル
export AOCL_BOARD_PACKAGE_ROOT=どこか/mandelblot_de0nanosoc/opencl_soc_bsp-de0_nano_with_display/c5soc
export ALTERAOCLSDKROOT=/opt/Altera/15.1/hld
source /opt/Altera/15.1/hld/init_opencl.sh
aoc -v device/mandelbrot_kernel.cl --board de0_nano_sharedonly_with_spi_tft
とか
ディスプレイがない場合の実行
ディスプレイがない場合にふつうに起動するとSDLのディスプレイチェックで終了する.なのでオプションが必要.
サンプルスクリプトの起動を参考に
source /de0_ocl_150_tft/init_opencl.sh
source /de0_ocl_150_tft/disable_auto_reprogram.sh
./mandelbrot --display=0 --test=1 --test-frames=10 --test-dump=10
とか.↓のように指定したらFPS計測の結果が出力された.ちょっとオプションのつけ方がわかってない
./mandelbrot --display=0 --test=true
標準出力に示されている生成速度をみると,18FPS-30FPSくらい,ってところのよう.
最初のオプションの場合には生成した画像がframe0.ppmからframe9.ppmに保存される.確認してみるとこんな感じ(convertでppm→png変換した結果)
OpenCLカーネルを変更してみる...rbfファイル変更編
プログラム実行時にハードウェアを変更することができなかった(要確認)ので,コンフィギュレーションファイルを変更する方法を試す.この環境ではSDカードの先頭パーティション(FAT32領域)のsoc_system.rbfがFPGAのコンフィギュレーションファイルに相当.
というわけで,aocを実行してOpenCLコードを合成すると生成されるtop.rbfで上書きしてみた.
結果はこんな感じ.

これは,マンデルブロー描画ルーチンで,黒にしていた部分を色テーブルの999に設定するよう変更したハードウェア.たしかに,オリジナルでは黒かったところが色テーブル999(黄色: main.cppで色テーブルを作っている)になっていることが確認できた.
        // Output black if we never finished, and a color from the look up table otherwise                                           
        //framebuffer[windowWidth * windowPosY + windowPosX] = (iterations == maxIterations)? BLACK : colorLUT[iterations];          
        framebuffer[windowWidth * windowPosY + windowPosX] = (iterations == maxIterations)? colorLUT[999] : colorLUT[iterations];




