- M1は長年OpenGL 4.1にとどまっていたが、Fedora Asahi Remixの最新M1/M2ドライバーが OpenGL 4.6 と OpenGL ES 3.2 を完全にサポート
- AsahiのオープンソースLinuxドライバーは、ベンダーの非適合な4.1ドライバーとは異なり、Khronosの一覧に掲載された 適合性認証済みドライバー であり、Blenderのような現代的なOpenGLワークロードとの互換性を広げる
- OpenGL 4.6はrobustness、SPIR-V、clip control、cull distance、compute shaders、強化されたtransform feedbackを要求するが、M1ハードウェア は最新のグラフィックス標準にそのまま適合しているわけではない
- ハードウェアにない機能はドライバーとコンパイラーの手法で補完される:geometry shaders、tessellation、transform feedbackはcompute shadersで、cull distanceとclip controlはshader変換で処理する
- 100,000件以上の適合性テストに合格するため、バッファーと画像の robustness をソフトウェアで実装し、clamp・preamble最適化・mipmap回避策で追加コストを削減
Fedora Asahi RemixでOpenGL 4.6/ES 3.2が利用可能に
- M1はこれまでOpenGL 4.1までしかサポートしていなかったが、今回 OpenGL 4.6 と OpenGL ES 3.2 をサポート
- 最新のM1/M2シリーズ向けドライバーは Fedora Asahi Remix をインストールすることで利用できる
- すでにインストール済みのユーザーは次のコマンドで更新する
- Asahiの オープンソースMesaドライバー は、ベンダーの非適合な4.1ドライバーとは異なり、最新のOpenGLバージョンに対する 適合性(conformance) を備える
- 適合した4.6/3.2ドライバーは、正確性を保証するために 100,000件以上のテスト に合格する必要がある
- 6か月前にOpenGL ES 3.1ドライバーとしてM1向けの標準グラフィックスAPI初の適合ドライバーが登場したのに続き、今回はOpenGL 4.6まで完了した
- Vulkan対応も進行中
OpenGL 4.1の壁を越えるための機能実装
- OpenGL 4.6は4.1と比べて複数の 必須機能 を追加している
- Robustness
- SPIR-V
- Clip control
- Cull distance
- Compute shaders
- アップグレードされたtransform feedback
- M1はOpenGL ES 3.1より新しいグラフィックス標準にはうまく適合していない
- Vulkanはいくつかの機能をオプションにしているが、DirectXとOpenGLをその上に階層化するには欠けている機能が必要になる
- M1の既存ソリューションはOpenGL 4.1の機能セットを越えられなかった
- ハードウェアサポートのない新機能は ドライバー手法 で実装される
- Geometry shaders、tessellation、transform feedbackはcompute shadersで処理する
- Cull distanceは変換された補間値として扱う
- Clip controlはvertex shader epilogueで実装する
バッファーrobustnessとM1のソフトウェア補正
- GPUは従来、安全性より性能を優先してきたため、shaderがバッファー範囲外を読み取る誤ったコードでは 未定義動作 が発生する可能性がある
- Webブラウザーのように信頼できないshaderを扱うアプリケーションにとって、このようなトレードオフは望ましくない
- グラフィックスAPI自体はセキュリティ境界ではないため、一定のsanitizationは必要になる
- APIの未定義動作を減らすことは多層防御に役立つ
- Robustnessを有効にすると、アプリケーションは一部の性能を犠牲にする代わりに、範囲外アクセスについて定義済みの動作を選択できる
- APIごとの範囲外バッファーロードの結果は異なる
- Direct3DとVulkan
robustBufferAccess2:0を返す
- OpenGLとVulkan
robustBufferAccess:0またはバッファー内の一部データを返す
- OpenGL ES:任意の値になり得るが、クラッシュはしない
- OpenGLの要件は、範囲外アクセスで0またはバッファー内データを返すことなので、最後の有効インデックスとアクセスインデックスの unsigned minimum を計算し、安全なインデックスでロードできる
- robustnessなしのuniform bufferロード:
load.i32 result, buffer, index
- robustness適用後:
umin idx, index, last の後に load.i32 result, buffer, idx
- M1の preamble は、すべてのthreadで同じ値を繰り返し計算せず、一度計算して再利用する
- uniform bufferのサイズは固定されているため、追加のrobustness演算もpreambleへ移動できる
- robust storage bufferでもload/store自体は移動できなくても、clamp計算はpreambleに移せる
Vertex buffer robustnessの実装
- グラフィックスAPIでは、アプリケーションがvertex bufferのGPU base addressとattribute layoutを設定する
- 各attributeはoffsetとformatを持つ
- bufferはvertexあたりのバイト数を示すstrideを持つ
- vertex shaderはvertexを基準にattributeを暗黙的にインデックスして読み取る
- 一部のハードウェアはrobust vertex fetchをネイティブに実装したり、bounds-checked bufferでソフトウェアfetchを高速化したりするが、M1にはどちらもない
- M1 GPUのメモリロードは64ビットbase addressとelement単位のoffsetを受け取り、
imad 整数multiply-add命令も提供する
- 32ビットattributeロードは
imad idx, stride/4, vertex, offset/4 と load.i32 result, base, idx の2命令で実装できる
- 4つの32ビット値が密に配置されたvector attributeは
load.v4i32 result, base, vertex << 2 の1命令でロードできる
- Robustnessにはclampが必要だが、vertex bufferサイズはbyte単位であり、最適化されたloadはvertex単位のindexを使う
- 1つのbuffer内の複数のattributeとoffsetを、attributeごとの個別のbase addressのように再解釈して問題を解く
- offsetをshaderで加算する代わりに、attributeごとのbaseを渡す
- byte単位のbufferサイズを各attributeに対するvertex単位のサイズへ変換できる
- offsetではなくvertex indexをclampする
- driverは各attribute formatのサイズを使い、最後の有効vertex indexを事前に計算してshaderへ渡す
- bufferが何もロードできないほど小さい場合、clampでは解決できないため、そのattributeのbufferを小さな zero buffer に差し替える
- attributeごとのbase addressを使うため、この判定もattributeごとに可能
- 最終的に、少量のdriver計算と
umin 1つ分のコストでrobust vertex bufferを実装する
Image robustnessとmipmap回避策
- Buffer robustnessに加えて image robustness も必要であり、範囲外のimage loadは0を返さなければならない
- Mipmapped imageは複数のlevel of detailを含む
- base levelは元画像
- 次の各levelは前のlevelを縮小した画像
- レンダリング時、ハードウェアは画面サイズに近いlevelを選択し、効率と視覚品質を改善する
- 仕様では、robustnessにおいてimage loadが次の場合に0を返すことを要求している
- XまたはY座標が範囲外の場合
- levelが範囲外の場合
- M1 GPUのimage load動作は要件と異なる
- XまたはY座標が範囲外なら0を返す
- levelが範囲外なら最後のlevelの値を返す
- ベンダーがハードウェア文書を公開していないため、この動作が意図したものなのかハードウェアバグなのかは分からず、適合性に合格するには回避策が必要になる
- 単純な回避策は、levelが有効なときだけloadし、そうでなければ0を返すbranch方式だが、branchは非効率
- よりよい方法は、範囲外levelでもloadがクラッシュしないことを利用し、先にloadしてからcompare-selectで0を選ぶこと
- ただしM1 GPUの命令セットはscalarで、image loadはred/green/blue/alphaの4component vectorを返す
- componentごとに
ulesel が必要になり、assemblyが大きくなる
- 最終的な回避策は、XまたはYが範囲外ならハードウェアが0を返す点を利用する
- 最大image幅は16384pxなので、Xを20000のような値に変えれば範囲外になる
- levelが有効なら元のXを使い、無効ならXを20000に変えてimage loadが0を返すようにする
- この方式はvector全体を選択せずscalarを1つだけ変えるため、コンパクトなassemblyにコンパイルされる
- 定数をuniform registerにあらかじめ載せておけば、回避策のコストは単一命令になる
- この方式でconformanceに合格した
1件のコメント
Hacker News の意見
Alyssa Rosenzweig は、コミュニティに貢献し続ける大きな贈り物のような存在だと思う
ブログ記事を読むたびに、現代の グラフィックスハードウェアの内部構造 について知らなかったことを必ず学べる
言葉より 実力 が毎回勝つことを示している仕事だ
ブログを読むだけでも頭が熱くなるほど解きほぐす内容が多く、結論は最後の文ではなく2文目にあるのに、結局ビット操作の一つひとつを追ってウサギ穴に入っていくことになる
段落あたりの気づきの数のようなベンチマークがあるなら、Alyssa が全部1位になりそうだ
いつか Apple が OpenGL 3.3 core を廃止したら、最終的にはみんなもそれに続いて廃止することになるのかもしれない
一般に OpenGL は Vulkan より使いやすいと聞くが、複雑すぎる API は経験の浅い開発者が GPU を活用するのを難しくし、参入障壁となってインディーゲーム開発者を締め出しかねない
最近はみんな Unity と Unreal を使うので、ゼロから作ったり別のエンジンを使ったりするのが奇妙に見えるし、Unity がさらに囲い込もうとした後でゲーム開発界隈が目を覚ます様子を見るのは興味深い一方で苛立たしくもある
ゲーム開発におけるオープンソースは常に厳しい状況で、Godot はあるものの Unity や Unreal に本気で対抗するのは難しそうに見える
Godot で十分可能だとしても、インディー開発者は Unity や Unreal のほうに慣れていて、そのまま残る可能性が高い
ゲーム開発の オープンソースの状況 は時に絶望的に感じられ、次世代グラフィックス API の登場も物事を楽にはしていない
OpenGL の三角形レンダリング例は約200行で、Vulkan の三角形レンダリング例は約1000行ある
Vulkan は非常に柔軟に設計されている代わりに、便利機能は多くない
いずれにせよ OpenGL はドライバの直接 API として公開するには高レベルすぎたし、Vulkan のような低レベル API を基盤レイヤーにして、その上に OpenGL のようなものを載せるほうが GPU ハードウェアの動作方式により合っている
また、全員が Unity と Unreal を使っているわけでもない
The Game Awards 2023 のゲーム・オブ・ザ・イヤー候補6作品はすべて自社エンジンで作られており、インディーでも Hades のように独自エンジンを作る開発者は今もいる
ただし大多数は既製のエンジンを使うのが確かだ
必要な機能をすべて満たし、状態ベースのレンダリングパイプラインを受け入れられるなら、新規プロジェクトで使うのも良い選択だ
まだ動作はするし、最近は Metal 上のレイヤーとして実行されるが、macOS や iOS 向けの GL コードをビルドすると廃止予定の警告がずっと出る
define で消すことはできる
OpenGL の問題は、GPU の動作方式からあまりに離れていて、良い性能を出しにくいことにある
この作業のうちどれほどが M1 GPU コード に結び付いていて、どれほどの機能の上に機能を重ねる実装が他の場所でも再利用できるのか気になる
Zink がよりプリミティブな Vulkan の上で複雑な OpenGL 機能を動かすやり方と非常によく似ているように見えるが、M1 にはまだターゲットにできる Vulkan バックエンドがない
結局は作業量の問題であり、さまざまなハードウェアに再利用できる
古くてよく理解されているが、現代のワークロードには単独では使いにくいハードウェアにも役立つ可能性がある
これによる 性能への影響 が、特に macOS で Metal を直接使う場合と比べてどの程度なのか、とても気になる
答えは間違いなく「状況による」だろうが、それでも気になる
記事の中に答えがあるのかもしれないが、ほとんど理解できなかった
「ハードウェアサポート」も通常は GPU マイクロコード で実装され、同じシリコンを通ることが多い
どんな機能でも性能のボトルネックになり得るし、実際に試してみるまではどこが詰まるか分かりにくい
Apple GPU がジオメトリシェーダーをネイティブにサポートしていないのは確かだが、ジオメトリシェーダーは設計が良くなく、GPU ハードウェアにうまく合わない
実際にサポートしているハードウェアでも遅いことで知られており、Nvidia がメッシュシェーディングを設計したのには理由がある
トランスフォームフィードバックもよく話題に上るが、Apple GPU はどのシェーダーステージからでも任意のメモリ位置に書き込めるので、トランスフォームフィードバックは実質的に不要だ
要点は、Apple が簡潔な コンピュートアーキテクチャ を実装する一方で、古い残滓や、うまく機能しないことが知られている機能をかなり切り捨てたという点だ
「M1 が OpenGL 4.1 に閉じ込められた」という表現は適切ではないように思う
OpenGL についてはかなり前から追っていないので、4.1 以降のどの機能を指しているのか分からないが、OpenGL ではできて Metal ではできない機能があるなら非常に驚くだろう
逆に Metal では可能だが OpenGL ではまったく不可能なことは多く、Metal シェーディング言語に完全なポインタがあることからしてその例だ
これは M1 の Fedora 向けのもの
macOS でも可能になったら驚きですが、それを作るには何が必要なのか気になります
M1 GPU 向けの初期 Mesa ドライバも、IOKit 経由で macOS の AGX ドライバにコマンドバッファを送る方式でブートストラップしていました
https://rosenzweig.io/blog/asahi-gpu-part-2.html
https://github.com/AsahiLinux/gpu/blob/main/demo/iokit.c
なので、GPU の surface を macOS の画面に合成できる対象として渡すための、Mesa 側の接着コードがもう少し必要です
まともな Vulkan や OpenGL 実装を作るには、GPU 処理を担当するカーネル側の相方が必要だと理解しています
おそらくそのため、macOS 向けのネイティブ Vulkan を実装しようとする人がいないのでしょう
ただ、Apple のドライバ上で可能ならどうなのかはよく分かりません
ただし、最適ではない レガシー API のためにそこまで多くのリソースを割く理由があるのかは分かりません
範囲外アクセスをトラップから任意データの返却に変えることを 堅牢性 と呼ぶのは、かなり面白いですね
グラフィックスプログラミングは確かに変です
GPU ドライバの本質は、壊れたアプリケーションを動かしたり、より速く動かしたりすることです
デフォルトを厳格にしたところで、壊れたコードをリリースするビデオゲーム業界の構造的な問題は直らず、ユーザーが離れるだけです
分岐が概して非常に高価なハードウェアでは、システムに境界ケースを最も効率的な方法で静かに処理させるフラグは有用に見えます
プログラマーが、そうした境界ケースが最終的なレンダリングフレームにほとんど影響しないと合理的に確信できる、有効な用途も多いと思います
GPU が全般的にトラップをあまり好まないという点と合わせると納得できます
Carmack も megatexture を設計した際、メーカーに仮想メモリのアイデアを受け入れさせるのは苦痛だったと語ったことがあります
「性能が安全より優先」という文化が支配する分野で他のプログラミング言語について話すのは、壁に向かって話すようなものです
間違いなく非常に興味深い取り組みですが、なぜ Vulkan を先に目標にしなかったのか気になります
最近ではより重要なターゲットに見えますし、その上にはすでに OpenGL 実装もあります
特定の OpenGL 機能をサポートするには、Vulkan ドライバがそれに対応する機能をサポートしている必要があり、通常は拡張が必要です
つまり、基本的な Vulkan ドライバを実装しただけで OGL 4.6 対応がただで手に入るわけではなく、Mesa に OGL 4.6 を Vulkan へ変換させるには、Vulkan ドライバに OGL 4.6 の機能 をすべて実装する必要があります
さらに Alyssa は、リバースエンジニアリングと OpenGL ドライバのプロジェクトをすでに経験している人です
詳しい事情は分かりませんが、慣れた API 向けのドライバを作るほうが、慣れていない API 向けのドライバを作るより、はるかに簡単で速い可能性が高いです
そこからより新しい OpenGL まで引き上げる作業は、完全な Vulkan 実装 より少ない作業で済んだ可能性が高く、Vulkan に必要なことも多く学べたのだと思います
そのため、より早く動くものを得るために 低いバージョンの OpenGL から選んだように見えます
90年代に John Carmack が Quake II で OpenGL を使うことにこだわっていなければ、3Dゲームにおいて OpenGL が存在感を持てなかったかもしれないと考えると、かなり驚きです
OpenGL を最終的にその姿にしたのは SGI と、複数のシステムやアーキテクチャで互換実装を作るための巨大な取り組みです
https://web.archive.org/web/19970707113513/http://www.opengl.org/
3Dゲームに関する多くのものは Doom と Quake のおかげです
Microsoft は API 改善のために大きな非互換変更を続ける勇気があり、OpenGL は互換性への懸念に足を引っ張られていたと見ていました
Direct3D はマルチスレッドをよりうまく扱い、最新バージョンでは状態管理もより優れていると述べました
それでも id Software が OpenGL にとどまっているのは惰性によるもので、利点はあるものの Direct3D に移行する計画はないと述べました
出典: https://www.bit-tech.net/news/gaming/pc/carmack-directx-better-opengl/1/