Shader的函数参数性能
Shader's function parameters performance
我正在尝试了解如何在着色器语言中实现传递参数。
看了好几篇文章和文档,还是有一些疑惑。特别是我试图理解 C++
函数调用的差异,特别强调性能。
HLSL,Cg
和 GLSL
之间略有不同,但我想下划线的实现非常相似。
到目前为止我所了解的内容:
- 除非另有说明,函数参数总是按值传递(即使对于矩阵也是如此吗?)
- 在此上下文中按值传递与
C++
的含义不同。不支持递归,所以不使用堆栈,大多数函数都是内联的,参数直接放入寄存器。
- 函数通常默认内联 (
HLSL
) 或者至少内联关键字始终受到编译器的尊重 (Cg
)
以上考虑是否正确?
现在2个具体问题:
传递矩阵作为函数参数
内联 float4 DoSomething(在 Mat4x4 mat 中,在 float3 vec 中)
{
...
}
考虑到上面的函数,在 C++
中这会很糟糕,使用引用肯定会更好:const Mat4x4&
.
着色器呢?这是一个不好的方法吗?我读到,例如 inout
限定符可用于通过引用传递矩阵,但实际上它暗示矩阵被调用函数修改..
数字(和参数类型)有什么含义吗?例如,是更好地使用具有有限参数集的函数?还是避免传递矩阵?
inout
修饰符是提高性能的有效方法吗?如果是这样,有人知道典型的编译器是如何实现的吗?
HLSL
和GLSL
有区别吗?
有人对此有提示吗?
当您考虑使用 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
个分量的向量。
如果参数通过引用传递,mat1
和 Foo()
内的 mat2
将别名相同的矩阵。这意味着对 mat2
的赋值也会更改 mat1
的值,结果是一个包含所有 1.0
分量的向量。哪个是错误的。
这当然是一个非常人为的例子,但优化必须有选择性才能在所有情况下正常工作。
我正在尝试了解如何在着色器语言中实现传递参数。
看了好几篇文章和文档,还是有一些疑惑。特别是我试图理解 C++
函数调用的差异,特别强调性能。
HLSL,Cg
和 GLSL
之间略有不同,但我想下划线的实现非常相似。
到目前为止我所了解的内容:
- 除非另有说明,函数参数总是按值传递(即使对于矩阵也是如此吗?)
- 在此上下文中按值传递与
C++
的含义不同。不支持递归,所以不使用堆栈,大多数函数都是内联的,参数直接放入寄存器。 - 函数通常默认内联 (
HLSL
) 或者至少内联关键字始终受到编译器的尊重 (Cg
)
以上考虑是否正确?
现在2个具体问题:
传递矩阵作为函数参数
内联 float4 DoSomething(在 Mat4x4 mat 中,在 float3 vec 中) { ... }
考虑到上面的函数,在 C++
中这会很糟糕,使用引用肯定会更好:const Mat4x4&
.
着色器呢?这是一个不好的方法吗?我读到,例如 inout
限定符可用于通过引用传递矩阵,但实际上它暗示矩阵被调用函数修改..
数字(和参数类型)有什么含义吗?例如,是更好地使用具有有限参数集的函数?还是避免传递矩阵?
inout
修饰符是提高性能的有效方法吗?如果是这样,有人知道典型的编译器是如何实现的吗?HLSL
和GLSL
有区别吗? 有人对此有提示吗?
当您考虑使用 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
个分量的向量。
如果参数通过引用传递,mat1
和 Foo()
内的 mat2
将别名相同的矩阵。这意味着对 mat2
的赋值也会更改 mat1
的值,结果是一个包含所有 1.0
分量的向量。哪个是错误的。
这当然是一个非常人为的例子,但优化必须有选择性才能在所有情况下正常工作。