【问题标题】:Reading contents from a generic MTLBuffer?从通用 MTLBuffer 中读取内容?
【发布时间】:2020-08-26 22:46:56
【问题描述】:

在我的应用程序中,我有一个MTLBuffer,它正在使用泛型类型进行实例化。在一种特殊情况下,缓冲区将保存与点云中的粒子相关的值,并被定义为这样;

struct ParticleUniforms {
    simd_float3 position;
    simd_float3 color;
    float confidence;
};

我正在像这样实例化我的MTLBuffer

guard let buffer = device.makeBuffer(length: MemoryLayout<Element>.stride * count, options: options) else {
   fatalError("Failed to create MTLBuffer.")
}

然而,我正在努力理解如何读取缓冲区的内容。更重要的是,我希望将缓冲区中每个项目的一个元素复制到 CPU 上的一个数组中,稍后我将使用该数组。

实际上,缓冲区包含ParticleUniforms 的集合,我想访问每个项目的position 值,将该位置保存到单独的数组中。

我在 Stack Overflow 上看到的所有示例似乎都将 MTLBuffer 显示为包含浮点数的集合,尽管我没有看到任何使用泛型类型的示例。

【问题讨论】:

    标签: ios metal mtlbuffer


    【解决方案1】:

    看来您要实现的目标只能通过将每个成员保存在连续块中的 C 结构来完成(C 结构的数组not 必须是连续的,但MemoryLayout&lt;Type&gt;.stride 将考虑任何潜在的填充)。 Swift 结构属性may not be contiguous,因此以下访问成员值的方法在实际中不起作用。不幸的是,在使用 void* 时,您需要知道数据描述的内容,这并不特别适合 Swift 泛型类型。不过,我会提供一个潜在的解决方案。

    C 文件:

    #ifndef Test_h
    #define Test_h
    
    #include <simd/simd.h>
    
    typedef struct {
        vector_float3 testA;
        vector_float3 testB;
    } CustomC;
    
    #endif /* Test_h */
    

    Swift 文件(假定为桥接头)

    import Metal
    
    // MARK: Convenience
    typealias MTLCStructMemberFormat = MTLVertexFormat
    
    @_functionBuilder
    struct ArrayLayout { static func buildBlock<T>(_ arr: T...) -> [T] { arr } }
    
    extension MTLCStructMemberFormat {
        var stride: Int {
            switch self {
            case .float2:  return MemoryLayout<simd_float2>.stride
            case .float3:  return MemoryLayout<simd_float3>.stride
            default:       fatalError("Case unaccounted for")
            }
        }
    }
    
    // MARK: Custom Protocol
    protocol CMetalStruct {
        /// Returns the type of the `ith` member
        static var memoryLayouts: [MTLCStructMemberFormat] { get }
    }
    
    // Custom Allocator
    class CustomBufferAllocator<Element> where Element: CMetalStruct {
        
        var buffer: MTLBuffer!
        var count: Int
        
        init(bytes: UnsafeMutableRawPointer, count: Int, options: MTLResourceOptions = []) {
            guard let buffer = device.makeBuffer(bytes: bytes, length: count * MemoryLayout<Element>.stride, options: options) else {
                fatalError("Failed to create MTLBuffer.")
            }
            self.buffer = buffer
            self.count = count
        }
        
        func readBufferContents<T>(element_position_in_array n: Int, memberID: Int, expectedType type: T.Type = T.self)
            -> T {
            let pointerAddition = n * MemoryLayout<Element>.stride
                let valueToIncrement = Element.memoryLayouts[0..<memberID].reduce(0) { $0 + $1.stride }
            return buffer.contents().advanced(by: pointerAddition + valueToIncrement).bindMemory(to: T.self, capacity: 1).pointee
        }
        
        func extractMembers<T>(memberID: Int, expectedType type: T.Type = T.self) -> [T] {
            var array: [T] = []
     
            for n in 0..<count {
                let pointerAddition = n * MemoryLayout<Element>.stride
                let valueToIncrement = Element.memoryLayouts[0..<memberID].reduce(0) { $0 + $1.stride }
                let contents = buffer.contents().advanced(by: pointerAddition + valueToIncrement).bindMemory(to: T.self, capacity: 1).pointee
                array.append(contents)
            }
            
            return array
        }
    }
    
    // Example
    
    // First extend the custom struct to conform to out type
    extension CustomC: CMetalStruct {
        @ArrayLayout static var memoryLayouts: [MTLCStructMemberFormat] {
            MTLCStructMemberFormat.float3
            MTLCStructMemberFormat.float3
        }
    }
    
    let device = MTLCreateSystemDefaultDevice()!
    var CTypes = [CustomC(testA: .init(59, 99, 0), testB: .init(102, 111, 52)), CustomC(testA: .init(10, 11, 5), testB: .one), CustomC(testA: .zero, testB: .init(5, 5, 5))]
    
    let allocator = CustomBufferAllocator<CustomC>(bytes: &CTypes, count: 3)
    let value = allocator.readBufferContents(element_position_in_array: 1, memberID: 0, expectedType: simd_float3.self)
    print(value)
    
    // Prints SIMD3<Float>(10.0, 11.0, 5.0)
    
    let group = allocator.extractMembers(memberID: 1, expectedType: simd_float3.self)
    print(group)
    
    // Prints [SIMD3<Float>(102.0, 111.0, 52.0), SIMD3<Float>(1.0, 1.0, 1.0), SIMD3<Float>(5.0, 5.0, 5.0)]
    

    这类似于MTLVertexDescriptor,除了内存是手动访问的,而不是通过[[stage_in]] 属性和传递给片段着色器顶点的每个实例的参数表。您甚至可以扩展分配器以接受带有属性名称的字符串参数,并保存一些映射到成员 ID 的字典。

    【讨论】:

    • 这是一个非常有用的解决方案!谢谢!在您的指导下,我能够有效地访问 MTLBuffer 的每个成员。我想我仍然有点不确定的地方是,当迭代 MTLBuffer 的计数 (0..&lt;count) 时,如果 MTLBuffer 没有特定位置的成员会发生什么。跳过了吗?
    • @Zbadhabit 我假设缓冲区中存储了CMetalStructcount 实例,每个实例在内存中由MemoryLayout&lt;Element&gt;.stride 分隔(“下一个”彼此;同样,数组的 C 结构可能不连续)。如果UnsafeRawMutablePointer(又名void*)以这种方式格式化,尝试将内存绑定到T类型最多会导致未定义的行为,但更有可能会终止程序访问或演员阵容不佳。您必须确保知道缓冲区的布局。
    猜你喜欢
    • 2015-02-26
    • 2017-11-04
    • 1970-01-01
    • 2018-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    相关资源
    最近更新 更多