用 16 位浮点数填充 MTLBuffer

Populating MTLBuffer with 16-bit Floats

我正在用 float2 向量填充 MTLBuffer。正在创建和填充缓冲区,如下所示:

struct Particle {
   var position: float2
   ...
}

let particleCount = 100000
let bufferSize = MemoryLayout<Particle>.stride * particleCount
particleBuffer = device.makeBuffer(length: bufferSize)!

var pointer = particleBuffer.contents().bindMemory(to: Particle.self, capacity: particleCount)
pointer = pointer.advanced(by: currentParticles)
pointer.pointee.position = [x, y]

在我的 Metal 文件中,缓冲区的访问方式如下:

struct Particle {
   float2 position;
   ...
};

kernel void compute(device Particle *particles [[buffer(0)]], … ) 

我需要在我的 Metal 计算内核中使用半精度浮点数。在 Metal 方面,它就像为数据类型指定 half2 一样简单。

在 CPU 方面,用半精度浮点数填充缓冲区的最佳方法是什么?

Half-precision 在 Swift 中浮动非常尴尬,因为还没有 Float16 类型(虽然 one has been proposed)并且支持非标准的 __fp16 类型Swift 也不完全支持 Clang。

但是,通过 type-punning 的魔力和桥接 header,您也许能够拼凑出一个可行的解决方案。

基本方法是这样的:在Objective-C header 中,声明一个具有两个uint16_t 成员的half2 类型。这些将是我们的存储类型。还要声明一个函数,它接受一个浮点数并将其写入就好像它是一个 __fp16 到一个 pointer-to-uint16_t:

typedef struct {
    uint16_t x, y;
} half2;

static void storeAsF16(float value, uint16_t *_Nonnull pointer) { *(__fp16 *)pointer = value; }

回到 Swift,您可以声明一个类型别名并在您的粒子结构定义中使用它:

typealias Half2 = half2

struct Particle {
    var position: Half2
}

(这里我将 lower-case 类型别名化为 Swift 其他名称;如果您可以跳过此步骤,只需将 Obj-C 类型命名为 Half2更喜欢)。

您需要将缓冲区绑定到您的半向量类型,而不是绑定到粒子类型:

var pointer = particleBuffer.contents().bindMemory(to: Half2.self, capacity: particleCount)

当我们使用效用函数存储一个浮点数时,相应半值的位模式被写入 UInt16:

var x: UInt16 = 0
var y: UInt16 = 0
storeAsF16(1.0, &x) // 0x3c00
storeAsF16(0.5, &y) // 0x3800

现在这对变量中有correctly-formated个半值,我们可以将它们写入缓冲区:

pointer.pointee = Half2(x: x, y: y)

请注意,此方法既不便携也不安全,尤其是因为 Swift 不对结构成员布局做出任何保证。可能还有其他不那么麻烦的方法;这就是过去对我有用的方法。