このライブラリは High Level Shading Language (HLSL) ソース コードを高水準の中間表現に変換し、デバイスに依存しない最適化を行い、OpenGL Shading Language (GLSL) 互換のソースコードを生成します。このライブラリは主に Mesa の GLSL コンパイラに基づいています。フロントエンドは HLSL をパースするために大幅に再記述され、HLSL Abstract Syntax Tree (AST) から Mesa の中間表現 (IR) を生成します。このライブラリは、Mesa の IR 最適化を活用してコードを単純化し、最終的に Mesa IR から GLSL ソース コードを生成します。この GLSL 生成は、glsl-optimizer の作業に基づきます。
GLSL コードの生成に加えて、コンパイラは global uniforms を配列にパックして設定を簡単かつ効率的にし、リフレクション メカニズムを提供し、高水準コードにどのuniforms が必要であるかを通知し、マッピング情報を提供し、高水準コードがリソースをランタイムの名前ではなくインデックスでバインドするようにします。
UnrealBuildTool は、HLSLCC などの外部ライブラリの変更を検知しません。 HLSLCC ライブラリを再ビルドする場合、OpenGLShaders.cpp にスペースを加えて、このモジュールの再リンクを強制します。
メイン ライブラリのエントリーポイントは、HLSLCrossCompile です。これは、要求されたオプションとともにソース HLSL からGLSL コードを生成するために必要なすべてのステップを行います。各ステージの概要は以下のとおりです。
操作 | 説明 |
---|---|
Preprocessing | このコードは、C のようなプリプロセッサで実行します。このステージはオプションで、NoPreprocess フラグを使用して省略できます。アンリアルでは、MCPP を使用してコンパイル前にプリプロセッシングを行っているため、このステップをスキップします。 |
Parsing | HLSL ソースが抽象構文木にパースされます。これは、関数、_mesa_hlsl_parse で行われます。lexer (字句解析器) は flex によって、parser (構文解析器) は bison によってそれぞれ生成されます。詳しい情報は、パースのセクションをご覧ください。 |
Compilation | AST は Mesa 中間表現にコンパイルされます。このプロセスは関数、_mesa_ast_to_hir で行われます。このステージ中、コンパイラは暗黙の変換、関数のオーバーロードの解決、組み込みに対する命令の生成などの機能を実行します。GLSL のメイン エントリーポイントが生成されます。以下をご覧ください。このステージでは、入出力の変数に対するグローバル宣言を IR に追加し、HLSL エントリーポイントの入力を計算し、HLSL エントリーポイントを呼び出し、出力をグローバル出力変数に書き込みます。 |
Optimization | いくつかの最適化パスが中間表現 (IR) で行われます。これには、関数のインライン化、デッドコード削除、定数伝搬、共通部分式の削除などがあります。詳細は、OptimizeIR、特に do_optimization_pass をご覧ください。 |
Uniform packing | Global uniforms は、保持されているマッピング情報と合わせて配列にパックされ、エンジンがパラメータを uniform 配列の関連部分にバインドできるようにします。詳細は、PackUniforms をご覧ください。 |
Final optimization | uniforms がパックされると、最適化の第二ラウンドが IR で実行され、uniforms をパックするときに、生成されるコードを単純化します。 |
Generate GLSL | 最後に、最適化された IR が GLSL ソースコードに変換されます。IR から GLSL への変換は比較的簡単です。すべての構造体、ユニフォーム バッファ、そのソース自体の定義を作るだけでなく、マッピング テーブルがファイルの一番上のコメントに書き出されます。このマッピング テーブルは、アンリアルによってパースされ、パラメータをバインドできるようにします。詳細は、GenerateGlsl、特に ir_gen_glsl_visitor クラスをご覧ください。 |
パース
HLSL パーサーは 2 つの部分でビルトインされています。すなわち、lexer と parser です。lexer は、正規表現を対応するトークンに一致させることで HLSL 入力をトークン化します。ソース ファイルは、hlsl_lexer.ll であり、flex によって処理され C コードを生成します。各行は、正規表現で始まり、C コードで記述された処理文が続きます。正規表現が一致すると、対応する C コードが実行されます。ステートは "yy" の接頭辞が付いたグローバル変数に格納されます。
パーサーは、ルールをトークン化した入力と一致させ、言語の文法を解釈し、ATS をビルドします。ソース ファイルは、hlsl_parser.yy で bison によって、処理され C コードを生成します。bison が使用する構文を十分に説明するのは、本ドキュメントの範囲外ですが、HLSL パーサーを見れば、基本的なことがわかるはずです。一般的には、反復的に評価されるトークンのシーケンスに一致するものとしてルールを定義します。ルールと一致すると、対応する C コードが実行され、ATS をビルドできるようになります。C コードブロック内のシンタックスは次のようになります。
[= このルールをパースした結果であり、通常は抽象構文木のノードです。 $1, $2, など = 現行ルールと一致したサブルールの出力 レキサーまたはパーサーに変更を加えたら、flex と bison を使用して C コードを再生成しなければなりません。GenerateParsers バッチファイルは、この再生成を行いますが、システムのどこに flex と bison がインストールされているかに基づきディレクトリをセットアップしなければなりません。README ファイルには、使用したバージョンに関する情報と、Windows 用にどこにバイナリをダウンロードできるかについての情報が含まれます。 ## コンパイル コンパイル中は、AST はトラバースされ、IR 命令を生成するために使用されます。理解すべき重要な概念は、IR は非常に低レベルの演算のシーケンスであるということです。そのため、暗黙の変換またはそれに類似したものは行わず、何もかも明示的に行わなければなりません。 以下は、対象となるいくつかの共通の関数です。 **apply_type_conversion** - これは、ある型の値を可能であれば別の型の値に変換します。暗黙の変換と明示的変換は、パラメータで制御されます。 **arithmetic_result_type** など。 - 入力値に演算を適用する結果の型を判断する関数のセットです。 **validate_assignment** -特定の型 lvalue にrvalue を代入できるかを判断します。必要があれば、認められた暗黙の変換が適用されます。 **do_assignment** - 可能であれば、validate_assignment を使用して rvalue を lvalue に代入します。 **ast_expression::hir** - AST の表現式ノードを IR 命令一式に変換します。 **process_initializer** - イニシャライザ表現式を変数に適用します。 **ast_struct_specifier::hir** - 宣言された構造を表すために集成型をビルドします。 **ast_cbuffer_declaration::hir** - 定数バッファ レイアウトの構造体をビルドし、それを uniform ブロックとして格納します。 **process_mul** - HLSL intrinsic mul を処理するための特殊コード。 **match_function_by_name** - 名前と入力パラメータのリストに基づき関数シグネチャを探索します。 **rank_parameter_lists** - 2 つのパラメータ リストを比較し、どれだけ密接にリストに一致しているかを示す数字ランクを代入しますこれはヘルパー関数で、オーバーロードを解決するために使用されます。最低ランクのシグネチャが勝り、最低ランクのシグネチャとして同じランクのシグネチャがあれば関数呼び出しが曖昧に宣言されます。ランク ゼロは、完全一致を示します。 **gen_texture_op** - ビルトインの HLSL テクスチャとサンプラのオブジェクトのメソッド呼び出しを処理します。 **_mesa_glsl_initialize_functions** - HLSL 組み込みに対する組み込み関数を生成します。ほとんどの関数 (例、sin, cos) は、IR コードを生成して演算を行いますが、一部 (例、transpose、determinant) は、ドライバーの GLSL コンパイラの演算を延期する関数呼び出しに残ります。 ## コンパイラを拡張する いくつかの機能の型を実装するヒントを以下に示します。 新しい表現式 ir_expression_operation 列挙型変数にエントリを追加します。 ir_expression コンストラクタで、新規表現式を処理し、入力オペランドの型に基づき表現式の型付けした結果をセットアップします。 可能であれば、ir_expression::constant_expression_value にハンドラを加えて、コンパイル時に定数式の評価を可能にします。 ir_validate::visit_leave(ir_expression *ir) にハンドラを追加し、式が正しいことを検証します。 GLSLExpressionTable にエントリを追加し、表現式を GLSL 表現式にマッピングします。 該当する場合はレキサーを修正して、表現式のトークンを認識するようにします。 該当する場合はパーサーを修正してトークンを認識し、適切な ast_expression ノードを作るようにします。 イントリンシック (Intrinsics) 組み込み関数の定義を _mesa_glsl_initialize_functions に追加します。 ほとんどの場合、イントリンシックは単一の表現式に直接マッピングします。その場合、単に新しい ir_expression を追加し、make_intrinsic_genType を使用してイントリンシック関数を生成します。 型 Glsl_type を追加して、IR 内で型を表現します。これを _mesa_glsl_initialize_types に追加するか、またはビルトイン型テーブルのひとつに追加します。例、glsl_type::builtin_core_typesテンプレート化された型では、例として glsl_type::get_sampler_instance をご覧ください。 レキサーを修正して必要なトークンを認識し、パーサーを修正してトークンを一致させます。例として、Texture2DArray をご覧ください。 パーサーを修正してトークンを認識し、必要な型の指定子を作成します。exture_type_specifier_nonarray は良い例です。 ast_type_specifier::hir を修正して、ユーザー定義の型を作成するために必要な処理を行います。例として構造体の処理をご覧ください。 ast_type_specifier::glsl_type を修正して、適切な glsl_type を戻します。 型にメソッドを含む場合、_mesa_ast_field_selection_to_hir を修正してメソッドを処理します。例として gen_texture_op をご覧ください。 属性、フラグ、修飾子 必要とする場所で IR および/または AST のノードに属性 / フラグ / 修飾子を追加します。 レキサーを修正して必要なトークンを認識します。 必要に応じて文法ルールを追加するには、パーサーを修正します。例、[loop] 属性のサポートを追加するつもりならば、iteration_statement のルールを修正し、その前のオプションの属性をアクセプトするようにします。以下のようなものになります。 iteration_statement を base_iteration_statement に変更し、 以下のように iteration_statement を追加します。 ~~~ iteration_attr base_iteration_statement { // result is the iteration statement (結果は反復処理文です)]= $2; // 属性を適用 \(->attr = $1; } base_iteration_statement { // result is the iteration statement (属性がなければ通過します)\) = $1; } ~~~
最後に属性について知る必要がある場所でコンパイラで修正します。