| /* Copyright (c) 2015-2025 The Khronos Group Inc. |
| * Copyright (c) 2015-2025 Valve Corporation |
| * Copyright (c) 2015-2025 LunarG, Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #pragma once |
| |
| #include <array> |
| #include <cstdarg> |
| #include <mutex> |
| #include <string> |
| #include <string_view> |
| #include <vector> |
| #include <memory> |
| |
| #include <vulkan/utility/vk_struct_helper.hpp> |
| #include <vulkan/vk_enum_string_helper.h> |
| |
| #include "containers/custom_containers.h" |
| #include "containers/small_vector.h" |
| #include "generated/vk_object_types.h" |
| #include "error_message/log_message_type.h" |
| |
| #if defined __ANDROID__ |
| #include <android/log.h> |
| [[maybe_unused]] static const char *kForceDefaultCallbackKey = "debug.vvl.forcelayerlog"; |
| #endif |
| |
| extern const char *kVUIDUndefined; |
| |
| typedef enum DebugCallbackStatusBits { |
| DEBUG_CALLBACK_UTILS = 0x00000001, // This struct describes a VK_EXT_debug_utils callback |
| DEBUG_CALLBACK_DEFAULT = 0x00000002, // An internally created callback, used if no user-defined callbacks are registered |
| DEBUG_CALLBACK_INSTANCE = 0x00000004, // An internally created temporary instance callback |
| } DebugCallbackStatusBits; |
| typedef VkFlags DebugCallbackStatusFlags; |
| |
| struct LogObjectList { |
| small_vector<VulkanTypedHandle, 4, uint32_t> object_list; |
| |
| template <typename HANDLE_T> |
| void add(HANDLE_T object) { |
| object_list.emplace_back(object, ConvertCoreObjectToVulkanObject(VkHandleInfo<HANDLE_T>::kVkObjectType)); |
| } |
| |
| void add(const LogObjectList &rhs) { |
| for (const auto &obj : rhs) { |
| object_list.emplace_back(obj); |
| } |
| } |
| |
| void add(const LogObjectList *rhs) { |
| if (!rhs) { |
| return; |
| } |
| for (const auto &obj : *rhs) { |
| object_list.emplace_back(obj); |
| } |
| } |
| |
| template <typename... HANDLE_T> |
| void add(HANDLE_T... objects) { |
| (..., add(objects)); |
| } |
| |
| void add(VulkanTypedHandle typed_handle) { object_list.emplace_back(typed_handle); } |
| |
| template <typename HANDLE_T> |
| LogObjectList(HANDLE_T object) { |
| add(object); |
| } |
| |
| template <typename... HANDLE_T> |
| LogObjectList(HANDLE_T... objects) { |
| (..., add(objects)); |
| } |
| |
| [[nodiscard]] auto size() const -> decltype(object_list.size()) { return object_list.size(); } |
| [[nodiscard]] auto empty() const -> decltype(object_list.empty()) { return object_list.empty(); } |
| [[nodiscard]] auto begin() const -> decltype(object_list.begin()) { return object_list.begin(); } |
| [[nodiscard]] auto end() const -> decltype(object_list.end()) { return object_list.end(); } |
| |
| LogObjectList(){}; |
| }; |
| |
| typedef struct VkLayerDbgFunctionState { |
| DebugCallbackStatusFlags callback_status; |
| |
| // Debug report related information |
| VkDebugReportCallbackEXT debug_report_callback_object; |
| PFN_vkDebugReportCallbackEXT debug_report_callback_function_ptr; |
| VkFlags debug_report_msg_flags; |
| |
| // Debug utils related information |
| VkDebugUtilsMessengerEXT debug_utils_callback_object; |
| VkDebugUtilsMessageSeverityFlagsEXT debug_utils_msg_flags; |
| VkDebugUtilsMessageTypeFlagsEXT debug_utils_msg_type; |
| PFN_vkDebugUtilsMessengerCallbackEXT debug_utils_callback_function_ptr; |
| |
| void *pUserData; |
| |
| bool IsUtils() const { return ((callback_status & DEBUG_CALLBACK_UTILS) != 0); } |
| bool IsDefault() const { return ((callback_status & DEBUG_CALLBACK_DEFAULT) != 0); } |
| bool IsInstance() const { return ((callback_status & DEBUG_CALLBACK_INSTANCE) != 0); } |
| } VkLayerDbgFunctionState; |
| |
| // TODO: Could be autogenerated for the specific handles for extra type safety... |
| template <typename HANDLE_T> |
| static inline uint64_t HandleToUint64(HANDLE_T h) { |
| return CastToUint64<HANDLE_T>(h); |
| } |
| |
| static inline uint64_t HandleToUint64(uint64_t h) { return h; } |
| |
| // Data we store per label for logging |
| struct LoggingLabel { |
| std::string name{}; |
| std::array<float, 4> color{}; |
| |
| void Reset() { *this = LoggingLabel(); } |
| bool Empty() const { return name.empty(); } |
| |
| VkDebugUtilsLabelEXT Export() const { |
| VkDebugUtilsLabelEXT out = vku::InitStructHelper(); |
| out.pLabelName = name.c_str(); |
| std::copy(color.cbegin(), color.cend(), out.color); |
| return out; |
| }; |
| |
| LoggingLabel() : name(), color({{0.f, 0.f, 0.f, 0.f}}) {} |
| LoggingLabel(const VkDebugUtilsLabelEXT *label_info) { |
| if (label_info && label_info->pLabelName) { |
| name = label_info->pLabelName; |
| std::copy_n(std::begin(label_info->color), 4, color.begin()); |
| } |
| } |
| |
| LoggingLabel(const LoggingLabel &) = default; |
| LoggingLabel &operator=(const LoggingLabel &) = default; |
| LoggingLabel &operator=(LoggingLabel &&) = default; |
| LoggingLabel(LoggingLabel &&) = default; |
| |
| template <typename Name, typename Vec> |
| LoggingLabel(Name &&name_, Vec &&vec_) : name(std::forward<Name>(name_)), color(std::forward<Vec>(vec_)) {} |
| }; |
| |
| struct LoggingLabelState { |
| std::vector<LoggingLabel> labels; |
| LoggingLabel insert_label; |
| |
| // Export the labels, but in reverse order since we want the most recent at the top. |
| void Export(std::vector<VkDebugUtilsLabelEXT> &exported_labels) const { |
| exported_labels.reserve(exported_labels.size() + 1 + labels.size()); |
| |
| if (!insert_label.Empty()) { |
| exported_labels.emplace_back(insert_label.Export()); |
| } |
| |
| std::for_each(labels.rbegin(), labels.rend(), [&exported_labels](const LoggingLabel &label) { |
| if (!label.Empty()) { |
| exported_labels.emplace_back(label.Export()); |
| } |
| }); |
| } |
| }; |
| |
| class TypedHandleWrapper { |
| public: |
| template <typename Handle> |
| TypedHandleWrapper(Handle h, VulkanObjectType t) : handle_(h, t) {} |
| |
| const VulkanTypedHandle &Handle() const { return handle_; } |
| VulkanObjectType Type() const { return handle_.type; } |
| |
| protected: |
| VulkanTypedHandle handle_; |
| }; |
| |
| struct Location; |
| |
| struct MessageFormatSettings { |
| bool json = false; |
| bool display_application_name = false; |
| std::string application_name; |
| }; |
| |
| #if defined(__clang__) |
| #define DECORATE_PRINTF(_fmt_argnum, _first_param_num) __attribute__((format(printf, _fmt_argnum, _first_param_num))) |
| #elif defined(__GNUC__) |
| #define DECORATE_PRINTF(_fmt_argnum, _first_param_num) __attribute__((format(gnu_printf, _fmt_argnum, _first_param_num))) |
| #else |
| #define DECORATE_PRINTF(_fmt_num, _first_param_num) |
| #endif |
| |
| class DebugReport { |
| public: |
| std::vector<VkLayerDbgFunctionState> debug_callback_list; |
| // We use unordered_set to use trivial hashing for filter_message_ids as we already store hashed values |
| vvl::unordered_set<uint32_t> filter_message_ids{}; |
| // This mutex is defined as mutable since the normal usage for a debug report object is as 'const'. The mutable keyword allows |
| // the layers to continue this pattern, but also allows them to use/change this specific member for synchronization purposes. |
| mutable std::mutex debug_output_mutex; |
| uint32_t duplicate_message_limit = 0; // zero will keep printing forever |
| const void *instance_pnext_chain{}; |
| bool force_default_log_callback{false}; |
| uint32_t device_created = 0; |
| MessageFormatSettings message_format_settings; |
| |
| void SetUtilsObjectName(const VkDebugUtilsObjectNameInfoEXT *pNameInfo); |
| void SetMarkerObjectName(const VkDebugMarkerObjectNameInfoEXT *pNameInfo); |
| std::string GetUtilsObjectNameNoLock(const uint64_t object) const; |
| std::string GetMarkerObjectNameNoLock(const uint64_t object) const; |
| |
| void SetDebugUtilsSeverityFlags(std::vector<VkLayerDbgFunctionState> &callbacks); |
| void RemoveDebugUtilsCallback(uint64_t callback); |
| |
| std::string FormatHandle(const char *handle_type_name, uint64_t handle) const; |
| |
| std::string FormatHandle(const VulkanTypedHandle &handle) const { |
| return FormatHandle(string_VulkanObjectType(handle.type), handle.handle); |
| } |
| |
| std::string FormatHandle(const TypedHandleWrapper &wrapper) const { return FormatHandle(wrapper.Handle()); } |
| |
| template <typename T, typename std::enable_if_t<!std::is_base_of<TypedHandleWrapper, T>::value, void *> = nullptr> |
| std::string FormatHandle(T handle) const { |
| return FormatHandle(VkHandleInfo<T>::Typename(), HandleToUint64(handle)); |
| } |
| |
| // Legacy way to log messages with C-style va_list |
| bool LogMessageVaList(VkFlags msg_flags, std::string_view vuid_text, const LogObjectList &objects, const Location &loc, |
| const char *format, va_list argptr); |
| // Formats messages to be in the proper format, handles VUID logic, any legacy issues, and finally calls the callback |
| bool LogMessage(VkFlags msg_flags, std::string_view vuid_text, const LogObjectList &objects, const Location &loc, |
| const std::string &main_message); |
| |
| void BeginQueueDebugUtilsLabel(VkQueue queue, const VkDebugUtilsLabelEXT *label_info); |
| void EndQueueDebugUtilsLabel(VkQueue queue); |
| void InsertQueueDebugUtilsLabel(VkQueue queue, const VkDebugUtilsLabelEXT *label_info); |
| |
| void BeginCmdDebugUtilsLabel(VkCommandBuffer command_buffer, const VkDebugUtilsLabelEXT *label_info); |
| void EndCmdDebugUtilsLabel(VkCommandBuffer command_buffer); |
| void InsertCmdDebugUtilsLabel(VkCommandBuffer command_buffer, const VkDebugUtilsLabelEXT *label_info); |
| void ResetCmdDebugUtilsLabel(VkCommandBuffer command_buffer); |
| void EraseCmdDebugUtilsLabel(VkCommandBuffer command_buffer); |
| |
| private: |
| std::string CreateMessageText(const Location &loc, std::string_view vuid_text, const std::string &main_message, |
| bool at_message_limit); |
| std::string CreateMessageJson(VkFlags msg_flags, const Location &loc, |
| const std::vector<VkDebugUtilsObjectNameInfoEXT> &object_name_infos, const uint32_t vuid_hash, |
| std::string_view vuid_text, const std::string &main_message, bool at_message_limit); |
| |
| VkDebugUtilsMessageSeverityFlagsEXT active_msg_severities{0}; |
| VkDebugUtilsMessageTypeFlagsEXT active_msg_types{0}; |
| vvl::unordered_map<uint32_t, uint32_t> duplicate_message_count_map{}; |
| |
| vvl::unordered_map<VkQueue, std::unique_ptr<LoggingLabelState>> debug_utils_queue_labels; |
| vvl::unordered_map<VkCommandBuffer, std::unique_ptr<LoggingLabelState>> debug_utils_cmd_buffer_labels; |
| vvl::unordered_map<uint64_t, std::string> debug_object_name_map; |
| vvl::unordered_map<uint64_t, std::string> debug_utils_object_name_map; |
| }; |
| |
| class Logger { |
| public: |
| Logger(DebugReport *dr) : debug_report(dr) {} |
| template <typename T> |
| std::string FormatHandle(T &&h) const { |
| return debug_report->FormatHandle(std::forward<T>(h)); |
| } |
| |
| // Debug Logging Helpers |
| bool DECORATE_PRINTF(5, 6) |
| LogError(std::string_view vuid_text, const LogObjectList &objlist, const Location &loc, const char *format, ...) const { |
| va_list argptr; |
| va_start(argptr, format); |
| const bool result = debug_report->LogMessageVaList(kErrorBit, vuid_text, objlist, loc, format, argptr); |
| va_end(argptr); |
| return result; |
| } |
| |
| // Currently works like LogWarning, but allows developer to better categorize the warning |
| bool DECORATE_PRINTF(5, 6) LogUndefinedValue(std::string_view vuid_text, const LogObjectList &objlist, const Location &loc, |
| const char *format, ...) const { |
| va_list argptr; |
| va_start(argptr, format); |
| const bool result = debug_report->LogMessageVaList(kWarningBit, vuid_text, objlist, loc, format, argptr); |
| va_end(argptr); |
| return result; |
| } |
| |
| bool DECORATE_PRINTF(5, 6) |
| LogWarning(std::string_view vuid_text, const LogObjectList &objlist, const Location &loc, const char *format, ...) const { |
| va_list argptr; |
| va_start(argptr, format); |
| const bool result = debug_report->LogMessageVaList(kWarningBit, vuid_text, objlist, loc, format, argptr); |
| va_end(argptr); |
| return result; |
| } |
| |
| bool DECORATE_PRINTF(5, 6) LogPerformanceWarning(std::string_view vuid_text, const LogObjectList &objlist, const Location &loc, |
| const char *format, ...) const { |
| va_list argptr; |
| va_start(argptr, format); |
| const bool result = debug_report->LogMessageVaList(kPerformanceWarningBit, vuid_text, objlist, loc, format, argptr); |
| va_end(argptr); |
| return result; |
| } |
| |
| bool DECORATE_PRINTF(5, 6) |
| LogInfo(std::string_view vuid_text, const LogObjectList &objlist, const Location &loc, const char *format, ...) const { |
| va_list argptr; |
| va_start(argptr, format); |
| const bool result = debug_report->LogMessageVaList(kInformationBit, vuid_text, objlist, loc, format, argptr); |
| va_end(argptr); |
| return result; |
| } |
| |
| bool DECORATE_PRINTF(5, 6) |
| LogVerbose(std::string_view vuid_text, const LogObjectList &objlist, const Location &loc, const char *format, ...) const { |
| va_list argptr; |
| va_start(argptr, format); |
| const bool result = debug_report->LogMessageVaList(kVerboseBit, vuid_text, objlist, loc, format, argptr); |
| va_end(argptr); |
| return result; |
| } |
| |
| void LogInternalError(std::string_view failure_location, const LogObjectList &obj_list, const Location &loc, |
| std::string_view entrypoint, VkResult err) const { |
| const std::string_view err_string = string_VkResult(err); |
| std::string vuid = "INTERNAL-ERROR-"; |
| vuid += entrypoint; |
| LogError(vuid, obj_list, loc, "at %s: %s() was called in the Validation Layer state tracking and failed with result = %s.", |
| failure_location.data(), entrypoint.data(), err_string.data()); |
| } |
| |
| DebugReport *debug_report{nullptr}; |
| }; |
| |
| VKAPI_ATTR VkResult LayerCreateMessengerCallback(DebugReport *debug_report, bool default_callback, |
| const VkDebugUtilsMessengerCreateInfoEXT *create_info, |
| VkDebugUtilsMessengerEXT *messenger); |
| |
| VKAPI_ATTR VkResult LayerCreateReportCallback(DebugReport *debug_report, bool default_callback, |
| const VkDebugReportCallbackCreateInfoEXT *create_info, |
| VkDebugReportCallbackEXT *callback); |
| |
| template <typename T> |
| static inline void LayerDestroyCallback(DebugReport *debug_report, T callback) { |
| std::unique_lock<std::mutex> lock(debug_report->debug_output_mutex); |
| debug_report->RemoveDebugUtilsCallback(CastToUint64(callback)); |
| } |
| |
| VKAPI_ATTR void ActivateInstanceDebugCallbacks(DebugReport *debug_report); |
| |
| VKAPI_ATTR void DeactivateInstanceDebugCallbacks(DebugReport *debug_report); |
| |
| VKAPI_ATTR VkBool32 VKAPI_CALL MessengerBreakCallback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, |
| VkDebugUtilsMessageTypeFlagsEXT message_type, |
| const VkDebugUtilsMessengerCallbackDataEXT *callback_data, void *user_data); |
| |
| VKAPI_ATTR VkBool32 VKAPI_CALL MessengerLogCallback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, |
| VkDebugUtilsMessageTypeFlagsEXT message_type, |
| const VkDebugUtilsMessengerCallbackDataEXT *callback_data, void *user_data); |
| |
| #ifdef VK_USE_PLATFORM_WIN32_KHR |
| VKAPI_ATTR VkBool32 VKAPI_CALL MessengerWin32DebugOutputMsg(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, |
| VkDebugUtilsMessageTypeFlagsEXT message_type, |
| const VkDebugUtilsMessengerCallbackDataEXT *callback_data, |
| void *user_data); |
| #endif |