Shader的函数参数性能

Shader's function parameters performance

我正在尝试了解如何在着色器语言中实现传递参数。

看了好几篇文章和文档,还是有一些疑惑。特别是我试图理解 C++ 函数调用的差异,特别强调性能。

HLSL,CgGLSL 之间略有不同,但我想下划线的实现非常相似。

到目前为止我所了解的内容:

以上考虑是否正确?

现在2个具体问题:

  1. 传递矩阵作为函数参数

    内联 float4 DoSomething(在 Mat4x4 mat 中,在 float3 vec 中) { ... }

考虑到上面的函数,在 C++ 中这会很糟糕,使用引用肯定会更好:const Mat4x4&.

着色器呢?这是一个不好的方法吗?我读到,例如 inout 限定符可用于通过引用传递矩阵,但实际上它暗示矩阵被调用函数修改..

  1. 数字(和参数类型)有什么含义吗?例如,是更好地使用具有有限参数集的函数?还是避免传递矩阵? inout 修饰符是提高性能的有效方法吗?如果是这样,有人知道典型的编译器是如何实现的吗?

  2. HLSLGLSL有区别吗? 有人对此有提示吗?

当您考虑使用 inout 限定参数时,您的第一个要点不起作用。

真正的问题是你在函数内部对参数做了什么,如果你修改了一个用in限定的参数,那么它就不能“通过引用传递”,必须制作一个副本。在现代硬件上,这可能不是什么大问题,但 Shader Model 2.0 在临时寄存器的数量方面非常有限,当 GLSL 和 Cg 首次出现时,我 运行 不止一次地陷入这类问题。

作为参考,请考虑以下 GLSL 代码:

vec4 DoSomething (mat4 mat, vec3 vec)
{
  // Pretty straight forward, no temporary registers are required to pass arguments.
  return vec4 (mat [0] + vec4 (vec, 0.0));
}

vec4 DoSomethingCopy (mat4 mat, vec3 vec)
{
  mat [0][0] = 0.0; // This requires the compiler to make a local copy of mat
  return vec4 (mat [0] + vec4 (vec, 0.0));
}

vec4 DoSomethingInOut (inout mat4 mat, in vec3 vec)
{
  mat [0][0] = 0.0; // No copy required, but the original mat is modified
  return vec4 (mat [0] + vec4 (vec, 0.0));
}

我无法真正评论性能,我唯一糟糕的经历与在旧 GPU 上达到实际硬件限制有关。当然,您应该假设任何时候必须复制某些内容都会对性能产生负面影响。

所有着色器函数都是内联的(禁止递归函数)。 reference/pointer 的概念在这里也是无效的。生成某些代码的唯一情况是在输入参数上写入时。但是,如果不再使用原始寄存器,编译器可能会使用相同的寄存器,并且不需要复制(mov 操作)。

底线:函数调用是免费的。

根据规范,值总是被复制。 in 参数在调用时复制,out 参数在 return 时复制,inout 参数在调用和 return 时复制。

规范语言(GLSL 4.50,第 6.1.1 节 "Function Calling Conventions"):

All arguments are evaluated at call time, exactly once, in order, from left to right. Evaluation of an in parameter results in a value that is copied to the formal parameter. Evaluation of an out parameter results in an l-value that is used to copy out a value when the function returns. Evaluation of an inout parameter results in both a value and an l-value; the value is copied to the formal parameter at call time and the lvalue is used to copy out a value when the function returns.

一个实现当然可以自由地优化它想要的任何东西,只要结果与记录的行为相同即可。但我认为您不能期望它以任何特定方式工作。

例如,通过引用传递所有inout参数不会被保存。说如果你有这个代码:

vec4 Foo(inout mat4 mat1, inout mat4 mat2) {
    mat1 = mat4(0.0);
    mat2 = mat4(1.0);
    return mat1 * vec4(1.0);
}

mat4 myMat;
vec4 res = Foo(myMat, myMat);

正确的结果是包含所有 0.0 个分量的向量。 如果参数通过引用传递,mat1Foo() 内的 mat2 将别名相同的矩阵。这意味着对 mat2 的赋值也会更改 mat1 的值,结果是一个包含所有 1.0 分量的向量。哪个是错误的。

这当然是一个非常人为的例子,但优化必须有选择性才能在所有情况下正常工作。