【翻译】Metal 着色语言规范--算子

这份文档介绍了 Metal 统一图形和计算语言。Metal 是一种基于 C++ 的编程语言,开发者可以用它来编写在 GPU 上执行的图形和通用数据并行计算代码。这是第三章:算子。

August 29, 2017 -
ios metal 翻译

这份文档翻译自 Metal 的官方文档:Metal 着色语言规范(Metal Shading Language Specification)

Metal 着色语言规范

版本 2.0

3 算子

这一章列出并解释 Metal 算子。

3.1 常量和矢量算子

  1. 算术算子,加(+),减(-),乘(*)和除(/),常量和矢量上的操作,整数和浮点数据类型。所有算术运算返回的结果都是与运算数类型转换之后的结果相同的内置类型(整数或浮点)。转换之后,以下情况是合法的:
    • 两个运算数都是常量。在这种情况,可以应用算子,结果是一个常量。
    • 一个运算数是常量,另一个是矢量。在这种情况,常量被转换为矢量算子使用的元素类型。常量类型被扩展成与矢量运算数有相同数量分量的矢量。在逐个分量上执行运算,结果是一个同样大小的矢量。
    • 两个运算数是同样大小的矢量。在这种情况,在逐个分量上执行运算,结果是一个同样大小的矢量。 整数类型上除法的值位于整数类型可表示的最大和最小值范围以外,如有符号整数类型的 TYPE_MIN/-1 或除以 0,不会导致错误,但结果是一个未指定的值。浮点类型除以 0 的结果是正负无穷值或 NaN,如 IEEE-754 标准所规定。(关于浮点运算数值进度的详情,见 7 节。)
  2. 算子模除(%)可以运算在常量和矢量整数数据类型上。所有算术运算返回的结果都是与运算数类型转换之后的结果相同的内置类型。以下情况是合法的:
    • 两个运算数都是常量。在这种情况,可以应用算子,结果是一个常量。
    • 一个运算数是常量,另一个是矢量。在这种情况,常量被转换为矢量算子使用的元素类型。常量类型被扩展成与矢量运算数有相同数量分量的矢量。在逐个分量上执行运算,结果是一个同样大小的矢量。
    • 两个运算数是同样大小的矢量。在这种情况,在逐个分量上执行运算,结果是一个同样大小的矢量。 对于第二个运算数是 0 的任意分量计算,其结果值都是不明确的的,而其它使用非零运算的分量的结果任然是明确的。如果运算数都是非负,余数是非负。如果其中一个或两个运算数都是负的,则结果是未定义的。
  3. 算术一元算子(+ 和 -)运算在标量和矢量,整数和浮点类型上。
  4. 后置和前置的递增和递减算子(– 和 ++)运算在标量和矢量整数类型上。所有一元算子工作在运算数的逐个分量上。其结果与运算数的类型相同。对于后置和前置的递增和递减,表达式必须是可以分配给(L-value)的表达式。前置的递增和递减在它们运算的表达式的内容上加或减 1,前置的递增或递减表达式的结果是修改后的值。后置的递增和递减表达式在它们运算的表达式的内容上加或减 1,但是结果表达式在执行后置递增或后置递减之前具有表达式的值。
  5. 关联运算大于(>)、小于(<)、大于或等于(>=)和小于或等于(<=)运算在标量和矢量,整数和浮点类型上。结果是一个 Boolean 型的标量和矢量。在运算数类型转换之后,以下情况是合法的:
    • 两个运算数都是标量。在这种情况下,运算被执行,结果是一个 bool。
    • 一个运算数是标量,另一个是矢量。在这种情况下,标量被转换成矢量运算数所使用的元素类型。标量类型被扩展成与矢量运算数有着相同分量数量的矢量。运算在逐个分量上执行,结果是一个 Boolean 矢量。
    • 两个运算数是同样类型的矢量。在这种情况下,运算在逐个分量上执行,结果是一个 Boolean 矢量。
      如果某个参数是 NaN,关联运算始终返回 false。
  6. 等式算子,等于(==)和不等于(!=),运算在标量和矢量上,整数和浮点类型上。全部等式算子的结果是一个 Boolean(bool 类型)的标量或矢量。在运算数类型转换之后,以下情况是合法的:
    • 两个运算数都是标量。在这种情况下,运算被执行,结果是一个 bool。
    • 一个运算数是标量,另一个是矢量。在这种情况下,标量被转换成矢量运算数所使用的元素类型。标量类型被扩展成与矢量运算数有着相同分量数量的矢量。运算在逐个分量上执行,结果是一个 Boolean 矢量。
    • 两个运算数是同样类型的矢量。在这种情况下,运算在逐个分量上执行,结果是一个 Boolean 矢量。
      所有其它隐式转换的情况都是合法的。如果一个或两个参数是“不是数字”(NaN),等式算子等于(==)返回 false。如果一个或两个参数是“不是数字”(NaN),等式算子不等于(!=)返回 true。
  7. 按位算子和(&)、或(|),排除算子或(^)、非(~)运算在除了内置标量和矢量浮点类型的全部标量和矢量内置类型上。对于内置矢量类型,在逐个分量上应用运算。如果一个运算数是标量,另一个是矢量,标量被转换成矢量运算数所使用的元素类型。标量类型被扩展成与矢量运算数有着相同分量数量的矢量。运算在逐个分量上执行,结果是一个同样大小的矢量。
  8. 逻辑算子和(&&)、或(||)运算在两个 Boolean 表达式上。结果是一个标量或矢量 Boolean。
  9. 逻辑一元算子非(!)运算在 Boolean 表达式上。结果是一个标量或矢量 Boolean。
  10. 三元选择算子(?:)运算在三个表达式上(exp1 ? exp2 : exp3)。这个算子计算第一个表达式 exp1,其结果必须是一个标量 Boolean。如果结果是 ture,它选择计算第二个表达式;否则它选择计算第三个表达式。第二个与第三个表达式里只有一个会被计算。第二和第三个表达式可以是任何类型,只有它们的类型一致;或者在某个表达式上可以应用 2.11 节里的转换,使它们类型一致;或者一个是矢量,而另一个是标量,这种情况下标量会被扩展成为与矢量类型相同的类型。这个结果匹配的类型是整个表达式的类型。
  11. 这些补码算子(~)。运算数必须是标量或矢量整数类型,并且其结果是它的运算数的补码。

