xiaohongshu
xiaohongshu
axiom-metal-migration-ref
Use when converting shaders or looking up API equivalents - GLSL to MSL, HLSL to MSL, GL/DirectX to Metal mappings, MTKView setup code
Full skill instructions
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 | [[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 |
| 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)) | |
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 |
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) |
mediump int | short (16-bit) |
highp int | int (32-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 | 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 Semantic | MSL Attribute |
|---|---|
SV_Position | [[position]] |
SV_Target0 | Return value / [[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 | 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)) | |
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 |
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 | MTLDevice |
| OpenGL | Metal |
|---|---|
glGenBuffers + glBufferData | device.makeBuffer(bytes:length:options:) |
glGenTextures + glTexImage2D | device.makeTexture(descriptor:) + texture.replace(region:...) |
glGenFramebuffers | MTLRenderPassDescriptor |
glGenVertexArrays | MTLVertexDescriptor |
glCreateShader + glCompileShader | Build-time compilation → 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 (decompose to triangles) |
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
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience