Search code examples
iosswiftmetalsimd-library

Alignment of simd_packed vector in Swift (vs Metal Shader language)


I have trouble understanding something about simd_packed vectors in the simd module in Swift. I use the example of float4, I hope someone can help.

My understanding is that simd_float4 is a typealias of SIMD4< Float>, and MemoryLayout<SIMD4< Float>>.alignment = 16 (bytes), hence MemoryLayout<simd_float4>.alignment = 16. Makes sense.

But the following I do not understand: simd_packed_float4 is also a typealias of SIMD4<Float>. And so MemoryLayout<simd_packed_float4>.alignment = 16.

What is the point of the "packed" in simd_packed_float4, then? Where is the "relaxed alignment" that the documentation talks about?

In the Metal Shader Language Specification (Version 2.4) ( https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf) in Table 2.4 (p.28), it says the alignment of packed_float4 is 4 (which is also the alignment of the scalar type, float), so this IS a "relaxed alignment" (as compared to the 16). That makes sense on its own, but how do I reconcile this to the above (simd_packed_float4 is typealias of SIMD4<Float> and MemoryLayout<simd_packed_float4> = 16)?


Solution

  • I actually think it's impossible to achieve relaxed alignment like this with a packed type in Swift. I think Swift compiler just can't bring the alignment attributes to actual Swift interface.

    I think this makes simd_packed_float4 useless in Swift.

    I have made a playground to check this, and using it as it's intended doesn't work.

    import simd
    
    MemoryLayout<simd_float4>.stride
    MemoryLayout<simd_packed_float4>.alignment
    
    let capacity = 8
    let buffer = UnsafeMutableBufferPointer<Float>.allocate(capacity: capacity)
    
    for i in 0..<capacity {
        buffer[i] = Float(i)
    }
    
    let rawBuffer = UnsafeMutableRawBufferPointer.init(buffer)
    
    let readAligned = rawBuffer.load(fromByteOffset: MemoryLayout<Float>.stride * 4, as: simd_packed_float4.self)
    
    print(readAligned)
    
    let readUnaligned = rawBuffer.load(fromByteOffset: MemoryLayout<Float>.stride * 2, as: simd_packed_float4.self)
    
    print(readUnaligned)
    

    Which will output

    SIMD4<Float>(4.0, 5.0, 6.0, 7.0)
    Swift/UnsafeRawPointer.swift:900: Fatal error: load from misaligned raw pointer
    

    If you do need to load or put unaligned simd_float4 vectors into buffers, I would suggest just making an extension that does this component-wise, so all the alignments work out, kinda like this

    extension UnsafeMutableRawBufferPointer {
        func loadFloat4(fromByteOffset offset: Int) -> simd_float4 {
            let x = rawBuffer.load(fromByteOffset: offset + MemoryLayout<Float>.stride * 0, as: Float.self)
            let y = rawBuffer.load(fromByteOffset: offset + MemoryLayout<Float>.stride * 1, as: Float.self)
            let z = rawBuffer.load(fromByteOffset: offset + MemoryLayout<Float>.stride * 2, as: Float.self)
            let w = rawBuffer.load(fromByteOffset: offset + MemoryLayout<Float>.stride * 3, as: Float.self)
    
            return simd_float4(x, y, z, w)
        }
    }
    
    let readUnaligned2 = rawBuffer.loadFloat4(fromByteOffset: MemoryLayout<Float>.stride * 2)
    print(readUnaligned2)
    

    Or you can even make it generic