スクリプトやテキストデータを記述する際、Cプリプロセッサの機能があればかなり便利になります。しかし、それを実装するのは大変です。コンパイラ付属のプリプロセッサは入出力がファイルなので使い勝手がわるい。何かないか探していたところDirectxのシェーダコンパイラ関連の関数にD3DPreprocessというものを発見、使ってみる。
D3DPreprocess
HRESULT D3DPreprocess(
in LPCVOID pSrcData,
in SIZE_T SrcDataSize,
in LPCSTR pSourceName,
in const D3D_SHADER_MACRO *pDefines,
in ID3DInclude pInclude,
out ID3DBlob *ppCodeText,
out ID3DBlob *ppErrorMsgs
);
HLSLコード用のプリプロセッサですが、スクリプトなどにも使えそうです。ファイルではなくデータ(メモリ)を指定します。引数には処理するデータのほかに、ID3DBlobで結果受け取り、/Dオプションに相当するD3D_SHADER_MACRO、そして#include処理を行うインターフェイスを指定します。
include処理のインターフェイス定義
#includeでのファイル読み込み処理を実装するインターフェイス(クラス)を指定します。プリプロセッサオプション/Iに相当する機能はD3DPreprocessにないためこのクラスで実装します。D3D_COMPILE_STANDARD_FILE_INCLUDEを指定するとデフォルトのinclude処理(カレントディレクトリとpSourceNameのディレクトリの検索)。
プリプロセス結果
コメント除去、include展開、define置き換えが行われます。それとプリプロセス前の行数とincludeしたファイル名がわかるように#lineが追加されます。スクリプトの解析などでエラー発生場所を特定できます(プリプロセス後の行数では発生場所がわからない)。
#line 6 "test.h"
lineからの行数でプリプロセス前のファイルと行数がわかる
ここはtest.hの8行目
サンプルコード
includeインターフェイス
//D3DPreprocessでのinclude処理インターフェイス
// 検索パスを3つまで追加可能
class IInclude : public ID3DInclude
{
public:
IInclude(const char* path0, const char* path1=nullptr, const char* path2=nullptr)
{
inc_path[0] = path0;
inc_path[1] = path1;
inc_path[2] = path2;
}
HRESULT __stdcall Open(D3D_INCLUDE_TYPE IncludeType, LPCSTR pFileName, LPCVOID pParentData, LPCVOID *ppData, UINT *pBytes)
{
switch(IncludeType) {
// とりあえず""と<>の区別なし
case D3D_INCLUDE_LOCAL: //#include ""
case D3D_INCLUDE_SYSTEM: //#include <>
break;
default:
return E_FAIL;
}
for(auto& p : inc_path){
if(!p)continue;
std::string path = p;
path += pFileName;
std::ifstream input;
input.open(path.c_str(), std::ios::binary);
if(!input.is_open())continue;
size_t fsize = (size_t)input.seekg(0, std::ios::end).tellg();
input.seekg(0, std::ios::beg);
void* data = ::operator new(fsize);
input.read(static_cast<char*>(data), fsize);
*ppData = data;
*pBytes = fsize;
return S_OK;
}
return E_FAIL;
}
HRESULT __stdcall Close(LPCVOID pData)
{
::operator delete(const_cast<void*>(pData));
return S_OK;
}
const char* inc_path[3];//検索パス
};
使用例
const char* file_path = "data/test.fx";
const char* inc_path = "data/";
std::ifstream input;
input.open("data/test.fx", std::ios::binary);
//if(input.is_open())//省略
size_t fsize = (size_t)input.seekg(0, std::ios::end).tellg();
input.seekg(0, std::ios::beg);
void* data = ::operator new(fsize);
input.read(static_cast<char*>(data), fsize);
IInclude iinc(inc_path);// /I inc_path
// Cプリプロセッサでの/Dオプション
D3D_SHADER_MACRO defines[] = {
{"DEBUG", "1"},// /DDEBUG=1
{nullptr, nullptr},// I'll be back
};
ID3DBlob* result = nullptr;
ID3DBlob* error = nullptr;
D3DPreprocess(data, fsize, file_path, defines, &iinc, &result, &error);
if(result)OutputDebugStringA( (const char*)result->GetBufferPointer() );
if(error)OutputDebugStringA( (const char*)error->GetBufferPointer() );
::operator delete(data);
if(result)result->Release();
if(error)error->Release();
おまけ
OutputDebugStringA関数などでエラー出力を行う場合
ファイルパス(数字):エラー文
という書式で出力すると、ダブルクリックでファイルパスのファイルをVisualStudioのエディタで開き、数字で指定した行数にエディットカーソルが移動した状態になります。
ファイルパス(10,8)
とすると10行、8文字目にカーソルが移動。
プリプロセスでエラーが発生したときに、ダブルクリックでエラー発生場所に移動できたので何か特殊なデバッグ機能がD3DPreprocessに備わっているのではと考えましたが、単にデバッグ出力を特定の書式にすることで有効になる機能でした。スクリプトやテキストデータの解析時に使えそうです。
「D3DPreprocessでプリプロセス」への1件のフィードバック