当尝试使用Metal从内存中快速将像素缓冲区绘制到屏幕时,我们使用MTLBuffer
创建MTLDevice.makeBuffer(bytesNoCopy:..)
个对象,以允许GPU直接从内存中读取像素而无需复制它。共享内存实际上是实现良好像素传输性能的必备条件。
问题是makeBuffer
需要页面对齐的内存地址和页面对齐length
。这些要求不仅存在于文档中 - 它们也是使用运行时断言强制执行的。
我写的代码必须处理各种传入的分辨率和像素格式,偶尔我会得到未对齐的缓冲区或未对齐的长度。在研究完之后,我发现了一个允许我为这些实例使用共享内存的黑客。
基本上我所做的是将未对齐的缓冲区地址向下舍入到最近的页面边界,并使用offset
中的makeTexture
参数来确保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不是这样做的呢?
答案 0 :(得分:7)
我已就此问题提交了Apple TSI(技术支持事件),答案基本上是是,它是安全的。如果有人感兴趣,以下是确切的回复:
在讨论了您的工程方法后,我们得出结论 是有效和安全的。一些值得注意的引言:
“框架不应该关心用户不拥有的事实 整个页面,因为它不应该在偏移之前读取 有效数据开始的地方。“
“它确实不应该[关心],但一般来说,如果开发人员可以使用 页面分配器而不是malloc用于它们的传入图像 会很好。“
至于为什么对齐约束/断言到位:
“通常将您不拥有的内存映射到另一个地址空间 有点icky,即使它在实践中有效。这就是我们的原因之一 所需的映射是页面对齐的,因为硬件确实是 映射(并获得写访问权限)到整个页面。“