Inhalte aus einem generischen MTLBuffer lesen?
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
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.