MacでVulkanを使う方法 (MoltenVK + Xcode/CMake)

こんにちはtatsyです。

今回は年末年始に時間があったので、MacでVulkanを使う方法をまとめてみたいと思います。

Vulkanとその他のGraphics API


VulkanはOpenGLと同じKhronos groupという団体が提唱するGraphics APIの仕様です。

OpenGLは仕様が少し古く、複数のGPUへの対応であったり、描画命令のオーバーヘッドの多さであったりが問題視されるようになってきました。それにともなって、より複雑ながらも、よりGPUのハードウェアに近い部分を扱えるように考えられたのがVulkanです。

Vulkanは公式にはMacOS上では動作しないのですが、Vulkanと同じような思想のもとにAppleが開発しているMetalというGraphics API上でVulkanを実装したMoltenVKというフレームワークがあります。

MoltenVK
https://moltengl.com/moltenvk/

今回はこちらを使ってVulkanのサンプルプログラムを動かすところまでを解説したいと思います。

MoltenVKの導入


まずはMoltenVKのWebページからプログラムをダウンロードします。MoltenVKは商用で利用するためには料金がかかるようなのですが、とりあえず試すだけであれば無料で使用できます。

以下のWebページを開いて、Free Trialのボタンをクリックするとプログラムをダウンロードできます。

MoltenVK
https://moltengl.com/moltenvk/

ダウンロードしたZIPファイルを解凍すると「Molten-0.13.0」(2017年1月1日現在)というフォルダが現れるので、これを適当なディレクトリに配置します。特にビルド等は必要ありません。

自分の場合には/usr/local/include/に上記のフォルダを「Molten」にリネームして配置しました。

これでMoltenVKの導入は完了です。

GLFWの導入


Vulkanの仕様を策定しているKhronosが公開しているサンプルではウィンドウを表示するためにGLFWを使用しています。ですので、これらのサンプルを利用するためにGLFWを導入します。

こちらはGitHubからソースコードを落としてビルドをします。

GLFW
https://github.com/glfw/glfw

ビルドをする方法は以下の通り。今回はCMakeを使ってビルドしました。

$ git clone https://github.com/glfw/glfw.git
$ cd glfw
$ mkdir build && cd build
$ cmake ..
$ make
$ sudo make install

これでGLFWの導入は完了です。

プログラムの作成


作成といっても、こちらに関しては三角形を描画するプログラムが900行近くありますので、今回はコードをそのまま使います。

コードは以下のページからダウンロードできます。

https://vulkan-tutorial.com/code/hello_triangle.cpp

あとはこちらのプログラムをビルドすればいいのですが、いくつかのライブラリをビルド環境に加える必要があります。

ビルド環境については、今回はXcodeとCMakeでやり方を解説したいと思います。

Xcodeの場合

Xcodeを開いて「Create a New Xcode Project」からプロジェクトを作成します。

プロジェクトの種類は以下の画像のように「Command Line Tool」を選択します。

あとは画面の指示に従いつつ、プロジェクトを作成します。

自分の場合の設定はこんな感じになります。

続きまして、フレームワークの追加です。自分が試したところでは、必要なフレームワークは

  • Cocoa
  • OpenGL
  • IOKit
  • CoreVideo
  • Metal
  • QuartzCore

の6つでした。これらはプロジェクト設定画面の「General」の一番下にある「Linked frameworks and libraries」から追加していきます。

追加したあとの画面はこんな感じです。

つづきましてはMoltenVKのフレームワークを追加します。先程同様に「Linked frameworks and libraries」の「+」ボタンを押し、現れる選択画面下側にある「Add Other…」をクリックします。

そうするとフレームワークの場所を指定するようになるのですが、今回はシステムのディレクトリにMoltenVKのファイルを置いたので、ちょっと工夫が必要です。

この画面で「Command+Shift+G」を押すと現れる「Go to the folder」という画面にて、「/usr/local/include/Molten/MoltenVK/macOS/MoltenVK.framework」という風にフレームワークのディレクトリを追加します。

そうすると「MoltenVK.framework」というファイルに選択が合うと思うので、そのまま右下の「Open」を押すとフレームワークが追加されます。ここまでで必要なフレームワークの追加は終了です。

続きましてはGLFWのインクルードディレクトリおよびライブラリのリンクの設定です。

プロジェクト設定画面の「Build Settings」を開き、真ん中あたりにある「Search Paths」の「Header Search Paths」および「Library Search Paths」にGLFWのものを追加します。

おそらくは以下のような設定になるかと思います。

続きまして、この少し上の方にある「Linking」の中から、「Other Linker Flags」を見つけてそこに「-lglfw3」を追加します。

以上で、ビルド環境の設定は完了です。おそらく自動で作成されていると思われる「main.cpp」にチュートリアルのソースコードを貼り付ければビルドできるはずです。

CMakeの場合

正直どのくらい需要があるか不明なのですが、自分は通常プロジェクトをCMakeで作りますので、そういう方のために書いておきます。

基本は私が作成したCMakeLists.txtを見ていただければと思うのですが、留意するとしてはフレームワークの追加方法かと思います。

フレームワークの追加のためにはCMAKE_EXE_LINKER_FLAGSという変数に-framework (フレームワーク名)という文字列を追加します。MoltenVK以外のフレームワークはこれで設定できます。

MoltenVKに関しては上記の-framework MoltenVKに加えて、find_libraryでフレームワークの場所を指定してあげます。具体的には

set(VULKAN_ROOT "/usr/local/include/Molten/MoltenVK/macOS/")
find_library(VULKAN_LIBRARY NAMES MoltenVK PATHS "${VULKAN_ROOT}")

