如何在 Metal Shader Language 中将浮点指针转换为 uint8_t 指针?

How do I cast a float pointer to a uint8_t pointer in Metal Shader Language?

我正在尝试在 kernel void 函数内用金属着色语言编写以下 C 代码:

float f = 2.1;
uint8_t byteArray[sizeof(f)];
for (int a = 0; a < sizeof(f); a++) {
    byteArray[a] = ((uint8_t*)&f)[a];
}

代码应该获取浮点值的字节数组。当我尝试用 Metal Shader Language 编写相同的代码时,出现以下构建时错误:

Pointer type must have explicit address space qualifier

我了解 Metal 限制指针的使用,并要求为内核函数的参数提供显式地址 space 属性,例如 deviceconstant 等。我将如何在 Metal Shading Language 中执行类型转换?

不知道具体的 metal,但在普通的 C 中,您希望将 fbyteArray 放在 union[=37= 中]

下面是一些示例代码:

#include <stdio.h>
#include <stdint.h>

union float_byte {
    float f;
    uint8_t byteArray[sizeof(float)];
};

union float_byte u;

void
dotest(float f)
{

    u.f = f;

    printf("%.6f",u.f);
    for (int idx = 0;  idx < sizeof(u.byteArray);  ++idx)
        printf(" %2.2X",u.byteArray[idx]);
    printf("\n");
}

int
main(void)
{

    dotest(2.1);
    dotest(7.6328);

    return 0;
}

程序输出如下:

2.100000 66 66 06 40
7.632800 E6 3F F4 40

更新:

even today, isn't it still technically UB to read a union member that wasn't the last one written to? Although, it sounds like this is widely supported now with implementation-defined behavior. One of many related questions: whosebug.com/questions/2310483/… – yano

不,由于多种原因,不是 UB。

可能是“实现定义”的行为,但因为CPU/processor字节顺序重新。内存中 [32 位] float 的格式,如果我们希望解释 byteArray

中的字节

但是,AFAICT,这不会影响 OP 的问题,因为重点只是获取一个字节缓冲区 [用于数据的 binary/serialization?]。

如果要解释数据(例如设计 DIY F.P。S/W 实现),则必须知道 float 的格式和字节顺序。这是 [可能] IEEE 784 格式和处理器字节顺序。

但是,使用union只是得到一个字节指针,没有问题。 不是甚至是“实现定义”的行为。

这与:

差不多
float my_f = f;
uint8_t *byteArray = (uint8_t *) &my_f;

而且,它之所以有效,是因为它必须有效。

此外,union [此处使用的] 是一个常见的习语,可以追溯到 1970 年代,因此 得到支持。

此外,它 有效 [因为它必须按设计]。

如果我们有:

void
funcA(float f)
{

    u.f = f;
}

void
funcB(void)
{
    for (int idx = 0;  idx < sizeof(u.byteArray);  ++idx)
        printf(" %2.2X",u.byteArray[idx]);
    printf("\n");
}

为了好玩,假设 funcAfuncB 位于不同的 .c 文件中。当调用 funcA 时,它会 [以可预测的方式] 更改 u 的内存。

u.

的布局不会发生任何变化

然后,我们调用funcBbyteArray 中字节的布局将是 same/predictable 数据。

这与将 float 作为 binary data:

写入文件类似并且工作方式相同
#include <unistd.h>

int fd;

void
writefloat(float f)
{
    float my_f = f;

    write(fd,&my_f,sizeof(float));
}

void
writefloat2(float f)
{

    write(fd,&f,sizeof(float));
}

void
writefloat3(float f)
{

    write(fd,&f,sizeof(f));
}

如果我们使用 uint32_t 而不是 float,也许会更容易看出这一点。我们可以做字节序测试。 [注意:这是粗略的,并没有考虑像 pdp11/vax] 这样的古怪字节顺序:

#include <stdio.h>
#include <stdint.h>

union uint_byte {
    uint32_t i;
    uint8_t b[sizeof(uint32_t)];
};

union uint_byte u;

int
main(void)
{

    u.i = 0x01020304;
    if (u.b[0] == 0x04)
        printf("cpu is little-endian\n");
    else
        printf("cpu is big-endian\n");

    return 0;
}

在 c++ (20) 中你有 bit_cast。 在 'Notes' 部分,您将看到,它只是作为对 memcpy.

的调用实现的

所以,为了避免'undefined behaviour'和安全起见(除了前面所有的,也是最快的),就这样做:

memcpy(byteArray, &f, sizeof f); //size of byteArray must be at least: sizeof f

这不是 C++,所以你必须用另一种方式来做。

您不能在 MSL 中正确使用联合或 reinterpret_cast

相反,有一个 4 uint8_t 向量的类型,它是 uchar4

为了做你想做的事,你会这样写。

float f = 2.1;
uchar4 ff = as_type<uchar4>(f);

请参阅 MSL spec,第 2.19 节类型转换和重新解释数据。

至于地址 space 限定符:

金属着色语言中的每个指针都有一个地址限定符。它可以是 deviceconstantthreadthreadgroup 等。请参阅规范中的第 4 章。

这些地址限定符来自于存在不同内存space的事实。您可以在上面的文档中阅读更多关于它们的信息。

由于 f 是局部变量,它在 thread space 中,因此您的 uint8_t 指针将具有 thread uint8_t* 类型,而不仅仅是 uint8_t.

所以你可能会这样做:

float f = 2.1;
thread uint8_t* ff = (thread uint8_t*)&f;

但我认为 as_type 方法更清晰。