mcpField
A
axiom-metal-migration-diag

axiom-metal-migration-diag

github.com/charleswiltgen開発1.2k163 回インストール今日更新
リポジトリ

axiom-metal-migration-diag について

グラフィックスコードをMetalへ移植する際の一般的な問題を診断する開発スキルです。症状別の手順に沿って、検証、シェーダー変換、GPUキャプチャ、リソース設定、性能分析を扱います。

主な機能

  1. 01Metal APIとシェーダーの検証を有効化
  2. 02黒画面や描画アーティファクトの診断
  3. 03GLSLからMSLへのシェーダー変換の確認
  4. 04GPUフレームとシェーダー実行の調査
  5. 05GPUとCPUの性能分析

活用シーン

  1. 01Metal移植後の黒画面の調査
  2. 02シェーダーコンパイルエラーの解決
  3. 03色や座標の不一致の修正
  4. 04GPU処理中のクラッシュや性能低下の追跡

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-metal-migration-diag

インストール後、Claude Code で/axiom-metal-migration-diagを入力して読み込まれたか確認します。

詳細

Metal 移行の診断

Metal への移植でよくある問題を体系的に診断します。

この診断スキルを使用するタイミング

次の場合にこのスキルを使用してください。

  • Metal への移植後、画面が真っ黒になる
  • Metal でシェーダーのコンパイルに失敗する
  • 色や座標が正しくない
  • パフォーマンスが元の実装より低下する
  • レンダリングにアーティファクトが表示される
  • GPU 処理中にアプリがクラッシュする

必須の最初の手順:Metal 検証を有効にする

所要時間:セットアップに 30 秒。手探りのデバッグでは数時間かかります。

デバッグを始める前に、必ず Metal 検証を有効にしてください。

Xcode → Edit Scheme → Run → Diagnostics
✓ Metal API Validation
✓ Metal Shader Validation
✓ GPU Frame Capture (Metal)

ほとんどの Metal のバグは、検証エラーとして明確に報告されます。検証を有効にせずにデバッグしている場合は、いったん中断して、まず有効にしてください。

症状 1:画面が真っ黒になる

判断フロー
Black screen after porting
│
├─ Are there Metal validation errors in console?
│   └─ YES → Fix validation errors first (see below)
│
├─ Is the render pass descriptor valid?
│   ├─ Check: view.currentRenderPassDescriptor != nil
│   ├─ Check: drawable = view.currentDrawable != nil
│   └─ FIX: Ensure MTKView.device is set, view is on screen
│
├─ Is the pipeline state created?
│   ├─ Check: makeRenderPipelineState doesn't throw
│   └─ FIX: Check shader function names match library
│
├─ Are draw calls being issued?
│   ├─ Add: encoder.label = "Main Pass" for frame capture
│   └─ DEBUG: GPU Frame Capture → verify draw calls appear
│
├─ Are resources bound?
│   ├─ Check: setVertexBuffer, setFragmentTexture called
│   └─ FIX: Metal requires explicit binding every frame
│
├─ Is the vertex data correct?
│   ├─ DEBUG: GPU Frame Capture → inspect vertex buffer
│   └─ FIX: Check buffer offsets, vertex count
│
├─ Are coordinates in Metal's range?
│   ├─ Metal NDC: X [-1,1], Y [-1,1], Z [0,1]
│   ├─ OpenGL NDC: X [-1,1], Y [-1,1], Z [-1,1]
│   └─ FIX: Adjust projection matrix or vertex shader
│
└─ Is clear color set?
    ├─ Default clear color is (0,0,0,0) — transparent black
    └─ FIX: Set view.clearColor or renderPassDescriptor.colorAttachments[0].clearColor
よくある修正方法

ドローアブルがない場合:

// BAD: Drawing before view is ready
override func viewDidLoad() {
    draw()  // metalView.currentDrawable is nil
}

// GOOD: Wait for delegate callback
func draw(in view: MTKView) {
    guard let drawable = view.currentDrawable else { return }
    // Safe to draw
}

関数名が正しくない場合:

// BAD: Function name doesn't match .metal file
descriptor.vertexFunction = library.makeFunction(name: "vertexMain")
// .metal file has: vertex VertexOut vertexShader(...)

