この投稿は、前の投稿「Vulkan。開発者ガイド。三角形の描画」の続きであり、 検証レイヤーの章の翻訳に専念しています。
コンテンツ
検証レイヤー
検証レイヤーとは何ですか?
Vulkan APIの設計は、ドライバーの負荷を最小限に抑えるという考えに基づいているため、デフォルトでは、エラーを検出する機能は大幅に制限されています。列挙内の誤った値やnullポインターの受け渡しなどの単純なエラーでさえ、通常は明示的に処理されず、クラッシュや未定義の動作につながります。 Vulkanを使用するには、各アクションの詳細な説明が必要なため、このようなエラーは非常に頻繁に発生する可能性があります。
この問題を解決するために、Vulkanは検証レイヤーを使用し ます。検証レイヤーは、追加の操作を実行するために関数呼び出しにプラグインできるオプションのコンポーネントです。検証レイヤーでは、次の操作を実行できます。
- エラーを検出するための仕様に従ってパラメータ値をチェックする
- リソースリークトラッキング
- ストリーミングの安全性チェック
- 各呼び出しとそのパラメーターのログ
- プロファイリングとリプレイのためのVulkanコールトラッキング
以下は、検証レイヤーに関数を実装する方法の例です。
VkResult vkCreateInstance(
const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkInstance* instance) {
if (pCreateInfo == nullptr || instance == nullptr) {
log("Null pointer passed to required parameter!");
return VK_ERROR_INITIALIZATION_FAILED;
}
return real_vkCreateInstance(pCreateInfo, pAllocator, instance);
}
検証レイヤーを相互に組み合わせて、必要なすべてのデバッグ機能を使用できます。また、検証レイヤーはデバッグビルドでは有効にし、リリースビルドでは完全に無効にすることができます。これは非常に便利です。
Vulkanには検証レイヤーが組み込まれていませんが、LunarGのVulkan SDKは、最も一般的なバグを追跡するための優れたレイヤーセットを提供します。すべてのレイヤーは オープンソースであり、追跡しているバグをいつでも確認できます。検証レイヤーのおかげで、未定義の動作に関連するさまざまなドライバーのエラーを回避できます。
検証レイヤーを使用するには、検証レイヤーをシステムにインストールする必要があります。たとえば、LunarGの検証レイヤーは、VulkanSDKがインストールされている場合にのみ使用できます。
以前は、Vulkanにはインスタンス固有とデバイス固有の2種類の検証レイヤーがありました。つまり、インスタンスレイヤーはグローバルVulkanオブジェクトに関連する呼び出しをチェックしますが、デバイスレイヤーは特定のGPUに関連する呼び出しのみをチェックします。この時点でデバイスレイヤーは非推奨になるため、インスタンス検証レイヤーはすべてのVulkan呼び出しに適用されます。仕様では、一部の実装で必要となる互換性の提供を含め、デバイスレベルの検証レイヤーを含めることを引き続き推奨しています。インスタンスと論理デバイスに同じレイヤーを指定します。これについては後で説明します。
検証レイヤーの使用
このセクションでは、VulkanSDKによって提供されるレイヤーを接続する方法を見ていきます。拡張機能の場合と同様に、それらを接続するレイヤーの名前を指定する必要があります。私たちに役立つすべてのチェックは、「
VK_LAYER_KHRONOS_validation
」という名前のレイヤーに収集され ます。
2つの構成定数を追加しましょう。最初の(validationLayers)には、含める検証レイヤーが一覧表示されます。 2番目(enableValidationLayers)は、ビルドモードに応じて接続を許可します。このマクロ
NDEBUG
はC ++標準の一部であり、「デバッグなし」の略です。
const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;
const std::vector<const char*> validationLayers = {
"VK_LAYER_KHRONOS_validation"
};
#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif
checkValidationLayerSupport
必要なすべてのレイヤーが使用可能かどうかをチェックする新しい関数を追加しましょう 。まず、を使用して使用可能なレイヤーのリストを取得しましょう
vkEnumerateInstanceLayerProperties
。その使用法は
vkEnumerateInstanceExtensionProperties
、前に見た関数と似ています 。
bool checkValidationLayerSupport() {
uint32_t layerCount;
vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
std::vector<VkLayerProperties> availableLayers(layerCount);
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
return false;
その後、からのすべてのレイヤー
validationLayers
がに存在するかどうかを確認 し
availableLayers
ます。あなたは接続する必要があるかもしれません
<cstring>
ため
strcmp
。
for (const char* layerName : validationLayers) {
bool layerFound = false;
for (const auto& layerProperties : availableLayers) {
if (strcmp(layerName, layerProperties.layerName) == 0) {
layerFound = true;
break;
}
}
if (!layerFound) {
return false;
}
}
return true;
この関数は次の場所で使用できるようになりました
createInstance
:
void createInstance() {
if (enableValidationLayers && !checkValidationLayerSupport()) {
throw std::runtime_error("validation layers requested, but not available!");
}
...
}
プログラムをデバッグモードで実行し、エラーがないことを確認します。
構造
VkInstanceCreateInfo
で、接続された検証レイヤーの名前を指定します。
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
createInfo.enabledLayerCount = 0;
}
チェックに合格した場合
vkCreateInstance
、エラーは返されません
VK_ERROR_LAYER_NOT_PRESENT
が、プログラムを実行してこれを確認することをお勧めします。
デバッグメッセージの傍受
デフォルトでは、検証レイヤーはデバッグメッセージを標準出力に送信しますが、コールバック関数を提供することで自分で処理できます。これにより、すべてのメッセージにエラー警告が含まれているわけではないため、受信するメッセージをフィルタリングできます。この手順をスキップする場合は、章の最後のセクションに直接スキップしてください。
コールバック関数を接続してメッセージを処理するには、を使用してデバッグメッセンジャーを構成する必要があります
VK_EXT_debug_utils
。
まず、
getRequiredExtensions
検証レイヤーが接続されているかどうかに応じて、必要な拡張機能のリストを返す関数を追加しましょう 。
std::vector<const char*> getRequiredExtensions() {
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
if (enableValidationLayers) {
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
return extensions;
}
GLFW拡張機能が必要であり、条件に基づいてデバッグメッセンジャー拡張機能が追加されます。
VK_EXT_DEBUG_UTILS_EXTENSION_NAME
タイプミスを避けるためにマクロを使用していることに注意してください 。
これで、この関数を
createInstance
次の場所で使用できます 。
auto extensions = getRequiredExtensions();
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();
プログラムを実行して、エラーが発生したかどうかを確認します
VK_ERROR_EXTENSION_NOT_PRESENT
。
それでは、コールバック関数自体が何であるかを見てみましょう。プロトタイプを使用して新しい静的メソッドを追加しましょう
PFN_vkDebugUtilsMessengerCallbackEXT
。
VKAPI_ATTR
そして
VKAPI_CALL
この方法は、正しいシグネチャを持っていることを確認してください。
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData) {
std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;
return VK_FALSE;
}
最初のパラメーターは、メッセージの重大度を決定します。
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT
:診断メッセージVK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
:リソースの作成などの情報メッセージVK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
:必ずしも正しくない動作に関するメッセージですが、エラーを示している可能性がありますVK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
:クラッシュにつながる可能性のある誤った動作に関するメッセージ
列挙の値は、比較操作を使用して、しきい値を超えるまたは下回るメッセージを除外できるように選択されます。たとえば、次のようになります。
if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
// Message is important enough to show
}
パラメータに
messageType
は、次の値を指定 できます。
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
:発生したイベントは仕様やパフォーマンスとは関係ありませんVK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
:発生したイベントが仕様に違反しているか、エラーの可能性を示しているVK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT
:バルカンは最適に使用されない可能性があります
パラメータ
pCallbackData
は
VkDebugUtilsMessengerCallbackDataEXT
、メッセージの詳細を含む構造 を参照します。構造の最も重要なメンバーは次のとおりです。
pMessage
:nullで終了する文字列としてのデバッグメッセージpObjects
:メッセージに関連するオブジェクトの記述子の配列objectCount
:配列内のオブジェクトの数
このパラメーター
pUserData
には、コールバック関数のセットアップ中に渡されたポインターが含まれています。
コールバック関数は
VkBool32
型を返します 。結果は、メッセージを生成した呼び出しを終了するかどうかを示します。コールバック関数がを返す
VK_TRUE
場合、呼び出しは中止され、エラーコードが返され
VK_ERROR_VALIDATION_FAILED_EXT
ます。原則として、これは検証レイヤー自体をテストする場合にのみ発生します。この場合、を返す必要があります
VK_FALSE
。
コールバック関数についてVulkanに伝えることは残っています。驚いたことに、Vulkanでデバッグコールバック関数を制御する場合でも、明示的に作成および破棄する必要がある記述子が必要です。このコールバック関数は、デバッグメッセンジャーの一部です。 、およびその数は無制限です。次の後に記述子のクラスメンバーを追加し
instance
ます。
VkDebugUtilsMessengerEXT debugMessenger;
次に
setupDebugMessenger
、
initVulkan
直後 から呼び出される関数を 追加します
createInstance
。
void initVulkan() {
createInstance();
setupDebugMessenger();
}
void setupDebugMessenger() {
if (!enableValidationLayers) return;
}
メッセンジャーとそのコールバック関数に関する詳細を構造に入力する必要があります。
VkDebugUtilsMessengerCreateInfoEXT createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
createInfo.pfnUserCallback = debugCallback;
createInfo.pUserData = nullptr; // Optional
このフィールド
messageSeverity
では、コールバック関数が呼び出される重大度を指定できます。
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
問題の可能性が通知され、コンソールに詳細なデバッグ情報が表示されないようにすることを除いて、すべての度数を設定し ます。
同様に、このフィールドで
messageType
は、 メッセージをタイプでフィルタリングできます。すべてのタイプを選択しましたが、不要なタイプはいつでも無効にできます。 コールバック関数へのポインタが
フィールドに
pfnUserCallback
渡されます 。オプションで、フィールドへのポインタを渡すことができ
pUserData
ます
pUserData
。これは、パラメータを介してコールバック関数に渡されます 。
検証レイヤーメッセージをカスタマイズしてコールバックをデバッグする方法は他にもありますが、これがVulkanを使い始めるための最良の方法であることに注意してください。他の方法の詳細については、拡張仕様を参照してください 。 オブジェクトを作成するには
、構造を関数
vkCreateDebugutilsMessengerEXT
に渡す必要が あります
VkDebugUtilsMessengerEXT
。これは拡張機能であるため、自動的には読み込まれません。を使用して自分でアドレスを見つける必要があります
vkGetInstanceProcAddr
。これを内部で行う独自のプロキシ関数を作成します。クラス定義の前に追加します
HelloTriangleApplication
。
VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) {
auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
if (func != nullptr) {
return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
} else {
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
この関数を使用してメッセンジャーを作成します。
if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
throw std::runtime_error("failed to set up debug messenger!");
}
最後から2番目のパラメーターはオプションです。これはアロケーターのコールバック関数であり、として指定し
nullptr
ます。残りのパラメーターは非常に単純です。メッセンジャーは特定のVulkanインスタンス(およびその検証レイヤー)に使用されるため、このインスタンスへのポインターを最初の引数として渡す必要があります。他の子オブジェクトについても、このパターンに出くわします。
オブジェクト
VkDebugUtilsMessengerEXT
は、を呼び出して破棄する必要があります
vkDestroyDebugUtilsMessengerEXT
。の場合と同様に
vkCreateDebugUtilsMessengerEXT
、この関数を明示的にロードする必要があります。
次に
CreateDebugUtilsMessengerEXT
、別のプロキシ関数を作成します。
void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
if (func != nullptr) {
func(instance, debugMessenger, pAllocator);
}
}
この関数がクラスの静的関数であるか、クラス外の関数であるかを確認してください。その後、関数で呼び出すことができます
cleanup
:
void cleanup() {
if (enableValidationLayers) {
DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
}
vkDestroyInstance(instance, nullptr);
glfwDestroyWindow(window);
glfwTerminate();
}
Vulkanのインスタンスをデバッグしています
検証レイヤーを使用したデバッグを追加しましたが、まだ少しあります。
vkCreateDebugUtilsMessengerEXT
有効なインスタンスをされて
vkDestroyDebugUtilsMessengerEXT
呼び出す必要があり、 インスタンスが破棄される前に呼び出さなければなりません。したがって、我々は中にデバッグすることはできません
vkCreateInstance
し、 まだ
vkDestroyInstance
。
ただし、仕様を注意深く読むと、 これら2つの機能用に個別のデバッグメッセンジャーを作成できることがわかります。これを行うには、
pNext
構造ポインタ
VkInstanceCreateInfo
を 構造 に設定する必要があります
VkDebugUtilsMessengerCreateInfoEXT
。まず、記入を
VkDebugUtilsMessengerCreateInfoEXT
別の方法に移しましょう 。
void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
createInfo.pfnUserCallback = debugCallback;
}
...
void setupDebugMessenger() {
if (!enableValidationLayers) return;
VkDebugUtilsMessengerCreateInfoEXT createInfo;
populateDebugMessengerCreateInfo(createInfo);
if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
throw std::runtime_error("failed to set up debug messenger!");
}
}
関数で再利用できます
createInstance
:
void createInstance() {
...
VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
...
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
populateDebugMessengerCreateInfo(debugCreateInfo);
createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo;
} else {
createInfo.enabledLayerCount = 0;
createInfo.pNext = nullptr;
}
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
throw std::runtime_error("failed to create instance!");
}
}
変数
debugCreateInfo
はifステートメントの外側にあるため、呼び出される前に破棄されません
vkCreateInstance
。この方法で追加のデバッグメッセンジャーを作成すると、
vkCreateInstance
と
vkDestroyInstance
で自動的に使用でき 、その後は破棄されます。
テスト
検証レイヤーの動作を確認するために、意図的に間違いを犯してみましょう。 関数内
の呼び出し
DestroyDebugUtilsMessengerEXT
を 一時的に削除して
cleanup
、プログラムを実行します。最終的には次のよう
になります。メッセージが送信された呼び出しを見つけるには、メッセージのコールバック関数にブレークポイントを追加し、呼び出しスタックを確認します。
設定
構造で指定されたもの以外にも、検証レベルの動作を管理するカスタマイズが多数あります
VkDebugUtilsMessengerCreateInfoEXT
。Vulkan SDKに移動し、ディレクトリを開きます
Config
。そこには、
vk_layer_settings.txt
レイヤーの設定方法を説明するファイルがあります 。
レイヤーを設定するには、ディレクトリにファイルをコピー
Debug
して
Release
、目的の動作を設定するための指示に従ってください。ただし、このガイドの残りの部分では、デフォルト設定を使用していることを前提としています。
将来的には、検証レイヤーを使用してそれらを追跡することがどれほど便利で効果的であるかを示すために、意図的に間違いを犯します。
C ++コード