Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 24 additions & 13 deletions samples/extensions/layer_settings/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ VK_EXT_layer_settings supersedes the older `VK_EXT_validation_features` and `VK_

- Enables the instance extension `VK_EXT_layer_settings` (optionally).
- Adds settings for the Khronos validation layer before the Vulkan instance is created:
- Enables Best Practices checks.
- Enables `debugPrintfEXT` support (so shader `debugPrintf` messages are emitted via validation).
** Enables Best Practices checks.
** Enables `debugPrintfEXT` support (so shader `debugPrintf` messages are emitted via validation).
- Provides an interactive UI with toggleable validation scenarios that demonstrate common mistakes:
- *Wrong Buffer Flags*: Creates a buffer with `TRANSFER_DST` usage but binds it as a vertex buffer (missing `VERTEX_BUFFER_BIT`).
- *Suboptimal Transitions*: Performs image layout transitions to `GENERAL` instead of more optimal layouts like `COLOR_ATTACHMENT_OPTIMAL`.
- *Many Small Allocations*: Creates numerous small memory allocations (512 bytes each) instead of suballocating from larger blocks.
** *Wrong Buffer Flags*: Creates a buffer with `TRANSFER_DST` usage but binds it as a vertex buffer (missing `VERTEX_BUFFER_BIT`).
** *Suboptimal Transitions*: Performs image layout transitions to `GENERAL` instead of more optimal layouts like `COLOR_ATTACHMENT_OPTIMAL`.
** *Many Small Allocations*: Creates numerous small memory allocations (512 bytes each) instead of suballocating from larger blocks.
- Displays validation messages in real-time within the application UI, showing warning counts per scenario and total statistics.
- Works on all platforms without requiring terminal access to see validation output.

Expand Down Expand Up @@ -86,6 +86,7 @@ If you are integrating layer settings directly into your own Vulkan app, the flo
#include <vulkan/vulkan.h>
#include <vector>
#include <cstring>
#include <ranges>

static const char *kValidationLayer = "VK_LAYER_KHRONOS_validation";

Expand All @@ -96,12 +97,21 @@ bool layer_settings_supported()
vkEnumerateInstanceExtensionProperties(kValidationLayer, &count, nullptr);
std::vector<VkExtensionProperties> exts(count);
vkEnumerateInstanceExtensionProperties(kValidationLayer, &count, exts.data());
for (auto &e : exts)
{
if (strcmp(e.extensionName, VK_EXT_LAYER_SETTINGS_EXTENSION_NAME) == 0)
return true;
}
return false;
return std::ranges::find_if(exts, [](auto const &e) {
return std::strcmp(e.extensionName, VK_EXT_LAYER_SETTINGS_EXTENSION_NAME) == 0;
}) != exts.end();
}

// Helper to check if VK_EXT_validation_features is advertised by the validation layer
bool validation_features_supported()
{
uint32_t count = 0;
vkEnumerateInstanceExtensionProperties(kValidationLayer, &count, nullptr);
std::vector<VkExtensionProperties> exts(count);
vkEnumerateInstanceExtensionProperties(kValidationLayer, &count, exts.data());
return std::ranges::find_if(exts, [](auto const &e) {
return std::strcmp(e.extensionName, VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME) == 0;
}) != exts.end();
}

VkInstance create_instance_with_layer_settings()
Expand Down Expand Up @@ -137,7 +147,8 @@ VkInstance create_instance_with_layer_settings()
ici.ppEnabledExtensionNames = instance_exts.data();
ici.enabledExtensionCount = static_cast<uint32_t>(instance_exts.size());

const bool has_layer_settings = layer_settings_supported();
const bool has_layer_settings = layer_settings_supported();
const bool has_validation_features = validation_features_supported();
if (has_layer_settings)
{
// You also need to add VK_EXT_layer_settings to the enabled instance extensions
Expand All @@ -146,7 +157,7 @@ VkInstance create_instance_with_layer_settings()
ici.enabledExtensionCount = static_cast<uint32_t>(instance_exts.size());
ici.pNext = &layer_settings_ci;
}
else
else if (has_validation_features)
{
// Fallback for older SDKs: use VK_EXT_validation_features
static const VkValidationFeatureEnableEXT vfe[] = {
Expand Down
129 changes: 51 additions & 78 deletions samples/extensions/layer_settings/layer_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,16 @@
#include "common/vk_initializers.h"

#include <array>
#include <format>

LayerSettingsSample::~LayerSettingsSample()
{
cleanup_scenarios();

if (debug_messenger_ != VK_NULL_HANDLE && has_instance())
{
VkInstance instance = get_instance().get_handle();
auto fpDestroy = reinterpret_cast<PFN_vkDestroyDebugUtilsMessengerEXT>(
vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"));
if (fpDestroy)
{
fpDestroy(instance, debug_messenger_, nullptr);
}
VkInstance instance = get_instance().get_handle();
vkDestroyDebugUtilsMessengerEXT(instance, debug_messenger_, nullptr);
debug_messenger_ = VK_NULL_HANDLE;
}
}
Expand Down Expand Up @@ -376,26 +372,30 @@ std::unique_ptr<vkb::core::InstanceC> LayerSettingsSample::create_instance()
info.pfnUserCallback = &LayerSettingsSample::debug_callback;
info.pUserData = this;

VkInstance instance = inst->get_handle();
auto fpCreate = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(
vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"));
if (fpCreate && debug_messenger_ == VK_NULL_HANDLE)
if (debug_messenger_ == VK_NULL_HANDLE)
{
VkResult res = fpCreate(instance, &info, nullptr, &debug_messenger_);
VkResult res = vkCreateDebugUtilsMessengerEXT(inst->get_handle(), &info, nullptr, &debug_messenger_);
if (res != VK_SUCCESS)
{
LOGW("Failed to create local debug messenger (res=%d)", int(res));
}
}
}

// Note: If VK_EXT_debug_utils is not enabled (e.g., disabled via CLI or platform
// constraints) or creation fails, the messenger will remain null and the sample
// will continue to run without collecting messages into the UI.
return inst;
}

LayerSettingsSample::LayerSettingsSample()
{
title = "Layer settings (VK_EXT_layer_settings)";

// Request VK_EXT_layer_settings as an optional instance extension so the
// framework enables it when available and consumes the layer settings below.
add_instance_extension(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME, /*optional*/ true);

// Configure the Khronos validation layer using layer settings. These settings are
// consumed by the validation layer at instance creation time.
//
Expand Down Expand Up @@ -450,6 +450,11 @@ LayerSettingsSample::LayerSettingsSample()
// Do not add this by default as it disables all validation. Leave as commented example.
// add_layer_setting(layer_setting);
}

// Initialize scenario state map so UI totals and logic have all keys even before toggling
scenario_states_.emplace(Scenario::WrongBufferFlags, ScenarioState{});
scenario_states_.emplace(Scenario::SuboptimalTransitions, ScenarioState{});
scenario_states_.emplace(Scenario::SmallAllocations, ScenarioState{});
}

bool LayerSettingsSample::prepare(const vkb::ApplicationOptions &options)
Expand All @@ -459,30 +464,6 @@ bool LayerSettingsSample::prepare(const vkb::ApplicationOptions &options)
return false;
}

// Install a local DebugUtils messenger to collect validation messages into the UI.
// If it was already created during create_instance(), skip to avoid duplication.
if (debug_messenger_ == VK_NULL_HANDLE && get_instance().is_enabled(VK_EXT_DEBUG_UTILS_EXTENSION_NAME))
{
VkDebugUtilsMessengerCreateInfoEXT info{VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT};
info.messageSeverity = 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;
info.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;
info.pfnUserCallback = &LayerSettingsSample::debug_callback;
info.pUserData = this;

auto instance = get_instance().get_handle();
auto fpCreate = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(
vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"));
if (fpCreate)
{
VK_CHECK(fpCreate(instance, &info, nullptr, &debug_messenger_));
}
}

// Build once; we record per-frame minimal CBs in render().
build_command_buffers();

Expand Down Expand Up @@ -580,30 +561,28 @@ VKAPI_ATTR VkBool32 VKAPI_CALL LayerSettingsSample::debug_callback(
(messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) ? "INFO" :
"VERBOSE";

char type_buf[32] = {};
int ofs = 0;
std::string type_str;
if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT)
ofs += snprintf(type_buf + ofs, sizeof(type_buf) - ofs, "%sGEN", ofs ? "|" : "");
type_str += (type_str.empty() ? "GEN" : "|GEN");
if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT)
ofs += snprintf(type_buf + ofs, sizeof(type_buf) - ofs, "%sVAL", ofs ? "|" : "");
type_str += (type_str.empty() ? "VAL" : "|VAL");
if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT)
ofs += snprintf(type_buf + ofs, sizeof(type_buf) - ofs, "%sPERF", ofs ? "|" : "");
type_str += (type_str.empty() ? "PERF" : "|PERF");

// Update scenario-specific counters based on message content
const char *msg_id = pCallbackData && pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "";
const char *msg = pCallbackData && pCallbackData->pMessage ? pCallbackData->pMessage : "";

char line[2048];
snprintf(line, sizeof(line), "[%s][%s] %s\n", sev, type_buf[0] ? type_buf : "-",
pCallbackData && pCallbackData->pMessage ? pCallbackData->pMessage : "<no message>");
std::string line = std::format("[{}][{}] {}\n",
sev,
type_str.empty() ? std::string("-") : type_str,
pCallbackData && pCallbackData->pMessage ? pCallbackData->pMessage : "<no message>");

// Increment error/warning counts per scenario and cache messages
bool message_cached = false;
if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
{
if (strstr(msg_id, "vkCmdBindVertexBuffers") || strstr(msg, "VERTEX_BUFFER_BIT"))
{
auto &state = self->scenario_states_[static_cast<size_t>(Scenario::WrongBufferFlags)];
auto &state = self->scenario_states_[Scenario::WrongBufferFlags];
state.error_count++;
if (state.enabled)
{
Expand All @@ -614,26 +593,20 @@ VKAPI_ATTR VkBool32 VKAPI_CALL LayerSettingsSample::debug_callback(
}
else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)
{
// Check for small-dedicated-allocation warnings and categorize by source
if (strstr(msg_id, "small-dedicated-allocation") || strstr(msg, "small-dedicated-allocation"))
{
// Count for all active scenarios that might trigger this warning
// WrongBufferFlags: creates one small buffer with wrong usage
// SuboptimalTransitions: creates one small image
// SmallAllocations: creates many small buffers
if (strstr(msg_id, "vkBindBufferMemory") || strstr(msg, "vkBindBufferMemory"))
{
// Buffer small-allocation warnings
if (self->scenario_states_[static_cast<size_t>(Scenario::SmallAllocations)].enabled)
if (self->scenario_states_[Scenario::SmallAllocations].enabled)
{
auto &state = self->scenario_states_[static_cast<size_t>(Scenario::SmallAllocations)];
auto &state = self->scenario_states_[Scenario::SmallAllocations];
state.warning_count++;
state.recent_messages.append(line);
message_cached = true;
}
if (self->scenario_states_[static_cast<size_t>(Scenario::WrongBufferFlags)].enabled)
if (self->scenario_states_[Scenario::WrongBufferFlags].enabled)
{
auto &state = self->scenario_states_[static_cast<size_t>(Scenario::WrongBufferFlags)];
auto &state = self->scenario_states_[Scenario::WrongBufferFlags];
state.warning_count++;
if (!message_cached)
{
Expand All @@ -644,10 +617,9 @@ VKAPI_ATTR VkBool32 VKAPI_CALL LayerSettingsSample::debug_callback(
}
else if (strstr(msg_id, "vkBindImageMemory") || strstr(msg, "vkBindImageMemory"))
{
// Image small-allocation warnings
if (self->scenario_states_[static_cast<size_t>(Scenario::SuboptimalTransitions)].enabled)
if (self->scenario_states_[Scenario::SuboptimalTransitions].enabled)
{
auto &state = self->scenario_states_[static_cast<size_t>(Scenario::SuboptimalTransitions)];
auto &state = self->scenario_states_[Scenario::SuboptimalTransitions];
state.warning_count++;
state.recent_messages.append(line);
message_cached = true;
Expand All @@ -656,9 +628,9 @@ VKAPI_ATTR VkBool32 VKAPI_CALL LayerSettingsSample::debug_callback(
}
else if (strstr(msg, "GENERAL") || strstr(msg, "layout"))
{
if (self->scenario_states_[static_cast<size_t>(Scenario::SuboptimalTransitions)].enabled)
if (self->scenario_states_[Scenario::SuboptimalTransitions].enabled)
{
auto &state = self->scenario_states_[static_cast<size_t>(Scenario::SuboptimalTransitions)];
auto &state = self->scenario_states_[Scenario::SuboptimalTransitions];
state.warning_count++;
state.recent_messages.append(line);
message_cached = true;
Expand All @@ -667,12 +639,11 @@ VKAPI_ATTR VkBool32 VKAPI_CALL LayerSettingsSample::debug_callback(
}

self->log_text_.append(line);
// Keep last ~8KB for UI display (more manageable size)
if (self->log_text_.size() > 8 * 1024)
{
self->log_text_.erase(0, self->log_text_.size() - 8 * 1024);
}
return VK_FALSE; // do not abort calls
return VK_FALSE;
}

void LayerSettingsSample::on_update_ui_overlay(vkb::Drawer &drawer)
Expand All @@ -693,9 +664,9 @@ void LayerSettingsSample::on_update_ui_overlay(vkb::Drawer &drawer)
drawer.text("");

// Scenario toggles
if (drawer.checkbox("Wrong Buffer Flags", &scenario_states_[static_cast<size_t>(Scenario::WrongBufferFlags)].enabled))
if (drawer.checkbox("Wrong Buffer Flags", &scenario_states_[Scenario::WrongBufferFlags].enabled))
{
auto &state = scenario_states_[static_cast<size_t>(Scenario::WrongBufferFlags)];
auto &state = scenario_states_[Scenario::WrongBufferFlags];
if (state.enabled)
{
// Only setup if not already set up (first enable or after cleanup)
Expand All @@ -718,12 +689,12 @@ void LayerSettingsSample::on_update_ui_overlay(vkb::Drawer &drawer)
}
ImGui::SameLine();
drawer.text(" Warnings: %u | Errors: %u",
scenario_states_[static_cast<size_t>(Scenario::WrongBufferFlags)].warning_count,
scenario_states_[static_cast<size_t>(Scenario::WrongBufferFlags)].error_count);
scenario_states_[Scenario::WrongBufferFlags].warning_count,
scenario_states_[Scenario::WrongBufferFlags].error_count);

if (drawer.checkbox("Suboptimal Transitions", &scenario_states_[static_cast<size_t>(Scenario::SuboptimalTransitions)].enabled))
if (drawer.checkbox("Suboptimal Transitions", &scenario_states_[Scenario::SuboptimalTransitions].enabled))
{
auto &state = scenario_states_[static_cast<size_t>(Scenario::SuboptimalTransitions)];
auto &state = scenario_states_[Scenario::SuboptimalTransitions];
if (state.enabled)
{
// Only setup if not already set up (first enable or after cleanup)
Expand All @@ -746,12 +717,12 @@ void LayerSettingsSample::on_update_ui_overlay(vkb::Drawer &drawer)
}
ImGui::SameLine();
drawer.text(" Warnings: %u | Errors: %u",
scenario_states_[static_cast<size_t>(Scenario::SuboptimalTransitions)].warning_count,
scenario_states_[static_cast<size_t>(Scenario::SuboptimalTransitions)].error_count);
scenario_states_[Scenario::SuboptimalTransitions].warning_count,
scenario_states_[Scenario::SuboptimalTransitions].error_count);

if (drawer.checkbox("Many Small Allocations", &scenario_states_[static_cast<size_t>(Scenario::SmallAllocations)].enabled))
if (drawer.checkbox("Many Small Allocations", &scenario_states_[Scenario::SmallAllocations].enabled))
{
auto &state = scenario_states_[static_cast<size_t>(Scenario::SmallAllocations)];
auto &state = scenario_states_[Scenario::SmallAllocations];
if (state.enabled)
{
// Only setup if not already set up (first enable or after cleanup)
Expand All @@ -774,15 +745,16 @@ void LayerSettingsSample::on_update_ui_overlay(vkb::Drawer &drawer)
}
ImGui::SameLine();
drawer.text(" Warnings: %u | Errors: %u",
scenario_states_[static_cast<size_t>(Scenario::SmallAllocations)].warning_count,
scenario_states_[static_cast<size_t>(Scenario::SmallAllocations)].error_count);
scenario_states_[Scenario::SmallAllocations].warning_count,
scenario_states_[Scenario::SmallAllocations].error_count);

drawer.text("");

// Check if all scenarios are disabled and clear log if so
bool any_enabled = false;
for (const auto &state : scenario_states_)
for (const auto &kv : scenario_states_)
{
const auto &state = kv.second;
if (state.enabled)
{
any_enabled = true;
Expand All @@ -797,8 +769,9 @@ void LayerSettingsSample::on_update_ui_overlay(vkb::Drawer &drawer)
// Total statistics
uint32_t total_warnings = 0;
uint32_t total_errors = 0;
for (const auto &state : scenario_states_)
for (const auto &kv : scenario_states_)
{
const auto &state = kv.second;
total_warnings += state.warning_count;
total_errors += state.error_count;
}
Expand Down
Loading
Loading