バルカン。開発者ガイド。検証レイヤー

私はIzhevskのCGTribeの翻訳者です。ここでは、VulkanAPIマニュアルの翻訳を共有しています。ソースリンク -vulkan-tutorial.com



この投稿は、前の投稿「Vulkan。開発者ガイド。三角形の描画」の続きであり、 検証レイヤーの章の翻訳に専念しています。



コンテンツ
1.



2.



3.



4.





  1. (pipeline)


5.



  1. Staging


6. Uniform-



  1. layout
  2. sets


7.



  1. Image view image sampler
  2. image sampler


8.



9.



10. -



11. Multisampling



FAQ







検証レイヤー







検証レイヤーとは何ですか?



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 ++コード



All Articles