Vulkan API — урок 8. Сообщения от Validation layers (+листинг)

К сожалению недостаточно только лишь включить слои, без дополнительных настроек сообщения от слоев до нас не дойдут.

Для получения этих сообщений мы должны настроить callback, что требует расширения  VK_EXT_debug_report.

Для начала создадим функцию getRequiredExtensions, что будет возвращать нам требуемый список расширений, зависящий от того, включены ли validation layers:

std::vector<const char*> getRequiredExtensions() {
    std::vector<const char*> extensions;

    unsigned int glfwExtensionCount = 0;
    const char** glfwExtensions;
    glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

    for (unsigned int i = 0; i < glfwExtensionCount; i++) {
        extensions.push_back(glfwExtensions[i]);
    }

    if (enableValidationLayers) {
        extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
    }

    return extensions;
}

Расширения, указанные GLFW всегда необходимы, но расширение для дебага добавлено условно. Обратите внимание, здесь используется макрос VK_EXT_DEBUG_REPORT_EXTENSION_NAME, эквивалентен строке «VK_EXT_debug_report». Использование макросов позволяет избежать ошибок.

Теперь используем эту функцию в  createInstance:

auto extensions = getRequiredExtensions();
createInfo.enabledExtensionCount = extensions.size();
createInfo.ppEnabledExtensionNames = extensions.data();

Запустите программу, что бы убедиться в отсутствии ошибки VK_ERROR_EXTENSION_NOT_PRESENT. В действительности нет необходимости проверять наличие данного расширения, т.к. оно уже должны быть среди доступных расширений validation layers.

Теперь давайте посмотрим как выглядит callback функция. Добавим новый статическую функцию debugCallback с прототипом  PFN_vkDebugReportCallbackEXT. VKAPI_ATTR и VKAPI_CALL гарантирует, что функция имеет правильный ключ для вызова из Vulkan’a.

static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
    VkDebugReportFlagsEXT flags,
    VkDebugReportObjectTypeEXT objType,
    uint64_t obj,
    size_t location,
    int32_t code,
    const char* layerPrefix,
    const char* msg,
    void* userData) {

    std::cerr << "validation layer: " << msg << std::endl;

    return VK_FALSE;
}

Первый параметр определяет тип сообщения, который может быть одним из следующих (битовые флаги):

  • VK_DEBUG_REPORT_INFORMATION_BIT_EXT
  • VK_DEBUG_REPORT_WARNING_BIT_EXT
  • VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT
  • VK_DEBUG_REPORT_ERROR_BIT_EXT
  • VK_DEBUG_REPORT_DEBUG_BIT_EXT

Параметр objType определяет тип объекта, отправляющего сообщение. К примеру если объект VkPhysicalDevice , тогда objType должен быть VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT. Это работает потому что все дескрипторы внутри Vulkan’a имеют тип  uint64_t.

Параметр msg содержит указатель на сообщение как таковое. И наконец параметр userData передает ваши собственные данные.

Все что теперь осталось — рассказать Vulkan’у о функции  callback. Возможно это покажется немного странным, но даже callback в Vulkan’е управляется через дескриптор, который должен создаваться и уничтожаться явным образом. Для этого добавим необходимый член класса прямо под instance:

VkDebugReportCallbackEXT callback;

Теперь добавим функцию setupDebugCallback, вызываемую из initVulkan прямо после createInstance:

void initVulkan() {
    createInstance();
    setupDebugCallback();
}

void setupDebugCallback() {
    if (!enableValidationLayers) return;

}

Далее заполним структуру, с детальной информацией о callback’е:

VkDebugReportCallbackCreateInfoEXT createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
createInfo.pfnCallback = debugCallback;

Поле flags позволяет фильтровать сообщения, определять какие вы хотите получать. Поле pfnCallback определяет указатель на функцию обратного вызова. Опционально так же доступна возможность передать указатель на поле pUserData, которое будет передано callback функции с помощью параметра userData. Для примера, вы можем использовать его, для передачи указателя в HelloTriangleApplication.

Эта структура должна быть передана в функцию vkCreateDebugReportCallbackEXT для создания объекта VkDebugReportCallbackEXT. К сожалению, потому что эта функция является функцией расширения, она не загружается автоматически. Необходимо найти её адрес при помощи vkGetInstanceProcAddr. Создаем собственную прокси функцию, которая работает в фоновом режиме. Добавим её в самом начале, выше определения VDeleter.

VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) {
    auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
    if (func != nullptr) {
        return func(instance, pCreateInfo, pAllocator, pCallback);
    } else {
        return VK_ERROR_EXTENSION_NOT_PRESENT;
    }
}

Функция vkGetInstanceProcAddr будет возвращать nullptr, если функция не была загружена. Теперь мы можем вызвать эту функцию для создания расширение объекта если это доступно:

if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) {
    throw std::runtime_error("failed to set up debug callback!");
}

Запустим программу и закроем окно, насмотревшись на пустое окно. В командной строке вы увидите:

validation_layer_test

