この出版物は、三角形の描画セクション、つまりセットアップサブセクション、ベースコード、およびインスタンスの章の翻訳に専念しています。
コンテンツ
ベースコード
一般的な構造
前の章では、Vulkanのプロジェクトを作成し、正しく構成し、コードスニペットを使用してテストする方法について説明しました。この章では、基本から始めます。
次のコードを検討してください。
#include <vulkan/vulkan.h>
#include <iostream>
#include <stdexcept>
#include <cstdlib>
class HelloTriangleApplication {
public:
void run() {
initVulkan();
mainLoop();
cleanup();
}
private:
void initVulkan() {
}
void mainLoop() {
}
void cleanup() {
}
};
int main() {
HelloTriangleApplication app;
try {
app.run();
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
まず、LunarGSDKのVulkanヘッダーファイルを含めます。ヘッダーファイルで
stdexcepts
あり
iostream
、エラー処理と配布に使用されます。ヘッダーファイル
cstdlib
はマクロ
EXIT_SUCCESS
とを提供します
EXIT_FAILURE
。
プログラム自体はHelloTriangleApplicationクラスにラップされており、Vulkanオブジェクトをクラスのプライベートメンバーとして格納します。そこで、関数から呼び出される、各オブジェクトを初期化するための関数も追加します
initVulkan
。その後、フレームをレンダリングするためのメインループを作成しましょう。これを行うに
mainLoop
は、ウィンドウが閉じるまでループが実行される関数を入力します。ウィンドウを閉じて終了した後、
mainLoop
リソースを解放する必要があります。これを行うには、を入力し
cleanup
ます。
操作中に重大なエラーが発生した場合、
std::runtime_error
関数
main
でキャッチされる例外がスローされ、説明がに表示され
std::cerr
ます。このようなエラーの1つは、たとえば、必要な拡張子がサポートされていないというメッセージである可能性があります。標準の例外タイプの多くを処理するために、より一般的なものをキャッチします
std::exception
。
以降のほぼすべての章では、から呼び出される新しい関数と、プログラムの最後に
initVulkan
リリースする必要のある新しいVulkanオブジェクトが追加され
cleanup
ます。
資源管理
Vulkanオブジェクトが不要になった場合は、破棄する必要があります。 C ++を使用すると、RAIIまたはヘッダーファイルによって提供されるスマートポインターを使用して、リソースの割り当てを自動的に解除できます
<memory>
。ただし、このチュートリアルでは、Vulkanオブジェクトをいつ割り当ておよび割り当て解除するかを明示的に記述することにしました。結局のところ、これはVulkanの仕事の特徴であり、起こりうる間違いを避けるために各操作を詳細に説明することです。
チュートリアルを読んだ後、コンストラクタでVulkanオブジェクトを受け取り、デストラクタでそれらの割り当てを解除するC ++クラスを作成することにより、自動リソース管理を実装できます。要件に応じて、
std::unique_ptr
またはの独自の削除機能を実装することもでき
std::shared_ptr
ます。 RAIIの概念は、大規模なプログラムに推奨されますが、それについてさらに学ぶことは役に立ちます。
バルカンオブジェクトを直接ような関数を使用して作成されvkCreateXXXなど機能使用して、別のオブジェクトを介して割り当てられvkAllocateXXXを。オブジェクトが他の場所で使用されていないことを確認した後、vkDestroyXXXまたはvkFreeXXXを使用してオブジェクトを破棄する必要があります。これらの機能のパラメータは通常、オブジェクトのタイプによって異なりますが、共通のパラメータが1つあります
pAllocator
。これは、カスタムメモリ割り当てにコールバックを使用できるようにするオプションのパラメータです。マニュアルでは必要ありません
nullptr
。引数として渡します。
GLFW統合
Vulkanは、オフスクリーンレンダリングを使用する場合、ウィンドウを作成しなくても正常に機能しますが、結果が画面に表示される場合ははるかに優れています。
まず、行を
#include <vulkan/vulkan.h>
次のように置き換えます。
#define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h>
関数
initWindow
を追加し、
run
他の呼び出しの前にメソッドからの呼び出しを追加します。
initWindow
GLFWを使用して、ウィンドウを初期化および作成します。
void run() {
initWindow();
initVulkan();
mainLoop();
cleanup();
}
private:
void initWindow() {
}
の最初の呼び出しは、GLFWライブラリを初期化
initWindow
する関数で
glfwInit()
ある必要があります。GLFWは、もともとOpenGLで動作するように設計されました。OpenGLコンテキストは必要ないため、次の呼び出しを使用して作成する必要がないことを示します。
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
この状況を処理するには個別の考慮が必要なため、ウィンドウのサイズを変更する機能を一時的に無効にします。
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
ウィンドウを作成することは残っています。これを行うには、プライベートメンバー
GLFWwindow* window;
を追加し、次のコマンドでウィンドウを初期化します。
window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr);
最初の3つのパラメーターは、ウィンドウの幅、高さ、およびタイトルを定義します。4番目のパラメーターはオプションで、ウィンドウが表示されるモニターを指定できます。最後のパラメーターはOpenGLに固有です。
これらの値は他の場所で必要になるため、ウィンドウの幅と高さに定数を使用すると便利です。クラス定義の前に次の行を追加します
HelloTriangleApplication
。
const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;
呼び出しを置き換えてウィンドウを作成します
window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
次の機能が必要です
initWindow
。
void initWindow() {
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
}
mainLoop
ウィンドウが閉じられるまでアプリケーションを実行し続けるための メソッドのメインループについて説明しましょう。
void mainLoop() {
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
}
}
このコードは問題を提起するべきではありません。ユーザーがウィンドウを閉じる前にXボタンを押すなどのイベントを処理します。また、このループから、個々のフレームをレンダリングする関数を呼び出します。
ウィンドウを閉じた後、リソースを解放してGLFWを終了する必要があります。まず、
cleanup
次のコードに追加しましょう。
void cleanup() {
glfwDestroyWindow(window);
glfwTerminate();
}
その結果、プログラムを開始した後
Vulkan
、プログラムが閉じるまで表示される名前のウィンドウが表示されます。Vulkanを操作するためのスケルトンができたので、最初のVulkanオブジェクトの作成に移りましょう。
インスタンス
インスタンス化
最初に行う必要があるのは、ライブラリを初期化するためのインスタンスを作成することです。インスタンスは、プログラムとVulkanライブラリ間のリンクであり、インスタンスを作成するには、プログラムに関する情報をドライバーに提供する必要があります。
メソッド
createInstance
を追加し、関数から呼び出します
initVulkan
。
void initVulkan() { createInstance(); }
インスタンスハンドルを保持するインスタンスメンバーをクラスに追加します。
private:
VkInstance instance;
次に、プログラムに関する情報を特別な構造に入力する必要があります。技術的には、データはオプションですが、これにより、ドライバーはプログラムでの作業を最適化するために役立つ情報を取得できます。この構造は呼ばれ
VkApplicationInfo
ます:
void createInstance() {
VkApplicationInfo appInfo{};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "Hello Triangle";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "No Engine";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_0;
}
前述のように、Vulkanの多くの構造では、sTypeメンバーに明示的な型定義が必要です。また、この構造には、他の多くの構造と同様に
pNext
、拡張機能の情報を提供できる要素が含まれています。値の初期化を使用して、構造をゼロで埋めます。
Vulkanの情報のほとんどは構造を介して渡されるため、インスタンスを作成するのに十分な情報を提供するには、もう1つの構造を入力する必要があります。次の構造が必要です。これは、使用するグローバル拡張機能と検証レイヤーをドライバーに指示します。 「グローバル」とは、拡張機能が特定のデバイスではなく、プログラム全体に適用されることを意味します。
VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo;
最初の2つのパラメーターは問題を引き起こしません。次の2つのメンバーは、必要なグローバル拡張を定義します。ご存知のように、VulkanAPIは完全にプラットフォームに依存しません。これは、ウィンドウシステムと対話するための拡張機能が必要であることを意味します。GLFWには、必要な拡張機能のリストを返す便利な組み込み関数があります。
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;
最後の2つの構造メンバーは、含めるグローバル検証レイヤーを定義します。これらについては次の章で詳しく説明するので、今のところこれらの値は空白のままにしておきます。
createInfo.enabledLayerCount = 0;
これで、インスタンスの作成に必要なすべての作業が完了しました。電話をかける
vkCreateInstance
:
VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
原則として、オブジェクトを作成するための関数のパラメーターは次の順序です。
- 必要な情報を含む構造へのポインタ
- カスタムアロケータへのポインタ
- 新しいオブジェクトの記述子が書き込まれる変数へのポインター
すべてが正しく行われると、インスタンス記述子はインスタンスに格納されます。ほとんどすべてのVulkan関数はVkResult値を返します。これは、
VK_SUCCESS
エラーコードまたはエラーコードのいずれかです。インスタンスが作成されたことを確認するために結果を保存する必要はありません。簡単なチェックを使用しましょう:
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
throw std::runtime_error("failed to create instance!");
}
次に、プログラムを実行して、インスタンスが正常に作成されたことを確認します。
サポートされている拡張機能の確認
Vulkanの ドキュメントを見ると、考えられるエラーコードの1つがであることがわかります
VK_ERROR_EXTENSION_NOT_PRESENT
。必要な拡張機能を指定するだけで、サポートされていない場合は機能を停止できます。これは、ウィンドウシステムインターフェイスなどの主要な拡張機能には意味がありますが、オプション機能をテストしたい場合はどうでしょうか。
インスタンス化する前にサポートされている拡張機能のリストを取得するには、vkEnumerateInstanceExtensionProperties関数を使用します..。関数の最初のパラメーターはオプションであり、特定の検証レイヤーで拡張機能をフィルター処理できるため、ここでは空白のままにします。この関数には、拡張子の数が書き込まれる変数へのポインターと、拡張子に関する情報が書き込まれるメモリー領域へのポインターも必要です。
拡張情報を格納するためのメモリを割り当てるには、最初に拡張の数を知る必要があります。拡張機能の数を要求するには、最後のパラメーターを空白のままにします。
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
拡張情報を格納するための配列を割り当てます(忘れないでください
include <vector>
):
std::vector<VkExtensionProperties> extensions(extensionCount);
拡張機能に関する情報をリクエストできるようになりました。
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
各VkExtensionProperties構造には、拡張機能の名前とバージョンが含まれています。それらは単純なforループでリストできます(
\t
ここにインデントタブがあります):
std::cout << "available extensions:\n";
for (const auto& extension : extensions) {
std::cout << '\t' << extension.extensionName << '\n';
}
createInstance
Vulkanサポートの詳細については 、このコードを関数に追加できます。関数によって返されるすべての拡張子
glfwGetRequiredInstanceExtensions
がサポートされている拡張子のリストに含まれているかどうかを確認する関数を作成してみることもできます。
クリーニング
プログラムを閉じる前に、 VkInstanceを破棄する必要があります。これは
cleanup
、 VkDestroyInstance関数を使用して実行できます。
void cleanup() {
vkDestroyInstance(instance, nullptr);
glfwDestroyWindow(window);
glfwTerminate();
}
vkDestroyInstance 関数のパラメーターは自明です。前の章で説明したように、Vulkanの割り当て関数と割り当て解除関数は、使用しないカスタムアロケーターへのオプションのポインターを受け入れ、を渡し
nullptr
ます。インスタンスを破棄する前に、他のすべてのVulkanリソースをクリーンアップする必要があります。
より複雑な手順に進む前に、デバッグを容易にするために検証レイヤーを設定する必要があります。
C ++コード