Complete reference for converting OpenGL/DirectX code to Metal.
Works with
AI-first code editor with Composer
Before installing skills in Cursor, ensure your development environment meets these requirements:
node --versionaxiom-metal-migration-refExecute the skills CLI command in your project's root directory to begin installation:
Fetches axiom-metal-migration-ref from charleswiltgen/axiom and configures it for Cursor.
The CLI shows a list of agents. Use arrow keys and space to select Cursor:
Confirm successful installation by checking the skill directory location:
Restart Cursor to activate axiom-metal-migration-ref. Access via /axiom-metal-migration-ref in your agent's command palette.
We perform automated surface-level scans (Gen AI Scanner, Socket, Snyk) during installation. These checks detect common vulnerabilities but do not guarantee complete security. Always review skill source code and verify the publisher's reputation before production use.
Skills execute code in your environment. Always review source, verify the publisher, and test in isolation before production.
Submit your Claude Code skill and start earning
Create detailed user stories, acceptance criteria, and feature specs
Example
Generate user stories for 'password reset feature' with acceptance criteria, edge cases, and test scenarios
Reduce spec writing time by 50%, ensure comprehensive coverage
Research competitors, compare features, identify gaps
Example
Analyze 5 competitor products, create feature comparison matrix, suggest differentiation opportunities
Complete competitive research in 2 hours instead of 2 days
Evaluate features using frameworks (RICE, ICE, Kano) and create prioritized backlogs
Example
Score 20 feature ideas using RICE framework, generate prioritized roadmap with rationale
1
total installs
1
this week
767
GitHub stars
0
upvotes
Run in your terminal
1
installs
1
this week
767
stars
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 |