axiom-metal-migration-diag
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMetal Migration Diagnostics
Metal移植诊断
Systematic diagnosis for common Metal porting issues.
针对常见Metal移植问题的系统化诊断方案。
When to Use This Diagnostic Skill
何时使用该诊断Skill
Use this skill when:
- Screen is black after porting to Metal
- Shaders fail to compile in Metal
- Colors or coordinates are wrong
- Performance is worse than the original
- Rendering artifacts appear
- App crashes during GPU work
在以下场景使用该Skill:
- 移植到Metal后出现黑屏
- Metal环境下着色器编译失败
- 颜色或坐标显示异常
- 性能比原版本差
- 出现渲染瑕疵
- GPU工作期间应用崩溃
Mandatory First Step: Enable Metal Validation
强制第一步:启用Metal验证
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.
时间成本:30秒设置 vs 数小时盲调
在进行任何调试之前,请启用Metal验证:
Xcode → Edit Scheme → Run → Diagnostics
✓ Metal API Validation
✓ Metal Shader Validation
✓ GPU Frame Capture (Metal)大多数Metal bug会产生明确的验证错误。如果您在未启用验证的情况下调试,请立即停止并先启用它。
Symptom 1: Black Screen
症状1:黑屏
Decision Tree
决策树
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移植后出现黑屏
│
├─ 控制台中是否有Metal验证错误?
│ └─ 是 → 优先修复验证错误(见下文)
│
├─ 渲染通道描述符是否有效?
│ ├─ 检查:view.currentRenderPassDescriptor != nil
│ ├─ 检查:drawable = view.currentDrawable != nil
│ └─ 修复:确保MTKView.device已设置,视图处于显示状态
│
├─ 管线状态是否已创建?
│ ├─ 检查:makeRenderPipelineState未抛出异常
│ └─ 修复:检查着色器函数名称与库中的名称匹配
│
├─ 是否已发起绘制调用?
│ ├─ 添加:encoder.label = "Main Pass" 以进行帧捕获
│ └─ 调试:通过GPU Frame Capture → 验证绘制调用是否存在
│
├─ 资源是否已绑定?
│ ├─ 检查:是否调用了setVertexBuffer、setFragmentTexture
│ └─ 修复:Metal要求每帧都显式绑定资源
│
├─ 顶点数据是否正确?
│ ├─ 调试:通过GPU Frame Capture → 检查顶点缓冲区
│ └─ 修复:检查缓冲区偏移量、顶点数量
│
├─ 坐标是否在Metal的范围内?
│ ├─ Metal NDC:X [-1,1], Y [-1,1], Z [0,1]
│ ├─ OpenGL NDC:X [-1,1], Y [-1,1], Z [-1,1]
│ └─ 修复:调整投影矩阵或顶点着色器
│
└─ 是否设置了清除颜色?
├─ 默认清除颜色为(0,0,0,0) —— 透明黑色
└─ 修复:设置view.clearColor或renderPassDescriptor.colorAttachments[0].clearColorCommon Fixes
常见修复方案
Missing Drawable:
swift
// 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:
swift
// 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:
swift
// 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.
缺少Drawable:
swift
// 错误:视图就绪前进行绘制
override func viewDidLoad() {
draw() // metalView.currentDrawable 为nil
}
// 正确:等待代理回调
func draw(in view: MTKView) {
guard let drawable = view.currentDrawable else { return }
// 可以安全绘制
}函数名称不匹配:
swift
// 错误:函数名称与.metal文件中的不匹配
descriptor.vertexFunction = library.makeFunction(name: "vertexMain")
// .metal文件中的内容:vertex VertexOut vertexShader(...)
// 正确:名称必须完全匹配
descriptor.vertexFunction = library.makeFunction(name: "vertexShader")缺少资源绑定:
swift
// 错误:假设状态像OpenGL一样持续存在
encoder.setRenderPipelineState(pso)
encoder.drawPrimitives(...) // 未绑定任何缓冲区!
// 正确:显式绑定所有资源
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 Frame Capture诊断:5-10分钟。无工具盲猜:1-4小时。
Symptom 2: Shader Compilation Errors
症状2:着色器编译错误
Decision Tree
决策树
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)着色器编译失败
│
├─ "Use of undeclared identifier"
│ ├─ 检查:是否包含#include <metal_stdlib>
│ ├─ 检查:是否添加using namespace metal;
│ └─ 修复:标准函数需要metal_stdlib
│
├─ "No matching function for call to 'texture'"
│ └─ GLSL的texture() → MSL的tex.sample(sampler, uv)
│ 修复:纹理采样是方法,需要采样器
│
├─ "Invalid type 'vec4'"
│ └─ GLSL的vec4 → MSL的float4
│ 修复:参考metal-migration-ref中的类型映射表
│
├─ "No matching constructor"
│ ├─ GLSL:vec4(vec3, float) 可用
│ ├─ MSL:float4(float3, float) 可用
│ └─ 检查:参数类型完全匹配
│
├─ "Attribute index out of range"
│ ├─ 检查:[[attribute(N)]] 与顶点描述符匹配
│ └─ 修复:必须配置vertexDescriptor.attributes[N]
│
├─ "Buffer binding index out of range"
│ ├─ 检查:[[buffer(N)]] 中的N < 31
│ └─ 修复:Metal每个阶段最多支持31个缓冲区绑定
│
└─ "Cannot convert value of type"
├─ MSL在隐式转换方面比GLSL更严格
└─ 修复:添加显式转换:float(intValue), int(floatValue)Common Conversions
常见转换示例
metal
// 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.
metal
// GLSL
vec4 color = texture(sampler2D, uv);
// MSL —— 纹理和采样器是分离的
float4 color = tex.sample(samp, uv);
// GLSL —— 浮点数的mod()
float x = mod(y, z);
// MSL —— 浮点数的fmod()
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分钟。
Symptom 3: Wrong Colors or Coordinates
症状3:颜色或坐标异常
Decision Tree
决策树
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渲染效果异常
│
├─ 图像倒置
│ ├─ 原因:Metal的Y轴与OpenGL相反
│ ├─ 修复(顶点着色器):pos.y = -pos.y
│ ├─ 修复(纹理加载):MTKTextureLoader的.origin: .bottomLeft
│ └─ 修复(UV坐标):在片段着色器中设置uv.y = 1.0 - uv.y
│
├─ 图像镜像
│ ├─ 原因:缠绕顺序或剔除模式错误
│ ├─ 修复:encoder.setFrontFacing(.counterClockwise)
│ └─ 修复:设置encoder.setCullMode(.back) 或 .none 进行测试
│
├─ 颜色交换(红/蓝)
│ ├─ 原因:像素格式不匹配
│ ├─ 检查:.bgra8Unorm vs .rgba8Unorm
│ └─ 修复:使纹理像素格式与数据格式匹配
│
├─ 颜色褪色/过亮
│ ├─ 原因:sRGB与线性色彩空间不匹配
│ ├─ 检查:是否为sRGB纹理使用.bgra8Unorm_srgb?
│ └─ 修复:使用_srgb格式变体以实现伽马校正渲染
│
├─ 深度冲突/Z-fighting
│ ├─ 原因:NDC Z范围不同
│ ├─ OpenGL:Z ∈ [-1, 1]
│ ├─ Metal:Z ∈ [0, 1]
│ └─ 修复:调整投影矩阵以适配Metal的Z范围
│
├─ 对象裁剪异常
│ ├─ 原因:近/远平面或视口设置错误
│ ├─ 检查:视口大小与Drawable大小匹配
│ └─ 修复:调用encoder.setViewport(MTLViewport(...))
│
└─ 透明度异常
├─ 原因:混合状态未配置
├─ 修复:pipelineDescriptor.colorAttachments[0].isBlendingEnabled = true
└─ 修复:设置sourceRGBBlendFactor、destinationRGBBlendFactorCoordinate System Fix
坐标系修复示例
swift
// 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.
swift
// 针对Metal的Z范围[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 Frame Capture检查纹理:5-10分钟。无工具:1-2小时。
Symptom 4: Performance Regression
症状4:性能退化
Decision Tree
决策树
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性能比OpenGL差
│
├─ 是否启用了验证?
│ └─ 验证会增加约30%的开销
│ 修复:发布版本禁用验证,调试版本保留
│
├─ 是否每帧都创建资源?
│ ├─ 错误:在draw()中调用device.makeBuffer()
│ └─ 修复:一次性创建缓冲区,通过三重缓冲复用
│
├─ 是否每帧都创建管线状态?
│ ├─ 错误:在draw()中调用makeRenderPipelineState()
│ └─ 修复:在初始化时一次性创建PSO,作为属性存储
│
├─ 绘制调用过多?
│ ├─ 调试:通过GPU Frame Capture → 统计绘制调用数量
│ └─ 修复:批处理几何体,使用实例化、间接绘制
│
├─ 是否存在GPU-CPU同步停顿?
│ ├─ 调试:通过Metal System Trace → 查找停顿点
│ ├─ 原因:waitUntilCompleted() 阻塞CPU
│ └─ 修复:使用信号量实现三重缓冲
│
├─ 缓冲区更新效率低下?
│ ├─ 错误:重建缓冲区以更新数据
│ └─ 修复:使用buffer.contents().copyMemory() 更新动态数据
│
├─ 存储模式错误?
│ ├─ .shared:适合小型动态数据
│ ├─ .private:适合静态GPU专属数据
│ └─ 修复:对不变化的几何体使用.private模式
│
└─ 缺少Metal特定优化?
├─ 参数缓冲区可减少绑定开销
├─ 间接绘制可减少CPU工作量
└─ 参考WWDC中关于Metal优化的会话Triple Buffering Pattern
三重缓冲模式示例
swift
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.
swift
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) {
// 等待可用缓冲区
inflightSemaphore.wait()
let buffer = uniformBuffers[currentBufferIndex]
// 可以安全写入 —— GPU已完成该缓冲区的使用
memcpy(buffer.contents(), &uniforms, uniformsSize)
let commandBuffer = commandQueue.makeCommandBuffer()!
// GPU完成时发送信号
commandBuffer.addCompletedHandler { [weak self] _ in
self?.inflightSemaphore.signal()
}
// ... 编码并提交
currentBufferIndex = (currentBufferIndex + 1) % Self.maxInflightFrames
}
}时间成本:使用Metal System Trace诊断:15-30分钟。盲猜:数小时。
Symptom 5: Crashes During GPU Work
症状5:GPU工作期间崩溃
Decision Tree
决策树
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渲染期间应用崩溃
│
├─ Metal框架中出现EXC_BAD_ACCESS
│ ├─ 原因:访问已释放的资源
│ ├─ 检查:GPU使用期间缓冲区/纹理是否被保留
│ └─ 修复:在命令缓冲区完成前保持强引用
│
├─ "Execution of the command buffer was aborted"
│ ├─ 原因:GPU超时(iOS上超过10秒)
│ ├─ 检查:着色器中是否有无限循环?
│ └─ 修复:添加提前退出条件,减少工作量
│
├─ "-[MTLDebugRenderCommandEncoder validateDrawCallWithArray:...]"
│ ├─ 原因:验证捕获到使用错误
│ └─ 修复:阅读验证消息 —— 它会明确告知问题所在
│
├─ "Fragment shader writes to non-existent render target"
│ ├─ 原因:着色器返回颜色但无颜色附件
│ └─ 修复:配置colorAttachments[0].pixelFormat
│
├─ 着色器中崩溃(SIGABRT)
│ ├─ 原因:缓冲区越界访问
│ ├─ 调试:启用着色器验证
│ └─ 修复:检查数组边界、缓冲区大小
│
└─ 设备断开/GPU重启
├─ 原因:严重GPU挂起
├─ 检查:是否有无限循环或大量过绘制
└─ 修复:简化着色器,降低绘制复杂度Resource Lifetime Fix
资源生命周期修复示例
swift
// 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()
}
}swift
// 错误:GPU完成前释放缓冲区
func draw(in view: MTKView) {
let buffer = device.makeBuffer(...)
encoder.setVertexBuffer(buffer, ...)
commandBuffer.commit()
// 缓冲区在作用域结束时释放 —— GPU仍在使用它!
}
// 正确:保留引用直到完成
class Renderer {
var currentBuffer: MTLBuffer? // 强引用
func draw(in view: MTKView) {
currentBuffer = device.makeBuffer(...)
encoder.setVertexBuffer(currentBuffer!, ...)
commandBuffer.addCompletedHandler { [weak self] _ in
// 现在可以安全释放
self?.currentBuffer = nil
}
commandBuffer.commit()
}
}Debugging Tools Quick Reference
调试工具快速参考
GPU Frame Capture
GPU Frame Capture
Xcode → Debug → Capture GPU Frame (Cmd+Opt+Shift+G)Use for:
- Inspecting buffer contents
- Viewing intermediate textures
- Checking draw call sequence
- Debugging shader variable values
- Understanding why something isn't rendering
Xcode → Debug → Capture GPU Frame (Cmd+Opt+Shift+G)适用场景:
- 检查缓冲区内容
- 查看中间纹理
- 检查绘制调用序列
- 调试着色器变量值
- 排查渲染失败原因
Metal System Trace (Instruments)
Metal System Trace(Instruments)
Instruments → Metal System Trace templateUse for:
- GPU/CPU timeline analysis
- Finding synchronization stalls
- Measuring encoder/buffer overhead
- Identifying bottlenecks
Instruments → Metal System Trace 模板适用场景:
- GPU/CPU时间线分析
- 查找同步停顿
- 测量编码器/缓冲区开销
- 识别性能瓶颈
Shader Debugger
着色器调试器
GPU Frame Capture → Select draw call → Debug buttonUse for:
- Step through shader execution
- Inspect variable values per pixel/vertex
- Find logic errors in shaders
GPU Frame Capture → 选择绘制调用 → 调试按钮适用场景:
- 单步执行着色器
- 检查每个像素/顶点的变量值
- 查找着色器中的逻辑错误
Validation Messages
验证消息
Most validation messages include:
- What went wrong
- Which resource/state
- What the expected value was
Always read the full message — it usually tells you exactly how to fix the problem.
大多数验证消息包含:
- 问题内容
- 涉及的资源/状态
- 预期值
务必阅读完整消息 —— 它通常会明确告知修复方法。
Diagnostic Checklist
诊断检查清单
When something doesn't work:
- Metal validation enabled? (Most bugs produce validation errors)
- GPU Frame Capture available? (Visual debugging is fastest)
- Console error messages? (Read them fully)
- Resources bound? (Metal requires explicit binding)
- Coordinates correct? (Y-flip, NDC Z range)
- Pipeline state created successfully? (Check for throw)
- Drawable available? (View must be on screen)
出现问题时,请检查:
- 是否启用了Metal验证?(大多数bug会产生验证错误)
- 是否可以使用GPU Frame Capture?(可视化调试最快)
- 控制台是否有错误消息?(完整阅读)
- 资源是否已绑定?(Metal要求显式绑定)
- 坐标是否正确?(Y轴翻转、NDC Z范围)
- 管线状态是否创建成功?(检查是否抛出异常)
- Drawable是否可用?(视图必须在屏幕上)
Resources
参考资源
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
WWDC:2019-00611, 2020-10602, 2020-10603
文档:/metal/debugging-metal-applications, /metal/gpu-capture
Skills:axiom-metal-migration, axiom-metal-migration-ref
最后更新:2025-12-29
支持平台:iOS 12+, macOS 10.14+, tvOS 12+
状态:全面的Metal移植诊断方案