You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Metal GPU缓冲区与CPU不同步:多物体渲染异常问题

问题根源与解决方案

我一眼就看出问题所在了——你遇到的是动态Uniform Buffer的异步更新与绘制调用的同步冲突,具体来说:

你当前的代码在一帧内只调用了一次updateDynamicBufferState(),这意味着两个立方体的Uniform数据都被写入了动态缓冲区的同一块内存区域。由于Metal的渲染命令是异步执行的,当你修改uniforms[0]准备绘制右侧立方体时,GPU可能还没完成左侧立方体的绘制(甚至还没读取对应的Uniform数据),这就导致第二次的修改直接覆盖了第一次的数据。最终GPU执行两次绘制时,读取的都是最后一次写入的右侧立方体矩阵,所以两个立方体重叠在一起,看起来只有第二个显示。

而用setVertexBytes时不会有这个问题,是因为每次调用都会把数据直接拷贝到命令缓冲区中,每个绘制调用的数据是独立的,不会被后续修改覆盖。


修复步骤

核心思路是:为每个物体分配动态缓冲区中独立的内存区域,并在绘制前切换到对应的偏移量。

1. 调整Uniform Buffer的管理逻辑

首先在Renderer类中添加一个方法,用于获取下一帧/下一个物体可用的Uniform缓冲区偏移:

private func nextUniformOffset() -> Int {
    let currentOffset = alignedUniformsSize * uniformBufferIndex
    // 循环更新索引,确保在maxBuffersInFlight范围内循环
    uniformBufferIndex = (uniformBufferIndex + 1) % maxBuffersInFlight
    return currentOffset
}

注意:如果你的一帧内需要渲染的物体数量超过maxBuffersInFlight(当前是3),需要增大maxBuffersInFlight的值,避免覆盖之前帧的Uniform数据。

2. 修改绘制代码,为每个物体独立设置Uniform

在draw(in:)方法中,把原来的左右立方体绘制代码替换为:

// 左侧立方体
var modelMatrix = matrix4x4_translation(-3, 0, 0)
var viewMatrix = matrix4x4_translation(0.0, 0.0, -8.0)
// 获取独立的Uniform偏移
let leftUniformOffset = nextUniformOffset()
// 获取对应内存的指针并写入数据
let leftUniforms = UnsafeMutableRawPointer(dynamicUniformBuffer.contents() + leftUniformOffset)
    .bindMemory(to: Uniforms.self, capacity: 1)
leftUniforms.pointee.modelViewMatrix = simd_mul(viewMatrix, modelMatrix)
print(leftUniforms.pointee)

// 切换到左侧立方体的Uniform缓冲区
renderEncoder.setVertexBuffer(dynamicUniformBuffer, offset: leftUniformOffset, index: BufferIndex.uniforms.rawValue)
renderEncoder.setFragmentBuffer(dynamicUniformBuffer, offset: leftUniformOffset, index: BufferIndex.uniforms.rawValue)
// 绘制左侧立方体
for submesh in mesh.submeshes {
    renderEncoder.drawIndexedPrimitives(type: submesh.primitiveType, 
                                        indexCount: submesh.indexCount, 
                                        indexType: submesh.indexType, 
                                        indexBuffer: submesh.indexBuffer.buffer, 
                                        indexBufferOffset: submesh.indexBuffer.offset)
}

// 右侧立方体
modelMatrix = matrix4x4_translation(3, 0, 0)
viewMatrix = matrix4x4_translation(0.0, 0.0, -8.0)
let rightUniformOffset = nextUniformOffset()
let rightUniforms = UnsafeMutableRawPointer(dynamicUniformBuffer.contents() + rightUniformOffset)
    .bindMemory(to: Uniforms.self, capacity: 1)
rightUniforms.pointee.modelViewMatrix = simd_mul(viewMatrix, modelMatrix)
print(rightUniforms.pointee)

// 切换到右侧立方体的Uniform缓冲区
renderEncoder.setVertexBuffer(dynamicUniformBuffer, offset: rightUniformOffset, index: BufferIndex.uniforms.rawValue)
renderEncoder.setFragmentBuffer(dynamicUniformBuffer, offset: rightUniformOffset, index: BufferIndex.uniforms.rawValue)
// 绘制右侧立方体
for submesh in mesh.submeshes {
    renderEncoder.drawIndexedPrimitives(type: submesh.primitiveType, 
                                        indexCount: submesh.indexCount, 
                                        indexType: submesh.indexType, 
                                        indexBuffer: submesh.indexBuffer.buffer, 
                                        indexBufferOffset: submesh.indexBuffer.offset)
}

3. 调整初始化与状态更新逻辑

原来的updateDynamicBufferState()方法可以保留,但要注意在draw(in:)开头调用它时,会初始化第一帧的uniformBufferIndex,然后我们的nextUniformOffset()会继续递增索引,确保每个物体的Uniform数据都在独立区域。


验证修复效果

修改后再用GPU帧捕获工具查看两次绘制调用的Uniform Buffer偏移量,你会发现它们指向了缓冲区的不同位置,数据分别对应左右两个立方体的矩阵。此时两个立方体应该会正常显示在屏幕两侧。

内容的提问来源于stack exchange,提问作者hyperknot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:46:06