Metal GPU Code Skill
Write production-quality Metal code with correct patterns, optimal performance, and clear explanations.
When to Read References
For detailed API topology, Metal 4 specifics, and Apple Silicon optimization patterns, read:
/mnt/skills/user/metal-gpu/references/metal-api-guide.md
Core Principles
- Always start with the device:
MTLCreateSystemDefaultDevice() — every Metal workflow begins here
- Command pattern: Device → Command Queue → Command Buffer → Command Encoder → Commit
- Shaders are MSL (Metal Shading Language): C++14-based, with Metal-specific types and attributes
- Resource management matters: Use appropriate storage modes, avoid unnecessary copies
- Triple buffering for render loops to keep CPU and GPU in parallel
Quick Reference: Metal Command Pipeline
MTLDevice
└─ makeCommandQueue() → MTLCommandQueue
└─ makeCommandBuffer() → MTLCommandBuffer
├─ makeRenderCommandEncoder(descriptor:) → MTLRenderCommandEncoder
├─ makeComputeCommandEncoder() → MTLComputeCommandEncoder
└─ makeBlitCommandEncoder() → MTLBlitCommandEncoder
Writing Shaders (MSL)
Use Metal Shading Language. Always include:
#include <metal_stdlib> and using namespace metal;
- Correct attribute qualifiers:
[[vertex_id]], [[position]], [[stage_in]], [[buffer(n)]], [[texture(n)]]
- Proper address space qualifiers:
device, constant, threadgroup, thread
Vertex Shader Pattern
#include <metal_stdlib>
using namespace metal;
struct VertexIn {
float3 position [[attribute(0)]];
float3 normal [[attribute(1)]];
float2 texCoord [[attribute(2)]];
};
struct VertexOut {
float4 position [[position]];
float3 normal;
float2 texCoord;
};
vertex VertexOut vertex_main(VertexIn in [[stage_in]],
constant float4x4 &mvp [[buffer(1)]]) {
VertexOut out;
out.position = mvp * float4(in.position, 1.0);
out.normal = in.normal;
out.texCoord = in.texCoord;
return out;
}
Fragment Shader Pattern
fragment float4 fragment_main(VertexOut in [[stage_in]],
texture2d<float> albedo [[texture(0)]],
sampler texSampler [[sampler(0)]]) {
float4 color = albedo.sample(texSampler, in.texCoord);
return color;
}
Compute Kernel Pattern
kernel void compute_main(device float *input [[buffer(0)]],
device float *output [[buffer(1)]],
uint id [[thread_position_in_grid]]) {
output[id] = input[id] * 2.0;
}
Swift-Side Setup Patterns
Render Pipeline Setup
let device = MTLCreateSystemDefaultDevice()!
let commandQueue = device.makeCommandQueue()!
// Load shaders
let library = device.makeDefaultLibrary()!
let vertexFunction = library.makeFunction(name: "vertex_main")
let fragmentFunction = library.makeFunction(name: "fragment_main")
// Pipeline descriptor
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = vertexFunction
pipelineDescriptor.fragmentFunction = fragmentFunction
pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
// Vertex descriptor
let vertexDescriptor = MTLVertexDescriptor()
vertexDescriptor.attributes[0].format = .float3 // position
vertexDescriptor.attributes[0].offset = 0
vertexDescriptor.attributes[0].bufferIndex = 0
vertexDescriptor.layouts[0].stride = MemoryLayout<SIMD3<Float>>.stride
pipelineDescriptor.vertexDescriptor = vertexDescriptor
let pipelineState = try! device.makeRenderPipelineState(descriptor: pipelineDescriptor)
Compute Pipeline Setup
let computeFunction = library.makeFunction(name: "compute_main")!
let computePipeline = try! device.makeComputePipelineState(function: computeFunction)
let commandBuffer = commandQueue.makeCommandBuffer()!
let encoder = commandBuffer.makeComputeCommandEncoder()!
encoder.setComputePipelineState(computePipeline)
encoder.setBuffer(inputBuffer, offset: 0, index: 0)
encoder.setBuffer(outputBuffer, offset: 0, index: 1)
let gridSize = MTLSize(width: elementCount, height: 1, depth: 1)
let threadGroupSize = MTLSize(
width: min(computePipeline.maxTotalThreadsPerThreadgroup, elementCount),
height: 1, depth: 1
)
encoder.dispatchThreads(gridSize, threadsPerThreadgroup: threadGroupSize)
encoder.endEncoding()
commandBuffer.commit()
MetalKit View Rendering
import MetalKit
class Renderer: NSObject, MTKViewDelegate {
let device: MTLDevice
let commandQueue: MTLCommandQueue
let pipelineState: MTLRenderPipelineState
func draw(in view: MTKView) {
guard let drawable = view.currentDrawable,
let descriptor = view.currentRenderPassDescriptor else { return }
let commandBuffer = commandQueue.makeCommandBuffer()!
let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor)!
encoder.setRenderPipelineState(pipelineState)
// Set buffers, draw primitives...
encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
encoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
}
}
Performance Best Practices
- Storage modes: Use
.shared on Apple Silicon (unified memory), .private for GPU-only data, .managed on Intel Macs
- Triple buffering: Rotate 3 buffers with a semaphore to avoid CPU/GPU stalls
- Avoid per-frame allocations: Reuse buffers and command encoders
- Use
dispatchThreads over dispatchThreadgroups when possible (Apple Silicon)
- Prefer tile-based deferred rendering patterns on Apple GPUs — use imageblocks and tile shaders
- Compile pipelines ahead of time: Pipeline creation is expensive, do it at load time
- Use Metal GPU frame capture in Xcode to profile and debug
Common Mistakes to Avoid
- Forgetting
encoder.endEncoding() before committing
- Mismatched buffer indices between Swift and MSL
- Using wrong pixel format for render targets
- Not handling
nil from optional Metal API calls
- Blocking the main thread waiting for GPU completion — use
addCompletedHandler instead
- Forgetting to set the vertex descriptor when using
[[stage_in]]
Metal 4 Notes
Metal 4 introduces a modernized core API. Key changes:
- New compilation API for finer shader compilation control
- Updated command encoding patterns
- See
references/metal-api-guide.md for the full Metal 4 API topology
Frameworks Ecosystem
| Framework |
Purpose |
| Metal |
Direct GPU access, shaders, pipelines |
| MetalKit |
View management, texture loading, model I/O |
| MetalFX |
Upscaling (temporal/spatial) for performance |
| Metal Performance Shaders |
Optimized compute & image processing kernels |
| Compositor Services |
Stereoscopic rendering for visionOS |
| RealityKit |
High-level 3D rendering (uses Metal underneath) |
1---2name: metal-gpu3description: Write, explain, and debug Metal GPU code including shaders (vertex, fragment, compute), render pipelines, compute pipelines, buffer/texture management, and Metal 4 APIs. Use this skill whenever the user mentions Metal, GPU programming, shaders, MSL (Metal Shading Language), render passes, compute kernels, MTLDevice, MTLCommandBuffer, MTLRenderPipelineState, or any Apple GPU/graphics programming topic. Also trigger when the user wants to do parallel computation on Apple devices, write GPU-accelerated code, or work with Metal Performance Shaders, MetalFX, MetalKit, or Compositor Services. Covers iOS, macOS, iPadOS, tvOS, and visionOS.4---56# Metal GPU Code Skill78Write production-quality Metal code with correct patterns, optimal performance, and clear explanations.910## When to Read References1112For detailed API topology, Metal 4 specifics, and Apple Silicon optimization patterns, read:13```14/mnt/skills/user/metal-gpu/references/metal-api-guide.md15```1617## Core Principles18191. **Always start with the device**: `MTLCreateSystemDefaultDevice()` — every Metal workflow begins here202. **Command pattern**: Device → Command Queue → Command Buffer → Command Encoder → Commit213. **Shaders are MSL (Metal Shading Language)**: C++14-based, with Metal-specific types and attributes224. **Resource management matters**: Use appropriate storage modes, avoid unnecessary copies235. **Triple buffering** for render loops to keep CPU and GPU in parallel2425## Quick Reference: Metal Command Pipeline2627```28MTLDevice29 └─ makeCommandQueue() → MTLCommandQueue30 └─ makeCommandBuffer() → MTLCommandBuffer31 ├─ makeRenderCommandEncoder(descriptor:) → MTLRenderCommandEncoder32 ├─ makeComputeCommandEncoder() → MTLComputeCommandEncoder33 └─ makeBlitCommandEncoder() → MTLBlitCommandEncoder34```3536## Writing Shaders (MSL)3738Use Metal Shading Language. Always include:39- `#include <metal_stdlib>` and `using namespace metal;`40- Correct attribute qualifiers: `[[vertex_id]]`, `[[position]]`, `[[stage_in]]`, `[[buffer(n)]]`, `[[texture(n)]]`41- Proper address space qualifiers: `device`, `constant`, `threadgroup`, `thread`4243### Vertex Shader Pattern44```metal45#include <metal_stdlib>46using namespace metal;4748struct VertexIn {49 float3 position [[attribute(0)]];50 float3 normal [[attribute(1)]];51 float2 texCoord [[attribute(2)]];52};5354struct VertexOut {55 float4 position [[position]];56 float3 normal;57 float2 texCoord;58};5960vertex VertexOut vertex_main(VertexIn in [[stage_in]],61 constant float4x4 &mvp [[buffer(1)]]) {62 VertexOut out;63 out.position = mvp * float4(in.position, 1.0);64 out.normal = in.normal;65 out.texCoord = in.texCoord;66 return out;67}68```6970### Fragment Shader Pattern71```metal72fragment float4 fragment_main(VertexOut in [[stage_in]],73 texture2d<float> albedo [[texture(0)]],74 sampler texSampler [[sampler(0)]]) {75 float4 color = albedo.sample(texSampler, in.texCoord);76 return color;77}78```7980### Compute Kernel Pattern81```metal82kernel void compute_main(device float *input [[buffer(0)]],83 device float *output [[buffer(1)]],84 uint id [[thread_position_in_grid]]) {85 output[id] = input[id] * 2.0;86}87```8889## Swift-Side Setup Patterns9091### Render Pipeline Setup92```swift93let device = MTLCreateSystemDefaultDevice()!94let commandQueue = device.makeCommandQueue()!9596// Load shaders97let library = device.makeDefaultLibrary()!98let vertexFunction = library.makeFunction(name: "vertex_main")99let fragmentFunction = library.makeFunction(name: "fragment_main")100101// Pipeline descriptor102let pipelineDescriptor = MTLRenderPipelineDescriptor()103pipelineDescriptor.vertexFunction = vertexFunction104pipelineDescriptor.fragmentFunction = fragmentFunction105pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm106107// Vertex descriptor108let vertexDescriptor = MTLVertexDescriptor()109vertexDescriptor.attributes[0].format = .float3 // position110vertexDescriptor.attributes[0].offset = 0111vertexDescriptor.attributes[0].bufferIndex = 0112vertexDescriptor.layouts[0].stride = MemoryLayout<SIMD3<Float>>.stride113pipelineDescriptor.vertexDescriptor = vertexDescriptor114115let pipelineState = try! device.makeRenderPipelineState(descriptor: pipelineDescriptor)116```117118### Compute Pipeline Setup119```swift120let computeFunction = library.makeFunction(name: "compute_main")!121let computePipeline = try! device.makeComputePipelineState(function: computeFunction)122123let commandBuffer = commandQueue.makeCommandBuffer()!124let encoder = commandBuffer.makeComputeCommandEncoder()!125encoder.setComputePipelineState(computePipeline)126encoder.setBuffer(inputBuffer, offset: 0, index: 0)127encoder.setBuffer(outputBuffer, offset: 0, index: 1)128129let gridSize = MTLSize(width: elementCount, height: 1, depth: 1)130let threadGroupSize = MTLSize(131 width: min(computePipeline.maxTotalThreadsPerThreadgroup, elementCount),132 height: 1, depth: 1133)134encoder.dispatchThreads(gridSize, threadsPerThreadgroup: threadGroupSize)135encoder.endEncoding()136commandBuffer.commit()137```138139### MetalKit View Rendering140```swift141import MetalKit142143class Renderer: NSObject, MTKViewDelegate {144 let device: MTLDevice145 let commandQueue: MTLCommandQueue146 let pipelineState: MTLRenderPipelineState147148 func draw(in view: MTKView) {149 guard let drawable = view.currentDrawable,150 let descriptor = view.currentRenderPassDescriptor else { return }151152 let commandBuffer = commandQueue.makeCommandBuffer()!153 let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor)!154155 encoder.setRenderPipelineState(pipelineState)156 // Set buffers, draw primitives...157 encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)158159 encoder.endEncoding()160 commandBuffer.present(drawable)161 commandBuffer.commit()162 }163}164```165166## Performance Best Practices1671681. **Storage modes**: Use `.shared` on Apple Silicon (unified memory), `.private` for GPU-only data, `.managed` on Intel Macs1692. **Triple buffering**: Rotate 3 buffers with a semaphore to avoid CPU/GPU stalls1703. **Avoid per-frame allocations**: Reuse buffers and command encoders1714. **Use `dispatchThreads` over `dispatchThreadgroups`** when possible (Apple Silicon)1725. **Prefer tile-based deferred rendering** patterns on Apple GPUs — use imageblocks and tile shaders1736. **Compile pipelines ahead of time**: Pipeline creation is expensive, do it at load time1747. **Use Metal GPU frame capture** in Xcode to profile and debug175176## Common Mistakes to Avoid177178- Forgetting `encoder.endEncoding()` before committing179- Mismatched buffer indices between Swift and MSL180- Using wrong pixel format for render targets181- Not handling `nil` from optional Metal API calls182- Blocking the main thread waiting for GPU completion — use `addCompletedHandler` instead183- Forgetting to set the vertex descriptor when using `[[stage_in]]`184185## Metal 4 Notes186187Metal 4 introduces a modernized core API. Key changes:188- New compilation API for finer shader compilation control189- Updated command encoding patterns190- See `references/metal-api-guide.md` for the full Metal 4 API topology191192## Frameworks Ecosystem193194| Framework | Purpose |195|-----------|---------|196| **Metal** | Direct GPU access, shaders, pipelines |197| **MetalKit** | View management, texture loading, model I/O |198| **MetalFX** | Upscaling (temporal/spatial) for performance |199| **Metal Performance Shaders** | Optimized compute & image processing kernels |200| **Compositor Services** | Stereoscopic rendering for visionOS |201| **RealityKit** | High-level 3D rendering (uses Metal underneath) |