// GOOD: Names must match exactly
descriptor.vertexFunction = library.makeFunction(name: "vertexShader")

リソースのバインドがない場合:

// BAD: Assumed state persists like OpenGL
encoder.setRenderPipelineState(pso)
encoder.drawPrimitives(...)  // No buffers bound!

// GOOD: Bind everything explicitly
encoder.setRenderPipelineState(pso)
encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
encoder.setVertexBytes(&uniforms, length: uniformsSize, index: 1)
encoder.setFragmentTexture(texture, index: 0)
encoder.drawPrimitives(...)

所要時間:GPU フレームキャプチャによる診断は 5~10 分です。ツールを使わずに推測すると 1~4 時間かかります。

症状 2:シェーダーのコンパイルエラー

判断フロー
Shader fails to compile
│
├─ "Use of undeclared identifier"
│   ├─ Check: #include <metal_stdlib>
│   ├─ Check: using namespace metal;
│   └─ FIX: Standard functions need metal_stdlib
│
├─ "No matching function for call to 'texture'"
│   └─ GLSL texture() → MSL tex.sample(sampler, uv)
│       FIX: Texture sampling is a method, needs sampler
│
├─ "Invalid type 'vec4'"
│   └─ GLSL vec4 → MSL float4
│       FIX: See type mapping table in metal-migration-ref
│
├─ "No matching constructor"
│   ├─ GLSL: vec4(vec3, float) works
│   ├─ MSL: float4(float3, float) works
│   └─ Check: Argument types match exactly
│
├─ "Attribute index out of range"
│   ├─ Check: [[attribute(N)]] matches vertex descriptor
│   └─ FIX: vertexDescriptor.attributes[N] must be configured
│
├─ "Buffer binding index out of range"
│   ├─ Check: [[buffer(N)]] where N < 31
│   └─ FIX: Metal has max 31 buffer bindings per stage
│
└─ "Cannot convert value of type"
    ├─ MSL is stricter than GLSL about implicit conversions
    └─ FIX: Add explicit casts: float(intValue), int(floatValue)
よくある変換
// GLSL
vec4 color = texture(sampler2D, uv);

// MSL — texture and sampler are separate
float4 color = tex.sample(samp, uv);

// GLSL — mod() for floats
float x = mod(y, z);

// MSL — fmod() for floats
float x = fmod(y, z);

// GLSL — atan(y, x)
float angle = atan(y, x);

// MSL — atan2(y, x)
float angle = atan2(y, x);

// GLSL — inversesqrt
float invSqrt = inversesqrt(x);

// MSL — rsqrt
float invSqrt = rsqrt(x);

所要時間:変換表を使えば、シェーダーあたり 2~5 分です。使わない場合は、シェーダーあたり 15~30 分かかります。

症状 3:色や座標が正しくない

判断フロー
Rendering looks wrong
│
├─ Image is upside down
│   ├─ Cause: Metal Y-axis is opposite OpenGL
│   ├─ FIX (vertex shader): pos.y = -pos.y
│   ├─ FIX (texture load): MTKTextureLoader .origin: .bottomLeft
│   └─ FIX (UV): uv.y = 1.0 - uv.y in fragment shader
│
├─ Image is mirrored
│   ├─ Cause: Winding order or cull mode wrong
│   ├─ FIX: encoder.setFrontFacing(.counterClockwise)
│   └─ FIX: encoder.setCullMode(.back) or .none to test
│
├─ Colors are swapped (red/blue)
│   ├─ Cause: Pixel format mismatch
│   ├─ Check: .bgra8Unorm vs .rgba8Unorm
│   └─ FIX: Match texture pixel format to data format
│
├─ Colors are washed out / too bright
│   ├─ Cause: sRGB vs linear color space
│   ├─ Check: Using .bgra8Unorm_srgb for sRGB textures?
│   └─ FIX: Use _srgb format variants for gamma-correct rendering
│
├─ Depth fighting / z-fighting
│   ├─ Cause: NDC Z range difference
│   ├─ OpenGL: Z in [-1, 1]
│   ├─ Metal: Z in [0, 1]
│   └─ FIX: Adjust projection matrix for Metal's Z range
│
├─ Objects clipped incorrectly
│   ├─ Cause: Near/far plane or viewport
│   ├─ Check: Viewport size matches drawable size
│   └─ FIX: encoder.setViewport(MTLViewport(...))
│
└─ Transparency wrong
    ├─ Cause: Blend state not configured
    ├─ FIX: pipelineDescriptor.colorAttachments[0].isBlendingEnabled = true
    └─ FIX: Set sourceRGBBlendFactor, destinationRGBBlendFactor
