Metal Shaders -- MSL for Apple Platforms
Decision Tree
What shader do you need?
|
|-- SwiftUI visual effect?
| |-- Per-pixel color transform (tint, invert, threshold)?
| | -> colorEffect -> references/swiftui-integration.md#colorEffect
| |-- Geometric distortion (ripple, wave, lens)?
| | -> distortionEffect -> references/swiftui-integration.md#distortionEffect
| |-- Multi-sample effect (blur, glow, shadow)?
| | -> layerEffect -> references/swiftui-integration.md#layerEffect
| \-- Standard filter (blur, edge detect)?
| -> MPS framework -> references/metal-performance-shaders.md
|
|-- Post-processing / render pass?
| |-- Bloom / glow extraction? -> references/fragment-shaders.md#bloom
| |-- Color grading / LUT? -> references/fragment-shaders.md#color-grading
| |-- Vignette? -> references/fragment-shaders.md#vignette
| |-- Chromatic aberration? -> references/fragment-shaders.md#chromatic-aberration
| |-- Film grain / CRT? -> references/fragment-shaders.md#film-grain
| \-- Gaussian blur? -> references/fragment-shaders.md#gaussian-blur
|
|-- GPU compute workload?
| |-- Particle simulation? -> references/compute-shaders.md#particles
| |-- Physics / cloth? -> references/compute-shaders.md#physics
| |-- Image processing? -> references/compute-shaders.md#image-processing
| |-- GPU-driven rendering? -> references/compute-shaders.md#gpu-driven
| \-- General parallel work? -> references/compute-shaders.md#architecture
|
|-- Procedural geometry / volumetrics?
| |-- SDF scene (shapes, booleans)? -> references/ray-marching.md#sdf
| |-- Volumetric fog / clouds? -> references/ray-marching.md#volumetric
| \-- Full ray marcher? -> references/ray-marching.md#ray-marcher
|
|-- Procedural noise / textures?
| |-- Perlin noise? -> references/noise-functions.md#perlin
| |-- Simplex noise? -> references/noise-functions.md#simplex
| |-- Worley / Voronoi? -> references/noise-functions.md#worley
| |-- fBm / fractal layers? -> references/noise-functions.md#fbm
| \-- Domain warping? -> references/noise-functions.md#domain-warping
|
\-- Performance / debugging?
|-- GPU frame capture? -> references/fragment-shaders.md#debugging
|-- Occupancy tuning? -> references/compute-shaders.md#performance
\-- half vs float? -> MSL Type Reference below
Quick Reference: Shader Types x Use Cases
| Type | MSL Entry Point | Use Cases | SwiftUI Modifier | Ref |
|---|---|---|---|---|
| Fragment (color) | fragment half4 fn(...) | Post-process, color grade, bloom | .colorEffect() | references/fragment-shaders.md |
| Fragment (distort) | fragment float2 fn(...) | Ripple, wave, lens warp | .distortionEffect() | references/swiftui-integration.md |
| Fragment (layer) | fragment half4 fn(...) | Blur, glow, shadow, multi-sample | .layerEffect() | references/swiftui-integration.md |
| Compute (kernel) | kernel void fn(...) | Particles, physics, image proc | N/A (MTLComputeCommandEncoder) | references/compute-shaders.md |
| Vertex | vertex VertexOut fn(...) | Mesh deformation, instancing | N/A (render pipeline) | Apple docs |
SwiftUI Integration Cheat Sheet
Three Modifier Types with Exact Signatures
colorEffect -> [[ stitchable ]] half4 name(float2 position, half4 color, <args...>)
Receives: pixel position + current color
Returns: new color
distortionEffect -> [[ stitchable ]] float2 name(float2 position, <args...>)
Receives: pixel position
Returns: source coordinate to sample from
Requires: maxSampleOffset
layerEffect -> [[ stitchable ]] half4 name(float2 position, SwiftUI::Layer layer, <args...>)
Receives: pixel position + layer (can sample any coordinate)
Returns: new color
Requires: maxSampleOffset
Swift-Side Parameter Passing
.colorEffect(ShaderLibrary.myShader(
.float(time), // float
.float2(CGSize(w, h)), // float2
.color(.red), // half4
.image(Image("tex")), // texture2d
.data(Data(...)) // device buffer
))
TimelineView Animation Pattern
TimelineView(.animation) { timeline in
let time = timeline.date.timeIntervalSinceReferenceDate
content
.colorEffect(ShaderLibrary.pulse(.float(time)))
}
MSL Type Quick Reference
| MSL Type | Bytes | Use For | Notes |
|---|---|---|---|
half | 2 | Scalars (color, alpha) | Prefer over float for color math |
half4 | 8 | Colors (RGBA) | Standard SwiftUI color type |
float | 4 | Position, time, angles | Full precision when needed |
float2 | 8 | UV coords, positions | Standard position type |
float3 | 12 | 3D position, RGB | |
float4 | 16 | Homogeneous coords | |
float4x4 | 64 | Transform matrices | |
texture2d<half> | -- | Color textures | Use half access for perf |
texture2d<float> | -- | Data textures (normals, depth) | When precision matters |
uint2 | 8 | Thread position | thread_position_in_grid |
ushort2 | 4 | Thread position (small) | Save registers |
Key MSL Qualifiers
| Qualifier | Meaning |
|---|---|
[[ stitchable ]] | Function can be called from SwiftUI shader modifiers |
[[ position ]] | Vertex output clip-space position |
[[ color(0) ]] | Fragment output to color attachment 0 |
[[ stage_in ]] | Interpolated vertex output → fragment input |
[[ thread_position_in_grid ]] | Global thread ID in compute |
[[ thread_position_in_threadgroup ]] | Local thread ID in threadgroup |
[[ threadgroup_position_in_grid ]] | Threadgroup ID |
[[ threads_per_threadgroup ]] | Threadgroup dimensions |
constant | Read-only buffer (uniform) |
device | Read/write buffer (SSBO) |
threadgroup | Shared memory within threadgroup |
thread | Per-thread local |
Performance Rules (Top 10)
- •Use
halfoverfloatfor color math -- conversions are free on Apple GPU - •Minimize texture samples -- each sample is expensive; cache in registers
- •Branch-free code -- SIMD groups execute both branches; use
mix()/select()instead ofif - •Threadgroup size = multiple of threadExecutionWidth (typically 32) -- avoids wasted lanes
- •Memory coalescing -- adjacent threads should access adjacent memory addresses
- •Avoid threadgroup bank conflicts -- stagger access patterns or pad shared arrays
- •Early termination in ray marching --
if (totalAlpha > 0.99) break; - •Separable filters -- 2-pass (H+V) is O(2N) vs O(N^2) for 2D convolution
- •Texture-based noise lookup vs computed -- precompute for static noise, compute for animated
- •Profile with GPU Frame Capture in Xcode before optimizing -- measure, don't guess
Cross-References
| Skill | Relation |
|---|---|
orb | T5 Metal ripple shader -- practical SwiftUI shader example |
swiftui-animation | Animation-driven shader parameters (TimelineView, spring) |
vfx-spell-effects | Effect composition using these shader patterns |
swiftui-expert-skill | SwiftUI view lifecycle for shader integration |
swiftui-performance-audit | Diagnosing GPU-related SwiftUI perf issues |
Key Resources
- •Metal Shading Language Specification (PDF)
- •Metal Overview -- Apple Developer
- •Metal Performance Shaders
- •Metal in SwiftUI -- Jacob Bartlett
- •Metal Shaders in SwiftUI -- Hacking with Swift
- •Inferno -- 17 SwiftUI Metal Shaders
- •The Book of Shaders -- Noise
- •Ray Marching & SDFs -- Jamie Wong
- •SDF Primitives -- Inigo Quilez
- •Ray Marching Distance Fields -- Inigo Quilez
Technique Map
- •Shader type routing — colorEffect (per-pixel), distortionEffect (geometric), layerEffect (multi-sample); because each modifier has distinct signature and use case.
- •Stitchable qualifier — Required for SwiftUI shader modifiers; because enables function call from SwiftUI.
- •half over float for color — Use half/half4 for color math; because conversions are free on Apple GPU; reduces register pressure.
- •Branch-free code — Use mix()/select() instead of if; because SIMD groups execute both branches; branches cost.
- •Profile with GPU Frame Capture — Measure before optimizing; because guessing causes wrong optimizations.
- •Threadgroup size = multiple of 32 — Align to threadExecutionWidth; because avoids wasted SIMD lanes.
- •Separable filters — 2-pass H+V for blur; because O(2N) vs O(N²) for 2D convolution.
Technique Notes
MSL types: half4 for colors, float2 for positions. Swift passes .float(time), .float2(CGSize), .color(), .image(). TimelineView drives animation. References: fragment-shaders.md, compute-shaders.md, ray-marching.md, noise-functions.md.
Prompt Architect Overlay
Role Definition: Metal shader expert for Apple platforms. Covers MSL, SwiftUI stitchable effects (colorEffect, distortionEffect, layerEffect), MPS, compute shaders, ray marching, and GPU performance.
Input Contract: Accepts shader type (visual effect, post-process, compute, procedural), target (SwiftUI, Metal pipeline), and performance constraints. Code or description of desired effect.
Output Contract: MSL code with correct signatures, Swift integration snippet, performance considerations. Routes to appropriate reference (fragment, compute, ray-marching, noise). Decision tree for shader type selection.
Edge Cases & Fallbacks: If SwiftUI modifier not working→verify stitchable, maxSampleOffset. If performance issue→profile first; check half vs float, texture samples, branch-free. If cross-platform→note iOS 17+ for SwiftUI shaders.