/* Copyright (c) 2015-2016 The Khronos Group Inc.
* Copyright (c) 2015-2016 Valve Corporation
* Copyright (c) 2015-2016 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.
*
* Author: Courtney Goeltzenleuchter <courtney@LunarG.com>
* Author: Tobin Ehlis <tobin@lunarg.com>
*
*/
#ifndef LAYER_LOGGING_H
#define LAYER_LOGGING_H
#include "vk_loader_layer.h"
#include "vk_layer_config.h"
#include "vk_layer_data.h"
#include "vk_layer_table.h"
#include "vk_loader_platform.h"
#include "vulkan/vk_layer.h"
#include <signal.h>
#include <cinttypes>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <unordered_map>
#include <vector>
typedef struct _debug_report_data {
VkLayerDbgFunctionNode *debug_callback_list;
VkLayerDbgFunctionNode *default_debug_callback_list;
VkFlags active_flags;
bool g_DEBUG_REPORT;
std::unordered_map<uint64_t, std::string> *debugObjectNameMap;
} debug_report_data;
template debug_report_data *GetLayerDataPtr<debug_report_data>(void *data_key,
std::unordered_map<void *, debug_report_data *> &data_map);
// Forward Declarations
static inline bool debug_report_log_msg(const debug_report_data *debug_data, VkFlags msgFlags,
VkDebugReportObjectTypeEXT objectType, uint64_t srcObject, size_t location, int32_t msgCode,
const char *pLayerPrefix, const char *pMsg);
// Add a debug message callback node structure to the specified callback linked list
static inline void AddDebugMessageCallback(debug_report_data *debug_data, VkLayerDbgFunctionNode **list_head,
VkLayerDbgFunctionNode *new_node) {
new_node->pNext = *list_head;
*list_head = new_node;
}
// Remove specified debug message callback node structure from the specified callback linked list
static inline void RemoveDebugMessageCallback(debug_report_data *debug_data, VkLayerDbgFunctionNode **list_head,
VkDebugReportCallbackEXT callback) {
VkLayerDbgFunctionNode *cur_callback = *list_head;
VkLayerDbgFunctionNode *prev_callback = cur_callback;
bool matched = false;
VkFlags local_flags = 0;
while (cur_callback) {
if (cur_callback->msgCallback == callback) {
matched = true;
prev_callback->pNext = cur_callback->pNext;
if (*list_head == cur_callback) {
*list_head = cur_callback->pNext;
}
debug_report_log_msg(debug_data, VK_DEBUG_REPORT_DEBUG_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_EXT,
reinterpret_cast<uint64_t &>(cur_callback->msgCallback), 0, 0, "DebugReport",
"Destroyed callback\n");
} else {
matched = false;
local_flags |= cur_callback->msgFlags;
}
prev_callback = cur_callback;
cur_callback = cur_callback->pNext;
if (matched) {
free(prev_callback);
}
}
debug_data->active_flags = local_flags;
}
// Removes all debug callback function nodes from the specified callback linked lists and frees their resources
static inline void RemoveAllMessageCallbacks(debug_report_data *debug_data, VkLayerDbgFunctionNode **list_head) {
VkLayerDbgFunctionNode *current_callback = *list_head;
VkLayerDbgFunctionNode *prev_callback = current_callback;
while (current_callback) {
prev_callback = current_callback->pNext;
debug_report_log_msg(debug_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_EXT,
(uint64_t)current_callback->msgCallback, 0, 0, "DebugReport",
"Debug Report callbacks not removed before DestroyInstance");
free(current_callback);
current_callback = prev_callback;
}
*list_head = NULL;
}
// Utility function to handle reporting
static inline bool debug_report_log_msg(const debug_report_data *debug_data, VkFlags msgFlags,
VkDebugReportObjectTypeEXT objectType, uint64_t srcObject, size_t location, int32_t msgCode,
const char *pLayerPrefix, const char *pMsg) {
bool bail = false;
VkLayerDbgFunctionNode *pTrav = NULL;
if (debug_data->debug_callback_list != NULL) {
pTrav = debug_data->debug_callback_list;
} else {
pTrav = debug_data->default_debug_callback_list;
}
while (pTrav) {
if (pTrav->msgFlags & msgFlags) {
auto it = debug_data->debugObjectNameMap->find(srcObject);
if (it == debug_data->debugObjectNameMap->end()) {
if (pTrav->pfnMsgCallback(msgFlags, objectType, srcObject, location, msgCode, pLayerPrefix, pMsg,
pTrav->pUserData)) {
bail = true;
}
} else {
std::string newMsg = "SrcObject name = ";
newMsg.append(it->second.c_str());
newMsg.append(" ");
newMsg.append(pMsg);
if (pTrav->pfnMsgCallback(msgFlags, objectType, srcObject, location, msgCode, pLayerPrefix, newMsg.c_str(),
pTrav->pUserData)) {
bail = true;
}
}
}
pTrav = pTrav->pNext;
}
return bail;
}
static inline debug_report_data *debug_report_create_instance(
VkLayerInstanceDispatchTable *table, VkInstance inst, uint32_t extension_count,
const char *const *ppEnabledExtensions) // layer or extension name to be enabled
{
debug_report_data *debug_data = (debug_report_data *)malloc(sizeof(debug_report_data));
if (!debug_data) return NULL;
memset(debug_data, 0, sizeof(debug_report_data));
for (uint32_t i = 0; i < extension_count; i++) {
// TODO: Check other property fields
if (strcmp(ppEnabledExtensions[i], VK_EXT_DEBUG_REPORT_EXTENSION_NAME) == 0) {
debug_data->g_DEBUG_REPORT = true;
}
}
debug_data->debugObjectNameMap = new std::unordered_map<uint64_t, std::string>;
return debug_data;
}
static inline void layer_debug_report_destroy_instance(debug_report_data *debug_data) {
if (debug_data) {
RemoveAllMessageCallbacks(debug_data, &debug_data->default_debug_callback_list);
RemoveAllMessageCallbacks(debug_data, &debug_data->debug_callback_list);
delete debug_data->debugObjectNameMap;
free(debug_data);
}
}
static inline debug_report_data *layer_debug_report_create_device(debug_report_data *instance_debug_data, VkDevice device) {
// DEBUG_REPORT shares data between Instance and Device,
// so just return instance's data pointer
return instance_debug_data;
}
static inline void layer_debug_report_destroy_device(VkDevice device) {
// Nothing to do since we're using instance data record
}
static inline void layer_destroy_msg_callback(debug_report_data *debug_data, VkDebugReportCallbackEXT callback,
const VkAllocationCallbacks *pAllocator) {
RemoveDebugMessageCallback(debug_data, &debug_data->debug_callback_list, callback);
RemoveDebugMessageCallback(debug_data, &debug_data->default_debug_callback_list, callback);
}
static inline VkResult layer_create_msg_callback(debug_report_data *debug_data, bool default_callback,
const VkDebugReportCallbackCreateInfoEXT *pCreateInfo,
const VkAllocationCallbacks *pAllocator, VkDebugReportCallbackEXT *pCallback) {
VkLayerDbgFunctionNode *pNewDbgFuncNode = (VkLayerDbgFunctionNode *)malloc(sizeof(VkLayerDbgFunctionNode));
if (!pNewDbgFuncNode) return VK_ERROR_OUT_OF_HOST_MEMORY;
// Handle of 0 is logging_callback so use allocated Node address as unique handle
if (!(*pCallback)) *pCallback = (VkDebugReportCallbackEXT)pNewDbgFuncNode;
pNewDbgFuncNode->msgCallback = *pCallback;
pNewDbgFuncNode->pfnMsgCallback = pCreateInfo->pfnCallback;
pNewDbgFuncNode->msgFlags = pCreateInfo->flags;
pNewDbgFuncNode->pUserData = pCreateInfo->pUserData;
if (default_callback) {
AddDebugMessageCallback(debug_data, &debug_data->default_debug_callback_list, pNewDbgFuncNode);
debug_data->active_flags |= pCreateInfo->flags;
} else {
AddDebugMessageCallback(debug_data, &debug_data->debug_callback_list, pNewDbgFuncNode);
debug_data->active_flags = pCreateInfo->flags;
}
debug_report_log_msg(debug_data, VK_DEBUG_REPORT_DEBUG_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_EXT,
(uint64_t)*pCallback, 0, 0, "DebugReport", "Added callback");
return VK_SUCCESS;
}
static inline PFN_vkVoidFunction debug_report_get_instance_proc_addr(debug_report_data *debug_data, const char *funcName) {
if (!debug_data || !debug_data->g_DEBUG_REPORT) {
return NULL;
}
if (!strcmp(funcName, "vkCreateDebugReportCallbackEXT")) {
return (PFN_vkVoidFunction)vkCreateDebugReportCallbackEXT;
}
if (!strcmp(funcName, "vkDestroyDebugReportCallbackEXT")) {
return (PFN_vkVoidFunction)vkDestroyDebugReportCallbackEXT;
}
if (!strcmp(funcName, "vkDebugReportMessageEXT")) {
return (PFN_vkVoidFunction)vkDebugReportMessageEXT;
}
return NULL;
}
// This utility (called at vkCreateInstance() time), looks at a pNext chain.
// It counts any VkDebugReportCallbackCreateInfoEXT structs that it finds. It
// then allocates an array that can hold that many structs, as well as that
// many VkDebugReportCallbackEXT handles. It then copies each
// VkDebugReportCallbackCreateInfoEXT, and initializes each handle.
static inline VkResult layer_copy_tmp_callbacks(const void *pChain, uint32_t *num_callbacks,
VkDebugReportCallbackCreateInfoEXT **infos, VkDebugReportCallbackEXT **callbacks) {
uint32_t n = *num_callbacks = 0;
const void *pNext = pChain;
while (pNext) {
// 1st, count the number VkDebugReportCallbackCreateInfoEXT:
if (((VkDebugReportCallbackCreateInfoEXT *)pNext)->sType == VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT) {
n++;
}
pNext = (void *)((VkDebugReportCallbackCreateInfoEXT *)pNext)->pNext;
}
if (n == 0) {
return VK_SUCCESS;
}
// 2nd, allocate memory for each VkDebugReportCallbackCreateInfoEXT:
VkDebugReportCallbackCreateInfoEXT *pInfos = *infos =
((VkDebugReportCallbackCreateInfoEXT *)malloc(n * sizeof(VkDebugReportCallbackCreateInfoEXT)));
if (!pInfos) {
return VK_ERROR_OUT_OF_HOST_MEMORY;
}
// 3rd, allocate memory for a unique handle for each callback:
VkDebugReportCallbackEXT *pCallbacks = *callbacks = ((VkDebugReportCallbackEXT *)malloc(n * sizeof(VkDebugReportCallbackEXT)));
if (!pCallbacks) {
free(pInfos);
return VK_ERROR_OUT_OF_HOST_MEMORY;
}
// 4th, copy each VkDebugReportCallbackCreateInfoEXT for use by
// vkDestroyInstance, and assign a unique handle to each callback (just
// use the address of the copied VkDebugReportCallbackCreateInfoEXT):
pNext = pChain;
while (pNext) {
if (((VkInstanceCreateInfo *)pNext)->sType == VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT) {
memcpy(pInfos, pNext, sizeof(VkDebugReportCallbackCreateInfoEXT));
*pCallbacks++ = (VkDebugReportCallbackEXT)pInfos++;
}
pNext = (void *)((VkInstanceCreateInfo *)pNext)->pNext;
}
*num_callbacks = n;
return VK_SUCCESS;
}
// This utility frees the arrays allocated by layer_copy_tmp_callbacks()
static inline void layer_free_tmp_callbacks(VkDebugReportCallbackCreateInfoEXT *infos, VkDebugReportCallbackEXT *callbacks) {
free(infos);
free(callbacks);
}
// This utility enables all of the VkDebugReportCallbackCreateInfoEXT structs
// that were copied by layer_copy_tmp_callbacks()
static inline VkResult layer_enable_tmp_callbacks(debug_report_data *debug_data, uint32_t num_callbacks,
VkDebugReportCallbackCreateInfoEXT *infos, VkDebugReportCallbackEXT *callbacks) {
VkResult rtn = VK_SUCCESS;
for (uint32_t i = 0; i < num_callbacks; i++) {
rtn = layer_create_msg_callback(debug_data, false, &infos[i], NULL, &callbacks[i]);
if (rtn != VK_SUCCESS) {
for (uint32_t j = 0; j < i; j++) {
layer_destroy_msg_callback(debug_data, callbacks[j], NULL);
}
return rtn;
}
}
return rtn;
}
// This utility disables all of the VkDebugReportCallbackCreateInfoEXT structs
// that were copied by layer_copy_tmp_callbacks()
static inline void layer_disable_tmp_callbacks(debug_report_data *debug_data, uint32_t num_callbacks,
VkDebugReportCallbackEXT *callbacks) {
for (uint32_t i = 0; i < num_callbacks; i++) {
layer_destroy_msg_callback(debug_data, callbacks[i], NULL);
}
}
// Checks if the message will get logged.
// Allows layer to defer collecting & formating data if the
// message will be discarded.
static inline bool will_log_msg(debug_report_data *debug_data, VkFlags msgFlags) {
if (!debug_data || !(debug_data->active_flags & msgFlags)) {
// Message is not wanted
return false;
}
return true;
}
#ifndef WIN32
static inline int string_sprintf(std::string *output, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
#endif
static inline int string_sprintf(std::string *output, const char *fmt, ...) {
std::string &formatted = *output;
va_list argptr;
va_start(argptr, fmt);
int reserve = vsnprintf(nullptr, 0, fmt, argptr);
va_end(argptr);
formatted.reserve(reserve + 1);
va_start(argptr, fmt);
int result = vsnprintf((char *)formatted.data(), formatted.capacity(), fmt, argptr);
va_end(argptr);
assert(result == reserve);
return result;
}
#ifdef WIN32
static inline int vasprintf(char **strp, char const *fmt, va_list ap) {
*strp = nullptr;
int size = _vscprintf(fmt, ap);
if (size >= 0) {
*strp = (char *)malloc(size + 1);
if (!*strp) {
return -1;
}
_vsnprintf(*strp, size + 1, fmt, ap);
}
return size;
}
#endif
// Output log message via DEBUG_REPORT
// Takes format and variable arg list so that output string
// is only computed if a message needs to be logged
#ifndef WIN32
static inline bool log_msg(const debug_report_data *debug_data, VkFlags msgFlags, VkDebugReportObjectTypeEXT objectType,
uint64_t srcObject, size_t location, int32_t msgCode, const char *pLayerPrefix, const char *format, ...)
__attribute__((format(printf, 8, 9)));
#endif
static inline bool log_msg(const debug_report_data *debug_data, VkFlags msgFlags, VkDebugReportObjectTypeEXT objectType,
uint64_t srcObject, size_t location, int32_t msgCode, const char *pLayerPrefix, const char *format,
...) {
if (!debug_data || !(debug_data->active_flags & msgFlags)) {
// Message is not wanted
return false;
}
va_list argptr;
va_start(argptr, format);
char *str;
if (-1 == vasprintf(&str, format, argptr)) {
// On failure, glibc vasprintf leaves str undefined
str = nullptr;
}
va_end(argptr);
bool result = debug_report_log_msg(debug_data, msgFlags, objectType, srcObject, location, msgCode, pLayerPrefix,
str ? str : "Allocation failure");
free(str);
return result;
}
static inline VKAPI_ATTR VkBool32 VKAPI_CALL log_callback(VkFlags msgFlags, VkDebugReportObjectTypeEXT objType, uint64_t srcObject,
size_t location, int32_t msgCode, const char *pLayerPrefix,
const char *pMsg, void *pUserData) {
char msg_flags[30];
print_msg_flags(msgFlags, msg_flags);
fprintf((FILE *)pUserData, "%s(%s): object: 0x%" PRIx64 " type: %d location: %lu msgCode: %d: %s\n", pLayerPrefix, msg_flags,
srcObject, objType, (unsigned long)location, msgCode, pMsg);
fflush((FILE *)pUserData);
return false;
}
static inline VKAPI_ATTR VkBool32 VKAPI_CALL win32_debug_output_msg(VkFlags msgFlags, VkDebugReportObjectTypeEXT objType,
uint64_t srcObject, size_t location, int32_t msgCode,
const char *pLayerPrefix, const char *pMsg, void *pUserData) {
#ifdef WIN32
char msg_flags[30];
char buf[2048];
print_msg_flags(msgFlags, msg_flags);
_snprintf(buf, sizeof(buf) - 1,
"%s (%s): object: 0x%" PRIxPTR " type: %d location: " PRINTF_SIZE_T_SPECIFIER " msgCode: %d: %s\n", pLayerPrefix,
msg_flags, (size_t)srcObject, objType, location, msgCode, pMsg);
OutputDebugString(buf);
#endif
return false;
}
static inline VKAPI_ATTR VkBool32 VKAPI_CALL DebugBreakCallback(VkFlags msgFlags, VkDebugReportObjectTypeEXT objType,
uint64_t srcObject, size_t location, int32_t msgCode,
const char *pLayerPrefix, const char *pMsg, void *pUserData) {
#ifdef WIN32
DebugBreak();
#else
raise(SIGTRAP);
#endif
return false;
}
// 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 reinterpret_cast<uint64_t>(h);
}
template <typename HANDLE_T>
uint64_t HandleToUint64(HANDLE_T h);
static inline uint64_t HandleToUint64(uint64_t h) { return h; }
#endif // LAYER_LOGGING_H