mcpField
A
axiom-metal-migration-ref

axiom-metal-migration-ref

github.com/charleswiltgen开发1.2k165 次安装今天更新
代码仓库

关于 axiom-metal-migration-ref

面向将 OpenGL 或 DirectX 渲染代码移植到 Metal 的开发者参考技能。内容涵盖着色器转换、API 对照、视图与管线设置,以及存储模式和同步。

主要功能

  1. 01GLSL 与 HLSL 到 MSL 映射
  2. 02OpenGL 与 Metal API 对照
  3. 03MTKView 和 CAMetalLayer 设置
  4. 04渲染管线及存储模式参考
  5. 05Metal Shader Converter 工作流

适用场景

  1. 01将 GLSL 着色器移植到 MSL
  2. 02将 HLSL 着色器转换为 MSL
  3. 03查找 OpenGL 调用的 Metal 替代项

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-metal-migration-ref

安装后在 Claude Code 中输入/axiom-metal-migration-ref确认已加载。

详细介绍

Metal 迁移参考

将 OpenGL/DirectX 代码转换为 Metal 的完整参考。

何时使用本参考

在以下情况下使用本参考:

  • 将 GLSL 着色器转换为 Metal Shading Language (MSL)
  • 将 HLSL 着色器转换为 MSL
  • 查找 GL/D3D API 在 Metal 中的对应项
  • 设置 MTKView 或 CAMetalLayer
  • 构建渲染管线
  • 使用 Metal Shader Converter 处理 DirectX

第 1 部分:GLSL 到 MSL 的转换

类型映射
GLSLMSL说明
voidvoid
boolbool
intint32 位有符号
uintuint32 位无符号
floatfloat32 位
doubleN/A使用 float(MSL 中没有 64 位浮点数)
vec2float2
vec3float3
vec4float4
ivec2int2
ivec3int3
ivec4int4
uvec2uint2
uvec3uint3
uvec4uint4
bvec2bool2
bvec3bool3
bvec4bool4
mat2float2x2
mat3float3x3
mat4float4x4
mat2x3float2x3列 × 行
mat3x4float3x4
sampler2Dtexture2d<float> + sampler在 MSL 中分离
sampler3Dtexture3d<float> + sampler
samplerCubetexturecube<float> + sampler
sampler2DArraytexture2d_array<float> + sampler
sampler2DShadowdepth2d<float> + sampler
内置变量映射
GLSLMSL阶段
gl_Position返回 [[position]]顶点
gl_PointSize返回 [[point_size]]顶点
gl_VertexID[[vertex_id]] 参数顶点
gl_InstanceID[[instance_id]] 参数顶点
gl_FragCoord[[position]] 参数片段
gl_FrontFacing[[front_facing]] 参数片段
gl_PointCoord[[point_coord]] 参数片段
gl_FragDepth返回 [[depth(any)]]片段
gl_SampleID[[sample_id]] 参数片段
gl_SamplePosition[[sample_position]] 参数片段
函数映射
GLSLMSL说明
texture(sampler, uv)tex.sample(sampler, uv)纹理上的方法
textureLod(sampler, uv, lod)tex.sample(sampler, uv, level(lod))
textureGrad(sampler, uv, ddx, ddy)tex.sample(sampler, uv, gradient2d(ddx, ddy))
texelFetch(sampler, coord, lod)tex.read(coord, lod)整数坐标
textureSize(sampler, lod)tex.get_width(lod)、tex.get_height(lod)分别调用
dFdx(v)dfdx(v)
dFdy(v)dfdy(v)
fwidth(v)fwidth(v)相同
mix(a, b, t)mix(a, b, t)相同
clamp(v, lo, hi)clamp(v, lo, hi)相同
smoothstep(e0, e1, x)smoothstep(e0, e1, x)相同
step(edge, x)step(edge, x)相同
mod(x, y)fmod(x, y)名称不同
fract(x)fract(x)相同
inversesqrt(x)rsqrt(x)名称不同
atan(y, x)atan2(y, x)名称不同
着色器结构转换

GLSL 顶点着色器:

