2015-08-31 6 views
5

Ich schreibe eine iOS-Anwendung mit Apples neuem Metal-Framework. Ich habe ein Array von Matrix4-Objekten (siehe Ray Wenderlich's tutorial), die ich über die MTLDevice.newBufferWithLength() -Methode an einen Shader übergeben muss. Das Matrix4-Objekt nutzt Apples GLKit (es enthält ein GLKMatrix4-Objekt).Effizientes Kopieren von Swift Array in Speicherpuffer für iOS Metal

Ich nutze Instanziierung mit den GPU-Anrufen.

Ich werde später in diesem auf eine Struktur ändern, die mehr Daten pro Instanz enthält (über die reine MATRIX4 Objekt.

  1. Wie kann ich das Array effizient kopieren von [MATRIX4] Objekte in diesen Puffer?

  2. gibt es einen besseren Weg, dies zu tun Noch einmal, ich diese erweitern werde eine Struktur mit mehr Daten in der Zukunft verwenden

Unten finden Sie eine Teilmenge von meinem Code ist?.:

let sizeofMatrix4 = sizeof(Float) * Matrix4.numberofElements() 

// This returns an array of [Matrix4] objects. 
let boxArray = createBoxArray(parentModelViewMatrix) 

let sizeOfUniformBuffer = boxArray.count * sizeOfMatrix4 
var uniformBuffer = device.newBufferWithLength(sizeofUniformBuffer, options: .CPUCacheModeDefaultCache) 
let bufferPointer = uniformBuffer?.contents() 

// Ouch - way too slow. How can I optimize? 
for i in 0..<boxArray.count 
{ 
    memcpy(bufferPointer! + (i * sizeOfMatrix4), boxArray[i].raw(), sizeOfMatrix4) 
} 

renderEncoder.setVertexBuffer(uniformBuffer, offset: 0, atIndex: 2) 

Hinweis: Die boxArray [i] .RAW() -Methode, wie dies in dem Objective-C-Code definiert ist:

- (void *)raw { 
    return glkMatrix.m; 
} 

Sie können sehen, ich durch jedes Array-Objekt bin Looping und dann memcpy machen. Ich tat dies, da ich Probleme hatte, das Array als eine zusammenhängende Menge von Speicher zu behandeln.

Danke!

+1

Sie sollten simd.float4x4 verwenden. – Jessy

Antwort

7

Ein Swift Array wird versprochen, zusammenhängender Speicher zu sein, aber Sie müssen sicherstellen, dass es wirklich ein Swift Array ist und nicht heimlich ein NSArray. Wenn Sie ganz sicher sein wollen, verwenden Sie ein ContiguousArray. Dadurch wird zusammenhängender Speicher sichergestellt, selbst wenn die darin enthaltenen Objekte zu ObjC überbrückbar sind. Wenn Sie noch mehr Kontrolle über den Speicher haben möchten, schauen Sie sich ManagedBuffer an.

Mit diesem sollten Sie newBufferWithBytesNoCopy(length:options:deallocator) verwenden, um einen MTL-Puffer um Ihren vorhandenen Speicher zu erstellen.

+0

Rob, danke für die Rückmeldung. Ich habe versucht, das herauszufinden, seit du eine Antwort geschrieben hast, aber ich habe einfach kein Glück. Würde es Ihnen etwas ausmachen, Quellcode bereitzustellen, der veranschaulicht, wie Sie mit einem Array von [Matrix4] -Objekten beginnen, den MTLDevice-Puffer erstellen und dann zu diesem Puffer memcpy? –

+1

Rob, glauben Sie, 'ManagedBuffer' bietet Seiten-ausgerichteten Speicher? Ein flüchtiger Blick auf den Quellcode und die Dokumentation deutet darauf hin, dass dies nur die Ausrichtung der Elemente (durch Beachtung von "MemoryLayout . Ausrichtung"), nicht Seitenausrichtung, erzwingt. Die Ausrichtung auf Seiten ist möglicherweise viel verschwenderischer, und daher würde ich erwarten, dass dies ausdrücklich erwähnt wird. – ldoogy

4

Ich habe dies mit einem Array von Partikeln, die ich an einen Compute-Shader übergeben.

Auf den Punkt gebracht, definiere ich einige Konstanten und eine Handvoll veränderbare Zeigern deklarieren und eine veränderbare Pufferzeiger:

let particleCount: Int = 1048576 
var particlesMemory:UnsafeMutablePointer<Void> = nil 
let alignment:UInt = 0x4000 
let particlesMemoryByteSize:UInt = UInt(1048576) * UInt(sizeof(Particle)) 
var particlesVoidPtr: COpaquePointer! 
var particlesParticlePtr: UnsafeMutablePointer<Particle>! 

var particlesParticleBufferPtr: UnsafeMutableBufferPointer<Particle>! 

Wenn ich die Teilchen aufgebaut, I die Zeiger bevölkern und verwenden posix_memalign() zuzuteilen der Speicher:

posix_memalign(&particlesMemory, alignment, particlesMemoryByteSize) 

    particlesVoidPtr = COpaquePointer(particlesMemory) 
    particlesParticlePtr = UnsafeMutablePointer<Particle>(particlesVoidPtr) 

    particlesParticleBufferPtr = UnsafeMutableBufferPointer(start: particlesParticlePtr, count: particleCount) 

die Schleife, die Teilchen zu bevölkern ist etwas anders - I nun Schleife über den Pufferzeiger:

for index in particlesParticleBufferPtr.startIndex ..< particlesParticleBufferPtr.endIndex 
    { 
     [...] 

     let particle = Particle(positionX: positionX, positionY: positionY, velocityX: velocityX, velocityY: velocityY) 

     particlesParticleBufferPtr[index] = particle 
    } 

Innerhalb des applyShader() -Funktion schaffe ich eine Kopie des Speichers, die sowohl als Eingangs- und Ausgangspuffer verwendet wird:

let particlesBufferNoCopy = device.newBufferWithBytesNoCopy(particlesMemory, length: Int(particlesMemoryByteSize), 
     options: nil, deallocator: nil) 

    commandEncoder.setBuffer(particlesBufferNoCopy, offset: 0, atIndex: 0) 

    commandEncoder.setBuffer(particlesBufferNoCopy, offset: 0, atIndex: 1) 

...und nach der Shader laufen hat, habe ich das Shared Memory (particlesMemory) zurück in den Pufferzeiger:

particlesVoidPtr = COpaquePointer(particlesMemory) 
    particlesParticlePtr = UnsafeMutablePointer(particlesVoidPtr) 

    particlesParticleBufferPtr = UnsafeMutableBufferPointer(start: particlesParticlePtr, count: particleCount) 

Es gibt einen aktuellen Swift 2.0-Version dieses at my GitHub repo here

+1

Können Sie die Unterschiede von Swift 2 beschreiben? –

2

Offensichtlich ist der Punkt des Shared-Memory und MTLDevice.makeBuffer(bytesNoCopy:...) soll redundante Speicherkopien vermeiden. Daher suchen wir idealerweise nach einem Design, das es uns ermöglicht, die Daten einfach zu manipulieren, nachdem sie bereits in das Objekt MTLBuffer geladen wurden.

Nachdem ich dies für eine Weile untersucht habe, habe ich beschlossen, eine semi-generische Lösung zu erstellen, die eine vereinfachte Zuordnung von seitenausgerichtetem Speicher ermöglicht, den Inhalt in diesen Speicher lädt und anschließend Ihre Elemente in diesem Shared manipuliert Speicherblock.

Ich habe eine Swift-Array-Implementierung namens PageAlignedArray erstellt, die der Schnittstelle und Funktionalität des integrierten Swift-Arrays entspricht, aber immer auf Seiten ausgerichtet Speicher, und so kann sehr leicht in eine MTLBuffer gemacht werden. Ich habe auch eine bequeme Methode hinzugefügt, um PageAlignedArray in einen Metallpuffer direkt umzuwandeln.

Natürlich können Sie Ihr Array auch später mutieren und Ihre Updates werden automatisch der GPU zur Verfügung gestellt, dank der Shared-Memory-Architektur. Beachten Sie jedoch, dass Sie Ihr MTLBuffer Objekt immer neu generieren müssen, wenn sich die Länge des Arrays ändert.

Hier ist ein schnelles Codebeispiel:

var alignedArray : PageAlignedContiguousArray<matrix_double4x4> = [matrixTest, matrixTest] 
    alignedArray.append(item) 
    alignedArray.removeFirst() // Behaves just like a built-in array, with all convenience methods 

    // When it's time to generate a Metal buffer: 
    let testMetalBuffer = device?.makeBufferWithPageAlignedArray(alignedArray) 

Das Beispiel verwendet matrix_double4x4, aber das Array sollte für alle Swift Werttypen arbeiten. Bitte beachten Sie, dass das Array, wenn Sie einen Referenztyp verwenden (z. B. jede Art von class), Zeiger auf Ihre Elemente enthält und daher nicht mit Ihrem GPU-Code verwendbar ist.

+1

Brilliant !!! Nur eine Frage - ich habe gerade daran gedacht, ein veränderbares Array mit dem Initialisierer zu erstellen, der einen veränderbaren Pufferzeiger nimmt - hast du darüber nachgedacht, und wenn ja, warum hast du es abgelehnt? –

+1

@DavidH Wie würde das Array wachsen, wenn es auf diese Weise eingerichtet wird? Ich hatte meine eigene Klasse, um die Zuweisung so vorzunehmen, dass das Array nach Bedarf wachsen konnte. – ldoogy

+1

Natürlich hast du recht. Ich dachte an ein veränderbares Array mit fester Größe, aber natürlich konnte ich niemanden daran hindern, zu append zu gehen. Nochmal, super Post! –