座標系の修正
// Fix projection matrix for Metal's Z range [0, 1]
func metalPerspectiveProjection(fovY: Float, aspect: Float, near: Float, far: Float) -> simd_float4x4 {
    let yScale = 1.0 / tan(fovY * 0.5)
    let xScale = yScale / aspect
    let zRange = far - near

    return simd_float4x4(rows: [
        SIMD4<Float>(xScale, 0, 0, 0),
        SIMD4<Float>(0, yScale, 0, 0),
        SIMD4<Float>(0, 0, far / zRange, 1),  // Metal: [0, 1]
        SIMD4<Float>(0, 0, -near * far / zRange, 0)
    ])
}

所要時間:GPU フレームキャプチャでテクスチャを確認すれば 5~10 分です。確認しない場合は 1~2 時間かかります。

症状 4:パフォーマンスの低下

判断フロー
Performance worse than OpenGL
│
├─ Enabling validation?
│   └─ Validation adds ~30% overhead
│       FIX: Disable for release builds, keep for debug
│
├─ Creating resources every frame?
│   ├─ BAD: device.makeBuffer() in draw()
│   └─ FIX: Create buffers once, reuse with triple buffering
│
├─ Creating pipeline state every frame?
│   ├─ BAD: makeRenderPipelineState() in draw()
│   └─ FIX: Create PSO once at init, store as property
│
├─ Too many draw calls?
│   ├─ DEBUG: GPU Frame Capture → count draw calls
│   └─ FIX: Batch geometry, use instancing, indirect draws
│
├─ GPU-CPU sync stalls?
│   ├─ DEBUG: Metal System Trace → look for stalls
│   ├─ Cause: waitUntilCompleted() blocks CPU
│   └─ FIX: Triple buffering with semaphore
│
├─ Inefficient buffer updates?
│   ├─ BAD: Recreating buffer to update
│   └─ FIX: buffer.contents().copyMemory() for dynamic data
│
├─ Wrong storage mode?
│   ├─ .shared: Good for small dynamic data
│   ├─ .private: Good for static GPU-only data
│   └─ FIX: Use .private for geometry that doesn't change
│
└─ Missing Metal-specific optimizations?
    ├─ Argument buffers reduce binding overhead
    ├─ Indirect draws reduce CPU work
    └─ See WWDC sessions on Metal optimization
トリプルバッファリングのパターン
class TripleBufferedRenderer {
    static let maxInflightFrames = 3

    let inflightSemaphore = DispatchSemaphore(value: maxInflightFrames)
    var uniformBuffers: [MTLBuffer] = []
    var currentBufferIndex = 0

    init(device: MTLDevice) {
        for _ in 0..<Self.maxInflightFrames {
            let buffer = device.makeBuffer(length: uniformsSize, options: .storageModeShared)!
            uniformBuffers.append(buffer)
        }
    }

    func draw(in view: MTKView) {
        // Wait for a buffer to be available
        inflightSemaphore.wait()

        let buffer = uniformBuffers[currentBufferIndex]
        // Safe to write — GPU is done with this buffer
        memcpy(buffer.contents(), &uniforms, uniformsSize)

        let commandBuffer = commandQueue.makeCommandBuffer()!

        // Signal when GPU is done
        commandBuffer.addCompletedHandler { [weak self] _ in
            self?.inflightSemaphore.signal()
        }

        // ... encode and commit

        currentBufferIndex = (currentBufferIndex + 1) % Self.maxInflightFrames
    }
}

所要時間:Metal System Trace による診断は 15~30 分です。推測では何時間もかかります。

症状 5:GPU 処理中のクラッシュ

