blob: 5581e8080059d40ca8634dccc6b46e0d388dcb37 [file] [log] [blame]
/* 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