Skip to content
Merged
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
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Windows|Linux|BSD")
option(BUILD_SCREENSHOT "Build screenshot layer" ON)
if(NOT WIN32)
option(BUILD_CPUTIMING "Build CPUTiming layer" ON)
option(BUILD_DEBUGMARKER "Build DebugMarker layer" ON)
endif()
option(BUILD_LAYERMGR "Build Vulkan Configurator" ON)
set(SDK_VERSION "" CACHE STRING "Vulkan SDK version")
Expand All @@ -106,6 +107,7 @@ elseif(ANDROID)
option(BUILD_APIDUMP "Build api_dump layer" ON)
option(BUILD_SCREENSHOT "Build screenshot layer" ON)
option(BUILD_CPUTIMING "Build CPUTiming layer" ON)
option(BUILD_DEBUGMARKER "Build DebugMarker layer" ON)
set(BUILD_MONITOR OFF)
set(BUILD_LAYERMGR OFF)

Expand All @@ -125,7 +127,7 @@ if(BUILD_TESTS)
add_subdirectory(tests)
endif()

if(BUILD_APIDUMP OR BUILD_MONITOR OR BUILD_SCREENSHOT OR BUILD_CPUTIMING)
if(BUILD_APIDUMP OR BUILD_MONITOR OR BUILD_SCREENSHOT OR BUILD_CPUTIMING OR BUILD_DEBUGMARKER)
message(STATUS "INFO: Building Vulkan Layers")
add_subdirectory(layersvt)
endif()
Expand Down
34 changes: 33 additions & 1 deletion layersvt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,43 @@ if(BUILD_CPUTIMING)
endif()
endif()

if(BUILD_DEBUGMARKER)
add_library(VkLayer_DebugMarker MODULE)
set_target_properties(VkLayer_DebugMarker PROPERTIES FOLDER "layers/debugmarker")
target_sources(VkLayer_DebugMarker PRIVATE
debug_marker_handwritten_dispatch.cpp
debug_marker_handwritten_functions.h
debug_marker_handwritten_functions_vk_ext_debug_marker.h
debug_marker_handwritten_functions_vk_ext_debug_utils.h
debug_marker.h
debug_marker.cpp
debug_marker_perfetto.h
debug_marker_perfetto.cpp
perfetto/perfetto.cc
vk_layer_table.cpp
vk_layer_table.h
layer_keep_alive.cpp
json/VkLayer_DebugMarker.json.in
)

if(CMAKE_SYSTEM_NAME MATCHES "Linux|BSD|DragonFly|GNU")
if (BUILD_WSI_XCB_SUPPORT)
target_compile_definitions(VkLayer_DebugMarker PRIVATE VK_USE_PLATFORM_XLIB_KHR)
endif()

if (BUILD_WSI_WAYLAND_SUPPORT)
target_compile_definitions(VkLayer_DebugMarker PRIVATE VK_USE_PLATFORM_WAYLAND_KHR)
endif()
endif()

target_compile_definitions(VkLayer_DebugMarker PRIVATE VK_ENABLE_BETA_EXTENSIONS)
endif()

if (BUILD_TESTS AND NOT RUN_ON_GITHUB)
add_subdirectory(test)
endif()

list(APPEND TOOL_LAYERS "VkLayer_api_dump" "VkLayer_screenshot" "VkLayer_monitor" "VkLayer_CPUTiming")
list(APPEND TOOL_LAYERS "VkLayer_api_dump" "VkLayer_screenshot" "VkLayer_monitor" "VkLayer_CPUTiming" "VkLayer_DebugMarker")
foreach(layer ${TOOL_LAYERS})
if (NOT TARGET "${layer}")
continue()
Expand Down
86 changes: 86 additions & 0 deletions layersvt/debug_marker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* 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.
*/

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

DebugMarker& DebugMarker::Get() {
static DebugMarker instance;
return instance;
}

void DebugMarker::SetVkInstance(VkPhysicalDevice phys_dev, VkInstance instance) {
std::lock_guard<std::mutex> lock(mutex_);
vk_instance_map_[phys_dev] = instance;
}

VkInstance DebugMarker::GetVkInstance(VkPhysicalDevice phys_dev) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = vk_instance_map_.find(phys_dev);
if (it != vk_instance_map_.end()) return it->second;
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";
Comment thread
olehkuznetsov marked this conversation as resolved.
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());
});
}
Comment thread
olehkuznetsov marked this conversation as resolved.
}

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;
}
125 changes: 125 additions & 0 deletions layersvt/debug_marker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/* 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 <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.
*
* 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.
*
* This class is a singleton and provides thread-safe access to its state.
*/
class DebugMarker {
public:
/**
* @brief Returns the singleton instance of the DebugMarker class.
* @return Reference to the DebugMarker singleton.
*/
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.
Comment thread
olehkuznetsov marked this conversation as resolved.
* @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.
* @param phys_dev The Vulkan physical device.
* @param instance The Vulkan instance.
*/
void SetVkInstance(VkPhysicalDevice phys_dev, VkInstance instance);

/**
* @brief Retrieves the Vulkan instance associated with a given physical device.
* @param phys_dev The Vulkan physical device.
* @return The associated Vulkan instance.
*/
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_;
};
107 changes: 107 additions & 0 deletions layersvt/debug_marker_handwritten_dispatch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/* 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.
*/