算子右移(»)、左移(«)运算在全部标量和矢量整数类型上。对于内置矢量类型,算子逐个应用在分量上。对于右移(»)、左移(«)算子,如果第一个运算数是标量,那么最右边的运算数必须是标量。如果第一个运算数是矢量,那么最右边的运算数可以是矢量或标量。

E1 « E2 的结果是 E1 在被作为无符号整数值的 E2 里,左移了 log2(N) 个最低有效位,如果 E1 是常量,这里 N 是用来表示 E1 数据类型的位数;或者如果 E1 是矢量,N 是用来表示 E1 元素类型的位数。空出的位数由 0 填充。

E1 » E2 的结果是 E1 在被作为无符号整数值的 E2 里,右移了 log2(N) 个最低有效位,如果 E1 是常量,这里 N 是用来表示 E1 数据类型的位数;或者如果 E1 是矢量,N 是用来表示 E1 元素类型的位数。如果 E1 有一个无符号类型或者 E1 有一个有符号类型和非负值,空出的位数由 0 填充。如果 E1 有一个有符号类型和一个负值,空出的位数由 1 填充。
12. 分配算子行为的说明采用 C++14 规范。对于 lvalue = expression 分配算子,如果表达式是标量类型,并且 lvalue 是矢量类型,标量被转换为矢量算子使用的元素类型。标量类型会被扩展成与矢量运算数有着相同分量数量的矢量。运算在逐个分量上执行,其结果是一个同样大小的矢量。

注意:

  • C++14 支持的算子(如 sizeof(T)、一元(&)运算和逗号(,)算子),但上面没有说明的,其行为说明采用 C++14 规范。
  • 无符号整数应服从算术模 2n,其中 n 是表示特定大小整数的值的位数。有符号整数溢出的结果是未定义的。
  • 对于组合运算数里的除法(/)算子产生丢弃任何分数部分的代数商6;如果商 a/b 可以在结果的类型中表示,那么 (a/b)b + a%b 等于 a。(此处不理解为什么???,原文:if the quotient a/b is representable in the type of the result, (a/b)b + a%b is equal to a.)

3.2 矩阵算子

算术算子加(+)、减(-)运算在矩阵上。两个矩阵必须有相同的行和列。运算是逐个在分量上完成的,其结果是同样大小的矩阵。算术算子乘(*)运算在:

  • 标量和矩阵,
  • 矩阵和标量,
  • 矢量和矩阵,
  • 矩阵和矢量
  • 或者矩阵和矩阵。

如果一个运算数是标量,矩阵的每个分量乘以标量值,结果是同样大小的矩阵。右矢量运算数被视为列矢量,左矢量运算数被视为行矢量。对于矢量-矩阵,矩阵-矢量和矩阵-矩阵的乘法运算,左侧运算数的列数需要等于右侧运算数的行数。乘法运算是线性代数乘法,产生的矢量或矩阵的行数与左侧运算数的行数相同,并且列数与右侧运算数的列数相同。

下面的例子假设这些矢量、矩阵和标量标量都已被初始化:

    float3 v;
    float3x3 m;
    float a = 3.0f;

下面是矩阵乘以标量:

    float3x3 m1 = m * a;

相当于:

    m1[0][0] = m[0][0] * a;
    m1[0][1] = m[0][1] * a;
    m1[0][2] = m[0][2] * a;
    m1[1][0] = m[1][0] * a;
    m1[1][1] = m[1][1] * a;
    m1[1][2] = m[1][2] * a;
    m1[2][0] = m[2][0] * a;
    m1[2][1] = m[2][1] * a;
    m1[2][2] = m[2][2] * a;

下面是矢量到矩阵乘法

    float3 u = v * m;

相当于:

    u.x = dot(v, m[0]);
    u.y = dot(v, m[1]);
    u.z = dot(v, m[2]);

下面是矩阵到矢量乘法

    float3 u = m * v;

相当于:

    u = v.x * m[0];
    u += v.y * m[1];
    u += v.z * m[2];

下面是矩阵到矩阵乘法

    float3x3    m, n, r;
    r = m * n;

相当于:

    r[0] = m[0] * n[0].x;
    r[0] += m[1] * n[0].y;
    r[0] += m[2] * n[0].z;

    r[1] = m[0] * n[1].x;
    r[1] += m[1] * n[1].y;
    r[1] += m[2] * n[1].z;

    r[2] = m[0] * n[2].x;
    r[2] += m[1] * n[2].y;
    r[2] += m[2] * n[2].z;

注意:上述矢量到矩阵,矩阵到矢量和矩阵到矩阵的乘法运算描述里,部分和的顺序是未定义的。