Inhalte aus einem generischen MTLBuffer lesen?

Aug 27 2020

In meiner App gibt es eine, MTLBufferdie mithilfe eines generischen Typs instanziiert wird. In einem bestimmten Fall enthält der Puffer Werte, die sich auf Partikel in einer Punktwolke beziehen, und ist als solche definiert.

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

Ich instanziiere mein MTLBufferso;

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

Ich habe jedoch Probleme zu verstehen, wie man den Inhalt des Puffers liest. Außerdem möchte ich ein Element jedes Elements im Puffer in ein Array auf der CPU kopieren, das ich zu einem späteren Zeitpunkt verwenden werde.

Tatsächlich enthält der Puffer eine Sammlung von ParticleUniforms, und ich möchte auf den positionWert jedes Elements zugreifen und diese Position in einem separaten Array speichern.

Alle Beispiele, die ich hier auf Stack Overflow gesehen habe, scheinen zu zeigen MTLBuffer, dass sie eine Sammlung von Floats enthalten, obwohl ich keine gesehen habe, die einen generischen Typ verwenden.

Antworten

1 Leftasanexercise Aug 28 2020 at 20:40

Es scheint, dass das, was Sie erreichen möchten, nur mit C-Strukturen erreicht werden kann, die jedes Element in einem zusammenhängenden Block halten (Arrays von C-Strukturen sind nicht unbedingt zusammenhängend, MemoryLayout<Type>.strideberücksichtigen jedoch mögliche Auffüllungen). Schnelle Struktureigenschaften sind möglicherweise nicht zusammenhängend , sodass die folgende Methode für den Zugriff auf Elementwerte auf praktische Weise nicht funktioniert. Wenn void*Sie mit arbeiten , müssen Sie leider wissen, was die Daten beschreiben, was für Swift-Generika-Typen nicht besonders geeignet ist. Ich werde jedoch eine mögliche Lösung anbieten.

C-Datei:

#ifndef Test_h
#define Test_h

#include <simd/simd.h>

typedef struct {
    vector_float3 testA;
    vector_float3 testB;
} CustomC;

#endif /* Test_h */

Schnelle Datei (Bridging-Header angenommen)

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)]

Dies ist ähnlich wie bei a MTLVertexDescriptor, außer dass auf den Speicher manuell zugegriffen wird und nicht über das [[stage_in]]Attribut und die Argumenttabelle, die an jede Instanz eines Scheitelpunkts des Fragment-Shaders übergeben werden. Sie können den Allokator sogar so erweitern, dass er einen Zeichenfolgenparameter mit dem Namen der Eigenschaft akzeptiert und ein Wörterbuch enthält, das den Mitglieds-IDs zugeordnet ist.