如何在 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 属性,例如 device
、constant
等。我将如何在 Metal Shading Language 中执行类型转换?
不知道具体的 metal
,但在普通的 C 中,您希望将 f
和 byteArray
放在 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");
}
为了好玩,假设 funcA
和 funcB
位于不同的 .c
文件中。当调用 funcA
时,它会 [以可预测的方式] 更改 u
的内存。
u
.
的布局不会发生任何变化
然后,我们调用funcB
。 byteArray
中字节的布局将是 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 限定符:
金属着色语言中的每个指针都有一个地址限定符。它可以是 device
、constant
、thread
、threadgroup
等。请参阅规范中的第 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
方法更清晰。
我正在尝试在 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 属性,例如 device
、constant
等。我将如何在 Metal Shading Language 中执行类型转换?
不知道具体的 metal
,但在普通的 C 中,您希望将 f
和 byteArray
放在 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");
}
为了好玩,假设 funcA
和 funcB
位于不同的 .c
文件中。当调用 funcA
时,它会 [以可预测的方式] 更改 u
的内存。
u
.
然后,我们调用funcB
。 byteArray
中字节的布局将是 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 限定符:
金属着色语言中的每个指针都有一个地址限定符。它可以是 device
、constant
、thread
、threadgroup
等。请参阅规范中的第 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
方法更清晰。