【发布时间】:2017-02-18 11:56:26
【问题描述】:
当尝试使用 Metal 从内存中快速将像素缓冲区绘制到屏幕时,我们使用 MTLDevice.makeBuffer(bytesNoCopy:..) 创建了 MTLBuffer 对象,以允许 GPU 直接从内存中读取像素而无需复制它。共享内存确实是实现良好像素传输性能的必备条件。
问题是makeBuffer 需要一个页面对齐的内存地址和一个页面对齐的length。这些要求不仅在文档中——它们还使用运行时断言来强制执行。
我正在编写的代码必须处理各种传入的分辨率和像素格式,有时我会遇到未对齐的缓冲区或未对齐的长度。在对此进行研究后,我发现了一个允许我为这些实例使用共享内存的 hack。
基本上我所做的是将未对齐的缓冲区地址向下舍入到最近的页面边界,并使用来自makeTexture 的offset 参数来确保GPU 从正确的位置开始读取。然后我将length 舍入到最接近的页面大小。显然,内存将是有效的(因为分配只能发生在页面边界上),我认为可以安全地假设 GPU 没有写入或破坏该内存。
这是我用来从未对齐缓冲区分配共享缓冲区的代码:
extension MTLDevice {
func makeTextureFromUnalignedBuffer(textureDescriptor : MTLTextureDescriptor, bufferPtr : UnsafeMutableRawPointer, bufferLength : UInt, bytesPerRow : Int) -> MTLTexture? {
var calculatedBufferLength = bufferLength
let pageSize = UInt(getpagesize())
let pageSizeBitmask = UInt(getpagesize()) - 1
let alignedBufferAddr = UnsafeMutableRawPointer(bitPattern: UInt(bitPattern: bufferPtr) & ~pageSizeBitmask)
let offset = UInt(bitPattern: bufferPtr) & pageSizeBitmask
assert(bytesPerRow % 64 == 0 && offset % 64 == 0, "Supplied bufferPtr and bytesPerRow must be aligned on a 64-byte boundary!")
calculatedBufferLength += offset
if (calculatedBufferLength & pageSizeBitmask) != 0 {
calculatedBufferLength &= ~(pageSize - 1)
calculatedBufferLength += pageSize
}
let buffer = self.makeBuffer(bytesNoCopy: alignedBufferAddr!, length: Int(calculatedBufferLength), options: .storageModeShared, deallocator: nil)
return buffer.makeTexture(descriptor: textureDescriptor, offset: Int(offset), bytesPerRow: bytesPerRow)
}
}
我已经在许多不同的缓冲区上对此进行了测试,它似乎运行良好(仅在 iOS 上测试,而不是在 macOS 上测试)。 我的问题是:这种方法安全吗?这不起作用的任何明显原因?
再一次,如果它是安全的,为什么首先要提出这些要求?为什么 API 不只是为我们做这件事?
【问题讨论】:
-
我也会在苹果开发者论坛上问这个问题。
标签: ios memory-management shared-memory metal