HLSL 转 Metal
为了方便游戏移植到iOS和Mac,苹果近年推出了一个Metal shader converter工具,这个工具可以搭配微软的DXC工具,实现HLSL转化为Metal
- 编写HLSL
- DXC将
.hlsl转化为.dxil
- MSC将
.dxil转化为.metallib
在了解HLSL转Metal之前,我们需要先知道原生Metal长什么样,我感觉和HLSL还是有不少区别的
桥接文件
首先Metal是有一个桥接的.h文件,这个文件连接了shader和程序(OC、Swift、C++),在这个文件中定义的枚举和结构,在两者中都可以使用
TODO:研究一下能不能使用enum class,这样写枚举污染命名空间
#import <simd/simd.h> typedef enum { VertexBuffer = 0, UVBuffer = 1, ColorBuffer = 2, TangentBuffer = 3, BitangentBuffer = 4, TerrainBuffer = 6, UniformsBuffer = 11, ParamsBuffer = 12, LightBuffer = 13, MaterialBuffer = 14, IdBuffer = 15 } BufferIndices;
|
extension BufferIndices { var index: Int { return Int(self.rawValue) } }
|
比如我在这个Common.h中定义了一个枚举,每个枚举值都有其对应的整数,我们可以在管线和shader都使用这个枚举
encoder.setVertexBytes( &uniforms, length: MemoryLayout<Uniforms>.stride, index: UniformsBuffer.index)
|
vertex VertexOut vertex_main(VertexIn in [[stage_in]], constant Uniforms &uniforms [[buffer(UniformsBuffer)]]) {}
|
HLSL
静态成员
HLSL可以在shader中创建并初始化一个静态成员,但是需要加static
static const float3x2 _positions = { -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, -1.0f };
|
Resource binding
HLSL的资产绑定是基于寄存器的
| 符号 |
含义 |
示例 |
| t |
SRV |
Texture2D _BaseMap : register(t1); |
| s |
samplers |
SamplerState _BaseMap_ST : register(s1); |
| u |
UAV |
|
| b |
CBV |
cbuffer ubo : register(b0) { UBO ubo; } |
SPIR-V拓展
[[vk::push_constant]]
HLSL中Push Constants其实也是一个cbuffer,只是不绑定任何寄存器,是一个$Global cbuffer,你可以直接
cbuffer Constants { float4x4 g_WorldViewProj; };
|
用于标记常量数据,使得可以被vkCmdPushConstants推送
struct PushConstant { float4x4 modelMatrix; };
[[vk::push_constant]]PushConstant pushConstant;
|
环境搭建
DXC on Mac
git clone
git clone https://github.com/microsoft/DirectXShaderCompiler.git
|
submodule init
cd DirectXShaderCompiler git submodule init git submodule update
|
build(话说这一步还挺慢的,感觉我的M1该退役了)
mkdir build cd build cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -C ../cmake/caches/PredefinedParams.cmake -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" make -j8
|
我们会得到一大堆生成内容,我们只需要build/bin/dxc-3.7和build/lib/libdxcompiler.dylib
- 将
build/bin/dxc-3.7拷贝到/usr/local/bin/
- 将
build/lib/libdxcompiler.dylib拷贝到/usr/local/lib
打开命令行,发现可以使用dxc命令了
dxc-3.7 -E MainVS -T vs_6_0 -Fo "grass.v.dxil" "grass.hlsl"
|
MSC
下载MSC后,双击安装即可
安装完毕你可以在/usr/local/lib找到对应的库,如果你的程序需要运行时生成 Metal Shader,可以将这个库嵌入项目中
你可以使用命令行编译了
metal-shaderconverter grass.v.dxil -o ./grass.metallib
|
使用参数--output-reflection-file还可以生成反射信息
metal-shaderconverter triangle.v.dxil -o ./triangle.metallib --output-reflection-file a.json
|
{ "EntryPoint": "MainVS", "FunctionConstants": [], "NeedsFunctionConstants": false, "Resources": [], "ShaderID": "9969595390685293918", "ShaderType": "Vertex", "TopLevelArgumentBuffer": [], "instance_id_index": 2, "max_primitives_per_mesh_threadgroup": 0, "needs_draw_params": true, "vertex_id_index": 1, "vertex_inputs": [ { "columnCount": 4, "elementType": "Float", "index": 0, "name": "position0" } ], "vertex_output_size_in_bytes": 16, "vertex_outputs": [ { "columnCount": 4, "elementType": "Float", "index": 1, "name": "sv_position0" } ] }
|
SPIRV
除了官方的MSC,还可以使用SPIRV
我不太清楚MSC要如何加入调试信息,但SPIRV可以很容易加入
dxc-3.7 -E MainPS -spirv -Zi -Qembed_debug -O0 -T ps_6_0 -Fo "triangle.frag.spirv" "triangle.hlsl" spirv-cross --msl triangle.frag.spirv --output triangle.frag.metal xcrun -sdk macosx metal -c -frecord-sources triangle.frag.metal -o triangle.frag.air xcrun -sdk macosx metallib triangle.frag.air -o triangle.frag.metallib
|
通过安装Metal Developer Tools For Windows,可以在Window下使用metal和metallib工具
MSC
片元着色器
片元着色器比较好实现
struct v2f { float4 position : SV_Position; };
float4 MainPS( v2f vin ) : SV_Target { return float4(1.0, 0.0, 0.0, 1.0); }
|
使用DXC和MSC编译后,得到triangle.f.metallib,并放在路径Metal-Tutorial/triangle.p.metallib
MTL::Library* lib = metalDevice->newLibrary(NS::String::string("Metal-Tutorial/triangle.p.metallib", NS::ASCIIStringEncoding), nullptr);
MTL::Function* frag = lib->newFunction(NS::String::string("MainPS", NS::ASCIIStringEncoding));
MTL::RenderPipelineDescriptor* renderPipelineDescriptor = MTL::RenderPipelineDescriptor::alloc()->init(); renderPipelineDescriptor->setFragmentFunction(frag);
|
顶点着色器
metal有两种传入顶点信息的方式
vertex descriptor
一种是使用VertexDescriptor描述VertexBuffer,shader大概长这样
struct VertexIn { float4 position [[attribute(Position)]]; float3 normal [[attribute(Normal)]]; float2 uv [[attribute(UV)]]; float3 color [[attribute(Color)]]; float3 tangent [[attribute(Tangent)]]; float3 bitangent [[attribute(Bitangent)]]; }; vertex VertexOut vertex_main(VertexIn in [[stage_in]]) {}
|
用HLSL写大概是
PSInput VSMain(float4 position : POSITION, float4 uv : TEXCOORD) {}
|
binding buffer
另一种是binding一个buffer和vertexID,类似于instance
struct Vertex { float3 position; float3 normal; float3 tangent; float3 bitangent; float2 textureCoordinate; int diffuseTextureIndex; int specularTextureIndex; int normalMapIndex; int emissiveMapIndex; }; vertex OutData vertexShader( uint vertexID [[vertex_id]], constant Vertex* vertexData [[buffer(0)]]) {}
|
用HLSL写,大概是
struct VertexData { float4 position; float4 normal; float4 texcoord; };
StructuredBuffer<VertexData> vertexData : register(t0, space0);
v2f MainVS( uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID ) { VertexData vd = vertexData[ vertexId ]; ... }
|