抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

HLSL 转 Metal

为了方便游戏移植到iOS和Mac,苹果近年推出了一个Metal shader converter工具,这个工具可以搭配微软的DXC工具,实现HLSL转化为Metal

  1. 编写HLSL
  2. DXC将.hlsl转化为.dxil
  3. MSC将.dxil转化为.metallib

原生Metal

在了解HLSL转Metal之前,我们需要先知道原生Metal长什么样,我感觉和HLSL还是有不少区别的

桥接文件

首先Metal是有一个桥接的.h文件,这个文件连接了shader和程序(OC、Swift、C++),在这个文件中定义的枚举和结构,在两者中都可以使用

TODO:研究一下能不能使用enum class,这样写枚举污染命名空间

// Common.h
#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;
// 某个swift中,使得枚举可以直接转为Int
extension BufferIndices {
var index: Int {
return Int(self.rawValue)
}
}

比如我在这个Common.h中定义了一个枚举,每个枚举值都有其对应的整数,我们可以在管线和shader都使用这个枚举

// 这是基于swift的管线代码,我们将UBO设置到UniformsBuffer.index的位置
encoder.setVertexBytes(
&uniforms,
length: MemoryLayout<Uniforms>.stride,
index: UniformsBuffer.index)
// 这是Metal shader,将ubo设置到UniformsBuffer所对应的位置
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.7build/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 ];
...
}

评论