Repository navigation
Add Debug Marker layer with Perfetto support #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
olehkuznetsov
merged 13 commits into
android-graphics:main
from
olehkuznetsov:debug-markers
Apr 15, 2026
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
a31173e
Add Debug Marker layer with Perfetto support
olehkuznetsov 1a92f0f
Add test for debug_maker layer
olehkuznetsov 2ccccc7
A bit of refactoring
olehkuznetsov da7199d
Add layersvt/debug_marker_handwritten_functions.h description
olehkuznetsov 5652843
some refactoring
olehkuznetsov ff58ea5
Improve test
olehkuznetsov 22c6355
Update debug_marker.h comment
olehkuznetsov 3158b87
Removed NODEVICE_SELECT=1
olehkuznetsov f83bf3c
clean up
olehkuznetsov 001d107
Explicity packet time is required for perfetto::TrackEvent::Trace.
olehkuznetsov e00b9a2
debug_marker add vkDestroyInstance for clean up
olehkuznetsov a7a1967
add "test only" note to Clear and HasDebugObjectName
olehkuznetsov 009bf12
Merge branch 'main' into debug-markers
olehkuznetsov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"; | ||
| 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()); | ||
| }); | ||
| } | ||
|
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; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. | ||
|
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_; | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.