[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6E6FZP9ME57B5W09AVKV2MC":3,"skill-same:01M6E6FZP9ME57B5W09AVKV2MC":67},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":33,"linkOnly":34,"markdownContent":35,"markdownBody":36,"frontMatter":37,"refName":44,"commitSha":45,"skillDirectory":46,"forksCount":47,"repositoryHtmlUrl":48,"repositoryHomepageUrl":49,"ownerHtmlUrl":50,"ownerBlogUrl":6,"ownerDescription":51,"updatedAt":52,"install":53,"resources":66},"01M6E6FZP9ME57B5W09AVKV2MC","axiom-metal-migration-diag",null,"Use when ANY Metal porting issue occurs - black screen, rendering artifacts, shader errors, wrong colors, performance regressions, GPU crashes","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FCharlesWiltgen\u002FAxiom\u002Faxiom-metal-migration-diag\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32],"metal","migration","axiom metal migration diag","macos","performance","artifacts","gpu","ios","use","issue","shader","black","screen","rendering","2026-11-10 10:12:30",false,"---\nname: axiom-metal-migration-diag\ndescription: Use when ANY Metal porting issue occurs - black screen, rendering artifacts, shader errors, wrong colors, performance regressions, GPU crashes\nlicense: MIT\ncompatibility: [iOS 12+, macOS 10.14+, tvOS 12+]\nmetadata:\n  version: \"1.0.0\"\n---\n\n# Metal Migration Diagnostics\n\nSystematic diagnosis for common Metal porting issues.\n\n## When to Use This Diagnostic Skill\n\nUse this skill when:\n- Screen is black after porting to Metal\n- Shaders fail to compile in Metal\n- Colors or coordinates are wrong\n- Performance is worse than the original\n- Rendering artifacts appear\n- App crashes during GPU work\n\n## Mandatory First Step: Enable Metal Validation\n\n**Time cost**: 30 seconds setup vs hours of blind debugging\n\nBefore ANY debugging, enable Metal validation:\n\n```\nXcode → Edit Scheme → Run → Diagnostics\n✓ Metal API Validation\n✓ Metal Shader Validation\n✓ GPU Frame Capture (Metal)\n```\n\nMost Metal bugs produce clear validation errors. If you're debugging without validation enabled, **stop and enable it first**.\n\n## Symptom 1: Black Screen\n\n### Decision Tree\n\n```\nBlack screen after porting\n│\n├─ Are there Metal validation errors in console?\n│   └─ YES → Fix validation errors first (see below)\n│\n├─ Is the render pass descriptor valid?\n│   ├─ Check: view.currentRenderPassDescriptor != nil\n│   ├─ Check: drawable = view.currentDrawable != nil\n│   └─ FIX: Ensure MTKView.device is set, view is on screen\n│\n├─ Is the pipeline state created?\n│   ├─ Check: makeRenderPipelineState doesn't throw\n│   └─ FIX: Check shader function names match library\n│\n├─ Are draw calls being issued?\n│   ├─ Add: encoder.label = \"Main Pass\" for frame capture\n│   └─ DEBUG: GPU Frame Capture → verify draw calls appear\n│\n├─ Are resources bound?\n│   ├─ Check: setVertexBuffer, setFragmentTexture called\n│   └─ FIX: Metal requires explicit binding every frame\n│\n├─ Is the vertex data correct?\n│   ├─ DEBUG: GPU Frame Capture → inspect vertex buffer\n│   └─ FIX: Check buffer offsets, vertex count\n│\n├─ Are coordinates in Metal's range?\n│   ├─ Metal NDC: X [-1,1], Y [-1,1], Z [0,1]\n│   ├─ OpenGL NDC: X [-1,1], Y [-1,1], Z [-1,1]\n│   └─ FIX: Adjust projection matrix or vertex shader\n│\n└─ Is clear color set?\n    ├─ Default clear color is (0,0,0,0) — transparent black\n    └─ FIX: Set view.clearColor or renderPassDescriptor.colorAttachments[0].clearColor\n```\n\n### Common Fixes\n\n**Missing Drawable**:\n```swift\n\u002F\u002F BAD: Drawing before view is ready\noverride func viewDidLoad() {\n    draw()  \u002F\u002F metalView.currentDrawable is nil\n}\n\n\u002F\u002F GOOD: Wait for delegate callback\nfunc draw(in view: MTKView) {\n    guard let drawable = view.currentDrawable else { return }\n    \u002F\u002F Safe to draw\n}\n```\n\n**Wrong Function Names**:\n```swift\n\u002F\u002F BAD: Function name doesn't match .metal file\ndescriptor.vertexFunction = library.makeFunction(name: \"vertexMain\")\n\u002F\u002F .metal file has: vertex VertexOut vertexShader(...)\n\n\u002F\u002F GOOD: Names must match exactly\ndescriptor.vertexFunction = library.makeFunction(name: \"vertexShader\")\n```\n\n**Missing Resource Binding**:\n```swift\n\u002F\u002F BAD: Assumed state persists like OpenGL\nencoder.setRenderPipelineState(pso)\nencoder.drawPrimitives(...)  \u002F\u002F No buffers bound!\n\n\u002F\u002F GOOD: Bind everything explicitly\nencoder.setRenderPipelineState(pso)\nencoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)\nencoder.setVertexBytes(&uniforms, length: uniformsSize, index: 1)\nencoder.setFragmentTexture(texture, index: 0)\nencoder.drawPrimitives(...)\n```\n\n**Time cost**: GPU Frame Capture diagnosis: 5-10 min. Guessing without tools: 1-4 hours.\n\n## Symptom 2: Shader Compilation Errors\n\n### Decision Tree\n\n```\nShader fails to compile\n│\n├─ \"Use of undeclared identifier\"\n│   ├─ Check: #include \u003Cmetal_stdlib>\n│   ├─ Check: using namespace metal;\n│   └─ FIX: Standard functions need metal_stdlib\n│\n├─ \"No matching function for call to 'texture'\"\n│   └─ GLSL texture() → MSL tex.sample(sampler, uv)\n│       FIX: Texture sampling is a method, needs sampler\n│\n├─ \"Invalid type 'vec4'\"\n│   └─ GLSL vec4 → MSL float4\n│       FIX: See type mapping table in metal-migration-ref\n│\n├─ \"No matching constructor\"\n│   ├─ GLSL: vec4(vec3, float) works\n│   ├─ MSL: float4(float3, float) works\n│   └─ Check: Argument types match exactly\n│\n├─ \"Attribute index out of range\"\n│   ├─ Check: [[attribute(N)]] matches vertex descriptor\n│   └─ FIX: vertexDescriptor.attributes[N] must be configured\n│\n├─ \"Buffer binding index out of range\"\n│   ├─ Check: [[buffer(N)]] where N \u003C 31\n│   └─ FIX: Metal has max 31 buffer bindings per stage\n│\n└─ \"Cannot convert value of type\"\n    ├─ MSL is stricter than GLSL about implicit conversions\n    └─ FIX: Add explicit casts: float(intValue), int(floatValue)\n```\n\n### Common Conversions\n\n```metal\n\u002F\u002F GLSL\nvec4 color = texture(sampler2D, uv);\n\n\u002F\u002F MSL — texture and sampler are separate\nfloat4 color = tex.sample(samp, uv);\n\n\u002F\u002F GLSL — mod() for floats\nfloat x = mod(y, z);\n\n\u002F\u002F MSL — fmod() for floats\nfloat x = fmod(y, z);\n\n\u002F\u002F GLSL — atan(y, x)\nfloat angle = atan(y, x);\n\n\u002F\u002F MSL — atan2(y, x)\nfloat angle = atan2(y, x);\n\n\u002F\u002F GLSL — inversesqrt\nfloat invSqrt = inversesqrt(x);\n\n\u002F\u002F MSL — rsqrt\nfloat invSqrt = rsqrt(x);\n```\n\n**Time cost**: With conversion table: 2-5 min per shader. Without: 15-30 min per shader.\n\n## Symptom 3: Wrong Colors or Coordinates\n\n### Decision Tree\n\n```\nRendering looks wrong\n│\n├─ Image is upside down\n│   ├─ Cause: Metal Y-axis is opposite OpenGL\n│   ├─ FIX (vertex shader): pos.y = -pos.y\n│   ├─ FIX (texture load): MTKTextureLoader .origin: .bottomLeft\n│   └─ FIX (UV): uv.y = 1.0 - uv.y in fragment shader\n│\n├─ Image is mirrored\n│   ├─ Cause: Winding order or cull mode wrong\n│   ├─ FIX: encoder.setFrontFacing(.counterClockwise)\n│   └─ FIX: encoder.setCullMode(.back) or .none to test\n│\n├─ Colors are swapped (red\u002Fblue)\n│   ├─ Cause: Pixel format mismatch\n│   ├─ Check: .bgra8Unorm vs .rgba8Unorm\n│   └─ FIX: Match texture pixel format to data format\n│\n├─ Colors are washed out \u002F too bright\n│   ├─ Cause: sRGB vs linear color space\n│   ├─ Check: Using .bgra8Unorm_srgb for sRGB textures?\n│   └─ FIX: Use _srgb format variants for gamma-correct rendering\n│\n├─ Depth fighting \u002F z-fighting\n│   ├─ Cause: NDC Z range difference\n│   ├─ OpenGL: Z in [-1, 1]\n│   ├─ Metal: Z in [0, 1]\n│   └─ FIX: Adjust projection matrix for Metal's Z range\n│\n├─ Objects clipped incorrectly\n│   ├─ Cause: Near\u002Ffar plane or viewport\n│   ├─ Check: Viewport size matches drawable size\n│   └─ FIX: encoder.setViewport(MTLViewport(...))\n│\n└─ Transparency wrong\n    ├─ Cause: Blend state not configured\n    ├─ FIX: pipelineDescriptor.colorAttachments[0].isBlendingEnabled = true\n    └─ FIX: Set sourceRGBBlendFactor, destinationRGBBlendFactor\n```\n\n### Coordinate System Fix\n\n```swift\n\u002F\u002F Fix projection matrix for Metal's Z range [0, 1]\nfunc metalPerspectiveProjection(fovY: Float, aspect: Float, near: Float, far: Float) -> simd_float4x4 {\n    let yScale = 1.0 \u002F tan(fovY * 0.5)\n    let xScale = yScale \u002F aspect\n    let zRange = far - near\n\n    return simd_float4x4(rows: [\n        SIMD4\u003CFloat>(xScale, 0, 0, 0),\n        SIMD4\u003CFloat>(0, yScale, 0, 0),\n        SIMD4\u003CFloat>(0, 0, far \u002F zRange, 1),  \u002F\u002F Metal: [0, 1]\n        SIMD4\u003CFloat>(0, 0, -near * far \u002F zRange, 0)\n    ])\n}\n```\n\n**Time cost**: With GPU Frame Capture texture inspection: 5-10 min. Without: 1-2 hours.\n\n## Symptom 4: Performance Regression\n\n### Decision Tree\n\n```\nPerformance worse than OpenGL\n│\n├─ Enabling validation?\n│   └─ Validation adds ~30% overhead\n│       FIX: Disable for release builds, keep for debug\n│\n├─ Creating resources every frame?\n│   ├─ BAD: device.makeBuffer() in draw()\n│   └─ FIX: Create buffers once, reuse with triple buffering\n│\n├─ Creating pipeline state every frame?\n│   ├─ BAD: makeRenderPipelineState() in draw()\n│   └─ FIX: Create PSO once at init, store as property\n│\n├─ Too many draw calls?\n│   ├─ DEBUG: GPU Frame Capture → count draw calls\n│   └─ FIX: Batch geometry, use instancing, indirect draws\n│\n├─ GPU-CPU sync stalls?\n│   ├─ DEBUG: Metal System Trace → look for stalls\n│   ├─ Cause: waitUntilCompleted() blocks CPU\n│   └─ FIX: Triple buffering with semaphore\n│\n├─ Inefficient buffer updates?\n│   ├─ BAD: Recreating buffer to update\n│   └─ FIX: buffer.contents().copyMemory() for dynamic data\n│\n├─ Wrong storage mode?\n│   ├─ .shared: Good for small dynamic data\n│   ├─ .private: Good for static GPU-only data\n│   └─ FIX: Use .private for geometry that doesn't change\n│\n└─ Missing Metal-specific optimizations?\n    ├─ Argument buffers reduce binding overhead\n    ├─ Indirect draws reduce CPU work\n    └─ See WWDC sessions on Metal optimization\n```\n\n### Triple Buffering Pattern\n\n```swift\nclass TripleBufferedRenderer {\n    static let maxInflightFrames = 3\n\n    let inflightSemaphore = DispatchSemaphore(value: maxInflightFrames)\n    var uniformBuffers: [MTLBuffer] = []\n    var currentBufferIndex = 0\n\n    init(device: MTLDevice) {\n        for _ in 0..\u003CSelf.maxInflightFrames {\n            let buffer = device.makeBuffer(length: uniformsSize, options: .storageModeShared)!\n            uniformBuffers.append(buffer)\n        }\n    }\n\n    func draw(in view: MTKView) {\n        \u002F\u002F Wait for a buffer to be available\n        inflightSemaphore.wait()\n\n        let buffer = uniformBuffers[currentBufferIndex]\n        \u002F\u002F Safe to write — GPU is done with this buffer\n        memcpy(buffer.contents(), &uniforms, uniformsSize)\n\n        let commandBuffer = commandQueue.makeCommandBuffer()!\n\n        \u002F\u002F Signal when GPU is done\n        commandBuffer.addCompletedHandler { [weak self] _ in\n            self?.inflightSemaphore.signal()\n        }\n\n        \u002F\u002F ... encode and commit\n\n        currentBufferIndex = (currentBufferIndex + 1) % Self.maxInflightFrames\n    }\n}\n```\n\n**Time cost**: Metal System Trace diagnosis: 15-30 min. Guessing: hours.\n\n## Symptom 5: Crashes During GPU Work\n\n### Decision Tree\n\n```\nApp crashes during rendering\n│\n├─ EXC_BAD_ACCESS in Metal framework\n│   ├─ Cause: Accessing released resource\n│   ├─ Check: Buffer\u002Ftexture retained during GPU use\n│   └─ FIX: Keep strong references until command buffer completes\n│\n├─ \"Execution of the command buffer was aborted\"\n│   ├─ Cause: GPU timeout (>10 sec on iOS)\n│   ├─ Check: Infinite loop in shader?\n│   └─ FIX: Add early exit conditions, reduce work\n│\n├─ \"-[MTLDebugRenderCommandEncoder validateDrawCallWithArray:...]\"\n│   ├─ Cause: Validation caught misuse\n│   └─ FIX: Read the validation message — it tells you exactly what's wrong\n│\n├─ \"Fragment shader writes to non-existent render target\"\n│   ├─ Cause: Shader returns color but no color attachment\n│   └─ FIX: Configure colorAttachments[0].pixelFormat\n│\n├─ Crash in shader (SIGABRT)\n│   ├─ Cause: Out-of-bounds buffer access\n│   ├─ DEBUG: Enable shader validation\n│   └─ FIX: Check array bounds, buffer sizes\n│\n└─ Device disconnected \u002F GPU restart\n    ├─ Cause: Severe GPU hang\n    ├─ Check: Infinite loop or massive overdraw\n    └─ FIX: Simplify shader, reduce draw complexity\n```\n\n### Resource Lifetime Fix\n\n```swift\n\u002F\u002F BAD: Buffer released before GPU finishes\nfunc draw(in view: MTKView) {\n    let buffer = device.makeBuffer(...)  \u002F\u002F Created here\n    encoder.setVertexBuffer(buffer, ...)\n    commandBuffer.commit()\n    \u002F\u002F buffer released at end of scope — GPU still using it!\n}\n\n\u002F\u002F GOOD: Keep reference until completion\nclass Renderer {\n    var currentBuffer: MTLBuffer?  \u002F\u002F Strong reference\n\n    func draw(in view: MTKView) {\n        currentBuffer = device.makeBuffer(...)\n        encoder.setVertexBuffer(currentBuffer!, ...)\n        commandBuffer.addCompletedHandler { [weak self] _ in\n            \u002F\u002F Safe to release now\n            self?.currentBuffer = nil\n        }\n        commandBuffer.commit()\n    }\n}\n```\n\n## Debugging Tools Quick Reference\n\n### GPU Frame Capture\n\n```\nXcode → Debug → Capture GPU Frame (Cmd+Opt+Shift+G)\n```\n\n**Use for**:\n- Inspecting buffer contents\n- Viewing intermediate textures\n- Checking draw call sequence\n- Debugging shader variable values\n- Understanding why something isn't rendering\n\n### Metal System Trace (Instruments)\n\n```\nInstruments → Metal System Trace template\n```\n\n**Use for**:\n- GPU\u002FCPU timeline analysis\n- Finding synchronization stalls\n- Measuring encoder\u002Fbuffer overhead\n- Identifying bottlenecks\n\n### Shader Debugger\n\n```\nGPU Frame Capture → Select draw call → Debug button\n```\n\n**Use for**:\n- Step through shader execution\n- Inspect variable values per pixel\u002Fvertex\n- Find logic errors in shaders\n\n### Validation Messages\n\nMost validation messages include:\n- What went wrong\n- Which resource\u002Fstate\n- What the expected value was\n\n**Always read the full message** — it usually tells you exactly how to fix the problem.\n\n## Diagnostic Checklist\n\nWhen something doesn't work:\n\n- [ ] **Metal validation enabled?** (Most bugs produce validation errors)\n- [ ] **GPU Frame Capture available?** (Visual debugging is fastest)\n- [ ] **Console error messages?** (Read them fully)\n- [ ] **Resources bound?** (Metal requires explicit binding)\n- [ ] **Coordinates correct?** (Y-flip, NDC Z range)\n- [ ] **Pipeline state created successfully?** (Check for throw)\n- [ ] **Drawable available?** (View must be on screen)\n\n## Resources\n\n**WWDC**: 2019-00611, 2020-10602, 2020-10603\n\n**Docs**: \u002Fmetal\u002Fdebugging-metal-applications, \u002Fmetal\u002Fgpu-capture\n\n**Skills**: axiom-metal-migration, axiom-metal-migration-ref\n\n---\n\n**Last Updated**: 2025-12-29\n**Platforms**: iOS 12+, macOS 10.14+, tvOS 12+\n**Status**: Comprehensive Metal porting diagnostics\n\n","# Metal Migration Diagnostics\n\nSystematic diagnosis for common Metal porting issues.\n\n## When to Use This Diagnostic Skill\n\nUse this skill when:\n- Screen is black after porting to Metal\n- Shaders fail to compile in Metal\n- Colors or coordinates are wrong\n- Performance is worse than the original\n- Rendering artifacts appear\n- App crashes during GPU work\n\n## Mandatory First Step: Enable Metal Validation\n\n**Time cost**: 30 seconds setup vs hours of blind debugging\n\nBefore ANY debugging, enable Metal validation:\n\n```\nXcode → Edit Scheme → Run → Diagnostics\n✓ Metal API Validation\n✓ Metal Shader Validation\n✓ GPU Frame Capture (Metal)\n```\n\nMost Metal bugs produce clear validation errors. If you're debugging without validation enabled, **stop and enable it first**.\n\n## Symptom 1: Black Screen\n\n### Decision Tree\n\n```\nBlack screen after porting\n│\n├─ Are there Metal validation errors in console?\n│   └─ YES → Fix validation errors first (see below)\n│\n├─ Is the render pass descriptor valid?\n│   ├─ Check: view.currentRenderPassDescriptor != nil\n│   ├─ Check: drawable = view.currentDrawable != nil\n│   └─ FIX: Ensure MTKView.device is set, view is on screen\n│\n├─ Is the pipeline state created?\n│   ├─ Check: makeRenderPipelineState doesn't throw\n│   └─ FIX: Check shader function names match library\n│\n├─ Are draw calls being issued?\n│   ├─ Add: encoder.label = \"Main Pass\" for frame capture\n│   └─ DEBUG: GPU Frame Capture → verify draw calls appear\n│\n├─ Are resources bound?\n│   ├─ Check: setVertexBuffer, setFragmentTexture called\n│   └─ FIX: Metal requires explicit binding every frame\n│\n├─ Is the vertex data correct?\n│   ├─ DEBUG: GPU Frame Capture → inspect vertex buffer\n│   └─ FIX: Check buffer offsets, vertex count\n│\n├─ Are coordinates in Metal's range?\n│   ├─ Metal NDC: X [-1,1], Y [-1,1], Z [0,1]\n│   ├─ OpenGL NDC: X [-1,1], Y [-1,1], Z [-1,1]\n│   └─ FIX: Adjust projection matrix or vertex shader\n│\n└─ Is clear color set?\n    ├─ Default clear color is (0,0,0,0) — transparent black\n    └─ FIX: Set view.clearColor or renderPassDescriptor.colorAttachments[0].clearColor\n```\n\n### Common Fixes\n\n**Missing Drawable**:\n```swift\n\u002F\u002F BAD: Drawing before view is ready\noverride func viewDidLoad() {\n    draw()  \u002F\u002F metalView.currentDrawable is nil\n}\n\n\u002F\u002F GOOD: Wait for delegate callback\nfunc draw(in view: MTKView) {\n    guard let drawable = view.currentDrawable else { return }\n    \u002F\u002F Safe to draw\n}\n```\n\n**Wrong Function Names**:\n```swift\n\u002F\u002F BAD: Function name doesn't match .metal file\ndescriptor.vertexFunction = library.makeFunction(name: \"vertexMain\")\n\u002F\u002F .metal file has: vertex VertexOut vertexShader(...)\n\n\u002F\u002F GOOD: Names must match exactly\ndescriptor.vertexFunction = library.makeFunction(name: \"vertexShader\")\n```\n\n**Missing Resource Binding**:\n```swift\n\u002F\u002F BAD: Assumed state persists like OpenGL\nencoder.setRenderPipelineState(pso)\nencoder.drawPrimitives(...)  \u002F\u002F No buffers bound!\n\n\u002F\u002F GOOD: Bind everything explicitly\nencoder.setRenderPipelineState(pso)\nencoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)\nencoder.setVertexBytes(&uniforms, length: uniformsSize, index: 1)\nencoder.setFragmentTexture(texture, index: 0)\nencoder.drawPrimitives(...)\n```\n\n**Time cost**: GPU Frame Capture diagnosis: 5-10 min. Guessing without tools: 1-4 hours.\n\n## Symptom 2: Shader Compilation Errors\n\n### Decision Tree\n\n```\nShader fails to compile\n│\n├─ \"Use of undeclared identifier\"\n│   ├─ Check: #include \u003Cmetal_stdlib>\n│   ├─ Check: using namespace metal;\n│   └─ FIX: Standard functions need metal_stdlib\n│\n├─ \"No matching function for call to 'texture'\"\n│   └─ GLSL texture() → MSL tex.sample(sampler, uv)\n│       FIX: Texture sampling is a method, needs sampler\n│\n├─ \"Invalid type 'vec4'\"\n│   └─ GLSL vec4 → MSL float4\n│       FIX: See type mapping table in metal-migration-ref\n│\n├─ \"No matching constructor\"\n│   ├─ GLSL: vec4(vec3, float) works\n│   ├─ MSL: float4(float3, float) works\n│   └─ Check: Argument types match exactly\n│\n├─ \"Attribute index out of range\"\n│   ├─ Check: [[attribute(N)]] matches vertex descriptor\n│   └─ FIX: vertexDescriptor.attributes[N] must be configured\n│\n├─ \"Buffer binding index out of range\"\n│   ├─ Check: [[buffer(N)]] where N \u003C 31\n│   └─ FIX: Metal has max 31 buffer bindings per stage\n│\n└─ \"Cannot convert value of type\"\n    ├─ MSL is stricter than GLSL about implicit conversions\n    └─ FIX: Add explicit casts: float(intValue), int(floatValue)\n```\n\n### Common Conversions\n\n```metal\n\u002F\u002F GLSL\nvec4 color = texture(sampler2D, uv);\n\n\u002F\u002F MSL — texture and sampler are separate\nfloat4 color = tex.sample(samp, uv);\n\n\u002F\u002F GLSL — mod() for floats\nfloat x = mod(y, z);\n\n\u002F\u002F MSL — fmod() for floats\nfloat x = fmod(y, z);\n\n\u002F\u002F GLSL — atan(y, x)\nfloat angle = atan(y, x);\n\n\u002F\u002F MSL — atan2(y, x)\nfloat angle = atan2(y, x);\n\n\u002F\u002F GLSL — inversesqrt\nfloat invSqrt = inversesqrt(x);\n\n\u002F\u002F MSL — rsqrt\nfloat invSqrt = rsqrt(x);\n```\n\n**Time cost**: With conversion table: 2-5 min per shader. Without: 15-30 min per shader.\n\n## Symptom 3: Wrong Colors or Coordinates\n\n### Decision Tree\n\n```\nRendering looks wrong\n│\n├─ Image is upside down\n│   ├─ Cause: Metal Y-axis is opposite OpenGL\n│   ├─ FIX (vertex shader): pos.y = -pos.y\n│   ├─ FIX (texture load): MTKTextureLoader .origin: .bottomLeft\n│   └─ FIX (UV): uv.y = 1.0 - uv.y in fragment shader\n│\n├─ Image is mirrored\n│   ├─ Cause: Winding order or cull mode wrong\n│   ├─ FIX: encoder.setFrontFacing(.counterClockwise)\n│   └─ FIX: encoder.setCullMode(.back) or .none to test\n│\n├─ Colors are swapped (red\u002Fblue)\n│   ├─ Cause: Pixel format mismatch\n│   ├─ Check: .bgra8Unorm vs .rgba8Unorm\n│   └─ FIX: Match texture pixel format to data format\n│\n├─ Colors are washed out \u002F too bright\n│   ├─ Cause: sRGB vs linear color space\n│   ├─ Check: Using .bgra8Unorm_srgb for sRGB textures?\n│   └─ FIX: Use _srgb format variants for gamma-correct rendering\n│\n├─ Depth fighting \u002F z-fighting\n│   ├─ Cause: NDC Z range difference\n│   ├─ OpenGL: Z in [-1, 1]\n│   ├─ Metal: Z in [0, 1]\n│   └─ FIX: Adjust projection matrix for Metal's Z range\n│\n├─ Objects clipped incorrectly\n│   ├─ Cause: Near\u002Ffar plane or viewport\n│   ├─ Check: Viewport size matches drawable size\n│   └─ FIX: encoder.setViewport(MTLViewport(...))\n│\n└─ Transparency wrong\n    ├─ Cause: Blend state not configured\n    ├─ FIX: pipelineDescriptor.colorAttachments[0].isBlendingEnabled = true\n    └─ FIX: Set sourceRGBBlendFactor, destinationRGBBlendFactor\n```\n\n### Coordinate System Fix\n\n```swift\n\u002F\u002F Fix projection matrix for Metal's Z range [0, 1]\nfunc metalPerspectiveProjection(fovY: Float, aspect: Float, near: Float, far: Float) -> simd_float4x4 {\n    let yScale = 1.0 \u002F tan(fovY * 0.5)\n    let xScale = yScale \u002F aspect\n    let zRange = far - near\n\n    return simd_float4x4(rows: [\n        SIMD4\u003CFloat>(xScale, 0, 0, 0),\n        SIMD4\u003CFloat>(0, yScale, 0, 0),\n        SIMD4\u003CFloat>(0, 0, far \u002F zRange, 1),  \u002F\u002F Metal: [0, 1]\n        SIMD4\u003CFloat>(0, 0, -near * far \u002F zRange, 0)\n    ])\n}\n```\n\n**Time cost**: With GPU Frame Capture texture inspection: 5-10 min. Without: 1-2 hours.\n\n## Symptom 4: Performance Regression\n\n### Decision Tree\n\n```\nPerformance worse than OpenGL\n│\n├─ Enabling validation?\n│   └─ Validation adds ~30% overhead\n│       FIX: Disable for release builds, keep for debug\n│\n├─ Creating resources every frame?\n│   ├─ BAD: device.makeBuffer() in draw()\n│   └─ FIX: Create buffers once, reuse with triple buffering\n│\n├─ Creating pipeline state every frame?\n│   ├─ BAD: makeRenderPipelineState() in draw()\n│   └─ FIX: Create PSO once at init, store as property\n│\n├─ Too many draw calls?\n│   ├─ DEBUG: GPU Frame Capture → count draw calls\n│   └─ FIX: Batch geometry, use instancing, indirect draws\n│\n├─ GPU-CPU sync stalls?\n│   ├─ DEBUG: Metal System Trace → look for stalls\n│   ├─ Cause: waitUntilCompleted() blocks CPU\n│   └─ FIX: Triple buffering with semaphore\n│\n├─ Inefficient buffer updates?\n│   ├─ BAD: Recreating buffer to update\n│   └─ FIX: buffer.contents().copyMemory() for dynamic data\n│\n├─ Wrong storage mode?\n│   ├─ .shared: Good for small dynamic data\n│   ├─ .private: Good for static GPU-only data\n│   └─ FIX: Use .private for geometry that doesn't change\n│\n└─ Missing Metal-specific optimizations?\n    ├─ Argument buffers reduce binding overhead\n    ├─ Indirect draws reduce CPU work\n    └─ See WWDC sessions on Metal optimization\n```\n\n### Triple Buffering Pattern\n\n```swift\nclass TripleBufferedRenderer {\n    static let maxInflightFrames = 3\n\n    let inflightSemaphore = DispatchSemaphore(value: maxInflightFrames)\n    var uniformBuffers: [MTLBuffer] = []\n    var currentBufferIndex = 0\n\n    init(device: MTLDevice) {\n        for _ in 0..\u003CSelf.maxInflightFrames {\n            let buffer = device.makeBuffer(length: uniformsSize, options: .storageModeShared)!\n            uniformBuffers.append(buffer)\n        }\n    }\n\n    func draw(in view: MTKView) {\n        \u002F\u002F Wait for a buffer to be available\n        inflightSemaphore.wait()\n\n        let buffer = uniformBuffers[currentBufferIndex]\n        \u002F\u002F Safe to write — GPU is done with this buffer\n        memcpy(buffer.contents(), &uniforms, uniformsSize)\n\n        let commandBuffer = commandQueue.makeCommandBuffer()!\n\n        \u002F\u002F Signal when GPU is done\n        commandBuffer.addCompletedHandler { [weak self] _ in\n            self?.inflightSemaphore.signal()\n        }\n\n        \u002F\u002F ... encode and commit\n\n        currentBufferIndex = (currentBufferIndex + 1) % Self.maxInflightFrames\n    }\n}\n```\n\n**Time cost**: Metal System Trace diagnosis: 15-30 min. Guessing: hours.\n\n## Symptom 5: Crashes During GPU Work\n\n### Decision Tree\n\n```\nApp crashes during rendering\n│\n├─ EXC_BAD_ACCESS in Metal framework\n│   ├─ Cause: Accessing released resource\n│   ├─ Check: Buffer\u002Ftexture retained during GPU use\n│   └─ FIX: Keep strong references until command buffer completes\n│\n├─ \"Execution of the command buffer was aborted\"\n│   ├─ Cause: GPU timeout (>10 sec on iOS)\n│   ├─ Check: Infinite loop in shader?\n│   └─ FIX: Add early exit conditions, reduce work\n│\n├─ \"-[MTLDebugRenderCommandEncoder validateDrawCallWithArray:...]\"\n│   ├─ Cause: Validation caught misuse\n│   └─ FIX: Read the validation message — it tells you exactly what's wrong\n│\n├─ \"Fragment shader writes to non-existent render target\"\n│   ├─ Cause: Shader returns color but no color attachment\n│   └─ FIX: Configure colorAttachments[0].pixelFormat\n│\n├─ Crash in shader (SIGABRT)\n│   ├─ Cause: Out-of-bounds buffer access\n│   ├─ DEBUG: Enable shader validation\n│   └─ FIX: Check array bounds, buffer sizes\n│\n└─ Device disconnected \u002F GPU restart\n    ├─ Cause: Severe GPU hang\n    ├─ Check: Infinite loop or massive overdraw\n    └─ FIX: Simplify shader, reduce draw complexity\n```\n\n### Resource Lifetime Fix\n\n```swift\n\u002F\u002F BAD: Buffer released before GPU finishes\nfunc draw(in view: MTKView) {\n    let buffer = device.makeBuffer(...)  \u002F\u002F Created here\n    encoder.setVertexBuffer(buffer, ...)\n    commandBuffer.commit()\n    \u002F\u002F buffer released at end of scope — GPU still using it!\n}\n\n\u002F\u002F GOOD: Keep reference until completion\nclass Renderer {\n    var currentBuffer: MTLBuffer?  \u002F\u002F Strong reference\n\n    func draw(in view: MTKView) {\n        currentBuffer = device.makeBuffer(...)\n        encoder.setVertexBuffer(currentBuffer!, ...)\n        commandBuffer.addCompletedHandler { [weak self] _ in\n            \u002F\u002F Safe to release now\n            self?.currentBuffer = nil\n        }\n        commandBuffer.commit()\n    }\n}\n```\n\n## Debugging Tools Quick Reference\n\n### GPU Frame Capture\n\n```\nXcode → Debug → Capture GPU Frame (Cmd+Opt+Shift+G)\n```\n\n**Use for**:\n- Inspecting buffer contents\n- Viewing intermediate textures\n- Checking draw call sequence\n- Debugging shader variable values\n- Understanding why something isn't rendering\n\n### Metal System Trace (Instruments)\n\n```\nInstruments → Metal System Trace template\n```\n\n**Use for**:\n- GPU\u002FCPU timeline analysis\n- Finding synchronization stalls\n- Measuring encoder\u002Fbuffer overhead\n- Identifying bottlenecks\n\n### Shader Debugger\n\n```\nGPU Frame Capture → Select draw call → Debug button\n```\n\n**Use for**:\n- Step through shader execution\n- Inspect variable values per pixel\u002Fvertex\n- Find logic errors in shaders\n\n### Validation Messages\n\nMost validation messages include:\n- What went wrong\n- Which resource\u002Fstate\n- What the expected value was\n\n**Always read the full message** — it usually tells you exactly how to fix the problem.\n\n## Diagnostic Checklist\n\nWhen something doesn't work:\n\n- [ ] **Metal validation enabled?** (Most bugs produce validation errors)\n- [ ] **GPU Frame Capture available?** (Visual debugging is fastest)\n- [ ] **Console error messages?** (Read them fully)\n- [ ] **Resources bound?** (Metal requires explicit binding)\n- [ ] **Coordinates correct?** (Y-flip, NDC Z range)\n- [ ] **Pipeline state created successfully?** (Check for throw)\n- [ ] **Drawable available?** (View must be on screen)\n\n## Resources\n\n**WWDC**: 2019-00611, 2020-10602, 2020-10603\n\n**Docs**: \u002Fmetal\u002Fdebugging-metal-applications, \u002Fmetal\u002Fgpu-capture\n\n**Skills**: axiom-metal-migration, axiom-metal-migration-ref\n\n---\n\n**Last Updated**: 2025-12-29\n**Platforms**: iOS 12+, macOS 10.14+, tvOS 12+\n**Status**: Comprehensive Metal porting diagnostics\n\n",{"name":5,"license":15,"metadata":38,"description":7,"compatibility":40},{"version":39},"1.0.0",[41,42,43],"iOS 12+","macOS 10.14+","tvOS 12+","main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002FCharlesWiltgen\u002FAxiom\u002Faxiom-metal-migration-diag",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":54,"prompt":64,"zipAvailable":65},[55,58,61],{"tool":56,"command":57},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill axiom-metal-migration-diag",{"tool":59,"command":60},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill axiom-metal-migration-diag",{"tool":62,"command":63},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill axiom-metal-migration-diag","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FCharlesWiltgen\u002FAxiom\u002Faxiom-metal-migration-diag\u002FSKILL.md",true,[],[68,82,92,101,107,114],{"skillNo":69,"name":70,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":71,"categories":72,"keywords":78,"publishedAt":33},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[73],{"categoryNo":74,"categoryName":75,"taxonomyType":76,"taxonomyTypeDesc":77},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[79,80,81],"model","model hierarchy","model hierarchy skill",{"skillNo":83,"name":84,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":85,"categories":86,"keywords":88,"publishedAt":33},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[87],{"categoryNo":74,"categoryName":75,"taxonomyType":76,"taxonomyTypeDesc":77},[89,90,91],"MCP","zoom","zoom mcp",{"skillNo":93,"name":94,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":95,"categories":96,"keywords":97,"publishedAt":33},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[98,99,100,90],"sdk","zoom apps sdk","apps",{"skillNo":102,"name":103,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":104,"categories":105,"keywords":106,"publishedAt":33},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[103],{"skillNo":108,"name":109,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":110,"categories":111,"keywords":113,"publishedAt":33},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[112],{"categoryNo":74,"categoryName":75,"taxonomyType":76,"taxonomyTypeDesc":77},[109],{"skillNo":115,"name":116,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":117,"categories":118,"keywords":119,"publishedAt":33},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[116]]