モチベーション
WWDC2018でOpenGLおよびOpenGL ES がdeprecatedになったというニュースは、10年以上OpenGLESを使ってきた私にとって少なからずショックでした。GLNextとしてMetal やVulkanの存在は知っていたものの、当面の間OpenGLESも併存すると考えていたからです。
このままでは、私自身が deprecated で obsolete な人間になってしまう危機感を強く感じました。いつまでもナウでヤングな人間でいたいので、ある種ショック療法的に、Vulkanの勉強を始めることにしました。Metalじゃないのは、私が組み込み屋だからです。
少しずつですが、OpenGLESとVulkanとの差分について書いていければと考えています。
環境
Windows10
VisualStudio2015
NVIDIA GeForce GTX 1060
LunarG Vulkan SDK
まずはVulkanインスタンス作成から
OpenGLESでは、まず最初に eglGetDisplay()
関数を用いて Display という概念のオブジェクトを取得します。この Display は、それ以降、コンテキストやサーフェースを生成するにあたっての全ての親玉になります。全ての親玉が Display であるということが示す通り、OpenGLES の世界観は「すべては画面表示(描画)のため」という思想にたっています。
これに対してVulkanでは、vkCreateInstance()
関数を用いて VkInstance というオブジェクトを生成するところから始まります。無味乾燥なオブジェクト名にしているのは、GPUを描画用としてだけではなくコンピューティング用として使うことも想定しているからだと思います。
vkCreateInstance()
VkInstanceを構築するAPIは次の通りです。
インスタンスを構築するための設定情報 inst_info を引数として渡します。
VkResult err;
VkInstance instance;
err = vkCreateInstance (&inst_info, // [in ] インスタンス構築情報
NULL, // [in ] 必要ならホストメモリアロケータを指定
&instance); // [out] 作成されたインスタンスが格納される
ホストCPU側のメモリ確保を行うメモリアロケータを外部から指定することができるあたりに仕様のこだわりを感じます。OpenGLESでは、ライブラリが内部で勝手にバカバカとmalloc()しますが、特に組み込み機器ではメモリ消費量のコントロールができないことが課題にされることもありました。
VkInstanceCreateInfo構造体
vkCreateInstance()
の第一引数では、下記の構造体を設定情報として指定します。
Vulkanの世界では、構造体の先頭メンバに、その構造体の型を明示的に記述させるようです。将来的にVulkanのバージョンアップにより構造体メンバの仕様が変更されてもアプリ互換性を維持するという目的のためでしょうか。あるいは、アプリが誤って間違ったポインタを引数に渡してしまった場合でもそれを検知可能にすることを狙っているのでしょうか。
VkInstanceCreateInfo inst_info = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, // この構造体の型。必ずVK_STRUCTURE_TYPE_INSTANCE_CREATE_INFOを指定
.pNext = &dbg_msg_info, // 拡張機能用のポインタ
.flags = 0, // reserve
.pApplicationInfo = &app_info, // アプリ固有情報
.enabledLayerCount = enabled_layer_count, // 有効化するグローバルレイヤの数
.ppEnabledLayerNames = NULL, // グローバルレイヤの名前
.enabledExtensionCount = enabled_extension_count, // 有効化するグローバル拡張の数
.ppEnabledExtensionNames = NULL, // グローバル拡張の名前
};
アプリが使いたいレイヤ(後述)および拡張機能は、あらかじめインスタンスを生成するタイミングで事前に宣言しておく必要があるようです。
OpenGLESでは拡張機能に対する事前宣言の必要はありませんでした。
ここでは、レイヤも拡張も使わない (num = 0) として構造体を初期化しています。
VkApplicationInfo構造体
VkInstance構築時の設定情報として、アプリ固有の情報(VkApplicationInfo)を VkInstanceCreateInfo にぶら下げることもできます。このアプリ固有情報は設定してもしなくてもどちらでも良いです。
const VkApplicationInfo app_info = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, // この構造体の型。必ずVK_STRUCTURE_TYPE_APPLICATION_INFOを指定
.pNext = NULL, // 拡張機能用のポインタ
.pApplicationName = APP_NAME, // アプリ名
.applicationVersion = 0, // アプリバージョン
.pEngineName = APP_NAME, // アプリを作るためのエンジン(もしあれば)
.engineVersion = 0, // エンジンのバージョン
.apiVersion = VK_API_VERSION_1_0, // アプリが使いたいVulkanのバージョン
};
アプリ名やエンジン名を指定することができますが、指定すると何が良いことがあるのでしょうか。Vulkanランタイムから見れば、「このアプリは有名なベンチマークアプリだ」ということを検知できるようになるので、例えば特定アプリを動かす時だけはGPUの制御ポリシをベンチマーク専用スペシャルモードに変化させて、電力をガンガン使ってでも、競合ベンダよりも良いスコアを出そうとする、なんてことは想像できます。が、そんなことを推奨しているのかな。
そういうズル(?)をするしないに関わらず、ライブラリとアプリとは疎結合であるべきだと思うのですけど。
また、アプリが使いたいVulkanのバージョン指定もこの構造体でひとまとめに設定します。OpenGLESでは、GLES2以降を使う場合はeglCreateContext() で明示的に指定する必要がありました。それと同様に、将来Vulkan2.0が出た時にはここで指定するのでしょうか。
今回のまとめ
vkCreateInstance()
とその引数である構造体について勉強しました。OpenGLESでいえば、eglGetDispay()
を呼んだにすぎません。こんなペースでやってたら、三角形一個を画面表示できるようになるのはいつになることやら。
ソースコード
https://github.com/terryky/VulkanSample
上記の 001_CreateInstance