#version 300 es
precision highp float;

layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec2 aTexCoord;

uniform mat4 uModelViewProjection;

out vec2 vTexCoord;

void main() {
    gl_Position = uModelViewProjection * vec4(aPosition, 1.0);
    vTexCoord = aTexCoord;
}

MSL 顶点着色器:

#include <metal_stdlib>
using namespace metal;

struct VertexIn {
    float3 position [[attribute(0)]];
    float2 texCoord [[attribute(1)]];
};

struct VertexOut {
    float4 position [[position]];
    float2 texCoord;
};

struct Uniforms {
    float4x4 modelViewProjection;
};

vertex VertexOut vertexShader(
    VertexIn in [[stage_in]],
    constant Uniforms& uniforms [[buffer(1)]]
) {
    VertexOut out;
    out.position = uniforms.modelViewProjection * float4(in.position, 1.0);
    out.texCoord = in.texCoord;
    return out;
}

GLSL 片段着色器:

#version 300 es
precision highp float;

in vec2 vTexCoord;
uniform sampler2D uTexture;

out vec4 fragColor;

void main() {
    fragColor = texture(uTexture, vTexCoord);
}

MSL 片段着色器:

fragment float4 fragmentShader(
    VertexOut in [[stage_in]],
    texture2d<float> tex [[texture(0)]],
    sampler samp [[sampler(0)]]
) {
    return tex.sample(samp, in.texCoord);
}
精度限定符

GLSL 精度限定符在 MSL 中没有直接对应项,MSL 使用显式类型:

GLSLMSL 对应项
lowp floathalf(16 位)
mediump floathalf(16 位)
highp floatfloat(32 位)
lowp intshort(16 位)
mediump intshort(16 位)
highp intint(32 位)
缓冲区对齐(关键)

GLSL/C 假设:

  • vec3:12 字节,可采用任意对齐方式
  • vec4:16 字节

MSL 要求:

  • float3:存储占用 12 字节,按 16 字节对齐
  • float4:存储占用 16 字节,按 16 字节对齐

解决方案:在 Swift 中使用 simd 类型定义 CPU-GPU 共享结构体:

import simd

struct Uniforms {
    var modelViewProjection: simd_float4x4  // Correct alignment
    var cameraPosition: simd_float3         // 16-byte aligned
    var padding: Float = 0                   // Explicit padding if needed
}

也可以在 MSL 中使用打包类型(速度较慢):

struct VertexPacked {
    packed_float3 position;  // 12 bytes, no padding
    packed_float2 texCoord;  // 8 bytes
};

第 2 部分:HLSL 到 MSL 的转换

类型映射
HLSLMSL备注
floatfloat
float2float2
float3float3
float4float4
halfhalf
intint
uintuint
boolbool
float2x2float2x2
float3x3float3x3
float4x4float4x4
Texture2Dtexture2d<float>
Texture3Dtexture3d<float>
TextureCubetexturecube<float>
SamplerStatesampler
RWTexture2Dtexture2d<float, access::read_write>
RWBufferdevice float* [[buffer(n)]]
StructuredBufferconstant T* [[buffer(n)]]
RWStructuredBufferdevice T* [[buffer(n)]]
语义映射
HLSL 语义MSL 属性
SV_Position[[position]]
SV_Target0返回值 / [[color(0)]]
SV_Target1[[color(1)]]
SV_Depth[[depth(any)]]
SV_VertexID[[vertex_id]]
SV_InstanceID[[instance_id]]
SV_IsFrontFace[[front_facing]]
SV_SampleIndex[[sample_id]]
SV_PrimitiveID[[primitive_id]]
SV_DispatchThreadID[[thread_position_in_grid]]
SV_GroupThreadID[[thread_position_in_threadgroup]]
SV_GroupID[[threadgroup_position_in_grid]]
SV_GroupIndex[[thread_index_in_threadgroup]]
函数映射
HLSLMSL备注
tex.Sample(samp, uv)tex.sample(samp, uv)小写形式
tex.SampleLevel(samp, uv, lod)tex.sample(samp, uv, level(lod))
tex.SampleGrad(samp, uv, ddx, ddy)tex.sample(samp, uv, gradient2d(ddx, ddy))
tex.Load(coord)tex.read(coord.xy, coord.z)拆分 coord
mul(a, b)a * b运算符
saturate(x)saturate(x)相同
lerp(a, b, t)mix(a, b, t)名称不同
frac(x)fract(x)名称不同
ddx(v)dfdx(v)名称不同
ddy(v)dfdy(v)名称不同
clip(x)if (x < 0) discard_fragment()手动处理
discarddiscard_fragment()函数调用
Metal Shader Converter(DirectX → Metal)

