在 OpenCL 中,我想使用“共享指数”表示来存储矢量(3D)以进行紧凑存储。通常,如果您存储 3D 浮点向量,您只需存储 3 个单独的浮点值(或正确对齐时为 4 个)。这需要 12 (16) 字节的单精度存储,如果您不需要这种精度,您可以使用“半”精度浮点数并将其缩小到 6 (8) 字节。
当使用半精度和 3 个单独的值时,内存看起来像这样(不考虑对齐):
- x坐标:1位符号,5位指数,10位尾数
- y坐标:1位符号,5位指数,10位尾数
- z坐标:1位符号,5位指数,10位尾数
我想通过使用共享指数将其缩小到 4 个字节,因为 OpenGL 在其内部纹理格式之一(“RGB9_E5”)中使用它。这意味着,绝对最大的分量决定了整数的指数是多少。然后,该指数隐含地用于每个分量。诸如带有隐含“1”的“标准化”存储之类的技巧。在这种情况下,尾数前面不起作用。这样的表示是这样工作的(我们可以调整实际参数,所以这是一个例子):
- x坐标:1位符号,8位尾数
- y坐标:1位符号,8位尾数
- z 坐标:1 位符号,8 位尾数
- 5 位共享指数
我想将它存储在 OpenCLuint
类型(32 位)或等效的东西(例如uchar4
)中。现在的问题是:
float3
我怎样才能尽可能快地从这个表示转换到这个表示?
我的想法是这样的,但我确信有一些“位黑客”技巧使用 IEEE 浮点数的位表示来规避浮点 ALU:
- 用作
uchar4
代表类型。将 x, y, z 尾数存储在 this 的 x, y, z 分量中uchar4
。w 分量被分成 5 个较低有效位(w & 0x1F)
用于共享指数和 3 个较高有效位(w >> 5) & 1
,分别是 x、y(w >> 6) & 1
和(w >> 7) & 1
z 的符号。 - 请注意,指数“偏置”了 16,即存储值 16 表示所表示的数字最高(不包括)1.0,存储值 19 意味着最高(不包括)8.0 的值,依此类推。
float3
可以使用以下代码将此表示“解包”为 a :float3 unpackCompactVector(uchar4 packed) { float exp = (float)(packed.w & 0x1F) - 16.0; float factor = exp2(exp) / 256.0; float x = (float)(packed.x) * factor * (packed.w & 0x20 ? -1.0 : 1.0); float y = (float)(packed.y) * factor * (packed.w & 0x40 ? -1.0 : 1.0); float z = (float)(packed.z) * factor * (packed.w & 0x80 ? -1.0 : 1.0); float3 result = { x, y, z }; return result; }
可以使用以下代码将a “打包”
float3
到此表示中:uchar4 packCompactVector(float3 vec) { float xAbs = abs(vec.x); uchar xSign = vec.x < 0.0 ? 0x20 : 0; float yAbs = abs(vec.y); uchar ySign = vec.y < 0.0 ? 0x40 : 0; float zAbs = abs(vec.z); uchar zSign = vec.z < 0.0 ? 0x80 : 0; float maxAbs = max(max(xAbs, yAbs), zAbs); int exp = floor(log2(maxAbs)) + 1; float factor = exp2(exp); uchar xMant = floor(xAbs / factor * 256); uchar yMant = floor(yAbs / factor * 256); uchar zMant = floor(zAbs / factor * 256); uchar w = ((exp + 16) & 0x1F) + xSign + ySign + zSign; uchar4 result = { xMant, yMant, zMant, w }; return result; }
我已经在 ideone 上在线放置了 C++ 中的等效实现。测试用例显示了从exp = 3
到的转换exp 4
(偏差为 16,这分别编码为 19 和 20),通过编码周围的数字8.0
。
这个实现似乎一见钟情。但:
- 我没有介绍一些极端情况,特别是(指数的)溢出和下溢。
- 我不想使用浮点数学函数,
log2
因为它们很慢。
你能提出一个更好的方法来实现我的目标吗?
请注意,我只需要一个 OpenCL“设备代码”,我不需要在主机程序中的表示之间进行转换。但我添加了C
标签,因为解决方案很可能独立于 OpenCL 语言功能(OpenCL 几乎是 C,它也使用 IEEE 754 浮点数,位操作的工作方式相同,等等)。