How to pass object name to a Vulkan allocation callback?

The structure of the “Unfreed memory blocks output text file” by the function custom_malloc at line 25 looks like this:

file: "source.cpp", line: 25, scope: VK_SYSTEM_ALLOCATION_SCOPE_OBJECT, size in bytes: 136
file: "source.cpp", line: 25, scope: VK_SYSTEM_ALLOCATION_SCOPE_OBJECT, size in bytes: 168
file: "source.cpp", line: 25, scope: VK_SYSTEM_ALLOCATION_SCOPE_OBJECT, size in bytes: 168
file: "source.cpp", line: 25, scope: VK_SYSTEM_ALLOCATION_SCOPE_OBJECT, size in bytes: 984

But as you can see above, there is no way to know what is the name of the Vulkan Create function, for example: name: “vkCreateGraphicsPipelines”, I mean “custom_malloc” is called inside it.
So, “How to pass object name to a Vulkan allocation callback?”. Should I use pUserData? But I cannot see any documentation about it?

static void* VKAPI_PTR vk_allocation(void *pUserData, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
{
    switch (allocationScope)
    {
    case VK_SYSTEM_ALLOCATION_SCOPE_COMMAND:
        setMemoryScope("VK_SYSTEM_ALLOCATION_SCOPE_COMMAND");
        break;
    case VK_SYSTEM_ALLOCATION_SCOPE_OBJECT:
        setMemoryScope("VK_SYSTEM_ALLOCATION_SCOPE_OBJECT");
        break;
    case VK_SYSTEM_ALLOCATION_SCOPE_CACHE:
        setMemoryScope("VK_SYSTEM_ALLOCATION_SCOPE_CACHE");
        break;
    case VK_SYSTEM_ALLOCATION_SCOPE_DEVICE:
        setMemoryScope("VK_SYSTEM_ALLOCATION_SCOPE_DEVICE");
        break;
    case VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE:
        setMemoryScope("VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE");
        break;
    default:
        setMemoryScope("UNKNOWN");
        break;
    }
// Line 25
    void *mem = custom_malloc(size);
    check_alignment(mem, alignment);
    return mem;
}

Let me make the explanation more clearly:

setCustomMallocObjectName("vkCreateGraphicsPipelines"); // This doesn't exist but how to do it?
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &_pipeline));

This is generally not a reasonable thing to do.

The way you’re expected to deal with this is to change the allocator struct that you give to a particular function. Either the allocator function itself, or the pUserData pointer in the allocator struct, should be used to identify the kind of allocation in question.

This is important because Vulkan is designed for multi-threaded. Global state like your hypothetical setCustomMallocObjectName is not a good idea if you have more than one thread.

By making the data allocator-specific, you make it possible for the data to work with a particular set of functions, with any questions of threading defined by how those functions deal with external synchronization.

Thanks.

How to give information to a “Vulkan Create function” such as “vkCreateGraphicsPipelines”? For examples give inputs “Name, Type, File, Line-Number, etc.” to that create function, so all the multiple “Custom Malloc(s)” inside it will use those inputs. My goal is to fix memory leak problem, so less precise information (input) is enough just for hints to fix memory leak.

How about pUserData?