Apple 用于将 DXIL(编译后的 HLSL)转换为 Metal 库的官方工具。

要求:

  • macOS 13+ 和 Xcode 15+
  • 或 Windows 10+ 和 VS 2019+
  • 目标设备:Argument Buffers Tier 2(macOS 14+、iOS 17+)

工作流:

# Step 1: Compile HLSL to DXIL using DXC
dxc -T vs_6_0 -E MainVS -Fo vertex.dxil shader.hlsl
dxc -T ps_6_0 -E MainPS -Fo fragment.dxil shader.hlsl

# Step 2: Convert DXIL to Metal library
metal-shaderconverter vertex.dxil -o vertex.metallib
metal-shaderconverter fragment.dxil -o fragment.metallib

# Step 3: Load in Swift
let vertexLib = try device.makeLibrary(URL: vertexURL)
let fragmentLib = try device.makeLibrary(URL: fragmentURL)

主要选项:

选项用途
-o <file>输出 metallib 路径
--minimum-gpu-family目标 GPU 系列
--minimum-os-build-version最低 OS 版本
--vertex-stage-in分离的顶点提取函数
-dualSourceBlending启用双源混合

支持的着色器模型:SM 6.0 - 6.6(对 6.6 功能有所限制)

第 3 部分:OpenGL API 到 Metal API

视图/上下文设置
OpenGLMetal
NSOpenGLViewMTKView
GLKViewMTKView
EAGLContextMTLDevice + MTLCommandQueue
CGLContextObjMTLDevice
资源创建
OpenGLMetal
glGenBuffers + glBufferDatadevice.makeBuffer(bytes:length:options:)
glGenTextures + glTexImage2Ddevice.makeTexture(descriptor:) + texture.replace(region:...)
glGenFramebuffersMTLRenderPassDescriptor
glGenVertexArraysMTLVertexDescriptor
glCreateShader + glCompileShader构建时编译 → MTLLibrary
glCreateProgram + glLinkProgramMTLRenderPipelineDescriptor → MTLRenderPipelineState
状态管理
OpenGLMetal
glEnable(GL_DEPTH_TEST)MTLDepthStencilDescriptor → MTLDepthStencilState
glDepthFunc(GL_LESS)descriptor.depthCompareFunction = .less
glEnable(GL_BLEND)pipelineDescriptor.colorAttachments[0].isBlendingEnabled = true
glBlendFuncsourceRGBBlendFactor, destinationRGBBlendFactor
glCullFaceencoder.setCullMode(.back)
glFrontFaceencoder.setFrontFacing(.counterClockwise)
glViewportencoder.setViewport(MTLViewport(...))
glScissorencoder.setScissorRect(MTLScissorRect(...))
绘制命令
OpenGLMetal
glDrawArrays(mode, first, count)encoder.drawPrimitives(type:vertexStart:vertexCount:)
glDrawElements(mode, count, type, indices)encoder.drawIndexedPrimitives(type:indexCount:indexType:indexBuffer:indexBufferOffset:)
glDrawArraysInstancedencoder.drawPrimitives(type:vertexStart:vertexCount:instanceCount:)
glDrawElementsInstancedencoder.drawIndexedPrimitives(...instanceCount:)
图元类型
OpenGLMetal
GL_POINTS.point
GL_LINES.line
GL_LINE_STRIP.lineStrip
GL_TRIANGLES.triangle
GL_TRIANGLE_STRIP.triangleStrip
GL_TRIANGLE_FAN不适用(拆分为三角形)

