axiom-metal-migration-diag by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-metal-migration-diag针对常见 Metal 移植问题的系统性诊断。
在以下情况时使用此技能:
时间成本:30 秒设置 vs 数小时盲目调试
在进行任何调试之前,请启用 Metal 验证:
Xcode → Edit Scheme → Run → Diagnostics
✓ Metal API Validation
✓ Metal Shader Validation
✓ GPU Frame Capture (Metal)
大多数 Metal 错误会产生清晰的验证错误。如果你在没有启用验证的情况下进行调试,请停止并首先启用它。
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
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
缺少可绘制对象:
// 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 小时。
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 分钟。
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 小时。
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 系统跟踪诊断:15-30 分钟。猜测:数小时。
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()
}
}
Xcode → Debug → Capture GPU Frame (Cmd+Opt+Shift+G)
用于:
Instruments → Metal System Trace template
用于:
GPU Frame Capture → Select draw call → Debug button
用于:
大多数验证消息包括:
始终阅读完整消息 — 它通常会确切地告诉你如何修复问题。
当某些内容不工作时:
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 移植诊断
每周安装
93
仓库
GitHub 星标
617
首次出现
Jan 21, 2026
安全审计
安装于
opencode77
claude-code73
codex72
gemini-cli69
cursor69
github-copilot66
Systematic diagnosis for common Metal porting issues.
Use this skill when:
Time cost : 30 seconds setup vs hours of blind debugging
Before ANY debugging, enable Metal validation:
Xcode → Edit Scheme → Run → Diagnostics
✓ Metal API Validation
✓ Metal Shader Validation
✓ GPU Frame Capture (Metal)
Most Metal bugs produce clear validation errors. If you're debugging without validation enabled, stop and enable it first.
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
Missing Drawable :
// 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
}
Wrong Function Names :
// 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")
Missing Resource Binding :
// 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(...)
Time cost : GPU Frame Capture diagnosis: 5-10 min. Guessing without tools: 1-4 hours.
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);
Time cost : With conversion table: 2-5 min per shader. Without: 15-30 min per shader.
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)
])
}
Time cost : With GPU Frame Capture texture inspection: 5-10 min. Without: 1-2 hours.
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
}
}
Time cost : Metal System Trace diagnosis: 15-30 min. Guessing: hours.
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()
}
}
Xcode → Debug → Capture GPU Frame (Cmd+Opt+Shift+G)
Use for :
Instruments → Metal System Trace template
Use for :
GPU Frame Capture → Select draw call → Debug button
Use for :
Most validation messages include:
Always read the full message — it usually tells you exactly how to fix the problem.
When something doesn't work:
WWDC : 2019-00611, 2020-10602, 2020-10603
Docs : /metal/debugging-metal-applications, /metal/gpu-capture
Skills : axiom-metal-migration, axiom-metal-migration-ref
Last Updated : 2025-12-29 Platforms : iOS 12+, macOS 10.14+, tvOS 12+ Status : Comprehensive Metal porting diagnostics
Weekly Installs
93
Repository
GitHub Stars
617
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode77
claude-code73
codex72
gemini-cli69
cursor69
github-copilot66
AI域名生成器 - 智能创意工具,检查可用性,为项目找到完美域名
571 周安装
AI每日站会助手 - 自动生成会议更新,集成GitHub/Jira/Claude Code
571 周安装
AI周报生成器 | 一键创建专业周报模板(个人/团队/高管/客户)
584 周安装
Markdown转Office文档工具:一键将MD转换为Word、PDF、PPT | Pandoc技能
582 周安装
Gemini 技能指南:代码审查、大上下文处理与自动化任务配置教程
572 周安装
skill-judge:AI Agent Skill 评估工具,优化知识增量与专家思维模式
572 周安装