OSX平台上使用d3dcompile
这个其实是源自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 Compiler、Unity hlsl2glslfork。KlayGE选择解析HLSL bytecode生成GLSL的方案,因此需要解决在OSX上运行d3dcompile生成HLSL bytecode的问题。ps. 看这些大大讨论感觉要学的东西还有好多好多(╯‵□′)╯︵┻━┻
回到正题,其实想要实现的功能就是在osx下调用d3dcompile_43.dll中的一个接口
HRESULT WINAPI |
但是看起来很简单的需求,结果被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)。兴冲冲的用dlopen
和dlsym
试一下:
void* h = dlopen("d3dcompiler_43.dll.so", RTLD_LAZY); |
结果一调用就崩溃,看了下asm是挂在EnterCriticalSection(&wpp_mutex);
,百思不得其解。
Try 2
重新翻了遍头文件,参考library.h,改用这俩函数。不过问题依旧:能定位到函数入口,但是一执行就挂。
extern void *wine_dlopen( const char *filename, int flag, 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); |
其实这个用法和Windows下是一样的;关键在于这个代码不能使用默认的gcc/clang编译,一定要用winegcc main.c
这样来编译!
运行结果1(编译之后反汇编):
/Users/anthony/Desktop >./a.out |
运行结果2(错误信息):
/Users/anthony/Desktop >./a.out |
还要记录一下两个坑:
winegcc
其实是一个gcc封装,用的时候还要带上-I/usr/local/include/wine/windows
来确保能d3dcompiler.h等头文件;- 如果同时引入iostream等会导致错误,我没去具体研究怎么解决、反正用C接口也能凑合了;
- 编译会生成
a.out
和a.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重新跑通了…)。