Итак, полученная ошибка говорит нам о том, что объек VkDebugReportCallbackEXT должен быть очищен при помощи вызова vkDestroyDebugReportCallbackEXT. Изменим переменную callback для использование обертки удаления. Аналогично следует поступить и с функцией vkCreateDebugReportCallbackEXT. Создадим другую прокси функцию прямо под  CreateDebugReportCallbackEXT:

void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) {
    auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");
    if (func != nullptr) {
        func(instance, callback, pAllocator);
    }
}

Теперь мы можем использовать VDeleter:

VDeleter<VkDebugReportCallbackEXT> callback{instance, DestroyDebugReportCallbackEXT};

При следующем запуске программы вы более не увидите сообщения об ошибке (я искренне на это надеюсь).

Конечно существует много больше настроек и возможностей, чем просто проверка флагов, указанных в структуре VkDebugReportCallbackCreateInfoEXT. В Vulkan SDK вы найдете папочку «Config», а в ней файл «vk_layer_settings.txt», который сможет вам рассказать еще много интересного.

В дальнейших уроках будут допущены некоторые ошибки, которые вы сможете отследить при помощи всего вышесказанного, и понять насколько эти слои могут быть полезны и осознать необходимость четкого понимания того, что вы делаете в Vulkan’е.

Код этого урока с учетом всех прошедших вы можете найти тут.

Main Admin

12 Comments

  1. В предпоследнем блоке кода надо `callback.replace()` заменить на `&callback`. На этом этапе мы пока не используем VDeleter. Его нужно будет позже использовать, чтобы исправить ошибку получаемую в Debug Report Callback.

    Странно, но у меня 6 раз дублируются сообщения «Debug Report callbacks not removed before DestroyInstance» из этого урока.

    • Обновил Vulkan SDK до 1.0.51.0. Теперь дублируется 4 раза сообщение «Debug Report callbacks not removed before DestroyInstance».
      Лог Vulkan Debug Report (со всеми флагами): https://pastebin.com/c0YfuwtM
      По логу видно, что callback добавляется 4 раза. Интересно почему. Функция `CreateDebugReportCallbackEXT` вызывается всего 1 раз. Почему это так происходит? Может для каждого потока создаётся Debug Report Callback?

      • Разница с тем, на что Вы смотрите из-за отсутствия у меня cleanup(), и отсутствия в другом источнике VDeleter

      • Итак, что могу сказать. Видимо в связи с обновлениями Vulkan’а произошли некоторые изменения (листинг оставлю нового вариант). К сожалению у меня на данный момент под рукой нет компьютера с Vulkan’ом, после выходных все перепроверю.

      • Источник, из которого черпал информацию ранее, изменил логику работы программы, отказался от VDeleter и теперь по окончанию работы функции run() вызывает cleanup() , причина такого перехода мне не понятна, ведь это был достаточно удобный способ (пусть и не идеальный в плане производительности) подчищать на ходу.
        Там в комментариях к «Базовому коду» написано примерно следующее (упрощено):
        Я удалил его (VDeleter) для упрощения, многие используют не C++, а C, C# и т.п., где не получится использовать VDeleter, но в реальных программах рекомендую его использовать для предотвращения проблем с использованием памяти. И в конце концов вы сможете написать тот вариант VDeleter, который подойдет именно вашей программе.

        • Я пробовал оригинальный вариант из https://vulkan-tutorial.com/ и вариант с этого сайта. Одно и тоже. Дело не в VDeleter.

          На SDK 1.0.49.0 у меня сообщений было всегда 6, обновившись до 1.0.51.0 стало 4. Причём все сообщения от «DebugReport» только повторяются. Остальные источники работают нормально (ObjectTracker, loader).

          Оставил комментарий на английском сайте. Может знает что это такое. Google и чтение документации мне ничего не дало.

        • Полный вывод данных приложения: https://pastebin.com/vt0aA3um

          Использовал WinAPI функцию для получения ID потока. Callback исполняется одним и тем же потоком. У меня ID потока в логе: 11848

          Также видно как выгружаются 5 слоёв (VkLayer_threading.dll, VkLayer_parameter_validation.dll, VkLayer_object_tracker.dll, VkLayer_core_validation.dll, VkLayer_unique_objects.dll) Может не только с потоками это связано, а может слои строят какую-нибудь цепочку callback’ов? 0_o

      • Интересно. Поставил студию, вулкан версии 51, скопировал код, и тоже получил 4 строки. Затем проверял версии вулкана 17 и 26, получал по 4 строки. Потом решил поставить версию 49, тоже получил 4 строки. Создается впечатление, что они вносят некоторые правки и в предыдущие версии (баги может какие исправляют), и потому результаты так разнятся даже в версиях, что ранее выдавали иной результат.

        • Мне дали подсказку на англоязычном учебнике в комментариях. Я заменил «VK_LAYER_LUNARG_standard_validation» слой, на «VK_LAYER_LUNARG_core_validation». Стал теперь один callback.

          Всё таки не от потоков это зависело, а от количества подключенных слоёв! Каждый из которых тоже имеет свои callback’и. Видимо при возникновении ошибки — всем слоям передаются данные об ошибке. Вот почему я получаю несколько вызовов моего callback’а.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *