5

私は OpenGL の学習を始めたので、C っぽいコードの過度の使用によって明らかに引き起こされている吐き気を避けるために、(自分用に) 小さな C++ フレームワークを作成したほうがよいと考えました。:)私は Qt に固執するつもりなので、フレームワークはいくつかの Qt クラスを使用します。

最初に本当に必要だったのは、シェーダーとプログラムを簡単に使用する方法でした。これがシェーダークラスの私の考えです。

class Shader
{
public:
    //create a shader with no source code 
    explicit Shader(GLenum shaderType);
    //create a shader with source code and compile it
    Shader(GLenum shaderType, const QString& sourceCode);
    //create a shader from source file and compile it
    Shader(GLenum shaderType, QFile& sourceFile);
    ~Shader();

    //change the source code and recompile
    void Source(QFile& sourceFile);
    void Source(const QString& sourceCode);

    GLuint get() const; //get the handle

private:
    //common part for creation in different constructors
    void createShader(GLenum shaderType); 
    //compile
    void compile();

private:
    GLuint handle;
};

さまざまな機能が何をしているかは明らかです。それぞれが関連する OpenGL ルーチンを呼び出し、エラーをチェックし、エラーが発生した場合は例外をスローします。コンストラクターは を呼び出しますglCreateShader。今、トリッキーな部分です。デストラクタを呼び出す必要がありますglDeleteShader(handle);が、この場合、ジレンマがあります。

オプション 1:割り当てとコピーを無効にします。これには、参照カウントを回避するという利点と、共有ポインタを使用してこれらをベクトルに配置し、一般的に渡す必要があるという欠点があります。

オプション 2:参照カウントを有効にします。これには、コピーを有効にして、コンテナーに格納できるという明らかな利点があります (後で、さまざまなシェーダーをプログラムに渡す必要があります)。欠点は次のとおりです。

Shader s1(GL_VERTEX_SHADER, QFile("MyVertexShader.vp"));
Shader s2(s1);
s2.Source(QFile("MyOtherVertexShader.vp"));

ご覧のとおり、s1 のソースを s2 経由で変更しました。これらは同じ内部シェーダー ハンドルを共有しているためです。正直なところ、ここでは大きな問題は見られません。私はクラスを書いたので、そのコピーセマンティクスがこのようなものであることはわかっており、問題ありません。問題は、この種の設計が受け入れられるかどうか確信が持てないことです。これはすべて Option1 + 共有ポインターで実現できますが、シェーダーを作成するたびに共有ポインターを持ちたくないという唯一の違いがあります (パフォーマンス上の理由ではなく、構文上の利便性のためです)。

Q1:オプションと、必要に応じてアイデア全体についてコメントしてください。1
Q2:オプション 2 を選択した場合、それを自分で実装する必要がありますか? または、boost または Qt に、派生またはメンバーを持つ準備ができたクラスがあり、無料の参照カウントを取得できますか?
Q3:Shader抽象クラスを作成し、3 つの派生クラスVertexShaderFragmentShader、およびGeometryShaderを作成するのはやり過ぎだということに同意しますか?

1私に既存の C++ OpenGL フレームワークを紹介してくれるなら、それは非常に良いことですが (私は実際に見つけたことがなかったので)、それは私の質問への回答というよりは補足的なものにすべきです。また、ドキュメントのどこかに QGLShader クラスを見たことがありますが、私のバージョンの Qt には明らかに存在せず、今すぐアップグレードを避ける理由があります。

アップデート

答えてくれてありがとう。最終的に、ソース関数を削除して、シェーダー クラスを不変にすることにしました。シェーダーは作成時にコンパイルされ、const 以外のメンバー関数はありません。したがって、単純な参照カウントは、すべての問題を一度に解決します。

4

2 に答える 2

3

私はすでにこれらのオプションを評価しており、シェーダークラスを実装したのは別の方法です。

