32.8K Views
November 27, 23
スライド概要
■概要
シェーダ言語はHLSLが一級市民としての地位を獲得していますが、すべてのプラットフォームで利用するにはトランスレータが不可欠です。
RE ENGINE向けに内製しているDirectX Shader Compilerの技術を活用したシェーダトランスレータをご紹介します。
トランスレータの社内史について触れた上で、既存のトランスレータを使わない理由やタイトル開発での採用実績、内製ならではの成果についてお話しします。
※CAPCOM Open Conference Professional RE:2023 で公開された動画を一部改変してスライド化しております。
■想定スキル
言語処理系(コンパイラ・インタプリタ)に興味がある方
詳細は下記公式サイトをご確認ください。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CAPCOM Open Conference Professional RE:2023
https://www.capcom-games.com/coc/2023/
カプコンR&Dの最新情報は公式Twitterをチェック!
https://twitter.com/capcom_randd
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
株式会社カプコンが誇るゲームエンジン「RE ENGINE」を開発している技術研究統括によるカプコン公式アカウントです。 これまでの技術カンファレンスなどで行った講演資料を公開しています。 【CAPCOM オープンカンファレンス プロフェッショナル RE:2023】 https://www.capcom-games.com/coc/2023/ 【CAPCOM オープンカンファレンス RE:2022】 https://www.capcom.co.jp/RE2022/ 【CAPCOM オープンカンファレンス RE:2019】 http://www.capcom.co.jp/RE2019/
シェーダトランスレータの実装と運用 「シェーダトランスレータの実装と運用」と題して発表をはじめます。 よろしくお願いします。 ©CAPCOM 1
本セッションの内容 「モンスターハンターライズ」「モンスターハンターライズ:サンブレイク」で採用した 内製シェーダトランスレータについて解説します。 このセッションではRE ENGINEのシェーダトランスレータについての話をします。 とくに「モンスターハンターライズ」「モンスターハンターライズ:サンブレイク」で採用した Nintendo Switch向けに開発した内製シェーダトランスレータの実装と運用について解説します。 ©CAPCOM 2 2
アジェンダ • RE ENGINEのシェーダの概要 • シェーダトランスレータの実装 • 最適化事例 • まとめと今後の展望 アジェンダです。 RE ENGINEのシェーダとマルチプラットフォーム対応の概要をお話したうえで、 内製シェーダトランスレータの実装についてと最適化事例についてお話します。 ©CAPCOM 3 3
RE ENGINEのシェーダの概要 まずはRE ENGINEのシェーダの概要からお話しします。 4 ©CAPCOM 4
RE ENGINEのシェーダ プログラマーがHLSLで記述するシェーダコード アーティストがノードベースエディタで作成するシェーダアセット ⇒ すべてHLSLで記述 RE ENGINEのシェーダは大きく分けて2つあります。 一つは主にエンジンのプログラマーがHLSLで記述するシェーダコード、 もう一つはアーティストがノードベースエディタで作成するシェーダアセットです。 5 ノードベースエディタで作成されたシェーダアセットも最終的にはHLSLのソースコードに変換されます。 したがって、すべてのシェーダはHLSLで記述されているということになります。 ©CAPCOM 5
シェーダのマルチプラットフォーム対応 HLSL以外が必要なプラットフォーム • Nintendo Switch • Vulkan • Metal ⇒ トランスレータで変換 RE ENGINEはご存じの通りマルチプラットフォームに対応したゲームエンジンです。 ですので、シェーダの記述にHLSL以外が必要なプラットフォーム向けにはトランスレータでの変換が必要になります。 6 そのようなプラットフォームはNintendo Switch、Vulkan、Metalが該当します。 ©CAPCOM 6
世の中のシェーダトランスレータ • DirectX Shader Compiler (DXC) • HLSL Cross Compiler (HLSLcc) • Glslang • SPIRV-Cross ここで、世の中に存在するシェーダトランスレータをいくつか見ていきます。 7 ©CAPCOM 7
DirectX Shader Compiler (DXC) DirectX向けのHLSLコンパイラ LLVM/Clangからフォークしたオープンソースプロジェクト HLSLからDXILへコンパイル • DXIL : DirectX向けのシェーダ中間言語 HLSL DXC DXIL DirectX まずDirectX Shader Compilerを紹介します。 DirectX Shader CompilerはMicrosoftが開発しているDirectX向けのHLSLコンパイラです。 8 以降ではDXCという略称で呼びます。 DXCはLLVM/Clangプロジェクトからフォークしたオープンソースプロジェクトとして開発されています。 DXCはHLSLをDXILへコンパイルします。 DXILとはDirectX向けのシェーダ中間言語です。 DXILはLLVM IRのサブセットで、抽象化されたアセンブリ言語のようなものです。 ©CAPCOM 8
DirectX Shader Compiler (DXC) DirectX向けのHLSLコンパイラ LLVM/Clangからフォークしたオープンソースプロジェクト HLSLからDXILへコンパイル • DXIL : DirectX向けのシェーダ中間言語 SPIR-Vコード生成も可能 • SPIR-V : Vulkan向けのシェーダ中間言語 HLSL DXIL DirectX SPV Vulkan DXC またDXCはGoogleの貢献によってSPIR-Vコード生成もできるようになっています。 SPIR-VとはVulkan向けのシェーダ中間言語です。 SPIR-VもLLVM IRに触発されていて、DXILとよく似たアセンブリ言語のようなものになっています。 9 SPIR-Vにも対応していることからDirectX向けだけでなくVulkan向けにも使用できるため、 DXCは現在もっとも広く使われているツールだと思います。 ©CAPCOM 9
HLSL Cross Compiler (HLSLcc) HLSLからGLSLへのトランスレータ 一度HLSLをFXCでコンパイルし、そのコンパイル結果のDXBCからGLSLへ変換する • FXC : DXC以前のDirectX向けのHLSLコンパイラ • DXBC : DXIL以前のDirectX向けのシェーダ中間言語 HLSL Shader Model 6.0以降の機能は使えない • Wave Intrinsics など HLSL FXC DXBC HLSLcc GLSL 次にHLSL Cross Compilerを紹介します。 HLSL Cross CompilerはHLSLからGLSLへのトランスレータです。 2023年現在ではほぼ使われなくなっていますが、DirectX Shader Compilerが登場した2017年前後までは、 さまざまなゲームエンジンで使われていたと思います。以降ではHLSLccと略称で呼びます。 10 HLSLccは一度HLSLをFXCでコンパイルし、そのコンパイル結果のDXBCからGLSLへ変換するトランスレータです。 FXCはDXCの前のDirectX向けのシェーダコンパイラで、DirectX 11時代までのレガシーなコンパイラです。 DXBCはDXCにおけるDXILに相当するもので、DirectX 11時代までのシェーダ中間言語です。 HLSLccはレガシーなFXCをベースとしたトランスレータですので、 HLSL Shader Model 6.0以降の機能は使えないのがデメリットです。 例えばWave Intrinsicsなどが使えません。 ©CAPCOM 10
Glslang Vulkan向けのGLSLコンパイラ GLSLからSPIR-Vへコンパイル GLSL Glslang SPV Vulkan 次にGlslang(ジーエル スラング)です。 GlslangはVulkan向けのGLSLコンパイラです。 Khronos Groupが開発しており、Vulkan SDKに付属しています。 11 GlslangはGLSLからSPIR-Vへのコンパイルをサポートします。 ©CAPCOM 11
Glslang Vulkan向けのGLSLコンパイラ GLSLからSPIR-Vへコンパイル 一応HLSLからのコンパイルもサポート • ただしHLSL Shader Model 5.0まで GLSL Glslang SPV Vulkan HLSL また、一応HLSLからのコンパイルもサポートしていて、HLSL Shader Model 5.0までの機能をサポートしています。 Vulkan向けのGLSLコンパイラとしては現在広く使われているとツールだと思います。 ©CAPCOM 12 12
SPIRV-Cross SPIR-Vから各種シェーダ言語へのトランスレータ SPV SPIRV-Cross GLSL Vulkan HLSL DirectX MSL Metal 最後にSPIRV-Crossを紹介します。 SPIRV-CrossはSPIR-Vをさまざまなシェーダ言語へ変換するツールです。 13 GLSL、HLSL、MSLなどへの変換ができます。 最後のMSLはMetal Shading Languageの略称です。 ©CAPCOM 13
RE ENGINEのアプローチ Nintendo Switch ⇒ 内製シェーダトランスレータ (HLSL → GLSL) 本セッション後半の内容 Vulkan ⇒ DXC SPIR-Vコード生成 (HLSL → SPIR-V) Metal ⇒ DXC SPIR-Vコード生成 + SPIRV-Cross (HLSL → SPIR-V → MSL) ご紹介したとおり、世の中にはさまざなシェーダトランスレータが存在しています。 これを踏まえて、RE ENGINEにおけるシェーダのマルチプラットフォーム対応で現在採用しているアプローチをご紹介します。 上から順にRE ENGINEが対応した順番に並べています。 14 まずNintendo Switchでは、先ほど紹介したシェーダトランスレータは使用せずに内製のシェーダトランスレータを採用しています。 次にVulkanではDXCのSPIR-Vコード生成を採用しています。 最後にMetalですが、Metalでも同じくDXCのSPIR-Vコード生成と、 SPIRV-CrossによるMSLへの変換というフローを採用しています。 RE ENGINEではこのようなアプローチでHLSLのシェーダのマルチプラットフォーム対応を実現しています。 本セッションの後半ではNintendo Switch向けの内製シェーダトランスレータについて解説します。 ©CAPCOM 14
内製シェーダトランスレータ開発の経緯 2017年頃、RE ENGINEのNintendo Switch対応でGLSLへの変換が必要に 当初はHLSLccを使用 • デバッグが困難 その前に、内製シェーダトランスレータの開発の経緯について述べておきます。 経緯としては、2017年頃、RE ENGINEのNintendo Switch対応でHLSLからGLSLへの変換が必要になりました。 15 当初はHLSLccを採用していました。 ですが、HLSLccは不具合が多かったことに加えて、変換結果のGLSLの可読性低くデバッグが困難という問題がありました。 ©CAPCOM 15
HLSLccの問題点: デバッグが困難 HLSLccは一度DXBCへコンパイルしたものをGLSLへ変換するものなので、 元のソースコードの形と大きく変わってしまうためデバッグが困難 HLSL GLSL HLSLccは一度DXBCへコンパイルしたものをGLSLへ変換するものなので、 変換後のソースコードは、元のソースコードの形と大きく変わってしまうためデバッグが困難です。 例えば、変換後のGLSLからは元のHLSLの変数名は消えてしまっています。 16 またどの部分が元のHLSLのコードに対応しているのかぱっと見ではよくわからないものになってしまっています。 この状況でどこかに不具合があった場合に原因を特定するのは非常に困難です。 ©CAPCOM 16
内製シェーダトランスレータ開発の経緯 2017年頃、RE ENGINEのNintendo Switch対応でGLSLへの変換が必要に 当初はHLSLccを使用 • デバッグが困難 • HLSL Shader Model 6.0以降の機能が使えない DXC SPIR-Vコード生成はまだ安定していなかった ⇒ デバッグの容易性と安定性を両立したトランスレータが必要になり内製を開始 またそれ以外にもHLSLccはSM6.0以降の機能が使えないためGPUの性能をフルに活用できないという懸念もありました。 そして、最後にDXCのSPIR-Vコード生成ですが、 これは当時はまだリリースされたばかりで動作も今のように安定していなかったため、 採用には至りませんでした。 17 そのため、デバッグの容易性と安定性を両立したトランスレータが必要になり、 内製を開始することになったというのが開発の経緯です。 ©CAPCOM 17
シェーダトランスレータの実装 では内製シェーダトランスレータの実装について説明していきます。 18 ©CAPCOM 18
全体の処理の流れ フロントエンド HLSL 入力HLSL DXC HLSL 整形済みHLSL バックエンド DXC AST 内製トランスレータ 抽象構文木 (AST: Abstract Syntax Tree) GLSL 出力GLSL さっそくですが、内製シェーダトランスレータの全体の処理の流れはこのようになっています。 便宜上、HLSLから抽象構文木(AST)を出力する部分までを「フロントエンド」、 抽象構文木からGLSLを出力する部分を「バックエンド」と呼びます。 19 もう少し詳しく見ていきます。 ©CAPCOM 19
① DXCのHLSL Rewriterによるコード整形
HLSL
DXC
入力HLSL
HLSL
DXC
内製トランスレータ
AST
抽象構文木
整形済みHLSL
GLSL
出力GLSL
$ dxr.exe -remove-unused-globals –HV 2021 –enble-16bit-types -T ps_6_7 -E CopyColorPS input.hlsl
Texture2D< float4 > CopyUtilityHDRImage;
Texture2D<float>
ReadonlyDepth;
HLSL
Texture2D<float4> CopyUtilityHDRImage;
float4 CopyColorPS(float4 vs_out : SV_Position) : SV_Target {
return CopyUtilityHDRImage.Load(uint3(vs_out.xy, 0));
}
HLSL
float loadReadOnlyDepth(int3 pos)
{
float depth = ReadonlyDepth.Load(pos.xyz).x;
return depth;
}
float4 CopyColorPS(float4 vs_out: SV_Position): SV_Target
{
return CopyUtilityHDRImage.Load( uint3(vs_out.xy,0));
}
まずはじめにDXCのHLSL Rewriterを使ってHLSLのコード整形を行っています。
HLSL Rewriterの実行ファイルはdxr.exeという名前です。
20
-remove-unused-globalsというオプションを使うと
エントリーポイントから参照されていない未使用の定義を除去してコード整形することができます。
左のHLSLをHLSL Rewriterでコード整形すると、右のようなHLSLになります。
エントリーポイントから参照されていない未使用のグローバル変数や関数定義が消えています。
©CAPCOM
20
② DXCによる抽象構文木の出力
HLSL
DXC
入力HLSL
HLSL
DXC
AST
内製トランスレータ
抽象構文木
整形済みHLSL
GLSL
出力GLSL
$ dxc.exe –ast-dump –HV 2021 –enble-16bit-types -T ps_6_7 -E CopyColorPS refined.hlsl
Texture2D<float4> CopyUtilityHDRImage;
float4 CopyColorPS(float4 vs_out : SV_Position) : SV_Target {
return CopyUtilityHDRImage.Load(uint3(vs_out.xy, 0));
}
HLSL
次にもう一度DXCを使用して、先ほど整形したHLSLから抽象構文木を出力します。
実行ファイルはdxc.exeを使用します。
21
-ast-dumpというオプションを使用すると抽象構文木を出力することができます。
抽象構文木は右のようにプレーンテキストで出力されます。
©CAPCOM
21
③ 抽象構文木からGLSLを出力 HLSL 入力HLSL DXC HLSL 整形済みHLSL DXC 内製トランスレータ AST 抽象構文木 GLSL 出力GLSL #version 460 core #extension GL_NV_gpu_shader5 : enable #extension GL_NV_bindless_texture : enable layout(std430) uniform; layout(std430) buffer; GLSL layout(binding = 0) uniform texture2D CopyUtilityHDRImage; layout(location = 0) out vec4 out_Target0; // CopyColorPS void main() { vec4 vs_out = vec4(gl_FragCoord.xyz, 1.0/gl_FragCoord.w); const ivec3 autogen_TempVar0 = ivec3(uvec3(vs_out.xy, 0)); out_Target0 = texelFetch(sampler2D(uint64_t(CopyUtilityHDRImage)), autogen_TempVar0.xy, autogen_TempVar0.z); return; } そして最後に抽象構文木からGLSLを出力しています。 以上が全体の処理の流れです。 22 ©CAPCOM 22
フロントエンドにDXCを利用するメリット フロントエンドにはDXCを利用 メリット • 自前でHLSLのパーサを書かなくてよい • DXCの抽象構文木には型情報も含まれている • 最新のHLSLの言語仕様に追従するのが楽 今見ていただいたとおり、フロントエンドにはDXCを使用しています。 これにはいくつかのメリットがあります。 23 一つ目のメリットは、自前でHLSLのパーサを書かなくてよいということです。 C言語ライクな言語のパーサを自前で書くのはそれなりに大変ですのでこれは結構なメリットです。 もう一つのメリットは、DXCの抽象構文木には型情報も含まれている点です。 この型情報は、この後説明するGLSLへの変換時にそのまま利用できます。 最後に、最新のHLSLの言語仕様に追従するのが楽ということもメリットとして挙げられます。 フロントエンド部分に関してはDXCを更新するだけで済むためです。 フロントエンドについては以上です。 ©CAPCOM 23
バックエンドの実装 抽象構文木のトラバース TranslationUnitDecl VarDecl VarDecl IntegerLiteral AST パース FunctionDecl 事前処理 ParmVarDecl GLSL出力 GLSL CompoundStmt VarDecl 抽象構文木 出力GLSL BinaryOperator DeclRefExpr 木構造のデータ構造 • • • • グローバルな定義の収集 (リソース変数、定数バッファ、構造体、関数) 定数バッファと構造体のメモリレイアウト計算 各関数内の簡易的なコントロールフロー解析 各関数の副作用のチェック ここからはバックエンドの実装について詳しく説明します。 バックエンドの処理の流れですが、まず抽象構文木はフロントエンドからプレーンテキストで出力されますので、 24 それをパースして木構造のデータ構造を復元します。 そしてそれをトラバースしながら逐次的にGLSLの文字列を出力しています。 実際のトラバースは2回行っていて、1回目は事前処理、2回目にGLSL出力を行っています。 事前処理の内容は時間の都合で詳しく説明はしませんが、 グローバルな定義の収集、定数バッファと構造体のメモリレイアウト計算、 各関数内の簡易的なコントロールフロー解析、各関数の副作用のチェックなどを行っています。 ©CAPCOM 24
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
簡単な変換例をお見せします。
左上の非常に簡単なHLSLをGLSLへ変換する処理の流れを具体的に説明します。
25
下のテキストは左上のHLSLのASTです。
©CAPCOM
25
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
まずHLSLの1行目はテクスチャ変数宣言になっています。
変数宣言はASTではVarDeclノードで表されています。
26
変数名と型の情報はこのように対応しています。
©CAPCOM
26
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
GLSLへ変換するとこのようになります。
変数名はそのまま使用して、テクスチャの型はHLSLの型からGLSLの型へ適切に変換しています。
27
ここではVulkanのSeparate textures and samplersの仕様に基づいて、GLSLのtexture2D型へ変換しています。
©CAPCOM
27
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
次は関数宣言です。
関数宣言はASTではFunctionDeclノードで表現されます。
28
関数名と関数の型はこのように対応しています。
引数の情報は子ノードのParmVarDeclノードで表現されます。
引数名と引数の型はこのように対応しています。
©CAPCOM
28
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
GLSLへ変換するとこのようになります。
型名だけ、HLSLの型からGLSLの型へ適切に変換しています。
©CAPCOM
29
29
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
次に関数の本体部分の中括弧です。
ASTではCompoundStmtノードになります。
30
©CAPCOM
30
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
GLSLでもそのまま中括弧に変換します。
31
©CAPCOM
31
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
次はreturn文です。
ASTではReturnStmtノードです。
32
©CAPCOM
32
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
return
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
そのまま変換します。
33
©CAPCOM
33
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
return
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
次がテクスチャ変数からのLoadです。
これはメンバ関数呼び出しになっていてCXXMemberCallExprノードで表されています。
34
呼び出しているメンバ関数の情報は子ノードのMemberExprノードに含まれています。
呼び出し元の変数の情報はさらにその子ノードのDeclRefExprノードに含まれています。
DeclRefExprノードは式の中で変数を使用しているときに現れるノードです。
©CAPCOM
34
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
return texelFetch(sampler2D(ColorTex),
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
GLSLへ変換するとこのようになります。
HLSLのテクスチャのLoad関数はGLSLではtexelFetch関数へ置き換えています。
35
第1引数はASTの型情報を使用してsampler2D型へ変換しています。
©CAPCOM
35
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
return texelFetch(sampler2D(ColorTex),
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
次に実引数部分です。
ASTではこのように表されています。
36
©CAPCOM
36
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
!?
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
return texelFetch(sampler2D(ColorTex), ???
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
これをGLSLへ変換したいのですが、これはそのままではうまく行きません。
37
©CAPCOM
37
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
return texelFetch(sampler2D(ColorTex),
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
texelFetch(sampler2D
sampler, ivec2 P, int
| |-ParmVarDecl
<col:17, col:23> col:23
used pos 'uint2':'vector<unsigned
int, 2>'
Texture2D::Load(int3
Location);
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
GLSL
lod);
HLSLのLoad関数と、GLSLのtexelFetch関数の型はこのようになっています。
この2つの関数は引数の数が一致していないため、GLSLへ変換する際には分配が必要になります。
©CAPCOM
38
38
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
return texelFetch(sampler2D(ColorTex),
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
そこで一時変数を作成したいのですが、この途中まで出力した式が邪魔になるので、
39
©CAPCOM
39
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
return texelFetch(sampler2D(ColorTex),
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
return texelFetch(sampler2D(ColorTex),
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
退避
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
一旦これを退避します。
40
©CAPCOM
40
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
const ivec3 temp =
GLSL
一時変数を作成
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
return texelFetch(sampler2D(ColorTex),
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
そして、一時変数を作成して、
41
©CAPCOM
41
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
const ivec3 temp = ivec3(uvec3(pos, 0));
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
return texelFetch(sampler2D(ColorTex),
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
実引数の式をこれに代入します。
42
©CAPCOM
42
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
const ivec3 temp = ivec3(uvec3(pos, 0));
return texelFetch(sampler2D(ColorTex),
GLSL
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
return texelFetch(sampler2D(ColorTex),
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
復元
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
そして、先ほど退避した式を復元して、
43
©CAPCOM
43
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
GLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
const ivec3 temp = ivec3(uvec3(pos, 0));
return texelFetch(sampler2D(ColorTex), temp.xy, temp.z);
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
一時変数の値を分配します。
44
©CAPCOM
44
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
GLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
const ivec3 temp = ivec3(uvec3(pos, 0));
return texelFetch(sampler2D(ColorTex), temp.xy, temp.z);
}
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
最後にCompoundStmtノードを抜けるときに中括弧を閉じて
45
©CAPCOM
45
簡単な変換例
Texture2D ColorTex;
float4 getColor(uint2 pos)
{
return ColorTex.Load(uint3(pos, 0));
}
HLSL
GLSL
uniform texture2D ColorTex;
vec4 getColor(uvec2 pos)
{
const ivec3 temp = ivec3(uvec3(pos, 0));
return texelFetch(sampler2D(ColorTex), temp.xy, temp.z);
}
|-VarDecl <line:6:1, col:11> col:11 used ColorTex 'Texture2D<vector<float, 4> >'
|-FunctionDecl <line:8:1, line:11:1> line:8:8 getColor 'float4 (uint2)'
| |-ParmVarDecl <col:17, col:23> col:23 used pos 'uint2':'vector<unsigned int, 2>'
| `-CompoundStmt <line:9:1, line:11:1>
|
`-ReturnStmt <line:10:5, col:39>
|
`-CXXMemberCallExpr <col:12, col:39> 'vector<float, 4>'
|
|-MemberExpr <col:12, col:21> '<bound member function type>' .Load
|
| `-DeclRefExpr <col:12> 'Texture2D<vector<float, 4> >' lvalue Var 'ColorTex' 'Texture2D<vector<float, 4> >'
|
`-ImplicitCastExpr <col:26, col:38> 'vector<int, 3>' <HLSLCC_IntegralCast>
|
`-CXXFunctionalCastExpr <col:26, col:38> 'uint3':'vector<unsigned int, 3>' functional cast to uint3 <NoOp>
|
`-InitListExpr <col:31, col:38> 'uint3':'vector<unsigned int, 3>'
|
|-ImplicitCastExpr <col:32> 'uint2':'vector<unsigned int, 2>' <LValueToRValue>
|
| `-DeclRefExpr <col:32> 'uint2':'vector<unsigned int, 2>' lvalue ParmVar 'pos' 'uint2':'vector<unsigned int, 2>'
|
`-ImplicitCastExpr <col:37> 'unsigned int' <IntegralCast>
|
`-IntegerLiteral <col:37> 'literal int' 0
完了です。
以上のような流れでASTから逐次的にGLSLを出力しています。
©CAPCOM
46
46
Delimited Continuations 一時変数挿入時のコードのカット&ペーストの考え方は、関数型言語の研究における 「Control Operatorを用いたlet insertion」という手法をヒントにしています。 Control Operator =「Continuationを操作するオペレータ」 • Continuation =「ある計算を行った後の残りの計算」 • Delimited Continuation =「残りの計算の一部」 • 例: exception, coroutine, call/cc, shift/reset, control/prompt float calc(float x, float y, float z) { float a = x * 3; float b = foo(2 * y, 3 * z); return a + b; } float calc(float x, float y, float z) { float a = x * 3; float b = foo(2 * y, 3 * z); return a + b; } 「3 * z」の残りの計算 = Continuation 「3 * z」の残りの計算の一部 = Delimited Continuation ここで少し余談にはなりますが変換を実装する際に用いている考え方をご紹介します。 一時変数挿入時のコードのカット&ペーストの考え方は、関数型言語の研究における 「Control Operatorを用いたlet insertion」という手法をヒントにしています。 47 馴染みがない方が多いと思いますので簡単に説明します。 Control OperatorとはプログラムのContinuationを操作するオペレータのことです。 Continuationとは「ある計算を行った後の残りの計算」を表す概念のことで、 とくに「残りの計算の一部」のことはDelimited Continuationと呼ばれています。 左下の図で黄色くハイライトされている部分は、 「3 * z」を計算しようとしているときの残りの計算、つまりContinuationを表しています。 右下の図で黄色くハイライトされている部分はDelimited Continuationを表しています。 Continuationの区切り方は自由ですが、ここではステートメントごとに区切っています。 ©CAPCOM 47
Delimited Continuations 一時変数挿入時のコードのカット&ペーストの考え方は、関数型言語の研究における 「Control Operatorを用いたlet insertion」という手法をヒントにしています。 Control Operator =「Continuationを操作するオペレータ」 • Continuation =「ある計算を行った後の残りの計算」 • Delimited Continuation =「残りの計算の一部」 • 例: exception, coroutine, call/cc, shift/reset, control/prompt float calc(float x, float y, float z) { float a = x * 3; float b = foo(2 * y, 3 * z); return a + b; } float calc(float x, float y, float z) { float a = x * 3; float b = foo(2 * y, 3 * z); return a + b; } 「3 * z」の残りの計算 = Continuation 「3 * z」の残りの計算の一部 = Delimited Continuation Control Operatorの例としては、我々ゲームプログラマーに馴染みがあるものではexceptionやcoroutineがあります。 exceptionについて少し補足説明します。 Continuationは実際のプログラム実行時にはStack frameで表現されるものです。 ローカル変数はStack frameに保存されていますし、 関数呼び出し後に再開する残りの計算を表すプログラムカウンタもStack frameに保存されます。 48 そしてexceptionはStack unwindingによってStack frameを操作するものですので、 Continuationを操作するオペレータであるとイメージしていただけるかと思います。 他には古くから知られている代表的なものとしてScheme言語のcall/ccがあります。 最後にshift/reset, control/promptというオペレータも代表例です。 こちらは近年Haskell言語のコンパイラにファーストクラスの言語機能として組み込まれています。 ©CAPCOM 48
let insertion let insertion =「計算式を一時変数に置き換える操作」 (fun x -> x * x) (1 + 2) let t = (1 + 2) in (fun x -> x * x) t 一時変数挿入時のコードのカット&ペースト • 実装上は「単なる文字列操作」 • 概念的には「Delimited Continuationの操作によるプログラム変換」 関数型言語の分野の研究の知見 ⇒ 正しくプログラム変換するための考え方の基礎 そしてlet insertionとはプログラム生成時に「計算式を一時変数に置き換える操作」のことです。 これはまさに先ほど変換の例で行っていた操作と同じ操作です。 一時変数挿入時のコードのカット&ペーストは、実装上は単なる文字列操作ですが、 概念的にはDelimited Continuationの操作によるプログラム変換であると言えます。 49 プログラムの計算結果を変えずに正しくプログラム変換するための考え方の基礎として 関数型言語の分野の研究内容を役立てています。 このように考える必然性は特にないのですが、 私にもともと関数型言語の分野の研究の知見があったため、それを実装に活かしています。 実際、C言語ライクな副作用をもつ手続き型言語で、 正しくプログラム変換するのはそれなりに難しいのでこの考え方は大いに役立ったと思っています。 ©CAPCOM 49
基本データ型のマッピング HLSL GLSL float float float3 vec3 float3x3 mat3 float3x4 mat3x4 int int int3 ivec3 half float16_t half3 f16vec3 話を戻します。 基本データ型のマッピングはこのようにしています。 50 特別難しいところはありませんが、行列の扱いについては少し注意が必要です。 ©CAPCOM 50
基本データ型のマッピング HLSL GLSL float float float3 vec3 float3x3 mat3 float3x4 mat3x4 int int int3 行 x列 half ivec3 float16_t half3 例: mul(float4(pos, 1), worldMat) 列x行 f16vec3 例: worldMat * vec4(pos, 1.0) 行列は、型名の2つの数字が表す意味がHLSLとGLSLでは逆になっています。 HLSLでは「行 x 列」ですが、GLSLでは「列 x 行」です。 RE ENGINEではHLSLの行列のメモリレイアウトはrow-majorで扱っていますが、 GLSLでは基本的にcolumn-majorなので、これでメモリレイアウトは一致します。 51 というわけで、GLSLでは常に転置した状態で行列を扱っているということになります。 もちろんこのままでは計算結果が合わなくなってしまうので、掛け算は順番を入れ替えることで対処しています。 ©CAPCOM 51
リソース型のマッピング HLSL GLSL cbuffer A { ... } uniform A { ... }; SamplerState sampler Texture2D texture2D RWTexture2D image2D ByteAddressBuffer readonly buffer { uint[]; }; StructuredBuffer<T> readonly buffer { T[]; }; RWByteAddressBuffer buffer { uint[] }; RWStructuredBuffer<T> buffer { T[] }; 次にリソース型のマッピングです。 こちらも特別難しいところはありません。 52 Constant BufferはUniform Bufferへ、 SamplerStateとTextureはそのままsamplerとtextureへ変換しています。 RWTextureはGLSLではImageへ変換しています。 最後にByteAddressBufferやStructuredBufferは、GLSLではShader Storage Bufferへ変換しています。 ©CAPCOM 52
入出力インタフェースの変換 HLSLのセマンティクスはGLSLの入出力変数へ変換 HLSL struct FullScreenTriangleTexVSOut { float4 positionViewport : SV_Position; float2 texCoord : TEXCOORD0; }; FullScreenTriangleTexVSOut FullScreenTriangleTexVS(uint vertexID : SV_VertexID) { FullScreenTriangleTexVSOut vsout; // ... vsout.texCoord = grid; return vsout; } struct FullScreenTriangleTexVSOut { vec4 positionViewport; vec2 texCoord; }; GLSL layout(location = 1) out vec2 out_TEXCOORD0; void main() { // input uint vertexID = uint(gl_VertexID - gl_BaseVertex); FullScreenTriangleTexVSOut vsout; // ... vsout.texCoord = grid; // output gl_Position = vsout.positionViewport; out_TEXCOORD0 = vsout.texCoord; return; } 入出力インタフェースの変換についても簡単に説明します。 シェーダステージ間の入出力はHLSLではセマンティクスで指定しますが、GLSLでは入出力変数のlocationで指定します。 53 HLSLのSystem Value Semanticsは、GLSLでは組み込み変数へ変換しています。 ©CAPCOM 53
入出力インタフェースの変換 HLSLのセマンティクスはGLSLの入出力変数へ変換 struct FullScreenTriangleTexVSOut { float4 positionViewport : SV_Position; float2 texCoord : TEXCOORD0; }; FullScreenTriangleTexVSOut FullScreenTriangleTexVS(uint vertexID : SV_VertexID) { FullScreenTriangleTexVSOut vsout; // ... vsout.texCoord = grid; return vsout; } HLSL struct FullScreenTriangleTexVSOut { vec4 positionViewport; vec2 texCoord; }; GLSL layout(location = 1) out vec2 out_TEXCOORD0; void main() { // input uint vertexID = uint(gl_VertexID - gl_BaseVertex); FullScreenTriangleTexVSOut vsout; // ... vsout.texCoord = grid; // output gl_Position = vsout.positionViewport; out_TEXCOORD0 = vsout.texCoord; return; } そして入出力の変換はこのような感じで行っています。 54 ©CAPCOM 54
HLSLセマンティクスとGLSL組み込み変数のマッピング HLSL GLSL SV_Position (as vertex shader input) gl_Position SV_Position (as pixel shader input) vec4(gl_FragCoord.xyz, 1.0/gl_FragCoord.w) SV_Depth gl_FragDepth SV_VertexID gl_VertexID - gl_BaseVertex SV_InstanceID gl_InstanceID SV_PrimitiveID gl_PrimitiveID SV_GroupID gl_WorkGroupID SV_GroupThreadID gl_LocalInvocationID SV_DispatchThreadID gl_GlobalInvocationID SV_GroupIndex gl_LocalInvocationIndex HLSLのセマンティクスとGLSLの組み込み変数のマッピングです。 代表的なものだけ載せていますが、このように変換しています。 ©CAPCOM 55 55
GLSLで特別な対処が必要な場合 メモリレイアウトが異なる場合の対処 buffer型を引数に取る・戻り値で返す関数の対処 GLSLへの変換は基本的にはここまでお話した内容でだいたい対応できます。 ですが、特別な対処が必要になる場合がありますので、それについてお話します。 ©CAPCOM 56 56
メモリレイアウトが異なる場合の対処 HLSLのfloat3は4byteアラインメント GLSLのvec3は16byteアラインメント (std430) HLSL struct BoundingAABB { float3 center; float3 extent; }; StructuredBuffer<BoundingAABB> AABBSRV; readonly buffer AABBSRV { BoundingAABB AABBSRV_Buffer[]; }; BoundingAABB center GLSL struct BoundingAABB { vec3 center; vec3 extent; }; BoundingAABB center extent extent 意図しない4byteのパディング 一つ目がメモリレイアウトが異なる場合への対処です。 例えばHLSLのfloat3型はGLSLへそのまま変換するとvec3型になりますが、 HLSLのfloat3は4byteアラインメントですが、GLSLのvec3は16byteアラインメントです。 57 この例では、BoundingAABBという構造体でfloat3型の変数を2つ宣言していますが、 下の図のようにそのまま変換しただけでは意図しないパディングが生まれてしまい、HLSLとGLSLで計算結果が変わってしまいます。 ©CAPCOM 57
メモリレイアウトが異なる場合の対処 ⇒ ベクトル型を配列型に変換することによって対処 struct BoundingAABB { vec3 center; float center[3]; vec3 extent; float extent[3]; }; readonly buffer AABBSRV { BoundingAABB AABBSRV_Buffer[]; }; GLSL 配列化したメンバを参照しているところには変換用のグルー関数を挿入 vec3 float[3] floatArrayToVector3(float[3] a) { return vec3(a[0], a[1], a[2]); } vector3ToFloatArray(vec3 v) { return float[3](a.x, a.y, a.z); } GLSL vec3 center = floatArrayToVector3(AABBSRV_Buffer[i].center); BoundingAABB aabb; aabb.center = vector3ToFloatArray(center); GL_EXT_scalar_block_layout拡張が使える場合はlayout(scalar) buffer;とするだけでもOK このような場合は、ベクトル型を配列型に変換することによって対処しています。 このようにvec3型は、3要素のfloat配列型に変換するようにしています。 58 配列化したメンバを参照しているところには変換用のグルー関数を自動生成して挿入することによって辻褄を合わせています。 これが自動生成したグルー関数です。 そして配列化したメンバを参照しているところには、これらの関数の呼び出しを挿入しています。 補足ですが、もしGL_EXT_scalar_block_layout拡張が使える場合には、layout(scalar) buffer;を先頭で宣言するだけでもOKです。 scalarレイアウトを使えばHLSLと全く同じメモリレイアウトにすることができるため配列化は不要になります。 メモリレイアウトが異なる場合の対処については以上です。 ©CAPCOM 58
buffer型を引数に取る・戻り値で返す関数の対処 GLSLではbuffer型のオブジェクトを関数の引数で渡したり、戻り値で返したり、ローカル変数に代入 したりできない(=第一級オブジェクトではない) • GL_EXT_buffer_reference拡張かGL_NV_shader_buffer_load拡張を使えば可能 ByteAddressBuffer Input; ByteAddressBuffer getInputBuffer() { return Input; } float readBuffer(ByteAddressBuffer buf, uint offset) { return asfloat(buf.Load(offset)); } HLSL uint addr = id * 4; ByteAddressBuffer input = getInputBuffer(); float v = readBuffer(input, addr) * 10.0f; readonly buffer Input { uint Input_Buffer[]; }; uint* getInputBuffer() { return Input_Buffer; // コンパイルエラー } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } GLSL uint addr = id * 4; uint* input = getInputBuffer(); float v = readBuffer(input, addr) * 10.0; ⇒ インライン展開することによって対処 もう一つの問題ですが、GLSLではbuffer型のオブジェクトを関数の引数で渡したり、戻り値で返したり、 ローカル変数に代入したりできません。 つまりGLSLではbuffer型は第一級オブジェクトではありません。 59 「第一級オブジェクト」というのは一般に「関数の引数で渡したり、戻り値で返したり、 ローカル変数に代入したりできるオブジェクト」のことです。 左のHLSLではByteAddressBufferを関数の戻り値で返したりしていますが、 これはGLSLへそのまま変換することができません。 右のGLSLはこのように変換したいという気持ちを書いたものですが、実際にはコンパイルエラーになります。 ©CAPCOM 59
buffer型を引数に取る・戻り値で返す関数の対処 GLSLではbuffer型のオブジェクトを関数の引数で渡したり、戻り値で返したり、ローカル変数に代入 したりできない(=第一級オブジェクトではない) • GL_EXT_buffer_reference拡張かGL_NV_shader_buffer_load拡張を使えば可能 ByteAddressBuffer Input; ByteAddressBuffer getInputBuffer() { return Input; } float readBuffer(ByteAddressBuffer buf, uint offset) { return asfloat(buf.Load(offset)); } uint addr = id * 4; ByteAddressBuffer input = getInputBuffer(); float v = readBuffer(input, addr) * 10.0f; HLSL readonly buffer Input { uint Input_Buffer[]; }; uint* getInputBuffer() { return Input_Buffer; // コンパイルエラー } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } GLSL uint addr = id * 4; uint* input = getInputBuffer(); float v = readBuffer(input, addr) * 10.0; ⇒ インライン展開することによって対処 一応、これは後からわかったことなのですが、GL_EXT_buffer_reference拡張か、 GL_NV_shader_buffer_load拡張を使えば右のGLSLのような書き方も可能にはなります。 ですが、内製シェーダトランスレータではこのような関数はインライン展開することによって対処しています。 60 先にお伝えしておきますが、この対処方法は当時試行錯誤した結果そうなったという部分で、 今思えば一番簡単な対処方法ではないと思っています。 実際、インライン展開の実装は結構大変です。 今であればGL_NV_shader_buffer_load拡張を使った変換を採用するようにしていたかもしれません。 ©CAPCOM 60
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; uint* input = getInputBuffer(); float v = readBuffer(input, addr) * 10.0; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 GLSL 値 インライン展開の手順を説明します。 まずbuffer型の変数の内容を伝搬するために、一般的なインタプリタの実装で行うように、 すべての変数宣言と代入をトラッキングするようにします。 61 順番に見ていきます。 ©CAPCOM 61
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; uint* input = getInputBuffer(); float v = readBuffer(input, addr) * 10.0; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 GLSL 最初のaddr変数の宣言ではこのようにテーブルに変数名と値を登録します。 62 ©CAPCOM 62
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; uint* input = getInputBuffer(); float v = readBuffer(input, addr) * 10.0; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 GLSL input 次の行のinput変数の宣言も同様に登録します。 63 ©CAPCOM 63
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; uint* input = getInputBuffer(); float v = readBuffer(input, addr) * 10.0; Lexical Scope Lexical Binding uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 GLSL input このようにテーブルはLexical Scopeを表していて、各行がLexical Bindingを表しています。 64 ©CAPCOM 64
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; uint* input = getInputBuffer(); float v = readBuffer(input, addr) * 10.0; インライン展開 uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 GLSL input 次にこの関数はbuffer型を返す関数なのでインライン展開したいのですが、 65 ©CAPCOM 65
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; uint* input = getInputBuffer(); float v = readBuffer(input, addr) * 10.0; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 GLSL input ここにContinuationがあるので、 66 ©CAPCOM 66
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; uint* input = getInputBuffer(); float v = readBuffer(input, addr) * 10.0; uint* input = ; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 GLSL input 退避 一旦退避します。 67 ©CAPCOM 67
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; getInputBuffer() float v = readBuffer(input, addr) * 10.0; uint* input = ; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 GLSL input そしてインライン展開を行います。 68 ©CAPCOM 68
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } float v = readBuffer(input, addr) * 10.0; uint* input = ; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 GLSL input このようになります。 69 ©CAPCOM 69
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } float v = readBuffer(input, addr) * 10.0; uint* input = ; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 GLSL input まず関数の戻り値を格納するための一時変数を作成しています。 GLSLではbuffer型はローカル変数でそのまま扱えないためコメントアウトしています。 70 ©CAPCOM 70
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } float v = readBuffer(input, addr) * 10.0; uint* input = ; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 GLSL temp0 input これもテーブルに登録します。 71 ©CAPCOM 71
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } float v = readBuffer(input, addr) * 10.0; uint* input = ; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 GLSL temp0 input 次にインライン展開後のreturn文は、今作成した一時変数への代入文に置き換えています。 72 ©CAPCOM 72
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } float v = readBuffer(input, addr) * 10.0; uint* input = ; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer GLSL input この代入の値もテーブルに登録します。 73 ©CAPCOM 73
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = ; float v = readBuffer(input, addr) * 10.0; uint* input = ; 復元 uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer GLSL input そしてContinuationを復元します。 74 ©CAPCOM 74
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float v = readBuffer(input, addr) * 10.0; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer GLSL input Continuationのホール部分は戻り値を格納した一時変数で置き換えます。 75 ©CAPCOM 75
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float v = readBuffer(input, addr) * 10.0; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer input temp0 GLSL この代入もテーブルに登録します。 76 ©CAPCOM 76
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float v = readBuffer(input, addr) * 10.0; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer input temp0 GLSL v 以下同様ですが、次の変数宣言をテーブルに登録します。 77 ©CAPCOM 77
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float v = readBuffer(input, addr) * 10.0; インライン展開 uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer input temp0 GLSL v 次にこの関数はbuffer型を引数に取る関数なので、これもインライン展開が必要です。 78 ©CAPCOM 78
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float v = readBuffer(input, addr) * 10.0; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer input temp0 GLSL v ここもまずContinuationを退避する必要がありますので、 79 ©CAPCOM 79
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float v = readBuffer(input, addr) * 10.0; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer input temp0 GLSL v float v = * 10.0; 退避 退避します。 80 ©CAPCOM 80
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; readBuffer(input, addr) uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer input temp0 GLSL v float v = * 10.0; そしてインライン展開を行います。 81 ©CAPCOM 81
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float temp1; { // inlining 'readBuffer' //uint* buf = input; offset = addr; temp1 = uintBitsToFloat(buf[offset]); } uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer input temp0 GLSL v float v = * 10.0; このようになります。 82 ©CAPCOM 82
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float temp1; { // inlining 'readBuffer' //uint* buf = input; offset = addr; temp1 = uintBitsToFloat(buf[offset]); } uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer input temp0 GLSL v float v = * 10.0; 先ほどと同様、まず関数の戻り値を格納するための一時変数を作成しています。 83 ©CAPCOM 83
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float temp1; { // inlining 'readBuffer' //uint* buf = input; offset = addr; temp1 = uintBitsToFloat(buf[offset]); } uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer input temp0 GLSL temp1 float v = * 10.0; v テーブルに登録します。 84 ©CAPCOM 84
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float temp1; { // inlining 'readBuffer' //uint* buf = input; offset = addr; temp1 = uintBitsToFloat(buf[offset]); } uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer input temp0 GLSL temp1 float v = * 10.0; v この部分は関数の引数を代入しています。 これもテーブルに登録しますが、 85 ©CAPCOM 85
インライン展開の例 GLSL readonly buffer Input { uint Input_Buffer[]; }; uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float temp1; { // inlining 'readBuffer' //uint* buf = input; offset = addr; temp1 = uintBitsToFloat(buf[offset]); } uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer input temp0 GLSL temp1 float v = * 10.0; 新しいLexical Scopeを作成して登録 v buf input offset addr 別のスコープなので新しいLexical Scopeを作成してそこに登録します。 86 ©CAPCOM 86
インライン展開の例 GLSL readonly buffer Input { uint Input_Buffer[]; }; uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float temp1; { // inlining 'readBuffer' //uint* buf = input; offset = addr; temp1 = uintBitsToFloat(buf[offset]); } uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer input temp0 GLSL temp1 float v = * 10.0; v buf input offset addr そしてこのbuf変数ですが、ここの扱いがポイントとなります。 これはbuffer型の変数ですが、buffer型のローカル変数はGLSLではそのまま扱えないので置き換えが必要になります。 87 そこでこのようにテーブルを辿って、今代入されている実際のリソース変数名を見つけます。 ©CAPCOM 87
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float temp1; { // inlining 'readBuffer' //uint* buf = input; offset = addr; temp1 = uintBitsToFloat(Input_Buffer[offset]); } uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer input temp0 GLSL temp1 float v = * 10.0; v buf input offset addr そして見つかった実際のリソース変数名で置き換えます。 このようにしてbuffer型を変数の内容を伝搬しています。 88 ©CAPCOM 88
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float temp1; { // inlining 'readBuffer' //uint* buf = input; offset = addr; temp1 = uintBitsToFloat(Input_Buffer[offset]); } uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } 変数 値 addr id * 4 temp0 Input_Buffer input temp0 GLSL temp1 float v = * 10.0; v buf input offset addr 残りも見ていきます。 89 ©CAPCOM 89
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float temp1; { // inlining 'readBuffer' //uint* buf = input; offset = addr; temp1 = uintBitsToFloat(Input_Buffer[offset]); } float v = * 10.0; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } GLSL 変数 値 addr id * 4 temp0 Input_Buffer input temp0 temp1 uintBitsToFloat(Input_Buffer[offset]) v buf input offset addr この代入をテーブルに登録します。 90 ©CAPCOM 90
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float temp1; { // inlining 'readBuffer' //uint* buf = input; offset = addr; temp1 = uintBitsToFloat(Input_Buffer[offset]); } float v = * 10.0; スコープを抜けるのでLexical Scopeを削除 uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } GLSL 変数 値 addr id * 4 temp0 Input_Buffer input temp0 temp1 uintBitsToFloat(Input_Buffer[offset]) v buf input offset addr スコープを抜けるときにはLexical Scopeを削除します。 91 ©CAPCOM 91
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float temp1; { // inlining 'readBuffer' //uint* buf = input; offset = addr; temp1 = uintBitsToFloat(Input_Buffer[offset]); } float v = * 10.0; float v = * 10.0; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } GLSL 変数 値 addr id * 4 temp0 Input_Buffer input temp0 temp1 uintBitsToFloat(Input_Buffer[offset]) v 復元 Continuationを復元して、 92 ©CAPCOM 92
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float temp1; { // inlining 'readBuffer' //uint* buf = input; offset = addr; temp1 = uintBitsToFloat(Input_Buffer[offset]); } float v = temp1 * 10.0; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } GLSL 変数 値 addr id * 4 temp0 Input_Buffer input temp0 temp1 uintBitsToFloat(Input_Buffer[offset]) v Continuationのホール部分を戻り値を格納した一時変数で置き換えます。 93 ©CAPCOM 93
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float temp1; { // inlining 'readBuffer' //uint* buf = input; offset = addr; temp1 = uintBitsToFloat(Input_Buffer[offset]); } float v = temp1 * 10.0; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } GLSL 変数 値 addr id * 4 temp0 Input_Buffer input temp0 temp1 uintBitsToFloat(Input_Buffer[offset]) v temp1 * 10.0 代入をテーブルに登録して、 94 ©CAPCOM 94
インライン展開の例 readonly buffer Input { uint Input_Buffer[]; }; GLSL uint addr = id * 4; //uint* temp0; { // inlining 'getInputBuffer' //temp0 = Input_Buffer; } //uint* input = temp0; float temp1; { // inlining 'readBuffer' //uint* buf = input; offset = addr; temp1 = uintBitsToFloat(Input_Buffer[offset]); } float v = temp1 * 10.0; uint* getInputBuffer() { return Input_Buffer; } float readBuffer(uint* buf, uint offset) { return uintBitsToFloat(buf[offset]); } GLSL 変数 値 addr id * 4 temp0 Input_Buffer input temp0 temp1 uintBitsToFloat(Input_Buffer[offset]) v temp1 * 10.0 完了です。 以上のような手順でソースコードレベルでインライン展開を行っています。 ©CAPCOM 95 95
最適化事例 ではここからは、内製シェーダトランスレータを使用した最適化事例を紹介します。 96 ©CAPCOM 96
インライン展開の応用によるGPU最適化
インライン展開をすべての関数に適用したところ、GPUパフォーマンスが改善
副作用を持たない関数呼び出しについては計算順序の入れ替えを許容していた
⇒ 偶然性能が改善するケースを発見
Before
return (v.z <= 0.0) ? signNotZeroMultiply(p, (vec2(1.0) - abs(p.yx))) : p;
After
この三項演算子がDivergent Branchになっていた
vec2 temp = signNotZeroMultiply(p, (vec2(1.0) - abs(p.yx)));
return (v.z <= 0.0) ? temp : p;
// inlining 'signNotZeroMultiply'
Divergent Branchが消え、Latencyが改善
本来はシェーダコンパイラの最適化に任せる領域
⇒ シェーダコンパイラがいつでも優秀であるとは限らない・・・
まず、インライン展開の応用によるGPU最適化の事例をご紹介します。
先ほど実装したインライン展開ですが、
試しにすべての関数に適用してみたところGPUパフォーマンスが改善するケースがありました。
実装したインライン展開は副作用を持たない関数呼び出しについては計算順序の入れ替えを許容していました。
そのためインライン展開によって計算順序が変わる場合があり、偶然性能が改善するケースを発見できました。
97
具体例を一つ上げます。
三項演算子の中で関数呼び出しをしている例です。
インライン展開を適用すると下のように三項演算子と関数呼び出しの計算順序が入れ替わります。
変更前のコンパイル結果では、この三項演算子はDivergent Branchになっていました。
変更後はDivergent Branchが消え、Latencyが改善します。
このような最適化は本来はシェーダコンパイラに任せる領域だとは思います。
ですが、シェーダコンパイラがいつでも優秀であるとは限らないため、ときにはこのような書き換えにも効果があるようです。
©CAPCOM
97
インライン展開の応用によるGPU最適化の結果 GPUの処理時間 Before After Diff Diff % 29.26ms 29.12ms -0.14ms -0.47% (30フレーム間の合計の平均値) 「モンスターハンターライズ」のシーンでの計測結果です。 1フレームあたりのGPUの処理時間を比較しています。 98 適用前は29.26msでしたが、適用後は29.12msになりました。 0.14msの改善効果がありました。 ©CAPCOM 98
適用前 (29.26ms) こちらが適用前、 99 ©CAPCOM 99
適用後 (29.12ms) こちらが適用後です。 100 ©CAPCOM 100
Static SamplerによるCPU最適化 固定ステートのサンプラを定数へ置き換えることにより、CPUでのバインド処理を除去 Before: (Normal) Sampler GLSL uniform texture2D BaseMap; uniform sampler BilinearWrap; vec4 color = texture(sampler2D(BaseMap, BilinearWrap), uv); After: Static Sampler GLSL uniform texture2D BaseMap; const uint64_t BilinearWrap = 1; vec4 color = texture(sampler2D(BaseMap, sampler(BilinearWrap)), uv); SamplerPoolの1番目にはBilinearWrapのサンプラを固定で登録するようにしておく GL_NV_bindless_texture拡張を使用したNVIDIA GPU向けに特化した実装 ⇒ 非標準のベンダー拡張はDXCやGlslangではサポートされていないことが多い 次にStatic SamplerによるCPU最適化事例を紹介します。 固定ステートのサンプラはシェーダ内の定数へ置き換えることによって、CPUでのバインド処理を除去することができます。 101 この例ではBilinearWrapのサンプラをuint64_t型の定数の1に置き換えています。 そしてSamplerPoolの1番目にはBilinearWrapのサンプラを固定で登録するようにしておきます。 こうすることで変更前と同じサンプリング結果を得つつ、サンプラのバインド処理だけ除去することができます。 この実装ではGL_NV_bindless_texture拡張を使用したNVIDIA GPU向けに特化した実装にしています。 これはNintendo Switchにおいて最高のパフォーマンスが得られる実装方法です。 このような非標準のベンダー拡張はDXCやGlslangではサポートされていないことが多いですが、 内製トランスレータであれば柔軟に取り入れることができます。 これは内製トランスレータならではの最適化手法であると言えると思います。 ©CAPCOM 101
Static SamplerによるCPU最適化の結果 バインド関数の処理時間の合計 バインド関数の呼び出し回数 Before After Diff Diff % 0.40ms 0.11ms -0.29ms -72.5% 109 11 -98 -89.9% (120フレーム間の合計の平均値) 「モンスターハンターライズ:サンブレイク」のシーンでの計測結果です。 サンプラのバインド関数の処理時間の合計と呼び出し回数を比較しています。 102 適用前は0.40msかかっていましたが、適用後は0.11msになりました。 呼び出し回数も109回から11回に減っています。 「モンスターハンターライズ:サンブレイク」では盟勇と呼ばれる味方NPCが追加されましたが、 そのためにCPU処理をさらに削減する必要がありました。 Static SamplerによるCPU最適化はこのための処理負荷削減に一役買っています。 ©CAPCOM 102
細かいGPU最適化: bitfieldInsert
GLSLの組み込み関数とインタフェースが少しだけ異なるエンジンのライブラリ関数
単純な引数の分配ではうまく行かない
エンジンのライブラリ関数
uint VIA_BFI(uint mask, uint insert, uint base) { return (mask & insert) | (~mask & base); }
GLSLの組み込み関数
ビットマスク
オフセット
ビット数
uint bitfieldInsert(uint base, uint insert, int offset, int bits);
⇒ 引数の式や値に応じて自動で変換することによって命令数をわずかに削減
VIA_BFI(3 << offset, insert, base);
bitfieldInsert(base, insert >> offset, offset, 2);
VIA_BFI(0x70, insert, base);
bitfieldInsert(base, insert >> 4, 4, 3);
VIA_BFI(0x55, insert, base);
VIA_BFI(0x55, insert, base);
非常に細かいGPU最適化の事例も紹介します。
GLSLにはビットフィールドの値を設定するbitfieldInsertという組み込み関数があります。
RE ENGINEのライブラリ関数にも同様の処理を行う関数が用意されているのですが、
インタフェースが少しだけ異なっていてそのまま変換できないというケースがありました。
エンジンのライブラリ関数はAMDのISAに合わせたインタフェースになっていて、
ビットフィールドの範囲をビットマスクで指定するようになっています。
103
一方で、GLSLの組み込み関数ではオフセットとビット数で指定するようになっています。
これは単純な引数の分配ではうまく変換できません。
このような場合には、引数の式の形や値に応じて自動的にインタフェースに合うように変換するようにしています。
エンジンのライブラリ関数の実装のままでも正しく動作はしますが、GLSLの組み込み関数を
使ったほうが命令数をわずかに削減できるためこのような変換を施しています。
3番目のケースのように、引数の値によっては変換できないケースもあります。その場合はそのままです。
このようなエンジンの都合に合わせた細かい最適化も取り入れています。
©CAPCOM
103
インラインGLSL
HLSLでは文字列リテラルを書くことは許されている
⇒ HLSL内に文字列リテラルでGLSLを書けるようにしている
(変換時にダブルクォーテーションを外すだけ)
HLSL
uint wavePrefixCountBits(uint mask)
{
"return bitCount(mask & gl_ThreadLtMaskNV);";
return 0;
}
uint wavePrefixCountBits(uint mask)
{
return bitCount(mask & gl_ThreadLtMaskNV);
return 0;
}
GLSL
DXCに手を入れる必要はない
|-FunctionDecl <line:12:1, line:15:1> line:12:6 used wavePrefixCountBits 'uint (uint)'
| |-ParmVarDecl <col:26, col:31> col:31 mask 'uint':'unsigned int'
| `-CompoundStmt <col:37, line:15:1>
|
|-StringLiteral <line:13:3> 'literal string' lvalue "return bitCount(mask & gl_ThreadLtMaskNV);"
|
`-ReturnStmt <line:14:3, col:10>
⇒ ちょっとしたGLSLの実験に便利
最後にインラインGLSLを紹介します。
HLSLではソースコード中に文字列リテラルを書くことが許されています。
これを利用して、HLSLの中で文字列リテラルでGLSLを書けるように対応しています。
104
変換の実装は非常に簡単で、文字列リテラルを見つけたら単にダブルクォーテーションを外すように変換しているだけです。
これによって簡易的なインラインGLSLを実現しています。DXCに手を加える必要はありません。
文字列リテラルはもともと許されているので、StringLiteralノードとして構文解析されます。
これを利用すればトランスレータの実装を変更することなくHLSLの中でそのままGLSLを書けるので、
ちょっとしたGLSLの実験に便利でした。
実際にこれを使ってプラットフォーム固有の最適な実装を行った部分もありました。
上のwavePrefixCountBits関数の実装はその例です。
以上で最適化事例の紹介を終わります。
©CAPCOM
104
まとめと今後の展望 まとめと今後の展望です。 105 ©CAPCOM 105
まとめ シェーダトランスレータは頑張れば作れる • フロントエンドはDXCを使用することで省力化、バックエンドはかなり愚直な実装 シェーダトランスレータの実装はとても大変 • GLSLの言語機能はHLSLほど充実していない • GLSLには言語拡張がたくさんあって使える機能の把握が難しい ⇒ 使いこなせばGPUのすべての機能にアクセスできる 内製ならでは成果 • エンジンとプラットフォームの都合にぴったり合った変換ができた • インライン展開を応用したGPU最適化 • プラットフォーム専用の拡張機能を使用したCPU最適化 本セッションのまとめです。 まず、シェーダトランスレータは頑張れば作れるということを示しました。 HLSLからGLSLへの変換という高級言語間の直接変換というアプローチは、 誰でも思いつく方法ですが、本当に実装したという話はあまり聞かないと思います。 そもそもニッチな内容なのでなかなか表に出てこないということもあると思いますが、 頑張って実装すればちゃんと製品で採用できるレベルのものが作れます。 フロントエンドはDXCを使用することで省力化できます。 バックエンドはかなり愚直な実装ですがちゃんと動くものになっています。 106 ですが、実際の実装はとても大変でした。 GLSLの言語機能はHLSLほど充実していない部分が多いので、トランスレータでその差を埋めるための実装が色々と必要でした。 メモリレイアウトが異なる場合への対処や、buffer型の変数の扱いのためにインライン展開を行うなど、 かなり頑張った実装も行いました。 ©CAPCOM 106
まとめ シェーダトランスレータは頑張れば作れる • フロントエンドはDXCを使用することで省力化、バックエンドはかなり愚直な実装 シェーダトランスレータの実装はとても大変 • GLSLの言語機能はHLSLほど充実していない • GLSLには言語拡張がたくさんあって使える機能の把握が難しい ⇒ 使いこなせばGPUのすべての機能にアクセスできる 内製ならでは成果 • エンジンとプラットフォームの都合にぴったり合った変換ができた • インライン展開を応用したGPU最適化 • プラットフォーム専用の拡張機能を使用したCPU最適化 そこでは関数型言語の分野の研究の知見を役立てて実装を行いました。 またGLSLには言語拡張がたくさんあるので使える機能の把握も難しかったです。 ですが、これに関してはトランスレータを開発したことで 各種言語拡張にもかなり詳しくなれたので結果的にはよかったと思っています。 使いこなせばGPUのすべての機能にアクセスできるので、 汎用のシェーダトランスレータではできないプラットフォーム専用の実装も可能になります。 107 そして内製ならではの成果としては、まず、エンジンとプラットフォームの都合にぴったり合った変換ができた点です。 これは当たり前のようのですが重要なポイントです。 汎用のシェーダトランスレータでは対応していないプラットフォーム専用の特別な機能にも柔軟に対応できました。 それ以外には最適化事例でご紹介した、インライン展開を応用したGPU最適化や、 プラットフォーム専用の拡張機能を使用したCPU最適化などがありました。 ©CAPCOM 107
今後の展望 DXC SPIR-Vコード生成への移行 • • • • 2023年現在ではほとんどのケースで問題なく動く 内製シェーダトランスレータはHLSLの進化への追従と保守が大変 内製シェーダトランスレータの開発を通してDXCのコードベースにも慣れた 必要に応じて独自のカスタマイズも視野に SPIR-Vのより積極的な活用 • さらなる最適化のために低レイヤーへのアプローチ • 必要に応じて独自にベンダー拡張も取り入れる ⇒ 利便性とパフォーマンスを両立して高品質なゲーム制作を 今後の展望です。 本セッションでは内製シェーダトランスレータのお話をしてきましたが、 実は今後はDXCのSPIR-Vコード生成への移行を検討しています。 2023年現在、DXCのSPIR-Vコード生成はほとんどのケースで問題なく動きます。 実際にRE ENGINEのVulkanやMetalでの実装でも採用していますが、問題なく動いています。 108 また近年はHLSLの進化が著しいです。 テンプレートや演算子オーバーロードなどのC++の言語機能が積極的に取り込まれています。 内製シェーダトランスレータもこれらに追従しなければならず保守にコストがかかっています。 そして本セッションではお話ししませんでしたが、 内製シェーダトランスレータの開発の際に、実はDXC自体にも少し手を加えています。 それを通してDXCのコードベースもある程度把握できています。 必要に応じて独自のカスタマイズを施すことも視野に入れて移行を検討したいと考えています。 ©CAPCOM 108
今後の展望 DXC SPIR-Vコード生成への移行 • • • • 2023年現在ではほとんどのケースで問題なく動く 内製シェーダトランスレータはHLSLの進化への追従と保守が大変 内製シェーダトランスレータの開発を通してDXCのコードベースにも慣れた 必要に応じて独自のカスタマイズも視野に SPIR-Vのより積極的な活用 • さらなる最適化のために低レイヤーへのアプローチ • 必要に応じて独自にベンダー拡張も取り入れる ⇒ 利便性とパフォーマンスを両立して高品質なゲーム制作を 最後にSPIR-Vのより積極的な活用です。 さらなる最適化のためにより低レイヤーへのアプローチも行っていきたいと考えています。 こちらも必要に応じて独自にベンダー拡張を取り入れて対応していきます。 109 今後の展望は以上ですが、これは内製シェーダトランスレータの開発が無駄だったということが言いたいのではないです。 実際、トランスレータを開発することでHLSLとGLSLの理解が非常に深まりましたし、 シェーダコンパイラの特性もよくわかるようになりました。 今後はこの開発で得られた知見をもとにより低レイヤーへアプローチしていきたいということです。 今後も利便性とパフォーマンスを両立するための技術研究開発で、高品質なゲーム制作を支えていきたいと思っています。 ©CAPCOM 109
参考文献 OpenGL 4.60 Quick Reference Card https://www.khronos.org/files/opengl46-quick-reference-card.pdf OpenGL Shading Language 4.60 Specification https://registry.khronos.org/OpenGL/specs/gl/GLSLangSpec.4.60.pdf Vulkan 1.3 Specification https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html Kai Nacke. "Learn LLVM 12". 2021. 中田育男. "コンパイラの構成と最適化". 第2版, 2009. N.D. Jones, C.K. Gomard, and P. Sestoft. "Partial Evaluation and Automatic Program Generation". 1993. Kenichi Asai and Oleg Kiselyov. "Introduction to Programming with Shift and Reset". CW 2011 Tutorial. !7942: Native, first-class, delimited continuations – Glasgow Haskell Compiler / GHC https://gitlab.haskell.org/ghc/ghc/-/merge_requests/7942 参考文献です。 110 ©CAPCOM 110
ご清聴ありがとうございました 以上です。ご清聴ありがとうございました。 111 ©CAPCOM 111