OSX平台上使用d3dcompile

Author Avatar
Kanglai Qian 12月 21, 2014

这个其实是源自KlayGE移植OSX上的一个需求:在OSX上希望使用d3dcompile,配合DXBC2GLSL来生成OpenGL Shader。之前移植的时候是使用的NVidia Cg凑合,但是Cg Profile在OpenGL 3.x开始不支持Multi-vendor,而且也发现对于tex2DLOD之类的函数支持的有问题。

其实就跨平台的Shader之前也有讨论和资料:HlslCrossCompiler支持OGL4.3关于Shader的跨平台方案的考虑Unreal 4 HLSL Cross CompilerUnity hlsl2glslfork。KlayGE选择解析HLSL bytecode生成GLSL的方案,因此需要解决在OSX上运行d3dcompile生成HLSL bytecode的问题。ps. 看这些大大讨论感觉要学的东西还有好多好多(╯‵□′)╯︵┻━┻

回到正题,其实想要实现的功能就是在osx下调用d3dcompile_43.dll中的一个接口

HRESULT WINAPI
D3DCompile(__in_bcount(SrcDataSize) LPCVOID pSrcData,
__in SIZE_T SrcDataSize,
__in_opt LPCSTR pSourceName,
__in_xcount_opt(pDefines->Name != NULL) CONST D3D_SHADER_MACRO* pDefines,
__in_opt ID3DInclude* pInclude,
__in LPCSTR pEntrypoint,
__in LPCSTR pTarget,
__in UINT Flags1,
__in UINT Flags2,
__out ID3DBlob** ppCode,
__out_opt ID3DBlob** ppErrorMsgs);

但是看起来很简单的需求,结果被Wine坑了一脸,且听我慢慢道来…

64-bit Wine

brew安装的默认就是32-bit,无法执行x64的dll或exe;而且KlayGE默认是编译的64-bit。我尝试下载源代码
自己编译,但是打开--enable-win64之后直接编译失败。Wine64文档上说 Please also note GccVersions (you need at least gcc 4.4 to compile Wine for x86-64 because of builtin_ms_va_list support),貌似这是个clang bug而且还没人管。

既然暂时找不到简单的解决方案,所以我准备先用32-bit凑合,走通流程再说。

纠结的踩坑之旅

动手写代码前,先查文档,然后导致我后面一系列弯路的杯具就开始了!一开始我是搜到了Winelib,结果就被第一段误导了 A good way to fix these is to try to compile applications for which we have the source under Winelib,以为有源代码才能用这货。Wine本身没有找到什么开发者文档、倒是有一些用户文档,没办法就直接看头文件上了。

Try 1

我翻了下wine/dlls文件夹,非常惊喜的发现直接有d3dcompiler_43.dll.so,而且配套的d3dcompiler_43.spec@ stdcall D3DCompile(ptr long str ptr ptr str str long long ptr ptr)表示这个API能直接用,看起来so easy的样子(后来发现自己真是naïve)。兴冲冲的用dlopendlsym试一下:

void* h = dlopen("d3dcompiler_43.dll.so", RTLD_LAZY);
pD3DCompile p = reinterpret_cast<pD3DCompile>(dlsym(h, "D3DCompile"));

结果一调用就崩溃,看了下asm是挂在EnterCriticalSection(&wpp_mutex);,百思不得其解。

Try 2

重新翻了遍头文件,参考library.h,改用这俩函数。不过问题依旧:能定位到函数入口,但是一执行就挂。

extern void *wine_dlopen( const char *filename, int flag, char *error, size_t errorsize );
extern void *wine_dlsym( void *handle, const char *symbol, char *error, size_t errorsize );

继续参考了loader/main.c里的一些初始化代码,加上之后依然不行。

Try 3

经过前两次尝试开始迷茫……丫到底行不行?于是尝试下运行在wine/dlls/d3dcompiler_43/tests下运行make hlsl.ok,妥妥的运行失败。难道说这个功能就是不对的?我查了下Wine test runs,最新的Release 1.7.33也是hlsl.c:141: Tests skipped: not compiling vertex shader due to lacking wine HLSL support!,也就是说D3DCompile编译失败(虽然它的程序没有崩溃)。

虽然官方自带的测试都运行不过,但我觉得这个很不科学:Wine在运行Windows上的游戏的时候不可能绕开这个函数。昨天在Wine Forum和IRC上问了但木有反馈。

Try 4

决定换个思路,直接下一个d3dcompile.dll,然后看一下有没有办法直接在OSX上调用dll。又是一通google之后返回了原点:没错就是WineLib! 在一个看起来是IRC聊天记录整理出来的Wiki里(话说还能再偷懒点么),有人提供了一个例子,实现了Calling a Native Windows dll from Linux。通过山寨这个,终于使用如下代码尝试成功:

HMODULE h = LoadLibraryEx("D3DCompiler_43.dll", NULL, 0);
pD3DCompile p = reinterpret_cast<pD3DCompile>(::GetProcAddress(h, "D3DCompile"));

其实这个用法和Windows下是一样的;关键在于这个代码不能使用默认的gcc/clang编译,一定要用winegcc main.c这样来编译!

运行结果1(编译之后反汇编):

/Users/anthony/Desktop  >./a.out
fixme:d3d9:Direct3DShaderValidatorCreate9 stub
HR 0
HR 0
//
// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111
vs_1_1
dcl_position v0
dcl_texcoord v1
mov oPos, v0
mov oT0.xy, v1

// approximately 2 instruction slots used

运行结果2(错误信息):

/Users/anthony/Desktop  >./a.out
HR -2147467259
No compiled
Z:\\Users\\anthony\\Desktop\\Shader@0x406A5570(5,5): error X3000: syntax error: unexpected token 'float3'

还要记录一下两个坑:

  • winegcc其实是一个gcc封装,用的时候还要带上-I/usr/local/include/wine/windows来确保能d3dcompiler.h等头文件;
  • 如果同时引入iostream等会导致错误,我没去具体研究怎么解决、反正用C接口也能凑合了;
  • 编译会生成a.outa.out.so两个文件,其中前者其实是一个脚本,运行./a.out --xxx等价于wine a.out.so --xxx

TODO

接下来要将这个东西结合到KlayGE里去。正如前面所说,由于架构问题,没办法直接将这个东西编译链接进去,所以我准备直接system调用配合文件来传数据,希望不要有别的坑了……

调试之坑

接入的时候,遇到了一个非常奇怪的问题:调试的时候很容易出现程序异常退出(exit code -1)

经过反复尝试,还有以下特点

  • 直接执行程序并不会产生任何问题;
  • 使用XCode调试时,会在执行system后再执行若干行挂掉;
  • 就算停在断点上的时候,也有几率挂掉;
  • AppCode表现一致。

我单独写了一个小程序反复通过system去调用wine,倒是一直没复现;猜测可能是KlayGE的多线程环境下,调用wine导致LLVM调试功能出问题。目前没能解决这个,只利用KlayGE的缓存kfx机制绕开。

问题解决

想起来就更新下…之前的问题通过运行wineserver -p解决了。目前KlayGE已移除Cg,全部转用wine咯(顺便我花了几个下午把linux重新跑通了…)。