Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions layersvt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ if(BUILD_DEBUGMARKER)
debug_marker/debug_marker.cpp
debug_marker/debug_marker_perfetto.h
debug_marker/debug_marker_perfetto.cpp
object_names/vulkan_object_names.h
object_names/vulkan_object_names.cpp
object_names/vulkan_object_names_perfetto.h
perfetto/perfetto.cc
vk_layer_table.cpp
vk_layer_table.h
Expand Down Expand Up @@ -207,10 +210,13 @@ if(BUILD_DEVICEMEMORYREPORT)
target_sources(VkLayer_DeviceMemoryReport PRIVATE
device_memory_report/device_memory_report_handwritten_dispatch.cpp
device_memory_report/device_memory_report_handwritten_functions.h
device_memory_report/device_memory_report_handwritten_functions_object_names.h
device_memory_report/device_memory_report.h
device_memory_report/device_memory_report.cpp
device_memory_report/device_memory_report_perfetto.h
device_memory_report/device_memory_report_perfetto.cpp
object_names/vulkan_object_names.h
object_names/vulkan_object_names.cpp
perfetto/perfetto.cc
vk_layer_table.cpp
vk_layer_table.h
Expand Down
48 changes: 0 additions & 48 deletions layersvt/debug_marker/debug_marker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
*/

#include "debug_marker.h"
#include "debug_marker_perfetto.h"
#include "perfetto/perfetto.h"

DebugMarker& DebugMarker::Get() {
static DebugMarker instance;
Expand All @@ -34,53 +32,7 @@ VkInstance DebugMarker::GetVkInstance(VkPhysicalDevice phys_dev) {
return VK_NULL_HANDLE;
}

void DebugMarker::SetDebugObjectName(uint64_t device, int32_t type, uint64_t handle, const char* name) {
std::lock_guard<std::mutex> lock(mutex_);

std::string name_str = name ? name : "NULL";
debug_object_names_[std::make_pair(type, handle)] = DebugObjectName(device, type, handle, name_str);

perfetto::TrackEvent::Trace([device, type, handle, name_str](perfetto::TrackEvent::TraceContext ctx) {
auto packet = ctx.NewTracePacket();
packet->set_timestamp(perfetto::base::GetBootTimeNs().count());
auto event = packet->set_vulkan_api_event()->set_vk_debug_utils_object_name();
event->set_vk_device(device);
event->set_object_type(type);
event->set_object(handle);
event->set_object_name(name_str.c_str());
});
}

void DebugMarker::EmitAllDebugMarkers() {
std::lock_guard<std::mutex> lock(mutex_);
for (const auto& entry : debug_object_names_) {
const auto& marker = entry.second;
uint64_t device = marker.vk_device;
int32_t type = marker.object_type;
uint64_t handle = marker.handle;
std::string name_str = marker.name;

perfetto::TrackEvent::Trace([device, type, handle, name_str](perfetto::TrackEvent::TraceContext ctx) {
auto packet = ctx.NewTracePacket();
packet->set_timestamp(perfetto::base::GetBootTimeNs().count());
auto event = packet->set_vulkan_api_event()->set_vk_debug_utils_object_name();
event->set_vk_device(device);
event->set_object_type(type);
event->set_object(handle);
event->set_object_name(name_str.c_str());
});
}
}

void DebugMarker::Clear() {
std::lock_guard<std::mutex> lock(mutex_);
vk_instance_map_.clear();
debug_object_names_.clear();
}

bool DebugMarker::HasDebugObjectName(int32_t type, uint64_t handle, const std::string& name) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = debug_object_names_.find(std::make_pair(type, handle));
if (it == debug_object_names_.end()) return false;
return it->second.name == name;
}
72 changes: 7 additions & 65 deletions layersvt/debug_marker/debug_marker.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,14 @@
#include <mutex>
#include <unordered_map>

#include <map>
#include <string>

/**
* The DebugMarker class is responsible for storing and managing debug marker
* information associated with Vulkan objects and emitting them to Perfetto traces.
*
* Currently, it primarily supports tracking and emitting object names.
*
* How it works:
* We do not store a history of events for setting object names. Instead, we store
* only the current name for each object (one name per object). This keeps the memory
* footprint small for most applications (proportional to the name size multiplied by
* the number of unique objects).
*
* Perfetto Session Support:
* This solution supports:
* - Starting a Perfetto session before the application starts.
* - Starting a Perfetto session after the application is already running.
* - Running multiple Perfetto sessions during a single application run.
*
* To support late-attach and multiple sessions, when a Perfetto session starts,
* we write all currently known object names to the trace. We retain the names in memory
* because a user might start another Perfetto session later, requiring us to emit
* all object names again.
* The DebugMarker class holds the state the DebugMarker layer needs beyond object names.
*
* A potential issue exists if an application constantly creates and destroys
* objects without bound, as we currently do not remove names for destroyed objects.
* Support for removing names on object destruction can be added later if needed.
* Object names themselves are not stored here: the tracking of (VkObjectType, handle) -> name,
* the mapping of the legacy VK_EXT_debug_marker object types and the replay of known names when a
* Perfetto session starts all live in layersvt::VulkanObjectNames
* (see object_names/vulkan_object_names.h), which this layer shares with the DeviceMemoryReport
* layer.
*
* This class is a singleton and provides thread-safe access to its state.
*/
Expand All @@ -60,30 +39,10 @@ class DebugMarker {
static DebugMarker& Get();

/**
* @brief Sets or updates the name associated with a Vulkan object.
* @param device The handle of the Vulkan device that owns the object.
* @param type The type of the Vulkan object (represented as int32_t).
* @param handle The handle of the Vulkan object.
* @param name The name to associate with the object.
*/
void SetDebugObjectName(uint64_t device, int32_t type, uint64_t handle, const char* name);

/**
* @brief Emits all stored debug markers to the tracing system.
*/
void EmitAllDebugMarkers();

/**
* @brief Clears all stored debug markers and instance mappings.
* @brief Clears all instance mappings.
* @note This function is for testing only.
*/
void Clear();

/**
* @brief Checks if a debug name is stored for a given object.
* @note This function is for testing only.
*/
bool HasDebugObjectName(int32_t type, uint64_t handle, const std::string& name);

/**
* @brief Associates a Vulkan physical device with its corresponding instance.
Expand All @@ -99,27 +58,10 @@ class DebugMarker {
*/
VkInstance GetVkInstance(VkPhysicalDevice phys_dev);


private:
struct DebugObjectName {
uint64_t vk_device;
int32_t object_type;
uint64_t handle;
std::string name;

DebugObjectName() = default;
DebugObjectName(uint64_t dev, int32_t type, uint64_t h, const std::string& n)
: vk_device(dev), object_type(type), handle(h), name(n) {}
};

std::mutex mutex_;
/**
* @brief Maps a physical device handle to its corresponding Vulkan instance handle.
*/
std::unordered_map<VkPhysicalDevice, VkInstance> vk_instance_map_;
/**
* @brief Maps a pair of (object_type, object_handle) to its debug name information.
* We use a pair as the key because handles are not guaranteed to be unique across different object types.
*/
std::map<std::pair<int32_t, uint64_t>, DebugObjectName> debug_object_names_;
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,61 +17,16 @@

#include <vulkan/vulkan.h>
#include "vk_layer_table.h"
#include "debug_marker.h"
#include "object_names/vulkan_object_names.h"

// This file contains handwritten functions for the VK_EXT_debug_marker extension.
// We only actively implement vkDebugMarkerSetObjectNameEXT to track object names for Perfetto traces.
// All other functions are simple passthroughs required to be provided so that the layer
// can claim full support for the extension.

// We need to remap objects using getVkObjectType because VK_EXT_debug_marker uses the legacy
// VkDebugReportObjectTypeEXT enum, while we store everything using the modern VkObjectType
// enum to be consistent with VK_EXT_debug_utils and standard trace packets.
static inline VkObjectType getVkObjectType(VkDebugReportObjectTypeEXT vk_debug_report_object_type) {
switch (vk_debug_report_object_type) {
case VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT: return VK_OBJECT_TYPE_UNKNOWN;
case VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT: return VK_OBJECT_TYPE_INSTANCE;
case VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT: return VK_OBJECT_TYPE_PHYSICAL_DEVICE;
case VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT: return VK_OBJECT_TYPE_DEVICE;
case VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT: return VK_OBJECT_TYPE_QUEUE;
case VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT: return VK_OBJECT_TYPE_SEMAPHORE;
case VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT: return VK_OBJECT_TYPE_COMMAND_BUFFER;
case VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT: return VK_OBJECT_TYPE_FENCE;
case VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT: return VK_OBJECT_TYPE_DEVICE_MEMORY;
case VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT: return VK_OBJECT_TYPE_BUFFER;
case VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT: return VK_OBJECT_TYPE_IMAGE;
case VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT: return VK_OBJECT_TYPE_EVENT;
case VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT: return VK_OBJECT_TYPE_QUERY_POOL;
case VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT: return VK_OBJECT_TYPE_BUFFER_VIEW;
case VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT: return VK_OBJECT_TYPE_IMAGE_VIEW;
case VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT: return VK_OBJECT_TYPE_SHADER_MODULE;
case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT: return VK_OBJECT_TYPE_PIPELINE_CACHE;
case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT: return VK_OBJECT_TYPE_PIPELINE_LAYOUT;
case VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT: return VK_OBJECT_TYPE_RENDER_PASS;
case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT: return VK_OBJECT_TYPE_PIPELINE;
case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT: return VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT;
case VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT: return VK_OBJECT_TYPE_SAMPLER;
case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT: return VK_OBJECT_TYPE_DESCRIPTOR_POOL;
case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT: return VK_OBJECT_TYPE_DESCRIPTOR_SET;
case VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT: return VK_OBJECT_TYPE_FRAMEBUFFER;
case VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT: return VK_OBJECT_TYPE_COMMAND_POOL;
case VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT: return VK_OBJECT_TYPE_SURFACE_KHR;
case VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT: return VK_OBJECT_TYPE_SWAPCHAIN_KHR;
case VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT_EXT: return VK_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT;
case VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_KHR_EXT: return VK_OBJECT_TYPE_DISPLAY_KHR;
case VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_MODE_KHR_EXT: return VK_OBJECT_TYPE_DISPLAY_MODE_KHR;
case VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT_EXT: return VK_OBJECT_TYPE_VALIDATION_CACHE_EXT;
case VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT: return VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION;
case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT: return VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE;
case VK_DEBUG_REPORT_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV_EXT: return VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV;
case VK_DEBUG_REPORT_OBJECT_TYPE_CU_MODULE_NVX_EXT: return VK_OBJECT_TYPE_CU_MODULE_NVX;
case VK_DEBUG_REPORT_OBJECT_TYPE_CU_FUNCTION_NVX_EXT: return VK_OBJECT_TYPE_CU_FUNCTION_NVX;
case VK_DEBUG_REPORT_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR_EXT: return VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR;
case VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_COLLECTION_FUCHSIA_EXT: return VK_OBJECT_TYPE_BUFFER_COLLECTION_FUCHSIA;
default:
return VK_OBJECT_TYPE_UNKNOWN;
}
}
//
// VK_EXT_debug_marker identifies objects with the legacy VkDebugReportObjectTypeEXT enum, while we
// store everything using the modern VkObjectType enum to be consistent with VK_EXT_debug_utils and
// standard trace packets. layersvt::VulkanObjectNames performs that remapping for us.

extern "C" {

Expand All @@ -98,7 +53,7 @@ VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerInsertEXT(VkCommandBuffer commandBuff

// Required for VK_EXT_debug_marker. Tracks object name state.
VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectNameEXT(VkDevice device, const VkDebugMarkerObjectNameInfoEXT* pNameInfo) {
DebugMarker::Get().SetDebugObjectName((uint64_t)device, (int32_t)getVkObjectType(pNameInfo->objectType), pNameInfo->object, pNameInfo->pObjectName);
layersvt::VulkanObjectNames::Get().SetObjectName(device, pNameInfo);
if (device_dispatch_table(device)->DebugMarkerSetObjectNameEXT) {
VkResult result = device_dispatch_table(device)->DebugMarkerSetObjectNameEXT(device, pNameInfo);
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

#include <vulkan/vulkan.h>
#include "vk_layer_table.h"
#include "debug_marker.h"
#include "object_names/vulkan_object_names.h"

extern "C" {

Expand Down Expand Up @@ -49,7 +49,7 @@ VKAPI_ATTR void VKAPI_CALL vkCmdInsertDebugUtilsLabelEXT(VkCommandBuffer command

// Required for VK_EXT_debug_utils. Tracks object name state.
VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectNameEXT(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* pNameInfo) {
DebugMarker::Get().SetDebugObjectName((uint64_t)device, (int32_t)pNameInfo->objectType, pNameInfo->objectHandle, pNameInfo->pObjectName);
layersvt::VulkanObjectNames::Get().SetObjectName(device, pNameInfo);
if (device_dispatch_table(device)->SetDebugUtilsObjectNameEXT) {
VkResult result = device_dispatch_table(device)->SetDebugUtilsObjectNameEXT(device, pNameInfo);
return result;
Expand Down
14 changes: 12 additions & 2 deletions layersvt/debug_marker/debug_marker_perfetto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,24 @@
*/

#include "debug_marker_perfetto.h"
#include "debug_marker.h"
#include "object_names/vulkan_object_names_perfetto.h"

PERFETTO_TRACK_EVENT_STATIC_STORAGE();

namespace layersvt {

// Binds the shared object name store to this layer's track event data source.
void EmitVulkanObjectName(const VulkanObjectName& object_name) {
WriteVulkanObjectNamePacket<perfetto::TrackEvent>(object_name);
}

} // namespace layersvt

class MarkerSessionObserver : public perfetto::TrackEventSessionObserver {
public:
void OnStart(const perfetto::DataSourceBase::StartArgs&) override {
DebugMarker::Get().EmitAllDebugMarkers();
// Replay the names of objects that were named before this session started.
layersvt::VulkanObjectNames::Get().EmitAll();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/

#include "device_memory_report_handwritten_functions.h"
#include "device_memory_report_handwritten_functions_object_names.h"
#include "vk_layer_table.h"
#include <string.h>

Expand Down Expand Up @@ -61,6 +62,8 @@ static PFN_vkVoidFunction devmemreport_known_device_extension_functions(const ch
if (strcmp(pName, "vkBindImageMemory2KHR") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkBindImageMemory2KHR);
if (strcmp(pName, "vkGetImageMemoryRequirements2KHR") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkGetImageMemoryRequirements2KHR);
if (strcmp(pName, "vkGetBufferMemoryRequirements2KHR") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkGetBufferMemoryRequirements2KHR);
if (strcmp(pName, "vkSetDebugUtilsObjectNameEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkSetDebugUtilsObjectNameEXT);
if (strcmp(pName, "vkDebugMarkerSetObjectNameEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkDebugMarkerSetObjectNameEXT);
return nullptr;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* Copyright (C) 2026 Google 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 <vulkan/vulkan.h>
#include "vk_layer_table.h"
#include "object_names/vulkan_object_names.h"

// Object naming intercepts for the DeviceMemoryReport layer.
//
// Memory counters are only actionable if the buffers and images behind them can be identified, so
// this layer publishes the object names the application sets. The bookkeeping itself (name storage,
// mapping of the legacy VK_EXT_debug_marker object types, and replaying known names when a tracing
// session starts) is shared with the DebugMarker layer through layersvt::VulkanObjectNames, so the
// two layers cannot drift apart on what a name is or when it changes.
//
// What they do not share is how a name reaches the trace. DebugMarker writes
// VulkanApiEvent.VkDebugUtilsObjectName packets, which trace_processor reads back only for render
// passes, render targets and command buffers; a name on a buffer or image in that form is parsed
// and then never surfaced. This layer therefore emits "VulkanObjectName" instant events instead,
// which land in the slice table and can be joined to the memory events by object handle. See
// EmitVulkanObjectName in device_memory_report_perfetto.cpp.
//
// Because the two layers emit different events, loading both does not duplicate either one.
//
// Unlike the DebugMarker layer, this layer does not claim VK_EXT_debug_utils or
// VK_EXT_debug_marker. It only observes these entry points when the underlying driver already
// implements them, which vkGetDeviceProcAddr / vkGetInstanceProcAddr verify before handing out
// these interceptors.

extern "C" {

// Observes VK_EXT_debug_utils object names, then passes the call down the chain.
VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectNameEXT(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* pNameInfo) {
layersvt::VulkanObjectNames::Get().SetObjectName(device, pNameInfo);
if (device_dispatch_table(device)->SetDebugUtilsObjectNameEXT) {
return device_dispatch_table(device)->SetDebugUtilsObjectNameEXT(device, pNameInfo);
}
return VK_SUCCESS;
}

// Observes legacy VK_EXT_debug_marker object names, then passes the call down the chain.
VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectNameEXT(VkDevice device, const VkDebugMarkerObjectNameInfoEXT* pNameInfo) {
layersvt::VulkanObjectNames::Get().SetObjectName(device, pNameInfo);
if (device_dispatch_table(device)->DebugMarkerSetObjectNameEXT) {
return device_dispatch_table(device)->DebugMarkerSetObjectNameEXT(device, pNameInfo);
}
return VK_SUCCESS;
}

} // extern "C"
Loading
Loading