#include "debug_marker_handwritten_functions.h"
#include "debug_marker_handwritten_functions_vk_ext_debug_marker.h"
#include "debug_marker_handwritten_functions_vk_ext_debug_utils.h"
#include "vk_layer_table.h"
#include <string.h>

extern "C" {

static PFN_vkVoidFunction debug_marker_known_instance_functions(const char* pName) {
if (strcmp(pName, "vkCreateInstance") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkCreateInstance);
if (strcmp(pName, "vkDestroyInstance") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkDestroyInstance);
if (strcmp(pName, "vkEnumeratePhysicalDevices") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkEnumeratePhysicalDevices);
if (strcmp(pName, "vkEnumeratePhysicalDeviceGroups") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkEnumeratePhysicalDeviceGroups);
if (strcmp(pName, "vkEnumerateInstanceExtensionProperties") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkEnumerateInstanceExtensionProperties);
if (strcmp(pName, "vkEnumerateInstanceLayerProperties") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkEnumerateInstanceLayerProperties);
if (strcmp(pName, "vkCreateDebugUtilsMessengerEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkCreateDebugUtilsMessengerEXT);
if (strcmp(pName, "vkDestroyDebugUtilsMessengerEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkDestroyDebugUtilsMessengerEXT);
if (strcmp(pName, "vkSubmitDebugUtilsMessageEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkSubmitDebugUtilsMessageEXT);
return nullptr;
}

static PFN_vkVoidFunction debug_marker_known_device_functions(const char* pName) {
if (strcmp(pName, "vkCreateDevice") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkCreateDevice);
if (strcmp(pName, "vkEnumerateDeviceLayerProperties") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkEnumerateDeviceLayerProperties);
if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkEnumerateDeviceExtensionProperties);

// VK_EXT_debug_marker
if (strcmp(pName, "vkCmdDebugMarkerBeginEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkCmdDebugMarkerBeginEXT);
if (strcmp(pName, "vkCmdDebugMarkerEndEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkCmdDebugMarkerEndEXT);
if (strcmp(pName, "vkCmdDebugMarkerInsertEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkCmdDebugMarkerInsertEXT);
if (strcmp(pName, "vkDebugMarkerSetObjectNameEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkDebugMarkerSetObjectNameEXT);
if (strcmp(pName, "vkDebugMarkerSetObjectTagEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkDebugMarkerSetObjectTagEXT);

// VK_EXT_debug_utils
if (strcmp(pName, "vkCmdBeginDebugUtilsLabelEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkCmdBeginDebugUtilsLabelEXT);
if (strcmp(pName, "vkCmdEndDebugUtilsLabelEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkCmdEndDebugUtilsLabelEXT);
if (strcmp(pName, "vkCmdInsertDebugUtilsLabelEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkCmdInsertDebugUtilsLabelEXT);
if (strcmp(pName, "vkSetDebugUtilsObjectNameEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkSetDebugUtilsObjectNameEXT);
if (strcmp(pName, "vkSetDebugUtilsObjectTagEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkSetDebugUtilsObjectTagEXT);
if (strcmp(pName, "vkQueueBeginDebugUtilsLabelEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkQueueBeginDebugUtilsLabelEXT);
if (strcmp(pName, "vkQueueEndDebugUtilsLabelEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkQueueEndDebugUtilsLabelEXT);
if (strcmp(pName, "vkQueueInsertDebugUtilsLabelEXT") == 0) return reinterpret_cast<PFN_vkVoidFunction>(vkQueueInsertDebugUtilsLabelEXT);

return nullptr;
}

EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const char* pName) {
PFN_vkVoidFunction func = debug_marker_known_instance_functions(pName);
if (func) {
return func;
}

// If it's a device function, we can also return it here if we want to support GIPA for device functions.
func = debug_marker_known_device_functions(pName);
if (func) {
return func;
}

if (instance == nullptr) {
return nullptr;
}

auto table = instance_dispatch_table(instance);
if (table == NULL) {
return nullptr;
}

if (table->GetInstanceProcAddr == NULL) {
return nullptr;
}

return table->GetInstanceProcAddr(instance, pName);
}

EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device, const char* pName) {
PFN_vkVoidFunction func = debug_marker_known_device_functions(pName);
if (func) {
return func;
}

if (device == nullptr) {
return nullptr;
}

if (device_dispatch_table(device)->GetDeviceProcAddr == NULL) {
return nullptr;
}

return device_dispatch_table(device)->GetDeviceProcAddr(device, pName);
}

} // extern "C"
Loading
Loading