AMD FidelityFX™ Single Pass Downsampler (SPD)
AMD FidelityFX Single Pass Downsampler (SPD) 提供了一个 AMD RDNA™ 架构优化的解决方案,用于生成纹理的最多 12 个 MIP 级别。
对于需要 3D 球形映射的 GPU 端动态生成的数据结构,两种最有用的映射是立方体贴图和八面体贴图。本文探讨了这两种映射的开销。
从立方体贴图采样涉及 VALU 工作和 VMEM 指令。在 GL 中,AMD_gcn_shader 以 cubeFaceIndexAMD() 和 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 coordinatev_cubesc_f32 v4, v2, v3, v0 // v4 = face t coordinatev_cubema_f32 v5, v2, v3, v0 // v5 = 2.0 * major axisv_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.5v_mad_legacy_f32 v5, v1, v2, s0 // v5 = faceS / abs(2.0 * majorAxis) + 1.5v_mad_legacy_f32 v4, v4, v2, s0 // v4 = faceT / abs(2.0 * majorAxis) + 1.5image_sample v[0:3], v[4:7], s[4:11], s[12:15] dmask:0xf1.5 常量设计为使输出面坐标(在上面的示例中为 v4 和 v5)的范围为 {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:256(op: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 opsfloat2 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:1(op: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 opsbool 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 VALUcoord = OctFlipped(coord) ? -coord : coord; // 4 VALUcoord = coord * 0.5 + 0.5; // 2 VALU在第一次抓取成本之后,对八面体贴图进行偏移纹理单元抓取只需 8 个 VALU 操作。