如何提高浮动lerp功能的速度?

时间:2016-08-07 02:07:40

标签: c++ optimization graphics simd

最近我正在编写一个软光栅渲染器,但它的速度真的很慢。通过性能测试,我发现float lerp函数是瓶颈。如何提高这个功能的速度?用simd?有什么想法吗?

inline float MathUtil::Lerp(float x1, float x2, float t)
{
    return x1 + (x2 - x1)*t;
}

//lerp vector
ZCVector MathUtil::Lerp(const ZCVector& v1, const ZCVector& v2, float t)
{
    return ZCVector(
        Lerp(v1.x, v2.x, t),
        Lerp(v1.y, v2.y, t),
        Lerp(v1.z, v2.z, t),
        v1.w
    );
}

//lerp ZCFLOAT2
ZCFLOAT2 MathUtil::Lerp(const ZCFLOAT2& v1, const ZCFLOAT2& v2, float t)
{
    return ZCFLOAT2(
        Lerp(v1.u, v2.u, t),
        Lerp(v1.v, v2.v, t)
    );
}

//lerp ZCFLOAT3
ZCFLOAT3 MathUtil::Lerp(const ZCFLOAT3& v1, const ZCFLOAT3& v2, float t)
{
    return ZCFLOAT3(
        Lerp(v1.x, v2.x, t),
        Lerp(v1.y, v2.y, t),
        Lerp(v1.z, v2.z, t)
    );
}

//lerp VertexOut
VertexOut MathUtil::Lerp(const VertexOut& v1, const VertexOut& v2, float t)
{
    return VertexOut(
        Lerp(v1.posTrans, v2.posTrans, t),
        Lerp(v1.posH, v2.posH, t),
        Lerp(v1.tex, v2.tex, t),
        Lerp(v1.normal, v2.normal, t),
        Lerp(v1.color, v2.color, t),
        Lerp(v1.oneDivZ, v2.oneDivZ, t)
    );
}

VertexOut的结构:

class VertexOut
{
public:

    ZCVector posTrans;

    ZCVector posH;

    ZCFLOAT2 tex;

    ZCVector normal;

    ZCFLOAT3 color;

    float oneDivZ;
}

填充三角形的scanlinefill函数,每个顶点都需要使用lerp函数,所以它会被调用很多次。

void Tiny3DDeviceContext::ScanlineFill(const VertexOut& left, const VertexOut& right,  int yIndex)
{
    float dx = right.posH.x - left.posH.x;

    for (float x = left.posH.x; x <= right.posH.x; x += 0.5f)
    {
        int xIndex = static_cast<int>(x + .5f);
        if(xIndex >= 0 && xIndex < m_pDevice->GetClientWidth())
        {

            float lerpFactor = 0;
            if (dx != 0)
            {
                lerpFactor = (x - left.posH.x) / dx;
            }


            float oneDivZ = MathUtil::Lerp(left.oneDivZ, right.oneDivZ, lerpFactor);
            if (oneDivZ >= m_pDevice->GetZ(xIndex,yIndex))
            {
                m_pDevice->SetZ(xIndex, yIndex, oneDivZ);
                //lerp get vertex
                VertexOut out = MathUtil::Lerp(left, right, lerpFactor);
                out.posH.y = yIndex;

                m_pDevice->DrawPixel(xIndex, yIndex, m_pShader->PS(out));
            }           
        }   
    }
}

1 个答案:

答案 0 :(得分:1)

此循环结构可能会根据需要运行lerp两次:

for (float x = left.posH.x; x <= right.posH.x; x += 0.5f) {
      int xIndex = static_cast<int>(x + .5f);
      ...
}

相反,(更准确地说),通过递增整数xIndex来循环,并为每个float x计算正确的xIndex

这可能会自动向量化,但您必须检查编译器输出以查看发生的情况。希望您用out.posH.y = yIndex;覆盖的Lerp会因为丢弃结果而被优化掉。如果没有,你可能会从制作一个不能执行该Lerp的包装函数中获得加速。

通过使用Struct-of-Arrays方法而不是AoS方法,可以使结构连续的所有内容保持对SIMD更友好。但是,你以相同的方式使用多个元素,因此它可能会使用两个标量和一个向量Lerp进行自动向量化。

有关SIMD内容的一些指南,请参阅代码Wiki,包括指向此very nice beginner / intermediate set of slides的链接。

也许你可以改变其他的东西,尤其是。 对代码进行更大规模的重组以减少整体工作。与使用SIMD有效地应用现代CPU的蛮力相比,这种优化通常可以为您提供更大的加速。

同时做两件事来加倍加速是真正使事情变得快速的原因。

缓存未命中和内存带宽瓶颈通常是一个很大的因素,因此优化访问模式可能会产生很大的不同。

如果您想了解更多低级详细信息,请参阅Agner Fog's optimization guide。他有一个C ++优化指南,但大多数好东西都是关于x86 asm。 (另请参阅标签wiki)。但请记住,在寻找高级优化之后,这种低级优化只是一个好主意