あかすくぱるふぇ

同人サークル「あかすくぱるふぇ」のブログです。

「Windows+VisualStudioで開発してるけど、Linuxでも実行したいし、うまくいかなければEclipse+CDTでデバッグしたい。とはいえ、VisualStudio用のファイルとEclipse用のファイルの両方を用意するのは面倒」

そんな場合にCMakeを使って、ソースファイルを共通化する方法を紹介します。

例として、main.cpp, hoge.h, hoge.cppの3つのファイルから成るプロジェクトを取り上げます。
hoge.hで宣言、hoge.cppで定義されている関数を、main.cpp内のmain関数で呼ぶ想定です。

・CMakeの使い方
CMakeの使い方については、以下のページがわかりやすいです。
http://www.wakayama-u.ac.jp/~chen/cmake/cmakecxx.html

今回の例では、CMakeLists.txtに最低限以下の記述をするだけで大丈夫です。
add_executable (hoge main.cpp hoge.h hoge.cpp)

・Windows+VisualStudio
上記ページの通りに実行するだけです。

・Linux+Eclipse
デバッグ実行できるよう、以下のオプションでcmakeを実行します。
cmake -DCMAKE_BUILD_TYPE=Debug ./
その後、Eclipseで以下のようにプロジェクトをインポートします。

新規→C/C++→Makefile Project with Existing Code→次へ
Existing Code Locationに、コードとMakefileのあるフォルダを指定→完了

なお、CMakeLists.txtが無くて、(他人が作った)Makefileだけがある場合は、
CMakeCache.txtのCMAKE_BUILD_TYPE:STRING=の後にDEGUGと記入すれば
デバッグビルドできるっぽいです。

これでビルド・実行・デバッグができるはずです。
以上です。

exeから呼び出す関数には、__declspec(dllexport)をつける。
ただし、defファイルを使えば__declspec(dllexport)の記述を省略できる。

名前装飾を無効化するために、extern "C"もつける。

DLLのリンクには、明示的リンクと暗黙的リンクとがある。
明示的リンクは、LoadLibrary()などで実現する。
暗黙的リンクは、関数宣言に、__declspec(import)をつけ、コンパイル時にlibファイルをリンクすることで実現する。

暗黙的リンクで利用するヘッダファイルは、
#ifdef MYDLL
#define DLLAPI extern "C" __declspec(dllexport)
#else
#define DLLAPI extern "C" __declspec(dllimport)
#endif
などとしておくと、DLL側とEXE側で共用できる。

以下は参考ページ。
http://exlight.net/devel/windows/dll/windll.html
http://akasuku.blog.jp/archives/43379993.html
http://fa11enprince.hatenablog.com/entry/2014/06/20/015808

以上です。

CUDA Sampleでよく行われている処理。
ただ、CUDA Sampleはごちゃごちゃしていてよくわからないので、最小構成を抜き出してみました。
int main(int argc, char *argv[])
{
// 画像ファイル入力
cvImg = cv::imread("Lenna.bmp", 0);

// 初期化
init(argc, argv);

glutMainLoop();

// 終了処理
finalize();

return 0;
}
main関数。
画像ファイルの入力と初期化を行った後に、glutMainLoop()を実行します。

void init(int argc, char *argv[])
{
size_t dataSize = cvImg.cols * cvImg.rows * sizeof(unsigned char);

glInit(argc, argv); // GLの初期化
GenTexture(); // テクスチャの生成
GenAndRegisterPBO(dataSize); // PBOの生成とCUDAへの登録
   
// 入力画像のGPU側メモリ確保
  checkCudaErrors(cudaMalloc((unsigned char**)&dImgIn, dataSize));
}
初期化関数です。
以下に各関数を載せます。

void glInit(int argc, char *argv[])
{
glutInit(&argc, argv);
glutInitWindowSize(cvImg.cols, cvImg.rows);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutCreateWindow(argv[0]);
glewInit();
glutDisplayFunc(display);
}

void GenTexture()
{
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, cvImg.cols, cvImg.rows,
      0, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}

void GenAndRegisterPBO(const size_t dataSize)
{
glGenBuffers(1, &pbo);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
glBufferData(GL_PIXEL_UNPACK_BUFFER, dataSize, 0, GL_STREAM_DRAW);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
cudaGraphicsGLRegisterBuffer(&cuda_pbo_resource, pbo,
      cudaGraphicsMapFlagsWriteDiscard);
}
glInit()はGLUT, GLEWのおなじみの処理。
GenTexture()はテクスチャ生成のおなじみの処理。
GenAndRegisterPBO()はPBOを生成し、そのPBOをCUDAに登録しています。
CUDAへの登録はcudaGraphicsGLRegisterBuffer()で行います。
cudaGraphicsMapFlagsWriteDiscardはPBOがCUDAからWriteOnlyであるというフラグです。

void display()
{
// カーネル関数で書き換える画像(PBO)のポインタを取得
unsigned char *dImgOut;
size_t dataSizeTmp;
checkCudaErrors(cudaGraphicsMapResources(1, &cuda_pbo_resource, 0));
checkCudaErrors(cudaGraphicsResourceGetMappedPointer(
(void **)&dImgOut, &dataSizeTmp, cuda_pbo_resource));

// 入力画像をGPUのグローバルメモリに転送
size_t dataSize = cvImg.cols * cvImg.rows * sizeof(unsigned char);
checkCudaErrors(cudaMemcpy(dImgIn, cvImg.data, dataSize, cudaMemcpyHostToDevice));

// カーネル関数の実行
dim3 grid(cvImg.rows);
dim3 block(1024);
invertKernel << <grid, block >> >(dImgOut, dImgIn, cvImg.cols);

checkCudaErrors(cudaGraphicsUnmapResources(1, &cuda_pbo_resource, 0));

glBindTexture(GL_TEXTURE_2D, tex);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cvImg.cols, cvImg.rows,
GL_LUMINANCE, GL_UNSIGNED_BYTE, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);

glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
{
glTexCoord2f(0, 1);
glVertex2f(-1, -1);
glTexCoord2f(1, 1);
glVertex2f(1, -1);
glTexCoord2f(1, 0);
glVertex2f(1, 1);
glTexCoord2f(0, 0);
glVertex2f(-1, 1);
}
glEnd();

glBindTexture(GL_TEXTURE_2D, 0);
glutSwapBuffers();
}
display関数です。
PBOのポインタ取得、入力画像のGPU転送、カーネル実行、テクスチャマッピングを行っています。
処理結果をCPUに戻さずに、PBOからテクスチャにコピー(GPU内で閉じてる)して描画しているのがポイントです。

念のため、カーネル関数の一例も載せておきます。
__global__ void invertKernel(unsigned char *out, const unsigned char *in, const int width)
{
for (int i = threadIdx.x; i < width; i += blockDim.x) {
int index = blockIdx.x * width + i;
out[index] = 255 - in[index];
}
}

以上です。

↑このページのトップヘ