まだ開発中(?)のBullet3OpenCLを試してみました。GPUを使って剛体の計算を高速に実行できることは確認できましたが、開発中のためか問題も多いようです。剛体1個でも10ms以上かかったり、フリーズ、GPUフリーズ等々、実戦投入はまだ先のようです。
デモの実行方法、HelloWorldプログラムの作成、処理時間の計測結果などについて説明していきます。
使用上の注意
実験中のプログラムのためPCがフリーズしたり、画面がおかしくなる可能性があります。実行の際は、他のアプリを終了させるなどPCが操作不能になることを想定しておいてください。
発生するタイミングは不定、ほとんどがOpenCL関数内で停止(デバッガでの確認)。今回自作したプログラムでも同様の現象が発生します。Bullet3OpenCLかOpenCLドライバーに問題があるようです。
以下のPC環境においてOpenCLデモプログラムのフリーズ、GPUフリーズなどの不具合を確認しています。
Windows10(64bit)
CPU : A8-7650k
GPU : Radeon R7
OpenCL Driver : 22.19.162.4
OpenGL Driver : 6.14.10.1374
Provider : Advanced Micro Devices, Inc.
Bullet3 (Bullet2.86)
最新のリリースver2.86に新バージョンのBullet3が同梱されています。ver3.xxとなってないため、まだ正式なものではなく開発中のものだと思われます。
http://bulletphysics.org/
https://github.com/bulletphysics/bullet3/releases
よりダウンロードしたソースコードを利用します。
OpenCLのインストール
比較的新しいGPUであれば、グラフィックボードのドライバーインストールでOpenCLドライバーも一緒にインストールされるようです。SDKなどのダウンロード、インストールは不要です。
Bullet3OpenCLデモのビルドと実行
基本的な手順は、Bullet Physicsを使ってみる (Bullet2.86 VS2017 DirectX11)のデモプログラムのビルドと実行と同じです。
build_visual_studio.batでVSプロジェクト作成、0_Bullet3Solution.slnを開き、ビルド&実行。ただし、デフォルトだとOpenCLデモが無効になっているため有効にする必要があります。
VSプロジェクト(App_BulletExampleBrowser)の「プロパティー>デバッグ>コマンド引数」に
--enable_experimental_opencl
を追加して実行するとデモプログラムのリストに「OpenCL (experimental)」が表示されOpenCLデモが実行可能になります。experimentalとあるのでまだ実験中のようです。
OpenCL.DLL読み込み失敗
#define CLEW_DYNLIB_OPEN LoadLibrary
を
#define CLEW_DYNLIB_OPEN LoadLibraryA
に変更
剛体の数や位置を変更
// 数の変更
examples/ExampleBrowser/OpenGLExampleBrowser.cpp (147)
int gGpuArraySizeX=45;
int gGpuArraySizeY=55;
int gGpuArraySizeZ=45;
// 位置の変更
examples/OpenCL/rigidbody/GpuConvexScene.cpp (285)
b3Vector3 position = b3MakeVector3(((j+1)&1)+i*2.2,1+j*2.,((j+1)&1)+k*2.2);
処理時間の測定
実行環境
Windows10(64bit)
CPU : A8-7650k
GPU : Radeon R7
計測結果
剛体の数 | 処理時間 |
2 | 10ms |
126 | 13ms |
1216 | 17ms |
5626 | 20ms |
12376 | 58ms |
34376 | 128ms |
Bullet2(CPU)では剛体5000個で140ms程なので、GPUの計算はかなり速いことがわかります。ただし、オーバーヘッドが10msほどあるため、数が少ないと効率がかなり悪くなっています。
まとめ
まだ開発中のためか問題が多いです。フリーズバグはプログラムやドライバーの修正でなんとこなるかもしれませんが、オーバーヘッドが大きすぎてゲームなどのリアルタイム計算に不向きです。安定したものなるには時間がかかりそうなので、しばらくは従来のBullet2を使ったほうがよさそうです。処理の高速化には Bullet Physicsのマルチスレッド対応について2(Bullet2.86 VS2017) のような描画などの他の処理と並列処理する方法が一番現実的だと思います。
Hello Bullet3OpenCL
剛体を追加して位置座標を出力するだけのプログラムです。まだ開発中、実験中のライブラリのため使用方法などが大きく変わる可能性があります。
//Hello Bullet3OpenCL
#define B3_USE_CLEW
#include "Bullet3Common/b3Vector3.h"
#include "Bullet3OpenCL/Initialize/b3OpenCLInclude.h"
#include "Bullet3OpenCL/Initialize/b3OpenCLUtils.h"
#include "Bullet3OpenCL/BroadphaseCollision/b3GpuSapBroadphase.h"
#include "Bullet3OpenCL/RigidBody/b3GpuRigidBodyPipeline.h"
#include "Bullet3OpenCL/RigidBody/b3GpuNarrowPhase.h"
#include "Bullet3OpenCL/RigidBody/b3GpuNarrowPhaseInternalData.h"
#include "Bullet3Collision/BroadPhaseCollision/b3DynamicBvhBroadphase.h"
#include "Bullet3Collision/NarrowPhaseCollision/b3ConvexUtility.h"
#include <windows.h>//OutputDebugStringA
#include <string>
namespace{
// position xyz, unused w, normal, uv
const float cube_vertices[] =
{
-1.0f, -1.0f, 1.0f, 1.0f, 0,0,1, 0,0,//0
1.0f, -1.0f, 1.0f, 1.0f, 0,0,1, 1,0,//1
1.0f, 1.0f, 1.0f, 1.0f, 0,0,1, 1,1,//2
-1.0f, 1.0f, 1.0f, 1.0f, 0,0,1, 0,1 ,//3
-1.0f, -1.0f, -1.0f, 1.0f, 0,0,-1, 0,0,//4
1.0f, -1.0f, -1.0f, 1.0f, 0,0,-1, 1,0,//5
1.0f, 1.0f, -1.0f, 1.0f, 0,0,-1, 1,1,//6
-1.0f, 1.0f, -1.0f, 1.0f, 0,0,-1, 0,1,//7
-1.0f, -1.0f, -1.0f, 1.0f, -1,0,0, 0,0,
-1.0f, 1.0f, -1.0f, 1.0f, -1,0,0, 1,0,
-1.0f, 1.0f, 1.0f, 1.0f, -1,0,0, 1,1,
-1.0f, -1.0f, 1.0f, 1.0f, -1,0,0, 0,1,
1.0f, -1.0f, -1.0f, 1.0f, 1,0,0, 0,0,
1.0f, 1.0f, -1.0f, 1.0f, 1,0,0, 1,0,
1.0f, 1.0f, 1.0f, 1.0f, 1,0,0, 1,1,
1.0f, -1.0f, 1.0f, 1.0f, 1,0,0, 0,1,
-1.0f, -1.0f, -1.0f, 1.0f, 0,-1,0, 0,0,
-1.0f, -1.0f, 1.0f, 1.0f, 0,-1,0, 1,0,
1.0f, -1.0f, 1.0f, 1.0f, 0,-1,0, 1,1,
1.0f,-1.0f, -1.0f, 1.0f, 0,-1,0, 0,1,
-1.0f, 1.0f, -1.0f, 1.0f, 0,1,0, 0,0,
-1.0f, 1.0f, 1.0f, 1.0f, 0,1,0, 1,0,
1.0f, 1.0f, 1.0f, 1.0f, 0,1,0, 1,1,
1.0f,1.0f, -1.0f, 1.0f, 0,1,0, 0,1,
};
static const int cube_indices[]=
{
0,1,2,0,2,3,//ground face
6,5,4,7,6,4,//top face
10,9,8,11,10,8,
12,13,14,12,14,15,
18,17,16,19,18,16,
20,21,22,20,22,23
};
}
void HelloBullet3OpenCL()
{
const int blockX = 15;
const int blockY = 55;
const int blockZ = 15;
const int rigid_max = blockX*blockY*blockZ + 100;
const int max_pairs_per_body = 16;
cl_platform_id platform;
cl_context context;
cl_device_id device;
cl_command_queue queue;
b3Config config;
b3GpuNarrowPhase* narrow_phase = nullptr;
b3GpuSapBroadphase* broad_phase = nullptr;
b3DynamicBvhBroadphase* dbvt_broad_phase = nullptr;
b3GpuRigidBodyPipeline* pipeline = nullptr;
int ground_id = -1;
int box_id = -1;
{// OpenCL初期化
int err_no = 0;
cl_device_type type_device = CL_DEVICE_TYPE_GPU;
int index_preferred_device = -1; // default
int index_preferred_platform = -1; // default
context = b3OpenCLUtils::createContextFromType(
type_device, &err_no, 0, 0,
index_preferred_device, index_preferred_platform,
&platform);
oclCHECKERROR(err_no, CL_SUCCESS);
int dev_num = b3OpenCLUtils::getNumDevices(context);
b3Assert(dev_num>0);
if(dev_num<=0)return;//デバイスなし
device = b3OpenCLUtils::getDevice(context, 0);
queue = clCreateCommandQueue(context, device, 0, &err_no);
oclCHECKERROR(err_no, CL_SUCCESS);
//OpenCL情報
b3OpenCLDeviceInfo info;
b3OpenCLUtils::getDeviceInfo(device, &info);
std::string device_info;
device_info += "DeviceName = ";
device_info += info.m_deviceName;
device_info += "\nDeviceVendor = ";
device_info += info.m_deviceVendor;
device_info += "\nDriverVersion = ";
device_info += info.m_driverVersion;
device_info += "\n";
OutputDebugStringA(device_info.c_str());
}
{// Bullet3 OpenCL初期化
// 各フェーズのクラスを作成すると
// OpenCLのソースコード(*.cl)をコンパイル
// コンパイル結果のバイナリデータをcacheフォルダにファイル出力
// 次回からコンパイルせずにバイナリファイル読み込み
// 開発用のファイル操作あり
// clファイルを探しタイムスタンプが新しければ再コンパイルを実行
// 通常はライブラリ公開時に削除する機能
int err_no = 0;
// config 剛体の最大数など
config.m_maxConvexBodies = b3Max(config.m_maxConvexBodies, rigid_max);
config.m_maxConvexShapes = config.m_maxConvexBodies;
config.m_maxBroadphasePairs = max_pairs_per_body*config.m_maxConvexBodies;
config.m_maxContactCapacity = config.m_maxBroadphasePairs;
//各フェーズ作成
narrow_phase = new b3GpuNarrowPhase(context, device, queue, config);
broad_phase = new b3GpuSapBroadphase(context, device, queue);
dbvt_broad_phase = new b3DynamicBvhBroadphase(config.m_maxConvexBodies);
// 剛体計算パイプライン
pipeline = new b3GpuRigidBodyPipeline(
context, device, queue,
narrow_phase, broad_phase, dbvt_broad_phase,
config);
}
{//地面の剛体作成(固定)
//BOX形状はないため、凸形状の多面体として形状登録
int stride = 9*sizeof(float);
int vtx_num = sizeof(cube_vertices)/stride;
int idx_num = sizeof(cube_indices)/sizeof(int);
b3Scalar scale = 400;
b3Vector4 scaling = b3MakeVector4(scale, scale, scale, 1);
int col_index = narrow_phase->registerConvexHullShape(&cube_vertices[0], stride, vtx_num, scaling);
b3Vector3 position = b3MakeVector3(0, -400, 0);
b3Quaternion rotation(0, 0, 0, 1);
ground_id= pipeline->registerPhysicsInstance(0.f, position, rotation, col_index, 0, false);
}
{//BOX剛体作成
int stride = 9*sizeof(float);
int vtx_num = sizeof(cube_vertices)/stride;
int idx_num = sizeof(cube_indices)/sizeof(int);
//凸形状コリジョン作成、追加(Cube作成)
b3Vector4 scaling = b3MakeVector4(1, 1, 1, 1);
int colIndex = narrow_phase->registerConvexHullShape(&cube_vertices[0], stride, vtx_num, scaling);
//剛体作成、追加
float mass = 1.f;
b3Vector3 position = b3MakeVector3(0, 5, 0);
b3Quaternion rotation(0, 0, 0, 1);
box_id = pipeline->registerPhysicsInstance(mass, position, rotation, colIndex, 0, false);
//BOX剛体作成 多数配置
if(0){
for(int i=0; i<blockX; i++){
for(int j=0; j<blockY; j++){
for(int k=0; k<blockZ; k++){
float mass = 1.f;
b3Vector3 position = b3MakeVector3(
((j+1)&1)+i*b3Scalar{ 2.2 },
1+j*b3Scalar{ 2 } +30,
((j+1)&1)+k*b3Scalar{ 2.2 });
pipeline->registerPhysicsInstance(mass, position, rotation, colIndex, 0, false);
}
}
}
}
}
{// GPUメモリに必要な情報書き込み
pipeline->writeAllInstancesToGpu();
narrow_phase->writeAllBodiesToGpu();
broad_phase->writeAabbsToGpu();
}
{//実行
for(int i=0;i<100;++i){
pipeline->stepSimulation(1./60.f);
// しばらく実行すると停止するかも
//剛体の姿勢情報取得
int obj_num = pipeline->getNumBodies();
b3GpuNarrowPhaseInternalData* np_data = narrow_phase->getInternalData();
np_data->m_bodyBufferGPU->copyToHost(*np_data->m_bodyBufferCPU);
//BOX剛体の位置取得
b3Vector4 pos = (const b3Vector4&)np_data->m_bodyBufferCPU->at(box_id).m_pos;
std::string pos_str = std::to_string(i);
pos_str += " : pos = ";
pos_str += std::to_string(pos[0]);
pos_str += ", ";
pos_str += std::to_string(pos[1]);
pos_str += ", ";
pos_str += std::to_string(pos[2]);
pos_str += "\n";
//位置出力
OutputDebugStringA(pos_str.c_str());
}
}
{// Bullet3 OpenCL終了
delete pipeline;//メモリリークあり m_data->m_gpuSolver削除忘れ
delete dbvt_broad_phase;
delete broad_phase;
delete narrow_phase;
}
{// OpenCL終了
clReleaseCommandQueue(queue);
clReleaseContext(context);
}
}