シェーダーを書き換えて、実行しました。ところが、画面は、前のまま変わりません。あるいは、真っ黒です。Visual Studioのビルドは、エラーなしで通っています。エラー一覧にも、何も出ていません。
HLSLのコンパイルは、多くの場合、プログラムの実行中に行われます。そのため、シェーダーの間違いは、ビルドでは検出されず、D3DCompileが返すエラーメッセージを、自分で取り出して、表示しなければ、見えません。
この記事では、エラーメッセージの取り出し方と、HLSLでよく出るエラー文を、実際に再現して並べました。エラー文が、間違いの、1行次を指すことがあるなど、読み方の注意点も確かめています。検証は、DirectXのシェーダーコンパイラ(d3dcompiler、FXC系)のD3DCompileで、MSVC 19.44(x64)から呼び出して行いました。
D3DCompileのエラーを、取り出す
D3DCompileは、コンパイルに成功すると、シェーダーのバイトコードを返し、失敗すると、エラーメッセージの入ったBlob(データの塊)を、返します。このBlobを、文字列として取り出して、表示します。
▼D3DCompileの呼び出しと、エラーの取り出し(hx_exp.cppから抜粋)
ID3DBlob* code = nullptr;
ID3DBlob* err = nullptr;
HRESULT hr = D3DCompile(c.source, strlen(c.source), "test.hlsl", nullptr, nullptr, c.entry, c.target, 0, 0, &code, &err);
std::printf("=== %s ===\n", c.title);
std::printf("D3DCompile: hr = 0x%08X (%s), 出力コード = %s, エラーBlob = %s\n",
(unsigned)hr, SUCCEEDED(hr) ? "成功" : "失敗", code ? "あり" : "なし", err ? "あり" : "なし");
if (err)
{
std::string msg((const char*)err->GetBufferPointer(), err->GetBufferSize());
while (!msg.empty() && (msg.back() == '\n' || msg.back() == '\0' || msg.back() == '\r')) msg.pop_back();
std::printf("%s\n", msg.c_str());
}ポイントは3つです。
HRESULTがFAILEDのときは、必ず、エラーBlobの中身を表示する。表示先は、コンソールか、OutputDebugStringA(Visual Studioの「出力」ウィンドウ)- 成功したとき(
S_OK)でも、エラーBlobに警告が入っていることがある(後述) - 使い終わった
ID3DBlobは、Release()する
エラー文の、読み方
エラー文は、次の形をしています。
▼エラー文の構造
C:\blogtest\hlsl\test.hlsl(4,12-17): error X3004: undeclared identifier 'colour'
├ ファイル名
├ (行,桁の範囲)
├ error か warning
├ X + 4桁の番号
└ メッセージ「行」と「桁の範囲」で場所を、「X3004」のような番号で、種類を、メッセージで、内容を、教えてくれます。番号は、検索するときの手がかりになります。では、よく出る8種類を、順に見ていきます。
よく出るエラー8種類
①セミコロンの書き忘れ:エラーは、「次の行」を指す
▼3行目に、セミコロンが無い
float4 PS(float4 pos : SV_POSITION) : SV_TARGET
{
float4 c = float4(1, 0, 0, 1)
return c;
}D3DCompile: hr = 0x80004005 (失敗), 出力コード = なし, エラーBlob = あり
C:\blogtest\hlsl\test.hlsl(4,5-10): error X3000: syntax error: unexpected token 'return'エラーは、4行目(return)を指しています。書き忘れているのは、3行目の末尾です。コンパイラは、「;が来るはずの場所に、returnが来た」と、次の行で初めて気づくので、報告される行が、1つ後ろにずれます。「エラーの行には、間違いが無いのに」と、悩んだら、1行前を見てください。
②変数名の打ち間違い:エラーが、2つ出る
▼colorを、colourと打ち間違えた
float4 PS(float4 pos : SV_POSITION) : SV_TARGET
{
float4 color = float4(1, 0, 0, 1);
return colour;
}D3DCompile: hr = 0x80004005 (失敗), 出力コード = なし, エラーBlob = あり
C:\blogtest\hlsl\test.hlsl(4,12-17): error X3004: undeclared identifier 'colour'
C:\blogtest\hlsl\test.hlsl(4,5-18): error X3080: 'PS': function must return a value1つ目のX3004(colourが宣言されていません)が、本当の原因です。2つ目のX3080(PSは、値を返さなければなりません)は、1つ目の、連鎖で出たエラーです。エラーが複数出たら、最初の1つから、直すのが基本です。
③型が合わない:暗黙に、変換できない
▼float3を、float4として返している
float4 PS(float4 pos : SV_POSITION) : SV_TARGET
{
float3 c = float3(1, 0, 0);
return c;
}D3DCompile: hr = 0x80004005 (失敗), 出力コード = なし, エラーBlob = あり
C:\blogtest\hlsl\test.hlsl(4,5-13): error X3017: cannot implicitly convert from 'float3' to 'float4'X3017は、「float3からfloat4へは、暗黙に変換できません」という意味です。float4(c, 1)のように、足りない成分を、明示します。
④戻り値の、セマンティクスの付け忘れ
▼「: SV_TARGET」が無い
float4 PS(float4 pos : SV_POSITION)
{
return float4(1, 0, 0, 1);
}D3DCompile: hr = 0x80004005 (失敗), 出力コード = なし, エラーBlob = あり
C:\blogtest\hlsl\test.hlsl(1,9-35): error X3503: 'PS': function return value missing semanticsX3503は、「戻り値に、セマンティクスがありません」という意味です。ピクセルシェーダーの戻り値には、: SV_TARGETを付けます。
⑤エントリポイントの名前が、違う
D3DCompile: hr = 0x80004005 (失敗), 出力コード = なし, エラーBlob = あり
error X3501: 'ps': entrypoint not found関数名は、PSなのに、D3DCompileには、小文字の"ps"を渡しました。エラーは、X3501(エントリポイントが見つかりません)です。HLSLでは、大文字と小文字が区別されます。このエラーには、ファイル名も、行番号も、付いていません。場所のヒントが無いので、文面だけで、「名前の渡し間違いかも」と、気づく必要があります。
⑥ターゲットが、古いコンパイラでは未対応
D3DCompile: hr = 0x8876086C (失敗), 出力コード = なし, エラーBlob = あり
error X3506: unrecognized compiler target 'ps_6_0'ps_6_0を渡すと、X3506(認識できないターゲット)で失敗しました。D3DCompile(FXC系のコンパイラ)は、ps_5_xまでの対応で、シェーダーモデル6以降には、新しいコンパイラ(DXC)が必要です。こちらも、ファイル名と行番号が、付いていません。
警告:成功しても、エラーBlobに入っている
⑦警告だけで、コンパイルは成功する
▼float4を、float3に代入している(成分が切り捨てられる)
float4 PS(float4 pos : SV_POSITION) : SV_TARGET
{
float3 c = float4(1, 0, 0, 1);
return float4(c, 1);
}D3DCompile: hr = 0x00000000 (成功), 出力コード = あり, エラーBlob = あり
C:\blogtest\hlsl\test.hlsl(3,12-33): warning X3206: implicit truncation of vector typehrはS_OKで、出力コードも、できています。それでも、エラーBlobに、警告(X3206: ベクトル型を暗黙に切り捨てました)が入っています。「hrが成功だから、エラーBlobは見なくてよい」と考えていると、この警告を、見逃します。
⑩警告を、エラーとして扱う
D3DCompile: hr = 0x88760B59 (失敗), 出力コード = なし, エラーBlob = あり
C:\blogtest\hlsl\test.hlsl(3,12-33): error X3129: warning treated as error
C:\blogtest\hlsl\test.hlsl(3,12-33): error X3206: implicit truncation of vector typeフラグD3DCOMPILE_WARNINGS_ARE_ERRORSを付けると、同じコードが、失敗になりました(X3129: 警告がエラーとして扱われました)。警告を、見逃したくない場合に使います。
⑧初期化していない変数:既定では、警告が出ない
▼cが、条件によっては、初期化されない
float4 PS(float4 pos : SV_POSITION) : SV_TARGET
{
float3 c;
if (pos.x > 100.0) c = float3(1, 0, 0);
return float4(c, 1);
}D3DCompile: hr = 0x00000000 (成功), 出力コード = あり, エラーBlob = なし既定のコンパイルでは、警告は1つも出ませんでした(エラーBlobも、ありません)。しかし、最適化を切って、デバッグ情報を付けると、結果が変わりました。
D3DCompile: hr = 0x00000000 (成功), 出力コード = あり, エラーBlob = あり
C:\blogtest\hlsl\test.hlsl(4,43): warning X4000: use of potentially uninitialized variable (c)X4000(初期化されていない可能性のある変数を使っています)の警告が、出ました。コンパイルのフラグによって、報告される警告が変わるので、開発中は、デバッグ用のフラグでも、コンパイルして、警告を確かめておくのがよさそうです。
ファイルが、見つからないとき:エラーBlobが、空
D3DCompileFromFile: hr = 0x80070002 (失敗), 出力コード = なし, エラーBlob = なし
(エラーBlobが無いので、GetBufferPointer()を呼ぶと、ヌルポインタを参照して落ちる)D3DCompileFromFileに、存在しないファイル名を渡すと、hrは0x80070002(ファイルが見つかりません)で失敗しますが、エラーBlobは、空(nullptr)でした。この状態で、失敗時の処理として、err->GetBufferPointer()を無条件に呼ぶと、ヌルポインタの参照で、プログラムが落ちます。実験のコードのように、if (err)で、確認してから、取り出してください。作業フォルダの、ズレが原因のことも多いので、相対パスと作業フォルダの記事も、参考になります。
原因の探し方:チェックリスト
D3DCompileの戻り値を、必ず確認する。FAILEDなら、エラーBlobを表示して、止める- エラーが複数あれば、最初の1つから、直す(後ろは、連鎖のことがある)
syntax errorは、報告された行の、1行前も見る(セミコロン、括弧の閉じ忘れ)- ファイル名・行番号が付かないエラー(
X3501・X3506)は、D3DCompileに渡した引数(エントリ名・ターゲット)を疑う - 成功しても、エラーBlobを確認する(警告が入っている)
D3DCompileFromFileは、ファイルが無いと、エラーBlobが空。nullptrを確認してから、取り出す
HLSLのコンパイルエラーの、よくある失敗例と対処法
①戻り値を確認せずに、そのまま使う
D3DCompileの失敗を、見逃して、CreatePixelShaderに、nullptrのBlobを渡すと、さらに別のエラーになります。原因は、1つ前の、コンパイルの失敗です。デバッグレイヤーを有効にしておくと、その次の段階の失敗も、教えてもらえます。
②エラー文の行番号を、そのまま信じる
実験①のとおり、構文エラーは、1行後ろにずれることがあります。報告された行と、その1つ前の行を、両方見てください。
③警告を、無視する
実験⑦のとおり、成功しても、警告は出ます。警告の中には、意図しない切り捨て(X3206)のように、見た目の不具合の原因になるものが、あります。
④実行時に、毎回コンパイルして、重くなる
シェーダーを、起動のたびにコンパイルすると、時間がかかります。開発中は、実行時のコンパイルで、手軽にエラーを確認し、配布するときは、事前にコンパイルした結果(.cso)を、読み込む方法もあります。アセットのバイナリベイクの記事の考え方も、近いものです。
注意点
- HLSLのシェーダーは、実行中にコンパイルされることが多く、間違いは、ビルドでは分からない
D3DCompileの戻り値と、エラーBlobを、必ず表示する。成功時の警告も、エラーBlobに入る- 構文エラーは、報告された行の、1行前が原因のことがある(実験①)
X3501・X3506には、ファイル名も行番号も付かない。引数のエントリ名・ターゲットを確認するps_6_0は、D3DCompileでは、認識されなかった(DXCが必要)D3DCompileFromFileで、ファイルが無いとき、エラーBlobはnullptr- 今回の検証は、
d3dcompilerのD3DCompile(FXC系)、MSVC 19.44(x64)で行った。エラー文の番号や文面は、コンパイラの版で変わりうる
この記事のポイント
- エラー文は、ファイル名(行,桁): error X番号: メッセージ。最初の1つから、直す
syntax errorは、1行前も見る- 成功しても、エラーBlobを確認する。警告が入っている。フラグによって、出る警告も変わる
- 戻り値を確認して、エラーBlobを
nullptr確認のうえ、表示する
シェーダーの間違いは、静かに、画面が変わらない、という症状で現れます。エラー文を表示する数行を書いておくだけで、「どこが、間違っているのか」が、すぐに分かります。最初に、この数行を、入れておいてください。

