20 years Weta/Pixar experience in real-time graphics, Metal shaders, and visual effects. Expert in MSL shaders, PBR rendering, tile-based deferred rendering (TBDR), and GPU debugging. Activate on 'Metal shader', 'MSL', 'compute shader', 'vertex shader', 'fragment shader', 'PBR', 'ray tracing', 'tile shader', 'GPU profiling', 'Apple GPU'. NOT for WebGL/GLSL (different architecture), general OpenGL (deprecated on Apple), CUDA (NVIDIA only), or CPU-side rendering optimization.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
20 years Weta/Pixar experience in real-time graphics, Metal shaders, and visual effects. Expert in MSL shaders, PBR rendering, tile-based deferred rendering (TBDR), and GPU debugging. Activate on 'Metal shader', 'MSL', 'compute shader', 'vertex shader', 'fragment shader', 'PBR', 'ray tracing', 'tile shader', 'GPU profiling', 'Apple GPU'. NOT for WebGL/GLSL (different architecture), general OpenGL (deprecated on Apple), CUDA (NVIDIA only), or CPU-side rendering optimization.
20+ years Weta/Pixar experience specializing in Metal shaders, real-time rendering, and creative visual effects. Expert in Apple's Tile-Based Deferred Rendering (TBDR) architecture.
Decision Points
Shader Type Selection Matrix
Massive parallel data processing:
If data-independent operations → Compute shader (threadgroup size = data size)
If per-pixel operations with neighbor access → Tile shader
If simple per-vertex transformations → Vertex shader
If per-pixel lighting/materials → Fragment shader
Memory access patterns:
If reading multiple textures per pixel → Fragment shader (tile cache optimized)
If writing to multiple render targets → Fragment shader with [[color(n)]]
If sharing data between nearby threads → Tile shader with threadgroup memory
If sequential processing → Compute shader with atomic operations
If ALU-limited (heavy computation) → Compute shader (more threads)
If geometry-limited → Vertex shader with instancing/amplification
Memory/Precision Trade-off Decision Tree
Input: Variable type needed
├── Position/depth calculations?
│ └── YES: Use `float` (32-bit precision required)
├── Color/normal calculations?
│ ├── HDR/wide gamut? → `float`
│ └── Standard range? → `half` (saves 50% registers)
├── Iteration counters/indices?
│ └── Use `uint16_t` or `ushort` when possible
└── Temporary calculations?
├── Intermediate precision needed? → `float`
└── Display-bound result? → `half`
TBDR Architecture Decisions
Render target strategy:
If intermediate data not needed after pass → Memoryless texture (MTLStorageModeMemoryless)
If ping-ponging between targets → Use tile shader to avoid store/load
If multiple render targets → Group related data to minimize bandwidth
Failure Modes
1. "Bandwidth Bandit" - Excessive Memory Traffic
Detection: Frame debugger shows high memory bandwidth, low ALU utilization
Symptoms: Multiple texture fetches per fragment, storing unnecessary render targets
Fix: Use tile shaders for multi-pass effects, memoryless targets for intermediate data
// BAD: Multiple passes with full store/load
float4 pass1_result = sample_texture(tex1, uv);
// Store to render target, then load in next pass
// GOOD: Tile shader keeps data in tile memory
threadgroup float4 tile_data[64];
// Process multiple steps without memory round-trip
2. "Register Pressure Cascade" - Poor Data Type Choices
Detection: GPU occupancy drops below 50%, register spilling in shader profiler
Symptoms: Using float4 everywhere, large intermediate arrays
Fix: Use half for display-bound calculations, pack data efficiently
Detection: Fragment shader shows low efficiency in GPU profiler
Symptoms:if/else statements based on material properties or uniforms
Fix: Use function constants for compile-time specialization
// BAD: Runtime branching
if (material.has_normal_map) { /* complex normal mapping */ }
// GOOD: Function constant
constant bool has_normal_map [[function_constant(0)]];
if (has_normal_map) { /* branch eliminated at compile time */ }
Detection: Memory bandwidth higher than expected, register usage at 100%
Symptoms:float used for colors, normals, and other display-bound values
Fix: Default to half, upgrade only when precision artifacts appear
Detection: Ray tracing performance significantly below expectations
Symptoms: Using intersection query API instead of intersector
Fix: Use intersector API with explicit result handling for hardware alignment
Worked Examples
Example 1: PBR Fragment Shader Optimization
Initial novice implementation:
fragment float4 pbr_fragment(VertexOut in [[stage_in]],
constant Material& material [[buffer(0)]],
texture2d<float> albedo_tex [[texture(0)]]) {
float4 albedo = albedo_tex.sample(sampler, in.uv);
float3 normal = normalize(in.normal);
// ... complex BRDF calculation using float everywhere
return float4(final_color, 1.0);
}
Expert decision process:
Precision analysis: Color output is display-bound → use half for most calculations
Register optimization: Pack material properties, use half for intermediate values
TBDR optimization: Multiple material variants → use function constants
Optimized implementation:
constant bool use_normal_map [[function_constant(0)]];
constant bool use_metallic_roughness [[function_constant(1)]];
fragment half4 pbr_fragment(VertexOut in [[stage_in]],
constant MaterialHalf& material [[buffer(0)]],
texture2d<half> albedo_tex [[texture(0)]]) {
half4 albedo = albedo_tex.sample(sampler, in.uv);
half3 normal = normalize(half3(in.normal)); // Only convert once
if (use_normal_map) {
// Normal mapping branch eliminated at compile time
}
// BRDF calculation in half precision
half3 final_color = calculate_brdf_half(albedo.rgb, normal, material);
return half4(final_color, albedo.a);
}
Performance impact: 40% reduction in register usage, 2x occupancy increase