最初のポイントは、CreateShaderとDeleteShaderには現在のコンテキストが必要であるということですが、これは常に正しいとは限りません。すべての関数はエラーを返し、後者はリークを引き起こす可能性があります。そこで、実際にCreateShaderとDeleteShaderを呼び出すCreateandDeleteルーチンを紹介します。このようにして、別のスレッド内でもオブジェクトを破棄することができます(シェーダー自体は、後でコンテキストが最新になるときに破棄されます。

2番目のポイントは、シェーダープログラムにリンクされたシェーダーオブジェクトは、再コンパイルせずに別のシェーダープログラムに再リンクできることです(ソースがプリプロセッサシンボルに依存している場合を除く)。したがって、プログラムの作成時に再利用するために、一般的に使用されるシェーダーオブジェクトのコレクションを収集します。

最後のポイントは、作成されたシェーダーオブジェクトをリークしない限り、ShaderObjectクラスの割り当ては問題ないということです。ソースの場合、ソースを変更できるか、シェーダーが無効になるか、シェーダーがダーティになって実際にコンパイルが必要になるかの2つのオプションがあると思います。

シェーダーソースはさまざまなシェーダーステージ用にコンパイルできるため、頂点、フラグメントなどの派生は避けることをお勧めします。オプションで、デフォルトを設定して、作成前に設定できます。もちろん、createメソッドを定義することで可能です。

もう1つのポイントは、プログラムがリンクされると、シェーダーオブジェクトが存在する必要がないことです。したがって、一般的なプリコンパイル済みシェーダーデータベースと組み合わせて、参照カウントはシェーダープログラム(特にリンクされていないプログラム)によってのみ使用され、リンクするためにそのシェーダーオブジェクトが必要であることを示します。この場合、冗長なプログラムの作成を回避するために、常にシェーダープログラムデータベースが必要です。このシナリオでは、割り当てとコピーは非常にまれな操作になりますが、これは公開しないでください。代わりに、フレンドメソッドを定義し、フレームワークで使用してください。

于 2011-08-28T08:43:59.073 に答える
3

オプション 1 を使用すると言います。オプション 2 でできることはすべて (スマート ポインターを介して) 実行できますが、オプション 2 では、必要がない場合でも間接コストを支払う必要があります。その上、間違いなく書きやすいです。

同様に、関数からオブジェクトを返すことができるように、C API をラップするときにハンドル本体/PIMPL を使用することを検討したことがあります (C ハンドル型はコピー可能であることが保証されていないため、そのためには間接化が必要でした)。非可動 - >可動変換(コピー可能にするstd::unique_ptr<T>のと同じくらい)なので、私はそれに反対することにしました。それ以来、「最も厳密な」移動/コピー セマンティクスを持つようにクラスを設計しています。shared_ptr<T>T

ただし、構文ノイズに関してはポイントがあります。Boost.Phoenix やラムダのようなものが役立つ傾向があります。それらがオプションではない場合、少なくともライブラリレベルのコードについては、 shared_shaderのまたは任意のラッパー(ラッパーラッパー?)を作成することは理にかなっていると思います(ここではそうだと思います)。転送関数を書くことの退屈さを助けるユーティリティを私は知りません。

また、シェーダーに関してはあまり知らないので、最後の質問に答えられるかどうかわかりません。さまざまなシェーダーの数が頻繁に変化する可能性がある場合、クラス階層を作成することは理にかなっていると思います。そうではないと思います。また、あなたのレベルでの実装が既存の API をラップしているため、その場合でも、新しいシェーダーが追加された場合にその API に転送するためにコードを再検討するのはそれほど面倒ではないと思います。


フェニックスの素晴らしさの例を求めているからです。

逆参照する必要がないと仮定してやりたいこと:

std::transform(begin, end, functor);

その代わり:

std::for_each(begin, end, *arg1 = ref(functor)(*arg1));

std::transform一部の Phoenix 機能 ( constructIIRC)を使用して (明確にすることが望ましい)引き続き使用することは可能ですが、それには割り当てが必要です。

于 2011-08-28T08:20:41.323 に答える