1. ホーム
  2. optimization

[解決済み] あらゆる最適化を考慮した場合、glUniform4f/glUniform4fvのどちらが速いのでしょうか?

2022-02-24 13:48:50

質問

以下はサインです。

glUniform4f(GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
glUniform4fv(GLint location, GLsizei count, const GLfloat *v);

私の考えでは、前者の方がメモリから値を取り出すことなく、レジスタから直接値を渡すことができるため、高速になるはずです。しかし、多くの意見を聞きたいと思います。

解決方法は?

一方 *v バリアントは、主に配列型のユニフォームを設定するために存在しますが、OpenGL仕様の 明示的に は、スカラー値を設定するために 1 のカウントを使うことで、配列バリアントを使うこともできます。

OpenGLの仕様書を引用します(自分で強調しました)。

<ブロッククオート

コマンド glUniform{1|2|3|4}{f|i}v は、以下のように変更するために使用することができます。 単一のユニフォーム変数 またはユニフォーム変数配列です。これらのコマンドは,カウントと,格納する値へのポインタを渡します。 をユニフォーム変数またはユニフォーム変数配列にロードします。 1のカウント を使用する必要があります。 の値を変更する場合 単一のユニフォーム変数 であり、かつカウントが1以上であること は、配列全体または配列の一部を変更するために使用することができます。

のものです。 OpenGL 2.1仕様 にも同じように読み取れます。 OpenGL 4.2仕様 .

実はその逆もOKなんです。型のユニフォームがあると仮定します。 vec3 v[2] を使い、その位置を問い合わせる。 glGetUniformLocation() を返すかもしれません。 6 . ということは 6 の位置は、実際には v[0] .


さて、最初の質問に戻ろう。どのバリアントがより速いのか?

一概には言えません。同じように速いかもしれませんし、どちらかが速いかもしれませんし、これは実装に大きく依存します。実際のところ、ほとんどの実装では、どちらか一方を優先して実装していると思われます。

例)次のようなコードを考えてみましょう。

void glUniform1f ( GLint location, GLfloat v0 ) {
    glUniform1fv(location, 1, &v0);
}

その場合、配列のバリアントの方が速いでしょう。しかし、以下のようなバリエーションも可能です。

void glUniform1fv ( GLint location, GLsizei count, GLfloat * value ) {
    int i;

    for (i = 0; i < count; i++) {
        glUniform1f(location, *value);
        value++;
        location++;
    }
}

その場合、非配列のバリアントの方が速いでしょう。

個人的には(これはあくまで個人的な意見ですが)、初期のOpenGLの実装では、配列バリアントを非配列バリアントで実装していたのではないかと思います。一方、それはまた、はるかに遅い実装であり、ループを含むため、最近のグラフィックアダプタではおそらく不要であり、したがって、最近の実装では、配列の変種の上に非配列の変種を実装することがほとんどでしょう。

配列のバリエーションには他にも利点があります。次のような関数を考えてみましょう。

struct v3 {
     GLfloat x;
     GLfloat y;
     GLfloat z;
};

void setUniform ( GLint location, struct v3 * vPtr ) {
    glUniform3f(location, vPtr->x, vPtr->y, vPtr->z);
}

配列でない関数を呼び出すためだけに vPtr を 3 回デリファレンスするのは、むしろ愚かで、以下の実装よりもほとんど速くありません。

void setUniform ( GLint location, struct v3 * vPtr ) {
    glUniform3fv(location, 1, (const GLfloat *)vPtr);
}

また、すべての配列型は常にちょうど3つのパラメータを持ちますが、他の型は最大5つまで持つことができます。関数に渡す引数が多ければ多いほど、引数がレジスタではなくスタック経由で渡された場合、関数呼び出し自体が遅くなります。また、関数コールの引数が多ければ多いほど、ハイブリッドコール方式を採用しているアーキテクチャでは、それらの引数をすべてレジスタ内で渡すことができなくなります。したがって、一般的なCPUで期待できる純粋な関数呼び出しのオーバーヘッドから考えると、引数の少ない関数呼び出しは、引数の多い関数呼び出しよりもかなり頻繁に高速になりますが、この違いは1秒間に数千回の呼び出しを行う場合のみ問題となり、通常均一値の場合はそうではありません。