判断フロー
App crashes during rendering
│
├─ EXC_BAD_ACCESS in Metal framework
│   ├─ Cause: Accessing released resource
│   ├─ Check: Buffer/texture retained during GPU use
│   └─ FIX: Keep strong references until command buffer completes
│
├─ "Execution of the command buffer was aborted"
│   ├─ Cause: GPU timeout (>10 sec on iOS)
│   ├─ Check: Infinite loop in shader?
│   └─ FIX: Add early exit conditions, reduce work
│
├─ "-[MTLDebugRenderCommandEncoder validateDrawCallWithArray:...]"
│   ├─ Cause: Validation caught misuse
│   └─ FIX: Read the validation message — it tells you exactly what's wrong
│
├─ "Fragment shader writes to non-existent render target"
│   ├─ Cause: Shader returns color but no color attachment
│   └─ FIX: Configure colorAttachments[0].pixelFormat
│
├─ Crash in shader (SIGABRT)
│   ├─ Cause: Out-of-bounds buffer access
│   ├─ DEBUG: Enable shader validation
│   └─ FIX: Check array bounds, buffer sizes
│
└─ Device disconnected / GPU restart
    ├─ Cause: Severe GPU hang
    ├─ Check: Infinite loop or massive overdraw
    └─ FIX: Simplify shader, reduce draw complexity
リソースのライフタイムの修正
// BAD: Buffer released before GPU finishes
func draw(in view: MTKView) {
    let buffer = device.makeBuffer(...)  // Created here
    encoder.setVertexBuffer(buffer, ...)
    commandBuffer.commit()
    // buffer released at end of scope — GPU still using it!
}

// GOOD: Keep reference until completion
class Renderer {
    var currentBuffer: MTLBuffer?  // Strong reference

    func draw(in view: MTKView) {
        currentBuffer = device.makeBuffer(...)
        encoder.setVertexBuffer(currentBuffer!, ...)
        commandBuffer.addCompletedHandler { [weak self] _ in
            // Safe to release now
            self?.currentBuffer = nil
        }
        commandBuffer.commit()
    }
}

デバッグツールのクイックリファレンス

GPU フレームキャプチャ
Xcode → Debug → Capture GPU Frame (Cmd+Opt+Shift+G)

用途:

  • バッファの内容を確認する
  • 中間テクスチャを表示する
  • 描画呼び出しの順序を確認する
  • シェーダー変数の値をデバッグする
  • 描画されない原因を把握する
Metal System Trace(Instruments)
Instruments → Metal System Trace template

用途:

  • GPU/CPU のタイムラインを分析する
  • 同期による停滞を特定する
  • エンコーダー/バッファーのオーバーヘッドを測定する
  • ボトルネックを特定する
シェーダーデバッガー
GPU Frame Capture → Select draw call → Debug button

用途:

  • シェーダーの実行をステップ実行する
  • ピクセル/頂点ごとの変数値を確認する
  • シェーダー内のロジックエラーを見つける
検証メッセージ

ほとんどの検証メッセージには、次の情報が含まれています。

  • 何が問題だったか
  • どのリソース/状態に関する問題か
  • 想定されていた値

メッセージ全体を必ず読んでください。通常、問題の修正方法が具体的に示されています。

診断チェックリスト

動作しない場合:

  • Metal 検証は有効になっていますか?(ほとんどのバグは検証エラーとして報告されます)
  • GPU フレームキャプチャを利用できますか?(視覚的なデバッグが最も速い方法です)
  • コンソールにエラーメッセージはありますか?(全文を読んでください)
  • リソースはバインドされていますか?(Metal では明示的なバインドが必要です)
  • 座標は正しいですか?(Y 軸反転、NDC の Z 範囲)
  • パイプライン状態は正常に作成されましたか?(throw を確認してください)
  • ドローアブルを利用できますか?(ビューが画面上に表示されている必要があります)

リソース

WWDC:2019-00611、2020-10602、2020-10603

ドキュメント:/metal/debugging-metal-applications、/metal/gpu-capture

スキル:axiom-metal-migration、axiom-metal-migration-ref


最終更新日:2025-12-29 プラットフォーム:iOS 12+、macOS 10.14+、tvOS 12+ ステータス:Metal 移植に関する包括的な診断

コメント