axiom-metal-migration-ref by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-metal-migration-ref将 OpenGL/DirectX 代码转换为 Metal 的完整参考。
在以下情况下使用本参考:
| GLSL | MSL | 备注 |
|---|---|---|
void | void | |
bool | bool |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
int | int | 32 位有符号 |
uint | uint | 32 位无符号 |
float | float | 32 位 |
double | N/A | 使用 float (MSL 中无 64 位浮点数) |
vec2 | float2 |
vec3 | float3 |
vec4 | float4 |
ivec2 | int2 |
ivec3 | int3 |
ivec4 | int4 |
uvec2 | uint2 |
uvec3 | uint3 |
uvec4 | uint4 |
bvec2 | bool2 |
bvec3 | bool3 |
bvec4 | bool4 |
mat2 | float2x2 |
mat3 | float3x3 |
mat4 | float4x4 |
mat2x3 | float2x3 | 列 x 行 |
mat3x4 | float3x4 |
sampler2D | texture2d<float> + sampler | 在 MSL 中分开 |
sampler3D | texture3d<float> + sampler |
samplerCube | texturecube<float> + sampler |
sampler2DArray | texture2d_array<float> + sampler |
sampler2DShadow | depth2d<float> + sampler |
| GLSL | MSL | 阶段 |
|---|---|---|
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]] 参数 | 片段 |
| GLSL | MSL | 备注 |
|---|---|---|
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 使用显式类型:
| GLSL | MSL 等效项 |
|---|---|
lowp float | half (16 位) |
mediump float | half (16 位) |
highp float | float (32 位) |
lowp int | short (16 位) |
mediump int | short (16 位) |
highp int | int (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 // 正确对齐
var cameraPosition: simd_float3 // 16 字节对齐
var padding: Float = 0 // 如果需要,显式填充
}
或者在 MSL 中使用打包类型(较慢):
struct VertexPacked {
packed_float3 position; // 12 字节,无填充
packed_float2 texCoord; // 8 字节
};
| HLSL | MSL | 备注 |
|---|---|---|
float | float | |
float2 | float2 | |
float3 | float3 | |
float4 | float4 | |
half | half | |
int | int | |
uint | uint | |
bool | bool | |
float2x2 | float2x2 | |
float3x3 | float3x3 | |
float4x4 | float4x4 | |
Texture2D | texture2d<float> | |
Texture3D | texture3d<float> | |
TextureCube | texturecube<float> | |
SamplerState | sampler | |
RWTexture2D | texture2d<float, access::read_write> | |
RWBuffer | device float* [[buffer(n)]] | |
StructuredBuffer | constant T* [[buffer(n)]] | |
RWStructuredBuffer | device 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]] |
| HLSL | MSL | 备注 |
|---|---|---|
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) | 拆分坐标 |
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() | 手动 |
discard | discard_fragment() | 函数调用 |
Apple 官方工具,用于将 DXIL(编译后的 HLSL)转换为 Metal 库。
要求 :
工作流程 :
# 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 | 最低操作系统版本 |
--vertex-stage-in | 分离的顶点获取函数 |
-dualSourceBlending | 启用双源混合 |
支持的着色器模型 : SM 6.0 - 6.6 (对 6.6 功能有限制)
| OpenGL | Metal |
|---|---|
NSOpenGLView | MTKView |
GLKView | MTKView |
EAGLContext | MTLDevice + MTLCommandQueue |
CGLContextObj | MTLDevice |
| OpenGL | Metal |
|---|---|
glGenBuffers + glBufferData | device.makeBuffer(bytes:length:options:) |
glGenTextures + glTexImage2D | device.makeTexture(descriptor:) + texture.replace(region:...) |
glGenFramebuffers | MTLRenderPassDescriptor |
glGenVertexArrays | MTLVertexDescriptor |
glCreateShader + glCompileShader | 构建时编译 → MTLLibrary |
glCreateProgram + glLinkProgram | MTLRenderPipelineDescriptor → MTLRenderPipelineState |
| OpenGL | Metal |
|---|---|
glEnable(GL_DEPTH_TEST) | MTLDepthStencilDescriptor → MTLDepthStencilState |
glDepthFunc(GL_LESS) | descriptor.depthCompareFunction = .less |
glEnable(GL_BLEND) | pipelineDescriptor.colorAttachments[0].isBlendingEnabled = true |
glBlendFunc | sourceRGBBlendFactor, destinationRGBBlendFactor |
glCullFace | encoder.setCullMode(.back) |
glFrontFace | encoder.setFrontFacing(.counterClockwise) |
glViewport | encoder.setViewport(MTLViewport(...)) |
glScissor | encoder.setScissorRect(MTLScissorRect(...)) |
| OpenGL | Metal |
|---|---|
glDrawArrays(mode, first, count) | encoder.drawPrimitives(type:vertexStart:vertexCount:) |
glDrawElements(mode, count, type, indices) | encoder.drawIndexedPrimitives(type:indexCount:indexType:indexBuffer:indexBufferOffset:) |
glDrawArraysInstanced | encoder.drawPrimitives(type:vertexStart:vertexCount:instanceCount:) |
glDrawElementsInstanced | encoder.drawIndexedPrimitives(...instanceCount:) |
| OpenGL | Metal |
|---|---|
GL_POINTS | .point |
GL_LINES | .line |
GL_LINE_STRIP | .lineStrip |
GL_TRIANGLES | .triangle |
GL_TRIANGLE_STRIP | .triangleStrip |
GL_TRIANGLE_FAN | N/A (分解为三角形) |
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()
}
}
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;
}
| 模式 | CPU 访问 | GPU 访问 | 使用场景 |
|---|---|---|---|
.shared | 读/写 | 读/写 | 小型动态数据,uniforms |
.private | 无 | 读/写 | 静态资源,渲染目标 |
.managed (macOS) | 读/写 | 读/写 | 需要部分更新的大型缓冲区 |
// Shared: CPU 和 GPU 都可访问 (iOS 典型)
let uniformBuffer = device.makeBuffer(length: size, options: .storageModeShared)
// Private: 仅 GPU 访问 (最适合静态几何体)
let vertexBuffer = device.makeBuffer(bytes: vertices, length: size, options: .storageModePrivate)
// Managed: 显式同步 (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 映射参考
每周安装
88
仓库
GitHub 星标
601
首次出现
Jan 21, 2026
安全审计
安装于
opencode73
claude-code69
codex68
gemini-cli65
cursor65
github-copilot62
Complete reference for converting OpenGL/DirectX code to Metal.
Use this reference when:
| GLSL | MSL | Notes |
|---|---|---|
void | void | |
bool | bool | |
int | int | 32-bit signed |
uint | uint | 32-bit unsigned |
float | float | 32-bit |
double | N/A | Use float (no 64-bit float in MSL) |
vec2 | float2 | |
vec3 | float3 | |
vec4 | float4 | |
ivec2 | int2 | |
ivec3 | int3 | |
ivec4 | int4 | |
uvec2 | uint2 | |
uvec3 | uint3 | |
uvec4 | uint4 | |
bvec2 | bool2 | |
bvec3 | bool3 | |
bvec4 | bool4 | |
mat2 | float2x2 | |
mat3 | float3x3 | |
mat4 | float4x4 | |
mat2x3 | float2x3 | Columns x Rows |
mat3x4 | float3x4 | |
sampler2D | texture2d<float> + sampler | Separate in MSL |
sampler3D | texture3d<float> + sampler | |
samplerCube | texturecube<float> + sampler | |
sampler2DArray | texture2d_array<float> + sampler | |
sampler2DShadow | depth2d<float> + sampler |
| GLSL | MSL | Stage |
|---|---|---|
gl_Position | Return [[position]] | Vertex |
gl_PointSize | Return [[point_size]] | Vertex |
gl_VertexID | [[vertex_id]] parameter | Vertex |
gl_InstanceID |
| GLSL | MSL | Notes |
|---|---|---|
texture(sampler, uv) | tex.sample(sampler, uv) | Method on texture |
textureLod(sampler, uv, lod) | tex.sample(sampler, uv, level(lod)) | |
textureGrad(sampler, uv, ddx, ddy) | tex.sample(sampler, uv, gradient2d(ddx, ddy)) | |
GLSL Vertex Shader :
#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 Vertex Shader :
#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 Fragment Shader :
#version 300 es
precision highp float;
in vec2 vTexCoord;
uniform sampler2D uTexture;
out vec4 fragColor;
void main() {
fragColor = texture(uTexture, vTexCoord);
}
MSL Fragment Shader :
fragment float4 fragmentShader(
VertexOut in [[stage_in]],
texture2d<float> tex [[texture(0)]],
sampler samp [[sampler(0)]]
) {
return tex.sample(samp, in.texCoord);
}
GLSL precision qualifiers have no direct MSL equivalent — MSL uses explicit types:
| GLSL | MSL Equivalent |
|---|---|
lowp float | half (16-bit) |
mediump float | half (16-bit) |
highp float | float (32-bit) |
lowp int | short (16-bit) |
GLSL/C assumes :
vec3: 12 bytes, any alignmentvec4: 16 bytesMSL requires :
float3: 12 bytes storage, 16-byte alignedfloat4: 16 bytes storage, 16-byte alignedSolution : Use simd types in Swift for CPU-GPU shared structs:
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
}
Or use packed types in MSL (slower):
struct VertexPacked {
packed_float3 position; // 12 bytes, no padding
packed_float2 texCoord; // 8 bytes
};
| HLSL | MSL | Notes |
|---|---|---|
float | float | |
float2 | float2 | |
float3 | float3 | |
float4 |
| HLSL Semantic | MSL Attribute |
|---|---|
SV_Position | [[position]] |
SV_Target0 | Return value / [[color(0)]] |
SV_Target1 | [[color(1)]] |
SV_Depth | [[depth(any)]] |
| HLSL | MSL | Notes |
|---|---|---|
tex.Sample(samp, uv) | tex.sample(samp, uv) | Lowercase |
tex.SampleLevel(samp, uv, lod) | tex.sample(samp, uv, level(lod)) | |
tex.SampleGrad(samp, uv, ddx, ddy) | tex.sample(samp, uv, gradient2d(ddx, ddy)) | |
Apple's official tool for converting DXIL (compiled HLSL) to Metal libraries.
Requirements :
Workflow :
# 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)
Key Options :
| Option | Purpose |
|---|---|
-o <file> | Output metallib path |
--minimum-gpu-family | Target GPU family |
--minimum-os-build-version | Minimum OS version |
--vertex-stage-in | Separate vertex fetch function |
-dualSourceBlending | Enable dual-source blending |
Supported Shader Models : SM 6.0 - 6.6 (with limitations on 6.6 features)
| OpenGL | Metal |
|---|---|
NSOpenGLView | MTKView |
GLKView | MTKView |
EAGLContext | MTLDevice + MTLCommandQueue |
CGLContextObj |
| OpenGL | Metal |
|---|---|
glGenBuffers + glBufferData | device.makeBuffer(bytes:length:options:) |
glGenTextures + glTexImage2D | device.makeTexture(descriptor:) + texture.replace(region:...) |
glGenFramebuffers |
| OpenGL | Metal |
|---|---|
glEnable(GL_DEPTH_TEST) | MTLDepthStencilDescriptor → MTLDepthStencilState |
glDepthFunc(GL_LESS) | descriptor.depthCompareFunction = .less |
glEnable(GL_BLEND) | pipelineDescriptor.colorAttachments[0].isBlendingEnabled = true |
| OpenGL | Metal |
|---|---|
glDrawArrays(mode, first, count) | encoder.drawPrimitives(type:vertexStart:vertexCount:) |
glDrawElements(mode, count, type, indices) | encoder.drawIndexedPrimitives(type:indexCount:indexType:indexBuffer:indexBufferOffset:) |
glDrawArraysInstanced | encoder.drawPrimitives(type:vertexStart:vertexCount:instanceCount:) |
glDrawElementsInstanced |
| OpenGL | Metal |
|---|---|
GL_POINTS | .point |
GL_LINES | .line |
GL_LINE_STRIP | .lineStrip |
GL_TRIANGLES | .triangle |
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()
}
}
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;
}
| Mode | CPU Access | GPU Access | Use Case |
|---|---|---|---|
.shared | Read/Write | Read/Write | Small dynamic data, uniforms |
.private | None | Read/Write | Static assets, render targets |
.managed (macOS) | Read/Write | Read/Write | Large buffers with partial updates |
// 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
Docs : /metal/migrating-opengl-code-to-metal, /metal/shader-converter, /metalkit/mtkview
Skills : axiom-metal-migration, axiom-metal-migration-diag
Last Updated : 2025-12-29 Platforms : iOS 12+, macOS 10.14+, tvOS 12+ Status : Complete shader conversion and API mapping reference
Weekly Installs
88
Repository
GitHub Stars
601
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode73
claude-code69
codex68
gemini-cli65
cursor65
github-copilot62
SpecStory 历史文件整理工具 - 按时间戳自动归档会话文件,保持项目目录整洁
101 周安装
OMC Doctor:Claude代码助手安装诊断与修复工具 - 解决OMC插件问题
101 周安装
微信文章转Markdown工具 - 高效抓取公众号文章并转换为Markdown格式,支持存档与AI处理
101 周安装
Go语言技术文档编写指南 - 专业API设计与文档生成技能
101 周安装
Mermaid图表专家技能 - 专业流程图、序列图、甘特图代码生成与可视化指南
101 周安装
销售自动化工具 - 冷邮件序列、跟进计划、话术模板与A/B测试完整指南
101 周安装
[[instance_id]] parameter |
| Vertex |
gl_FragCoord | [[position]] parameter | Fragment |
gl_FrontFacing | [[front_facing]] parameter | Fragment |
gl_PointCoord | [[point_coord]] parameter | Fragment |
gl_FragDepth | Return [[depth(any)]] | Fragment |
gl_SampleID | [[sample_id]] parameter | Fragment |
gl_SamplePosition | [[sample_position]] parameter | Fragment |
texelFetch(sampler, coord, lod)tex.read(coord, lod) |
| Integer coords |
textureSize(sampler, lod) | tex.get_width(lod), tex.get_height(lod) | Separate calls |
dFdx(v) | dfdx(v) |
dFdy(v) | dfdy(v) |
fwidth(v) | fwidth(v) | Same |
mix(a, b, t) | mix(a, b, t) | Same |
clamp(v, lo, hi) | clamp(v, lo, hi) | Same |
smoothstep(e0, e1, x) | smoothstep(e0, e1, x) | Same |
step(edge, x) | step(edge, x) | Same |
mod(x, y) | fmod(x, y) | Different name |
fract(x) | fract(x) | Same |
inversesqrt(x) | rsqrt(x) | Different name |
atan(y, x) | atan2(y, x) | Different name |
mediump int | short (16-bit) |
highp int | int (32-bit) |
float4half | half |
int | int |
uint | uint |
bool | bool |
float2x2 | float2x2 |
float3x3 | float3x3 |
float4x4 | float4x4 |
Texture2D | texture2d<float> |
Texture3D | texture3d<float> |
TextureCube | texturecube<float> |
SamplerState | sampler |
RWTexture2D | texture2d<float, access::read_write> |
RWBuffer | device float* [[buffer(n)]] |
StructuredBuffer | constant T* [[buffer(n)]] |
RWStructuredBuffer | device T* [[buffer(n)]] |
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]] |
tex.Load(coord)tex.read(coord.xy, coord.z) |
| Split coord |
mul(a, b) | a * b | Operator |
saturate(x) | saturate(x) | Same |
lerp(a, b, t) | mix(a, b, t) | Different name |
frac(x) | fract(x) | Different name |
ddx(v) | dfdx(v) | Different name |
ddy(v) | dfdy(v) | Different name |
clip(x) | if (x < 0) discard_fragment() | Manual |
discard | discard_fragment() | Function call |
MTLDeviceMTLRenderPassDescriptor |
glGenVertexArrays | MTLVertexDescriptor |
glCreateShader + glCompileShader | Build-time compilation → MTLLibrary |
glCreateProgram + glLinkProgram | MTLRenderPipelineDescriptor → MTLRenderPipelineState |
glBlendFuncsourceRGBBlendFactor, destinationRGBBlendFactor |
glCullFace | encoder.setCullMode(.back) |
glFrontFace | encoder.setFrontFacing(.counterClockwise) |
glViewport | encoder.setViewport(MTLViewport(...)) |
glScissor | encoder.setScissorRect(MTLScissorRect(...)) |
encoder.drawIndexedPrimitives(...instanceCount:) |
GL_TRIANGLE_STRIP | .triangleStrip |
GL_TRIANGLE_FAN | N/A (decompose to triangles) |