I found an example but I don’t believe it will help me, the code below is from “Vulkan C++ examples and demos > examples > debugmaker.cpp” (https://github.com/SaschaWillems/Vulkan), but the big problem is ‘active’ is always false because vkDebugMarkerSetObjectName and the others variables are all ‘nullptr’. It’s explained in the comment below that a “graphics debugger” is needed. But the problem is it requires Visual Studio 2017, and I’m using Visual Studio 2015. Also, I don’t believe it will help me because it only requires a “graphics debugger”, so I’m stuck.

bool active = false;
bool extensionPresent = false;

PFN_vkDebugMarkerSetObjectTagEXT vkDebugMarkerSetObjectTag = VK_NULL_HANDLE;
PFN_vkDebugMarkerSetObjectNameEXT vkDebugMarkerSetObjectName = VK_NULL_HANDLE;
PFN_vkCmdDebugMarkerBeginEXT vkCmdDebugMarkerBegin = VK_NULL_HANDLE;
PFN_vkCmdDebugMarkerEndEXT vkCmdDebugMarkerEnd = VK_NULL_HANDLE;
PFN_vkCmdDebugMarkerInsertEXT vkCmdDebugMarkerInsert = VK_NULL_HANDLE;

// Get function pointers for the debug report extensions from the device
void setup(VkDevice device, VkPhysicalDevice physicalDevice)
{
	// Check if the debug marker extension is present (which is the case if run from a graphics debugger)
	uint32_t extensionCount;
	vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, nullptr);
	std::vector<VkExtensionProperties> extensions(extensionCount);
	vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, extensions.data());
	for (auto extension : extensions) {
		if (strcmp(extension.extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME) == 0) {
			extensionPresent = true;
			break;
		}
	}

	if (extensionPresent) {
		// The debug marker extension is not part of the core, so function pointers need to be loaded manually
		vkDebugMarkerSetObjectTag = (PFN_vkDebugMarkerSetObjectTagEXT)vkGetDeviceProcAddr(device, "vkDebugMarkerSetObjectTagEXT");
		vkDebugMarkerSetObjectName = (PFN_vkDebugMarkerSetObjectNameEXT)vkGetDeviceProcAddr(device, "vkDebugMarkerSetObjectNameEXT");
		vkCmdDebugMarkerBegin = (PFN_vkCmdDebugMarkerBeginEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerBeginEXT");
		vkCmdDebugMarkerEnd = (PFN_vkCmdDebugMarkerEndEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerEndEXT");
		vkCmdDebugMarkerInsert = (PFN_vkCmdDebugMarkerInsertEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerInsertEXT");
		// Set flag if at least one function pointer is present
		active = (vkDebugMarkerSetObjectName != VK_NULL_HANDLE);
	}
	else {
		std::cout << "Warning: " << VK_EXT_DEBUG_MARKER_EXTENSION_NAME << " not present, debug markers are disabled.";
		std::cout << "Try running from inside a Vulkan graphics debugger (e.g. RenderDoc)" << std::endl;
	}
}

Why am I showing it? Because of the code below, but ‘active’ is always false, because I’m not using a “debugging application” or “graphics debugger”, just my own C++ app.

// Sets the debug name of an object
// All Objects in Vulkan are represented by their 64-bit handles which are passed into this function
// along with the object type
void setObjectName(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, const char *name)
{
	// Check for valid function pointer (may not be present if not running in a debugging application)
	if (active)
	{
		VkDebugMarkerObjectNameInfoEXT nameInfo = {};
		nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT;
		nameInfo.objectType = objectType;
		nameInfo.object = object;
		nameInfo.pObjectName = name;
		vkDebugMarkerSetObjectName(device, &nameInfo);
	}
}

The code below is not important to read, just in case someone else needs to know how to display unfreed memory when the program exits (outputs will show up when vkDestroyDevice and vkDestroyInstance are called), but it will show that all ‘name’ are NULL because they are not set, and I’m not using a “graphics debugger” (origin of the code: ImGui SDL Vulkan):

static void CleanupVulkan()
{
    vkDestroyDescriptorPool(g_Device, g_DescriptorPool, g_Allocator);

#ifdef IMGUI_VULKAN_DEBUG_REPORT
    // Remove the debug report callback
    auto vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(g_Instance, "vkDestroyDebugReportCallbackEXT");
	vkDestroyDebugReportCallbackEXT(g_Instance, g_DebugReport, g_Allocator);
#endif // IMGUI_VULKAN_DEBUG_REPORT

	vkDestroyDevice(g_Device, g_Allocator);
	vkDestroyInstance(g_Instance, g_Allocator);
}

Back to the question: “Why cannot I set vkDebugMarkerSetObjectName? And it’s always nullptr”.

Simpler question: “DebugReport” is designed for “graphics debugger”, so I would prefer using the custom allocation callback, using of pUserData, because it’s simpler, but I don’t know how to do it, but how?

Solved, pass a ‘hint’ in the beginning, and there’s an end because there can be multiple allocation callbacks inside a Vulkan create function.

beginDebugMemoryHint("vkCreateSampler"); // This passes a 'hint' to the allocation callbacks.
auto result = vkCreateSampler(device, &samplerInfo, nullptr, &t[i].texture->_sampler);
VERIFY(result == VkResult::VK_SUCCESS);
endDebugMemoryHint(); // This resets the 'hint' to avoid confusion for the next allocation callback.

Unfreed Memory Blocks Output Text File

file: "source.cpp", line: 25, scope: VK_SYSTEM_ALLOCATION_SCOPE_OBJECT, hint: vkCreateSampler, size in bytes: 136
file: "source.cpp", line: 25, scope: VK_SYSTEM_ALLOCATION_SCOPE_OBJECT, hint: vkCreateSampler, size in bytes: 168
file: "source.cpp", line: 25, scope: VK_SYSTEM_ALLOCATION_SCOPE_OBJECT, hint: vkCreateImageView, size in bytes: 168
file: "source.cpp", line: 25, scope: VK_SYSTEM_ALLOCATION_SCOPE_OBJECT, hint: vkCreateImageView, size in bytes: 984

The weird thing in programming is sometimes I go in wrong direction, sometimes the solution can be extremely complex while there is a very simple solution. I went in a restaurant and I suddenly realize the solution while I eat foods. It was answered after a very long time.

And where is this “hint” stored? Is it thread-safe?

In a file named “debugmemory.h”:

#define malloc(size)  ((__file__ = __FILE__, __line__ = __LINE__, 0) ? 0 : MALLOC(size, MEMORYBLOCKTYPE_MALLOC))

In a file named “debugmemory.cpp”:

const char *__file__  = nullptr;
int         __line__  = 0;
const char *__scope__ = nullptr;
const char *__hint__  = nullptr;

...

void beginDebugMemoryHint(const char *hint) {
	if (__hint__ != nullptr) {
		show_error("Debug memory hint must be ended.");
	}
	__hint__ = hint;
}
void endDebugMemoryHint() {
	__hint__ = nullptr;
}

When a ‘malloc’ or ‘new’ is called then these global variables above are set, and the ‘hint’ you were talking about is stored there. I suppose that the variable __hint __ is accessible by all the multiple threads. The important for the moment is there is no crash, the important is just showing a less precise hint in order to fix memory leak, that’s why I call it literally a hint. When I will become advanced in multi-threading, it will be a different story.

That’s not how you write threaded code. If your code is not provably thread-safe, then it should be assumed not to be.

This is like planting a time bomb in your code with a random timer.

Also, you’re not allowed to declare any identifiers with double-underscores in C or C++. Those are reserved for the implementation, and undefined behavior results from declaring such things.

1 Like