第 4 部分:完整设置示例

MTKView 设置(推荐)
import MetalKit

class GameViewController: UIViewController {
    var metalView: MTKView!
    var renderer: Renderer!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Create Metal view
        guard let device = MTLCreateSystemDefaultDevice() else {
            fatalError("Metal not supported")
        }

        metalView = MTKView(frame: view.bounds, device: device)
        metalView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        metalView.colorPixelFormat = .bgra8Unorm
        metalView.depthStencilPixelFormat = .depth32Float
        metalView.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 1)
        metalView.preferredFramesPerSecond = 60
        view.addSubview(metalView)

        // Create renderer
        renderer = Renderer(metalView: metalView)
        metalView.delegate = renderer
    }
}

class Renderer: NSObject, MTKViewDelegate {
    let device: MTLDevice
    let commandQueue: MTLCommandQueue
    var pipelineState: MTLRenderPipelineState!
    var depthState: MTLDepthStencilState!
    var vertexBuffer: MTLBuffer!

    init(metalView: MTKView) {
        device = metalView.device!
        commandQueue = device.makeCommandQueue()!
        super.init()

        buildPipeline(metalView: metalView)
        buildDepthStencil()
        buildBuffers()
    }

    private func buildPipeline(metalView: MTKView) {
        let library = device.makeDefaultLibrary()!

        let descriptor = MTLRenderPipelineDescriptor()
        descriptor.vertexFunction = library.makeFunction(name: "vertexShader")
        descriptor.fragmentFunction = library.makeFunction(name: "fragmentShader")
        descriptor.colorAttachments[0].pixelFormat = metalView.colorPixelFormat
        descriptor.depthAttachmentPixelFormat = metalView.depthStencilPixelFormat

        // Vertex descriptor (matches shader's VertexIn struct)
        let vertexDescriptor = MTLVertexDescriptor()
        vertexDescriptor.attributes[0].format = .float3
        vertexDescriptor.attributes[0].offset = 0
        vertexDescriptor.attributes[0].bufferIndex = 0
        vertexDescriptor.attributes[1].format = .float2
        vertexDescriptor.attributes[1].offset = MemoryLayout<SIMD3<Float>>.stride
        vertexDescriptor.attributes[1].bufferIndex = 0
        vertexDescriptor.layouts[0].stride = MemoryLayout<Vertex>.stride
        descriptor.vertexDescriptor = vertexDescriptor

        pipelineState = try! device.makeRenderPipelineState(descriptor: descriptor)
    }

    private func buildDepthStencil() {
        let descriptor = MTLDepthStencilDescriptor()
        descriptor.depthCompareFunction = .less
        descriptor.isDepthWriteEnabled = true
        depthState = device.makeDepthStencilState(descriptor: descriptor)
    }

    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
        // Handle resize
    }

    func draw(in view: MTKView) {
        guard let drawable = view.currentDrawable,
              let descriptor = view.currentRenderPassDescriptor,
              let commandBuffer = commandQueue.makeCommandBuffer(),
              let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
            return
        }

        encoder.setRenderPipelineState(pipelineState)
        encoder.setDepthStencilState(depthState)
        encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
        encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertexCount)
        encoder.endEncoding()

        commandBuffer.present(drawable)
        commandBuffer.commit()
    }
}
CAMetalLayer 设置(自定义控制)
import Metal
import QuartzCore

class MetalLayerView: UIView {
    var metalLayer: CAMetalLayer!
    var device: MTLDevice!
    var commandQueue: MTLCommandQueue!
    var displayLink: CADisplayLink?

