Apple 推出 metal后,除了新的metal framewrok外,也多了一种新的shader语言,最近工作也做了一些metal移植的测试,主要还是现有引擎如何可以快速支持metal的解决方案。这里也想对边写写自己的心得。
metal shader的语法特性更接近SM5的hlsl,所以sm4或sm5的hlsl转化成metal shader更简单,性能跟GLES3.1相似,提供了Vextrex shader, Fragment Shader和Computer Shader。加上移动端图形API的新特性的支持,所以使得次世代游戏引擎面向移动端的移植成为了可能。
像UE4,CE3这种的大型引擎来说,本身shader代码量就很大,要支持多平台,如果写多套shader,不论是在开发还是测试上都会让工作量成倍增加,所以都是选择转化shader的方式来跨平台。目前常用的shader转化,要么是FXC编译HLSL的字节码转化,要么就是直接源代码之间转化。对HLSL->GLSL而言,用字节码转化更简单。但metal shader不提供字节码的格式,所以字节码转化方式可能是行不通了。UE4在开发时的2年里也搜索过能解决SM5的HLSL转化的跨平台编译器,结果只能自己基于Mesa3D的功能进行扩展,开发了HLSLCC。
目前的几个HLSL->GLSL的方案:
hlsl2glslfork,由ATI的HLSL2GLSL发展而来,也基于了一部分monoshader的代码(UE3最早就是用的monoshader),shader源代码之间的翻译,问题是只支持DX9风格的HLSL,不支持DX10和DX11的一些语法,也没有geometry shader,tesseliaton shadr和 compute shader,现在Unity3d还是用的这个。
KlayGE的HLSL2GLSL,是从HLSL的字节码转化为GLSL。
UE4的HLSLCC,通过在他们在GDC2014上的ppt来看,也是受到glsl-optimizer的设计的影响,这两个都是参考Mesa3D的,不过Mesa3D只是GLSL的分析和转化为Mesa IR,HLSLCC改成了SM5的HLSL的分析功能,并添加了IR->GLSL的converter。并且在UE4.3后,也加入了metal shader的支持。
接下来也是想结合HLSLCC的ppt,说一些自己的看法,HLSLCC的转化流程,是HLSL->AST->IR->GLSL/Metal + ParameterMap,输出使用了glsl-optimizer一部分功能,但实际运行UE4的HLSLCC的时候,还是会有不少问题的。
1. shader marco,像ue的.usf,ce的.fxc,多少会通过使用宏来做一些shader的条件编译,静态分支处理等等,也就类似uber shader的概念,但基本上没有什么转化工具能搞定那么复杂的工作,而且也经常会有vs,ps的多个Entry函数(Main)写到一个usf文件里的情况,直接整个.usf扔给转化工具也是不现实的,ue和ce的解决方法,还是要经过内部的管理,把每个入口函数的部分独立出来,如UE4就是通过定义Golbalshader子类,自己来设置参数和入口函数名等等。然后独立的shader入口函数进行转化。而#include,#define,#if 一类的宏和分支的处理,进行转化前要通过,只留下runtime必须的部分,转化成不同平台的shader并编译为cache保存。
2. OGL通过uniform从每帧从CPU->GPU传递参数,为了减少API调用次数,所以DX11提供了const buffer,而ES3.0和metal也提供了uniform buffer的概念,因为对于大型游戏来说,uniform update调用API的数量也是很大一部分消费,所以需要使用const buffer来根据参数更新频率和顺序进行组织,而在不支持这种概念的DX9上,CE3使用了模拟的const buffer来对参数进行缓存重组再进行更新,而UE4虽然直接支持的DX11,但对移动平台ES2.0的设备,没有uniform buffer的特性,他们使用了模拟的Uniform buffer功能(ppt里叫shadow buffer),基于更新频率打包到uniform array。所以,HLSLCC除了输出GLSL外,也有生成Parameter Map的功能。
3. shader version方面,如果是DX11 HLSL的转化,对hlslcc进行一些修改定制后就可以,本身hlslcc也支持不同版本GL和metal的输出,如果是DX9 风格HLSL的话,就需要进行一些预处理工作了,dx9和dx11的主要变化在semantic,texture和sample的使用上有些变化。例如:
Pixel Shader的input,DX11里改为了SV_Position,output里,COLOR被SV_Target替代
DX9:
struct vs2ps
{
float4 Pos : POSITION;
float4 TexCd : TEXCOORD0;
};
DX11:
struct vs2ps
{
float4 Pos: SV_POSITION;
float4 TexCd: TEXCOORD0;
};
DX9:
float4 PS(vs2ps In): COLOR
DX11:
float4 PS(vs2ps In): SV_Target
Texture Sample的定义:
DX9:
texture Tex <string uiname="Texture";>;
sampler Samp = sampler_state //sampler for doing the texture-lookup
{
Texture = (Tex); //apply a texture to the sampler
MipFilter = LINEAR; //sampler states
MinFilter = LINEAR;
MagFilter = LINEAR;
};
DX11:
Texture2D texture;
SamplerState g_samLinear
{
Filter = MIN_MAG_MIP_LINEAR;
AddressU = Wrap;
AddressV = Wrap;
};
如果要Sample Texture:
DX9:
float4 col = tex2D(Samp, In.TexCd.xy);
DX11:
float4 col = texture2d.Sample( g_samLinear, In.TexCd.xy);
DX9:
float4 col = tex2Dlod(Samp, float4(In.TexCd.xy,,level));
DX11:
float4 col = texture2d.SampleLevel( g_samLinear, In.TexCd.xy,level);
DX9:
float4 col = tex2Dproj(Samp,screenProj.xyzw);
DX11:
float4 col = Samp.Sample(g_samLinear, .creenProj.xy / screenProj.w );
4 GLES3.1和metal里都提供了compute shader的功能,metal shader里叫kernel function,很可惜,这部分UE4也还没支持,所以HLSLCC里也还没实现Compute Shader的转化,但相信不久以后也会支持了,拭目以待吧。
总结,自己这方面的工作也是刚开始,所以描述的深度也有限,随着额工作深入,应该会对HLSLCC和各种API如何优化做进一步分析