How to use explicit serialization to get data from metal data to Swift struct

I'm trying to get data from a Metal Mesh Vertex array to a Swift struct.


I know that Swift structs and C struct may not be equal. I also know I could do this with a c briding header but that introduces some other issues.


How do I do this with explicit serialization from the metal data to an array of Swift structs?


It would be helpful to also see the deserialization of the same data.


import PlaygroundSupport
import MetalKit
import simd

struct Vertex {
    var position: vector_float3
    var normal: vector_float3
    var texture: vector_float2
    
    init() {
        position = float3(repeating: 0.0)
        normal = float3(repeating: 0.0)
        texture = float2(repeating: 0.0)
    }
    
    init(pos: vector_float3, nor: vector_float3, text: vector_float2) {
        position = pos
        normal = nor
        texture = text
    }
}

guard let device = MTLCreateSystemDefaultDevice() else {
    fatalError("GPU is not supported")
}

let allocator = MTKMeshBufferAllocator(device: device)

let mesh = MDLMesh.init(boxWithExtent: [0.75, 0.75, 0.75], segments: [1, 1, 1], inwardNormals: false, geometryType: .quads, allocator: allocator)

// Details about the vertex
print("Details about the vertex")
print( mesh.vertexDescriptor )

// Look at the vertex detail
// This doesn't work because, I think, of the way Swift pads the memory in structs.
print("Number of vertex: ", mesh.vertexCount)
let buff = mesh.vertexBuffers[0]

// This gives the wrong result but is nice code
let count = buff.length / MemoryLayout.stride
print("Wrong result!")
print("Space in buffer for vertex: ", count)
let wrongResult = buff.map().bytes.bindMemory(to: Vertex.self, capacity: count)
for i in 0 ..< count {
    print( "Vertex: ", i, wrongResult[i])
}

// This gives the correct result but is really ugly code
print("Correct result!")
let tempResult = buff.map().bytes.bindMemory(to: Float.self, capacity: mesh.vertexCount*8)
var result = Array(repeating: Vertex(), count: mesh.vertexCount)
for i in 0 ..< mesh.vertexCount {
    result[i].position.x = tempResult[i*8 + 0]
    result[i].position.y = tempResult[i*8 + 1]
    result[i].position.z = tempResult[i*8 + 2]
    result[i].normal.x = tempResult[i*8 + 3]
    result[i].normal.y = tempResult[i*8 + 4]
    result[i].normal.z = tempResult[i*8 + 5]
    result[i].texture.x = tempResult[i*8 + 6]
    result[i].texture.y = tempResult[i*8 + 7]
}
for i in 0 ..< mesh.vertexCount {
    print( "Vertex: ", i, result[i])
}

Replies

Two issues:

- Swift has no feature to control layouts of struct.

- `vector_float3` (`SIMD<Float>` in Swift 5) should be aligned to 4-Float boundry, even in C/Metal.


You can try something like this:

typealias RawFloat3 = (x: Float, y: Float, z: Float)
typealias RawFloat2 = (x: Float, y: Float)
struct Vertex {
    var position: RawFloat3 = (0, 0, 0)
    var normal: RawFloat3 = (0, 0, 0)
    var texture: RawFloat2 = (0, 0)
}


(Of course you may need to convert each member to `float3` or `float2`.)


I feel this less ugly, please try.

Hi OOPer,


- Swift has no feature to control layouts of struct.


Completely agree. So how best, in Swift 5, can I explicity define how the data bits poputate the contents of the struct and vice versus from the content of a struct to data bits? I understand this won't happen automagically so will need a function to encode and decode the bits to/from a struct. How does this get expanded to handle an array of these structs?


Eskimo - kind of referenced it in his post here: https://forums.developer.apple.com/message/353390#353390

"I generally run into this issue from the perspective of networking, and there it makes sense to add an explicit serialisation and deserialisation layer."


- `vector_float3` (`SIMD<Float>` in Swift 5) should be aligned to 4-Float boundry, even in C/Metal.


When you run the example included in the question you can see the response from the buffer in the MDLMesh there is no padding. And in the Swift struct there is at least padding on the vector_float3 and I'm not sure about the vector_float2.


I would rather not contort my Swift struct to try and force a memory layout that aligns with C but instead control hown the binary bits get mapped to and from it.


I've got some larger, much more complicated, arrays of structs I need to pass to and from compute kernals. This is a much simpler example of the problem I'm trying to solve.


Ideas?


Thank you,


Andrew

When you run the example included in the question you can see the response from the buffer in the MDLMesh there is no padding.

So what? It does not mean vector_float3 can be mis-aligned. It just means MDLMesh does not contain 2-vector_float3 and a vector_float2, MDLMesh contains a sequence made of 8-Float.


I explicity define how the data bits poputate the contents of the struct and vice versus from the content of a struct to data bits?

Have you tried my struct?