のようにしています。ここで名前をVULKAN_FRAMEWORKとかではなくしているのはWindows等で使うときと名前を揃えるためで、Macでしか使わないのであれば、別の名前でも大丈夫です。

あとはtarget_link_librariesVULKAN_LIBRARYをリンクしてあげればビルドできると思います。

Shaderプログラムの作成


先ほどサンプルプログラムをダウンロードしたGitHubにはShaderがないので、ShaderをGLSLで作成します。

上記のサンプルプログラムはVertexを3つ入力するプログラムです(611行目のvkCmdDrawの第2引数)。

このプログラムではVertex buffer等で頂点座標を渡しているわけではないのでVertex shaderの中で頂点位置を宣言して、三角形を作成します。

#version 450
#extension GL_ARB_separate_shader_objects : enable

out gl_PerVertex {
    vec4 gl_Position;
};

vec2 positions[3] = vec2[](
    vec2( 0.0, -0.5),
    vec2( 0.5,  0.5),
    vec2(-0.5,  0.5)
);

void main() {
    gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
}

Fragment shaderに関しては、特に何もせず、単に頂点の色を出力します。

#version 450
#extension GL_ARB_separate_shader_objects : enable

layout(location = 0) out vec4 outColor;

void main() {
    outColor = vec4(1.0, 0.0, 0.0, 1.0);
}

より詳細な説明に関してはVulkanの公式チュートリアルで解説されています。

https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules

Shaderのビルド


Vulkanでは、GLSL等のシェーダプログラムをビルドしてSPIR-Vというバイナリファイルに変換して使用します。この返還のためにはMoltenVKに付属している「MoltenVKShaderConverter」というプログラムを使用します。

サンプルプログラムでは、Vertex shaderのファイル名がvert.spv、Fragment shaderのファイル名がfrag.spvです。

これらのファイルをそれぞれvert.glslfrag.glslというファイルから生成することにすると、ビルドのためのコマンドは

$ MoltenVKShaderConverter -gi vert.glsl -so vert.spv -t v
$ MoltenVKShaderConverter -gi frag.glsl -so frag.spv -t f

のようになります。-giはGLSLの入力ファイルを、-soはSPIR-Vの出力ファイルを-tはGLSLのシェーダの種類を示すためのオプションです。

プログラムの実行


プログラムが実行できると以下のような画面が出力されるはずです。

どうやらMoltenVKがTrial版の場合には、画面にロゴが表示される仕様になっているみたいです。とはいえ、きちんと三角形を表示することができました。

その他の注意点


自分の実行環境 (MacOSX Sierra, Intel HD Graphics 4400) の場合には、以下のような不具合がありました。

  1. Validation layersが使用できない?
    24行目から28行目にかけて宣言されているenableValidationLayersですが、自分の環境ではtrueのとき、すなわちValidation layerが作成されるときには、プログラムが例外を投げて停止しました(原因は調べていません)。

  2. Vertex shaderで定義した頂点色がうまくFragment shaderに渡せない
    頂点の色が赤だけだと面白くないので、色が補完されるようにVertex shaderを以下のように書き換えたのですが、そうすると三角形が表示されなくなりました。
#version 450
#extension GL_ARB_separate_shader_objects : enable

out gl_PerVertex {
    vec4 gl_Position;
};

layout(location = 0) out vec3 fragColor;

vec2 positions[3] = vec2[](
    vec2( 0.0, -0.5),
    vec2( 0.5,  0.5),
    vec2(-0.5,  0.5)
);

vec3 colors[3] = vec3[](
    vec3(1.0, 0.0, 0.0),
    vec3(0.0, 1.0, 0.0),
    vec3(0.0, 0.0, 1.0)
);

void main() {
    gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
    fragColor = colors[gl_VertexIndex];
}

なお、以下のようにif文で分岐するプログラムにすると正しく動きました。見た感じgl_VertexIndexが正しく扱えていないように見えるのですが、原因はよくわかりませんでした。

#version 450
#extension GL_ARB_separate_shader_objects : enable

out gl_PerVertex {
    vec4 gl_Position;
};

layout(location = 0) out vec3 fragColor;

vec2 positions[3] = vec2[](
    vec2( 0.0, -0.5),
    vec2( 0.5,  0.5),
    vec2(-0.5,  0.5)
);

vec3 colors[3] = vec3[](
    vec3(1.0, 0.0, 0.0),
    vec3(0.0, 1.0, 0.0),
    vec3(0.0, 0.0, 1.0)
);

void main() {
    gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
    if (gl_VertexIndex % 3 == 0) fragColor = colors[0];
    if (gl_VertexIndex % 3 == 1) fragColor = colors[1];
    if (gl_VertexIndex % 3 == 2) fragColor = colors[2];
}

ちなみに上記のサンプルプログラムを改良して、もう少しあとのチュートリアルにでてくるVertex bufferを使うと上記の問題は起こりませんでした。

また、これもサンプルプログラムではないのですが、Vertex shaderからFragment shaderに値を渡すときにlocationを指定しないとうまく動かないこともありました。もしそういった症状に合われた方は、locationを指定してあげると治るかもしれません。

まとめ


というわけで、今回はVulkanをMacで使う方法について簡単に紹介をしました。一応、それなりに正しく動いているように見えるのですが、MoltenVKには公式ページでアナウンスされているようにいくつかの制約があります。

たとえば、Geometry shaderやCompute shaderが使えなかったりします。

https://www.moltengl.com/docs/readme/moltenvk-0.13.0-readme-user-guide.html#limitations

とはいえ、MacでVulkanを使う方法は今のところ他に存在しないので、これからサポート範囲が広がってくるといいなと思っております。

Vertex bufferやUniform buffer等の使い方についても引き続き勉強していきたいと思います。

最後までお読みいただきありがとうございました。