    override class var layerClass: AnyClass { CAMetalLayer.self }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    private func setup() {
        device = MTLCreateSystemDefaultDevice()!
        commandQueue = device.makeCommandQueue()!

        metalLayer = layer as? CAMetalLayer
        metalLayer.device = device
        metalLayer.pixelFormat = .bgra8Unorm
        metalLayer.framebufferOnly = true

        displayLink = CADisplayLink(target: self, selector: #selector(render))
        displayLink?.add(to: .main, forMode: .common)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        metalLayer.drawableSize = CGSize(
            width: bounds.width * contentScaleFactor,
            height: bounds.height * contentScaleFactor
        )
    }

    @objc func render() {
        guard let drawable = metalLayer.nextDrawable(),
              let commandBuffer = commandQueue.makeCommandBuffer() else {
            return
        }

        let descriptor = MTLRenderPassDescriptor()
        descriptor.colorAttachments[0].texture = drawable.texture
        descriptor.colorAttachments[0].loadAction = .clear
        descriptor.colorAttachments[0].storeAction = .store
        descriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 1)

        guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
            return
        }

        // Draw commands here
        encoder.endEncoding()

        commandBuffer.present(drawable)
        commandBuffer.commit()
    }
}
计算着色器设置
class ComputeProcessor {
    let device: MTLDevice
    let commandQueue: MTLCommandQueue
    var computePipeline: MTLComputePipelineState!

    init() {
        device = MTLCreateSystemDefaultDevice()!
        commandQueue = device.makeCommandQueue()!

        let library = device.makeDefaultLibrary()!
        let function = library.makeFunction(name: "computeKernel")!
        computePipeline = try! device.makeComputePipelineState(function: function)
    }

    func process(input: MTLBuffer, output: MTLBuffer, count: Int) {
        let commandBuffer = commandQueue.makeCommandBuffer()!
        let encoder = commandBuffer.makeComputeCommandEncoder()!

        encoder.setComputePipelineState(computePipeline)
        encoder.setBuffer(input, offset: 0, index: 0)
        encoder.setBuffer(output, offset: 0, index: 1)

        let threadGroupSize = MTLSize(width: 256, height: 1, depth: 1)
        let threadGroups = MTLSize(
            width: (count + 255) / 256,
            height: 1,
            depth: 1
        )

        encoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupSize)
        encoder.endEncoding()

        commandBuffer.commit()
        commandBuffer.waitUntilCompleted()
    }
}
// Compute shader
kernel void computeKernel(
    device float* input [[buffer(0)]],
    device float* output [[buffer(1)]],
    uint id [[thread_position_in_grid]]
) {
    output[id] = input[id] * 2.0;
}

第 5 部分:存储模式与同步

缓冲区存储模式
模式CPU 访问GPU 访问使用场景
.shared读/写读/写小型动态数据、uniform
.private无读/写静态资源、渲染目标
.managed (macOS)读/写读/写带部分更新的大型缓冲区
// Shared: CPU and GPU both access (iOS typical)
let uniformBuffer = device.makeBuffer(length: size, options: .storageModeShared)

// Private: GPU only (best for static geometry)
let vertexBuffer = device.makeBuffer(bytes: vertices, length: size, options: .storageModePrivate)

// Managed: Explicit sync (macOS)
#if os(macOS)
let buffer = device.makeBuffer(length: size, options: .storageModeManaged)
// After CPU write:
buffer.didModifyRange(0..<size)
#endif
纹理存储模式
let descriptor = MTLTextureDescriptor.texture2DDescriptor(
    pixelFormat: .rgba8Unorm,
    width: 1024,
    height: 1024,
    mipmapped: true
)

// For static textures (loaded once)
descriptor.storageMode = .private
descriptor.usage = [.shaderRead]

// For render targets
descriptor.storageMode = .private
descriptor.usage = [.renderTarget, .shaderRead]

// For CPU-readable (screenshots, readback)
descriptor.storageMode = .shared  // iOS
descriptor.storageMode = .managed  // macOS
descriptor.usage = [.shaderRead, .shaderWrite]

资源

WWDC:2016-00602、2018-00604、2019-00611

文档:/metal/migrating-opengl-code-to-metal、/metal/shader-converter、/metalkit/mtkview

技能:axiom-metal-migration、axiom-metal-migration-diag


最后更新:2025-12-29 平台:iOS 12+、macOS 10.14+、tvOS 12+ 状态:完整的着色器转换和 API 映射参考

评论

支持 Markdown