最近使用材質節點生成HLSL代碼并開始編譯,也順手開始搭建一個正規的Shader包含及編譯流程,所以用到了D3DXCompileShader及ID3DXInclude
ID3DXInclude的使用方法在OGRE 1.65(或者類似的版本)的OgreD3D9HLSLProgram.cpp中有源代碼可供參考
STDMETHOD(Open)(D3DXINCLUDE_TYPE IncludeType,
LPCSTR pFileName,
LPCVOID pParentData,
LPCVOID *ppData,
UINT *pByteLen
)
此函數發生在代碼中有#include XXX時, FileName就是XXX
ppData需要由用戶提供FileName對應的源碼內容,ANSI格式
pByteLen是代碼長度
STDMETHOD(Close)(LPCVOID pData)
此函數的pData既是Open中提供的ppData, 用戶可以用于自行釋放內存等操作
本來按照正常流程, HLSL代碼應該可以正常編譯,我的HLSL功能大概描述:
Material.hlsl 提供基礎光照模型函數
Material_Generated.hlsl 由材質節點生成的代碼,包含Main入口, #include "Material.hlsl"
結果D3DXCompileShader報了一個莫名其妙的錯誤,找不到Main入口. 找了很久都沒發現代碼有什么問題. 于是開始回看OGRE代碼,發現了大俠們的一句救命留言:
// copy into separate c-string
// Note - must NOT copy the null terminator, otherwise this will terminate
// the entire program string!
馬上查閱我的代碼,為LoadFileToString函數添加一個參數,是否添加終結符0
bool LoadFileToString( const char* FileName, AString& Content, bool TerminateString )
{
wchar Buffer[MAX_PATH];
FileStream TFile;
if ( !TFile.Open(StringConverter::AnsiToUnicode( Buffer, MAX_PATH, FileName), FAM_Read ) )
{
return false;
}
dword FileSize = TFile.GetSize();
Content.resize( FileSize + (TerminateString ? 1: 0) );
TFile.ReadBuffer( (void*)Content.data(), FileSize * sizeof( char ) );
if ( TerminateString )
Content[FileSize] = 0;
return true;
}
D3DXCompileShader的Source 是需要終結的源碼, 但是在ID3DXInclude的實現類Open函數中,返回給ppData的,堅決不能有終止符0.
總結:
HLSL的代碼編譯過程是依賴#include將不同的文件碎片組合到一起后才開始解析,因此用戶提供的字符串尾部帶有終結符, 編譯器是沒有理由為你檢查數據的正確性的.去掉包含終結符才是正確的做法