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];