从立方体和八面体抓取数据

首次发布时间:
Timothy Lottes's avatar
Timothy Lottes

对于需要 3D 球形映射的 GPU 端动态生成的数据结构,两种最有用的映射是立方体贴图和八面体贴图。本文探讨了这两种映射的开销。

立方体贴图

从立方体贴图采样涉及 VALU 工作和 VMEM 指令。在 GL 中,AMD_gcn_shadercubeFaceIndexAMD()cubeFaceCoordAMD() 的形式提供了对这些通常隐藏的 VALU 指令的访问。例如,在向表示立方体面的分层图像进行图像存储时,这些指令可能很有用。下面分解一个简单的 HLSL 着色器可提供 VALU 工作详情。

TextureCube t; SamplerState s;
float4 main(float3 p : TEXCOORD) : SV_Target \{ return t.Sample(s, p); }

其被反汇编为以下 VMEM 和 VALU 指令。

v_cubetc_f32 v1, v2, v3, v0 // v1 = face s coordinate
v_cubesc_f32 v4, v2, v3, v0 // v4 = face t coordinate
v_cubema_f32 v5, v2, v3, v0 // v5 = 2.0 * major axis
v_cubeid_f32 v6, v2, v3, v0 // v6 = face index (0 to 5)
v_rcp_f32 v2, abs(v5) // v2 = 1.0 / abs(2.0 * majorAxis)
s_mov_b32 s0, 0x3fc00000 // s0 = 1.5
v_mad_legacy_f32 v5, v1, v2, s0 // v5 = faceS / abs(2.0 * majorAxis) + 1.5
v_mad_legacy_f32 v4, v4, v2, s0 // v4 = faceT / abs(2.0 * majorAxis) + 1.5
image_sample v[0:3], v[4:7], s[4:11], s[12:15] dmask:0xf

1.5 常量设计为使输出面坐标(在上面的示例中为 v4v5)的范围为 {1.0 <= x < 2.0},这在位编码方面比 {0.0 <= x < 1.0} 具有优势,因为整个输出范围内的上层尾数位是恒定的。

总 VALU 开销为 10 个操作(v_rcp_f32 计算为 4 个操作)。在估算着色器成本时,通常有用 VALU 指令(op)、带宽字节(byte)和 32 位每像素 2D 纹理获取 VMEM 指令(tex)的比率来考虑 GPU 的 op:byte:tex。Fury Nano 的单位为千兆/秒的数字为 4096:512:256op:byte:tex),这可以简化为以下比率 16:2:1。请注意 flop = op * 2,因为一个 FMA 或 MAD 是 2 个浮点运算。

可以从 Wikipedia 获取其他 AMD GPU 的提取比率。立方体贴图抓取的 10 个 VALU 操作开销可能占 VMEM 抓取指令期间 VALU 容量的 62.5% 左右(假设缓存命中,实际结果会有所不同)。

八面体贴图

立方体贴图非常适合过滤查找,但在点采样和手动过滤时有一个缺点:通过 2D 纹理单元偏移来稳健地采样纹理单元邻域非常复杂。

一种替代方法是使用八面体映射,如 Krzysztof Narkowicz 的八面体法向量编码博客文章和其他文章中所述。八面体的八个面被展平并展开成一个 2D 方形。从非归一化的 {x,y,z} 坐标到 {-1 到 1} 范围内的归一化 {x,y} 坐标的八面体映射可以如下完成。

// 2 temp/return VGPRs
// 2 temp SGPRs (one bool)
// 17 VALU ops
float2 Oct3To2(float3 n) \{
float tx,ty;
bool neg;
// project into 2D
tx = abs(n.x) + abs(n.y);
tx = tx + abs(n.z);
tx = rcp(tx); // counts for 4 VALU ops
n.x = n.x * tx;
n.y = n.y * tx;
// unfold if on other half in Z
// n.xy range from \{-1.0 to 1.0} to output range \{0.0 to 1.0}
tx = 1.0 - abs(n.y);
neg = n.x < 0.0;
tx = neg ? -tx : tx;
ty = 1.0 - abs(n.x);
neg = n.y < 0.0;
ty = neg ? -ty : ty;
neg = n.z <= 0.0;
n.x = neg ? tx : n.x;
n.y = neg ? ty : n.y;
return n.xy; }

上面的着色器代码是 1:1 映射到输出反汇编的。它需要额外的 {-1 到 1} 到 {0 到 1} 的缩放和偏移,2 个 VALU 操作,以获得总共 19 个 VALU 操作的抓取。对于单个点采样纹理抓取,这使得八面体贴图的成本几乎是立方体贴图的 2 倍。

另外请注意,假设缓存命中,回到 16:1op:tex)比率,理论上生成八面体贴图的坐标比从纹理抓取更昂贵。排除纹理单元偏移超出纹理边缘的情况,上面的 Oct3To2() * 0.5 + 0.5 纹理坐标将与 2D 纹理单元偏移一起正常工作。

不幸的是,GPU 没有八面体环绕模式,但可以使用镜像重复环绕模式和一些 VALU 工作来模拟。

// Check for offset over texture edge,
// 1 temp VGPR
// 2 temp/return SGPRs (one bool)
// 2 VALU ops
bool OctFlipped(float2 r) \{
float t = max(abs(r.x), abs(r.y));
return t >= 1.0; }
// Example of computing mirrored repeat sampling
// of an octahedron map with a small texel offset.
// Note this is not designed to solve the double wrap case.
// The "base" is as computed by Oct3To2() above.
float2 coord = base + float2(-2.0, 2.0); // 2 VALU
coord = OctFlipped(coord) ? -coord : coord; // 4 VALU
coord = coord * 0.5 + 0.5; // 2 VALU

在第一次抓取成本之后,对八面体贴图进行偏移纹理单元抓取只需 8 个 VALU 操作。

Timothy Lottes's avatar

Timothy Lottes

Timothy Lottes 是 AMD 图形性能研发团队的成员。提供第三方网站链接仅为方便用户,除非明确说明,AMD 对此类链接网站的内容概不负责,且不暗示任何认可。

相关视频

© . This site is unofficial and not affiliated with AMD.