From f38769b357cf76ae263e464b953b7acc17a5459e Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 11 Aug 2026 13:03:52 +0000 Subject: [PATCH 01/16] Vulkan Memory Report layer: Vulkan Memory by Usage Type Implements VK_LAYER_GOOGLE_DeviceMemoryReport layer providing memory tracking by Usage Type. Exposes VK_EXT_device_memory_report and classifies allocations by Vulkan usage types (surface, texture, buffer, other). Emits Perfetto counters starting with vulkan.mem.app.usage.* and vulkan.mem.driver.usage.*. --- CMakeLists.txt | 1 + layersvt/CMakeLists.txt | 42 +- .../VkLayer_DeviceMemoryReport.def | 22 + .../VkLayer_DeviceMemoryReport.json.in | 17 + .../device_memory_report.cpp | 186 ++++++++ .../device_memory_report.h | 212 +++++++++ ...ice_memory_report_handwritten_dispatch.cpp | 91 ++++ ...vice_memory_report_handwritten_functions.h | 403 ++++++++++++++++++ .../device_memory_report_perfetto.cpp | 33 ++ .../device_memory_report_perfetto.h | 29 ++ layersvt/test/CMakeLists.txt | 5 +- layersvt/test/test_devicememoryreport.cpp | 186 ++++++++ 12 files changed, 1225 insertions(+), 2 deletions(-) create mode 100644 layersvt/device_memory_report/VkLayer_DeviceMemoryReport.def create mode 100644 layersvt/device_memory_report/VkLayer_DeviceMemoryReport.json.in create mode 100644 layersvt/device_memory_report/device_memory_report.cpp create mode 100644 layersvt/device_memory_report/device_memory_report.h create mode 100644 layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp create mode 100644 layersvt/device_memory_report/device_memory_report_handwritten_functions.h create mode 100644 layersvt/device_memory_report/device_memory_report_perfetto.cpp create mode 100644 layersvt/device_memory_report/device_memory_report_perfetto.h create mode 100644 layersvt/test/test_devicememoryreport.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b80cbd2318..e16790f3a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,7 @@ if (BUILD_WERROR) endif() option(BUILD_TESTS "Build tests") +option(BUILD_DEVICEMEMORYREPORT "Build DeviceMemoryReport layer" ON) option(RUN_ON_CI "Build only tests that can run on C.I." ON) if(BUILD_TESTS) diff --git a/layersvt/CMakeLists.txt b/layersvt/CMakeLists.txt index 2e5a64324d..92538d5e3a 100644 --- a/layersvt/CMakeLists.txt +++ b/layersvt/CMakeLists.txt @@ -201,11 +201,47 @@ if(BUILD_DEBUGMARKER) target_compile_definitions(VkLayer_DebugMarker PRIVATE VK_ENABLE_BETA_EXTENSIONS) endif() +if(BUILD_DEVICEMEMORYREPORT) + add_library(VkLayer_DeviceMemoryReport MODULE) + set_target_properties(VkLayer_DeviceMemoryReport PROPERTIES FOLDER "layers/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.h + device_memory_report/device_memory_report.cpp + device_memory_report/device_memory_report_perfetto.h + device_memory_report/device_memory_report_perfetto.cpp + perfetto/perfetto.cc + vk_layer_table.cpp + vk_layer_table.h + layer_keep_alive.cpp + device_memory_report/VkLayer_DeviceMemoryReport.json.in + ) + + target_include_directories(VkLayer_DeviceMemoryReport PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/device_memory_report + ${CMAKE_CURRENT_BINARY_DIR} + ) + + if(CMAKE_SYSTEM_NAME MATCHES "Linux|BSD|DragonFly|GNU") + if (BUILD_WSI_XCB_SUPPORT) + target_compile_definitions(VkLayer_DeviceMemoryReport PRIVATE VK_USE_PLATFORM_XLIB_KHR) + endif() + + if (BUILD_WSI_WAYLAND_SUPPORT) + target_compile_definitions(VkLayer_DeviceMemoryReport PRIVATE VK_USE_PLATFORM_WAYLAND_KHR) + endif() + endif() + + target_compile_definitions(VkLayer_DeviceMemoryReport 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" "VkLayer_DebugMarker") +list(APPEND TOOL_LAYERS "VkLayer_api_dump" "VkLayer_screenshot" "VkLayer_monitor" "VkLayer_CPUTiming" "VkLayer_DebugMarker" "VkLayer_DeviceMemoryReport") foreach(layer ${TOOL_LAYERS}) if (NOT TARGET "${layer}") continue() @@ -217,6 +253,8 @@ foreach(layer ${TOOL_LAYERS}) set(layer_dir "cpu_timing/") elseif(layer STREQUAL "VkLayer_screenshot") set(layer_dir "screenshot/") + elseif(layer STREQUAL "VkLayer_DeviceMemoryReport") + set(layer_dir "device_memory_report/") else() set(layer_dir "") endif() @@ -265,6 +303,8 @@ foreach(layer ${TOOL_LAYERS}) set(INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cpu_timing/${layer}.json.in") elseif(layer STREQUAL "VkLayer_screenshot") set(INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/screenshot/json/${layer}.json.in") + elseif(layer STREQUAL "VkLayer_DeviceMemoryReport") + set(INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/device_memory_report/${layer}.json.in") else() set(INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/json/${layer}.json.in") endif() diff --git a/layersvt/device_memory_report/VkLayer_DeviceMemoryReport.def b/layersvt/device_memory_report/VkLayer_DeviceMemoryReport.def new file mode 100644 index 0000000000..12e17d232b --- /dev/null +++ b/layersvt/device_memory_report/VkLayer_DeviceMemoryReport.def @@ -0,0 +1,22 @@ +; 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. + +LIBRARY VkLayer_DeviceMemoryReport +EXPORTS +vkGetInstanceProcAddr +vkGetDeviceProcAddr +vkEnumerateInstanceExtensionProperties +vkEnumerateInstanceLayerProperties +vkEnumerateDeviceExtensionProperties +vkEnumerateDeviceLayerProperties diff --git a/layersvt/device_memory_report/VkLayer_DeviceMemoryReport.json.in b/layersvt/device_memory_report/VkLayer_DeviceMemoryReport.json.in new file mode 100644 index 0000000000..9cbbf16db0 --- /dev/null +++ b/layersvt/device_memory_report/VkLayer_DeviceMemoryReport.json.in @@ -0,0 +1,17 @@ +{ + "file_format_version" : "1.2.0", + "layer" : { + "name": "VK_LAYER_GOOGLE_DeviceMemoryReport", + "type": "GLOBAL", + "library_path": "@JSON_LIBRARY_PATH@", + "api_version": "@JSON_VERSION@", + "implementation_version": "1", + "description": "Vulkan Device Memory Report Layer", + "device_extensions": [ + { + "name": "VK_EXT_device_memory_report", + "spec_version": "2" + } + ] + } +} diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp new file mode 100644 index 0000000000..c1160dc233 --- /dev/null +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -0,0 +1,186 @@ +/* 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 "device_memory_report.h" +#include "device_memory_report_perfetto.h" +#include "perfetto/perfetto.h" +#include +#include + +DeviceMemoryReport& DeviceMemoryReport::Get() { + static DeviceMemoryReport instance; + return instance; +} + +void DeviceMemoryReport::SetVkInstance(VkPhysicalDevice phys_dev, VkInstance instance) { + std::lock_guard lock(map_mutex_); + vk_instance_map_[phys_dev] = instance; +} + +VkInstance DeviceMemoryReport::GetVkInstance(VkPhysicalDevice phys_dev) { + std::lock_guard lock(map_mutex_); + auto it = vk_instance_map_.find(phys_dev); + if (it != vk_instance_map_.end()) return it->second; + return VK_NULL_HANDLE; +} + +void VKAPI_PTR DeviceMemoryReport::MemoryReportCallback(const VkDeviceMemoryReportCallbackDataEXT* pCallbackData, void* pUserData) { + DeviceMemoryReport::Get().OnMemoryReportEvent(pCallbackData); +} + +// Maps Vulkan image or buffer usage flags to a Perfetto memory track usage category name. +static const char* GetUsageCategoryName(bool is_image, uint32_t usage_flags) { + if (is_image) { + if (usage_flags & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) return "depth_stencil_attachment"; + if (usage_flags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) return "color_attachment"; + if (usage_flags & VK_IMAGE_USAGE_SAMPLED_BIT) return "texture"; + if (usage_flags & (VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT)) return "transfer_image"; + return "image"; + } + if (usage_flags & VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) return "vertex_buffer"; + if (usage_flags & VK_BUFFER_USAGE_INDEX_BUFFER_BIT) return "index_buffer"; + if (usage_flags & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) return "uniform_buffer"; + if (usage_flags & (VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)) return "staging_buffer"; + return "buffer"; +} + +void DeviceMemoryReport::AddCounterBytes(const std::string& track, uint64_t size) { + uint64_t& bytes = usage_memory_bytes_[track]; + bytes += size; + TRACE_COUNTER("vulkan", GetCounterTrack(track.c_str()), bytes); +} + +void DeviceMemoryReport::SubtractCounterBytes(const std::string& track, uint64_t size) { + uint64_t& bytes = usage_memory_bytes_[track]; + bytes = (bytes >= size) ? (bytes - size) : 0; + TRACE_COUNTER("vulkan", GetCounterTrack(track.c_str()), bytes); +} + +void DeviceMemoryReport::ApplyUsageToHandle(uint64_t handle, const std::string& usage_str) { + auto alloc_it = object_alloc_size_map_.find(handle); + if (alloc_it == object_alloc_size_map_.end() || alloc_it->second == 0) return; + + bool is_driver = object_is_driver_map_[handle]; + std::string new_usage_track = (is_driver ? "vulkan.mem.driver.usage." : "vulkan.mem.app.usage.") + usage_str; + + auto& old_usage_track = object_usage_applied_map_[handle]; + if (old_usage_track == new_usage_track) return; + + if (!old_usage_track.empty()) { + SubtractCounterBytes(old_usage_track, alloc_it->second); + } + old_usage_track = new_usage_track; + AddCounterBytes(new_usage_track, alloc_it->second); +} + +void DeviceMemoryReport::RemoveUsageFromHandle(uint64_t handle, uint64_t free_size) { + auto it = object_usage_applied_map_.find(handle); + if (it != object_usage_applied_map_.end()) { + if (!it->second.empty()) { + SubtractCounterBytes(it->second, free_size); + } + object_usage_applied_map_.erase(it); + } +} + +void DeviceMemoryReport::OnBindBufferMemory(uint64_t buffer_handle, uint64_t memory_handle) { + std::lock_guard lock(counter_mutex_); + auto usage_it = object_usage_map_.find(buffer_handle); + if (usage_it != object_usage_map_.end() && !usage_it->second.empty()) { + object_usage_map_[memory_handle] = usage_it->second; + ApplyUsageToHandle(memory_handle, usage_it->second); + } +} + +void DeviceMemoryReport::OnBindImageMemory(uint64_t image_handle, uint64_t memory_handle) { + OnBindBufferMemory(image_handle, memory_handle); +} + +void DeviceMemoryReport::OnCreateImage(uint64_t image_handle, VkImageUsageFlags usage) { + std::lock_guard lock(counter_mutex_); + object_usage_map_[image_handle] = GetUsageCategoryName(true, usage); +} + +void DeviceMemoryReport::OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFlags usage) { + std::lock_guard lock(counter_mutex_); + object_usage_map_[buffer_handle] = GetUsageCategoryName(false, usage); +} + +void DeviceMemoryReport::OnDestroyObject(uint64_t object_handle) { + std::lock_guard lock(counter_mutex_); + object_usage_map_.erase(object_handle); +} + +void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackDataEXT* pCallbackData) { + std::lock_guard lock(counter_mutex_); + bool is_driver = (pCallbackData->flags & VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT) != 0; + + if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT || + pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_IMPORT_EXT) { + object_alloc_size_map_[pCallbackData->objectHandle] += pCallbackData->size; + object_is_driver_map_[pCallbackData->objectHandle] = is_driver; + + auto usage_it = object_usage_map_.find(pCallbackData->objectHandle); + const std::string& usage = (usage_it != object_usage_map_.end() && !usage_it->second.empty()) + ? usage_it->second + : "unbound_memory"; + ApplyUsageToHandle(pCallbackData->objectHandle, usage); + } else if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT || + pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_UNIMPORT_EXT) { + uint64_t free_size = pCallbackData->size; + auto alloc_it = object_alloc_size_map_.find(pCallbackData->objectHandle); + if (free_size == 0 && alloc_it != object_alloc_size_map_.end()) { + free_size = alloc_it->second; + } + + RemoveUsageFromHandle(pCallbackData->objectHandle, free_size); + + if (alloc_it != object_alloc_size_map_.end()) { + object_alloc_size_map_.erase(alloc_it); + object_is_driver_map_.erase(pCallbackData->objectHandle); + } + } +} + +void DeviceMemoryReport::SetHasMemoryReportCallback(VkDevice device, bool has_callback) { + std::lock_guard lock(counter_mutex_); + has_callback_map_[device] = has_callback; +} + +void DeviceMemoryReport::OnAllocateMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize size) { + std::lock_guard lock(counter_mutex_); + if (has_callback_map_[device]) return; + memory_size_map_[memory] = size; + + uint64_t handle = reinterpret_cast(memory); + object_alloc_size_map_[handle] = size; + object_is_driver_map_[handle] = false; + ApplyUsageToHandle(handle, "unbound_memory"); +} + +void DeviceMemoryReport::OnFreeMemory(VkDevice device, VkDeviceMemory memory) { + std::lock_guard lock(counter_mutex_); + if (has_callback_map_[device]) return; + auto it = memory_size_map_.find(memory); + if (it != memory_size_map_.end()) { + VkDeviceSize size = it->second; + memory_size_map_.erase(it); + + uint64_t handle = reinterpret_cast(memory); + RemoveUsageFromHandle(handle, size); + object_alloc_size_map_.erase(handle); + object_is_driver_map_.erase(handle); + } +} diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h new file mode 100644 index 0000000000..e181eabed6 --- /dev/null +++ b/layersvt/device_memory_report/device_memory_report.h @@ -0,0 +1,212 @@ +/* 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 +#include +#include +#include + +#ifndef VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT +#define VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT 0x00000001 +#endif + +/** + * The DeviceMemoryReport class is responsible for tracking Vulkan device memory + * allocations and object associations, sending live memory usage counters to Perfetto traces. + * + * It tracks allocations per object handle, categorizing memory by usage + * types (e.g., textures, surfaces, buffers). + * + * How it works: + * The layer intercepts Vulkan memory allocation and object creation events, using either + * VK_EXT_device_memory_report callbacks (when supported by the underlying driver) or falling back + * to direct allocation intercepts (vkAllocateMemory/vkFreeMemory). + * Object bindings (vkBindBufferMemory, vkBindImageMemory) are tracked to attribute memory allocations to usage categories. + * + * Track Categories: + * Memory usage counters are reported to Perfetto under: + * - Driver vs Application allocations (e.g., vulkan.mem.driver.* vs vulkan.mem.app.*) + * - Usages (vulkan.mem.*.usage.) + * + * This class is a singleton and provides thread-safe access to its state. + */ +class DeviceMemoryReport { + public: + /** + * @brief Returns the singleton instance of the DeviceMemoryReport class. + * @return Reference to the DeviceMemoryReport singleton. + */ + static DeviceMemoryReport& Get(); + + /** + * @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); + + /** + * @brief Static callback invoked by the VK_EXT_device_memory_report extension. + * @param pCallbackData Pointer to the memory report callback data structure. + * @param pUserData User data pointer (unused). + */ + static void VKAPI_PTR MemoryReportCallback(const VkDeviceMemoryReportCallbackDataEXT* pCallbackData, void* pUserData); + + /** + * @brief Processes a device memory report event received from the Vulkan driver callback. + * @param pCallbackData Pointer to the memory report callback data structure. + */ + void OnMemoryReportEvent(const VkDeviceMemoryReportCallbackDataEXT* pCallbackData); + + /** + * @brief Sets whether a Vulkan device active callback is installed for VK_EXT_device_memory_report. + * @param device The Vulkan device handle. + * @param has_callback True if driver callback is active for the device, false otherwise. + */ + void SetHasMemoryReportCallback(VkDevice device, bool has_callback); + + /** + * @brief Handles fallback memory allocation tracking when driver callback is unavailable. + * @param device The Vulkan device handle. + * @param memory The VkDeviceMemory handle being allocated. + * @param size The size of the allocation in bytes. + */ + void OnAllocateMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize size); + + /** + * @brief Handles fallback memory free tracking when driver callback is unavailable. + * @param device The Vulkan device handle. + * @param memory The VkDeviceMemory handle being freed. + */ + void OnFreeMemory(VkDevice device, VkDeviceMemory memory); + + /** + * @brief Tracks binding of buffer memory to correlate buffer usage with memory allocations. + * @param buffer_handle The 64-bit handle of the Vulkan buffer. + * @param memory_handle The 64-bit handle of the Vulkan device memory. + */ + void OnBindBufferMemory(uint64_t buffer_handle, uint64_t memory_handle); + + /** + * @brief Tracks binding of image memory to correlate image usage with memory allocations. + * @param image_handle The 64-bit handle of the Vulkan image. + * @param memory_handle The 64-bit handle of the Vulkan device memory. + */ + void OnBindImageMemory(uint64_t image_handle, uint64_t memory_handle); + + /** + * @brief Tracks creation of a Vulkan image and its usage flags. + * @param image_handle The 64-bit handle of the Vulkan image. + * @param usage Usage flags for the created image. + */ + void OnCreateImage(uint64_t image_handle, VkImageUsageFlags usage); + + /** + * @brief Tracks creation of a Vulkan buffer and its usage flags. + * @param buffer_handle The 64-bit handle of the Vulkan buffer. + * @param usage Usage flags for the created buffer. + */ + void OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFlags usage); + + /** + * @brief Handles destruction of a Vulkan object, cleaning up tracked usage state. + * @param object_handle The 64-bit handle of the destroyed Vulkan object. + */ + void OnDestroyObject(uint64_t object_handle); + + private: + /** + * @brief Applies a usage category track counter update for an object allocation handle. + * @param handle The 64-bit handle of the object or memory allocation. + * @param usage_str The usage category string. + */ + void ApplyUsageToHandle(uint64_t handle, const std::string& usage_str); + + /** + * @brief Removes a usage category track counter update for a freed object allocation handle. + * @param handle The 64-bit handle of the object or memory allocation. + * @param free_size The number of bytes being freed. + */ + void RemoveUsageFromHandle(uint64_t handle, uint64_t free_size); + + /** + * @brief Increments trace counter for a memory track. + */ + void AddCounterBytes(const std::string& track, uint64_t size); + + /** + * @brief Decrements trace counter for a memory track with underflow protection. + */ + void SubtractCounterBytes(const std::string& track, uint64_t size); + + /** + * @brief Mutex protecting access to the physical device to instance mapping table. + */ + std::mutex map_mutex_; + + /** + * @brief Maps a physical device handle to its corresponding Vulkan instance handle. + */ + std::unordered_map vk_instance_map_; + + /** + * @brief Mutex protecting access to memory tracking tables and counter states. + */ + std::mutex counter_mutex_; + + /** + * @brief Maps a Vulkan device handle to a boolean indicating if driver memory report callback is active. + */ + std::unordered_map has_callback_map_; + + /** + * @brief Maps a device memory handle to its allocation size in bytes for fallback tracking. + */ + std::unordered_map memory_size_map_; + + /** + * @brief Maps an object handle to its total allocated memory size in bytes. + */ + std::unordered_map object_alloc_size_map_; + + /** + * @brief Maps an object handle to a boolean indicating if it is a driver-internal allocation. + */ + std::unordered_map object_is_driver_map_; + + /** + * @brief Maps an object handle to its determined usage category string. + */ + std::unordered_map object_usage_map_; + + /** + * @brief Maps an object handle to the usage track string currently applied to it. + */ + std::unordered_map object_usage_applied_map_; + + /** + * @brief Maps a usage track name to its current total memory usage in bytes. + */ + std::unordered_map usage_memory_bytes_; +}; diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp new file mode 100644 index 0000000000..c60d034a41 --- /dev/null +++ b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp @@ -0,0 +1,91 @@ +/* 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 "device_memory_report_handwritten_functions.h" +#include "vk_layer_table.h" +#include + +extern "C" { + +static PFN_vkVoidFunction devmemreport_known_instance_functions(const char* pName) { + if (strcmp(pName, "vkGetInstanceProcAddr") == 0) return reinterpret_cast(vkGetInstanceProcAddr); + if (strcmp(pName, "vkCreateInstance") == 0) return reinterpret_cast(vkCreateInstance); + if (strcmp(pName, "vkDestroyInstance") == 0) return reinterpret_cast(vkDestroyInstance); + if (strcmp(pName, "vkEnumeratePhysicalDevices") == 0) return reinterpret_cast(vkEnumeratePhysicalDevices); + if (strcmp(pName, "vkEnumeratePhysicalDeviceGroups") == 0) return reinterpret_cast(vkEnumeratePhysicalDeviceGroups); + if (strcmp(pName, "vkEnumerateInstanceExtensionProperties") == 0) return reinterpret_cast(vkEnumerateInstanceExtensionProperties); + if (strcmp(pName, "vkEnumerateInstanceLayerProperties") == 0) return reinterpret_cast(vkEnumerateInstanceLayerProperties); + return nullptr; +} + +static PFN_vkVoidFunction devmemreport_known_device_functions(const char* pName) { + if (strcmp(pName, "vkGetDeviceProcAddr") == 0) return reinterpret_cast(vkGetDeviceProcAddr); + if (strcmp(pName, "vkCreateDevice") == 0) return reinterpret_cast(vkCreateDevice); + if (strcmp(pName, "vkDestroyDevice") == 0) return reinterpret_cast(vkDestroyDevice); + if (strcmp(pName, "vkEnumerateDeviceLayerProperties") == 0) return reinterpret_cast(vkEnumerateDeviceLayerProperties); + if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) return reinterpret_cast(vkEnumerateDeviceExtensionProperties); + if (strcmp(pName, "vkAllocateMemory") == 0) return reinterpret_cast(vkAllocateMemory); + if (strcmp(pName, "vkFreeMemory") == 0) return reinterpret_cast(vkFreeMemory); + if (strcmp(pName, "vkBindBufferMemory") == 0) return reinterpret_cast(vkBindBufferMemory); + if (strcmp(pName, "vkBindImageMemory") == 0) return reinterpret_cast(vkBindImageMemory); + if (strcmp(pName, "vkCreateImage") == 0) return reinterpret_cast(vkCreateImage); + if (strcmp(pName, "vkDestroyImage") == 0) return reinterpret_cast(vkDestroyImage); + if (strcmp(pName, "vkCreateBuffer") == 0) return reinterpret_cast(vkCreateBuffer); + if (strcmp(pName, "vkDestroyBuffer") == 0) return reinterpret_cast(vkDestroyBuffer); + return nullptr; +} + +EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const char* pName) { + PFN_vkVoidFunction func = devmemreport_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 = devmemreport_known_device_functions(pName); + if (func) { + return func; + } + + if (instance == nullptr) { + return nullptr; + } + + auto table = instance_dispatch_table(instance); + if (table == NULL || 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 = devmemreport_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" diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h new file mode 100644 index 0000000000..9ec1ae6eb2 --- /dev/null +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -0,0 +1,403 @@ +/* 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 +#include +#include +#include +#include +#include "vk_layer_table.h" +#include "device_memory_report.h" +#include "device_memory_report_perfetto.h" + +// This file contains handwritten implementations for Vulkan functions intercepted by +// the VK_LAYER_GOOGLE_DeviceMemoryReport layer: +// +// Core infrastructure & lifecycle: +// - vkCreateInstance: Initializes Perfetto tracing and the instance dispatch table. +// - vkEnumeratePhysicalDevices / vkEnumeratePhysicalDeviceGroups: Tracks the mapping +// between physical devices and instances to support dispatch table lookups. +// - vkCreateDevice / vkDestroyDevice: Initializes/destroys device dispatch tables and +// injects VK_EXT_device_memory_report callback registration into device creation. +// +// Memory tracking & debugging intercepts: +// - vkAllocateMemory / vkFreeMemory: Tracks direct allocations/frees as fallbacks. +// - vkBindBufferMemory / vkBindImageMemory: Associates buffer/image handles with memory. +// - vkSetDebugUtilsObjectNameEXT / vkDebugMarkerSetObjectNameEXT: Associates debug names +// and markers with object handles for labeled memory reporting. +// - vkEnumerate*ExtensionProperties / vkEnumerate*LayerProperties: Advertises the layer +// and support for the VK_EXT_device_memory_report extension. + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +#if defined(__GNUC__) && __GNUC__ >= 4 +#define EXPORT_FUNCTION __attribute__((visibility("default"))) +#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590) +#define EXPORT_FUNCTION __attribute__((visibility("default"))) +#else +#define EXPORT_FUNCTION +#endif + +#define LAYER_NAME "VK_LAYER_GOOGLE_DeviceMemoryReport" +#define LAYER_DESCRIPTION "Vulkan Device Memory Report Layer" + +static std::once_flag g_perfetto_init_flag; + +extern "C" { + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance) { + std::call_once(g_perfetto_init_flag, []() { InitializeDeviceMemoryReportPerfetto(); }); + + // Get the function pointer + VkLayerInstanceCreateInfo* chain_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO); + assert(chain_info->u.pLayerInfo != 0); + PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; + assert(fpGetInstanceProcAddr != 0); + PFN_vkCreateInstance fpCreateInstance = (PFN_vkCreateInstance)fpGetInstanceProcAddr(NULL, "vkCreateInstance"); + if (fpCreateInstance == NULL) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + // Call the function and create the dispatch table + chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; + VkResult result = fpCreateInstance(pCreateInfo, pAllocator, pInstance); + if (result == VK_SUCCESS) { + initInstanceTable(*pInstance, fpGetInstanceProcAddr); + } + + return result; +} + +// Intercept physical device enumeration to store physical device to instance mapping. +VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDevices(VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices) { + if (instance_dispatch_table(instance)->EnumeratePhysicalDevices == NULL) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + VkResult result = instance_dispatch_table(instance)->EnumeratePhysicalDevices(instance, pPhysicalDeviceCount, pPhysicalDevices); + + if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && pPhysicalDeviceCount != nullptr && pPhysicalDevices != nullptr) { + for (uint32_t i = 0; i < *pPhysicalDeviceCount; ++i) { + DeviceMemoryReport::Get().SetVkInstance(pPhysicalDevices[i], instance); + } + } + return result; +} + +// Intercept physical device group enumeration to store physical device to instance mapping. +VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* pPhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties* pPhysicalDeviceGroupProperties) { + if (instance_dispatch_table(instance)->EnumeratePhysicalDeviceGroups == NULL) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + VkResult result = instance_dispatch_table(instance)->EnumeratePhysicalDeviceGroups(instance, pPhysicalDeviceGroupCount, pPhysicalDeviceGroupProperties); + + if ((result == VK_SUCCESS || result == VK_INCOMPLETE) && pPhysicalDeviceGroupCount != nullptr && pPhysicalDeviceGroupProperties != nullptr) { + for (uint32_t i = 0; i < *pPhysicalDeviceGroupCount; ++i) { + for (uint32_t j = 0; j < pPhysicalDeviceGroupProperties[i].physicalDeviceCount; ++j) { + DeviceMemoryReport::Get().SetVkInstance(pPhysicalDeviceGroupProperties[i].physicalDevices[j], instance); + } + } + } + return result; +} + +VKAPI_ATTR void VKAPI_CALL vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks* pAllocator) { + dispatch_key key = get_dispatch_key(instance); + instance_dispatch_table(instance)->DestroyInstance(instance, pAllocator); + destroy_instance_dispatch_table(key); +} + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { + std::call_once(g_perfetto_init_flag, []() { InitializeDeviceMemoryReportPerfetto(); }); + + // Get the function pointer + VkLayerDeviceCreateInfo* chain_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO); + assert(chain_info->u.pLayerInfo != 0); + PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; + PFN_vkGetDeviceProcAddr fpGetDeviceProcAddr = chain_info->u.pLayerInfo->pfnNextGetDeviceProcAddr; + VkInstance vk_instance = DeviceMemoryReport::Get().GetVkInstance(physicalDevice); + PFN_vkCreateDevice fpCreateDevice = (PFN_vkCreateDevice)fpGetInstanceProcAddr(vk_instance, "vkCreateDevice"); + if (fpCreateDevice == NULL) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + // Call the function and create the dispatch table + chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; + + // Check if the underlying driver supports VK_EXT_device_memory_report + bool supports_memory_report = false; + uint32_t ext_count = 0; + if (vk_instance != VK_NULL_HANDLE && instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties) { + if (instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties(physicalDevice, nullptr, &ext_count, nullptr) == VK_SUCCESS && ext_count > 0) { + std::vector exts(ext_count); + if (instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties(physicalDevice, nullptr, &ext_count, exts.data()) == VK_SUCCESS) { + for (const auto& ext : exts) { + if (strcmp(ext.extensionName, VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME) == 0) { + supports_memory_report = true; + break; + } + } + } + } + } + + // If supported, inject VK_EXT_device_memory_report callback into pNext chain + VkDeviceCreateInfo modified_create_info = *pCreateInfo; + std::vector enabled_extensions; + for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; ++i) { + enabled_extensions.push_back(pCreateInfo->ppEnabledExtensionNames[i]); + } + + VkDeviceDeviceMemoryReportCreateInfoEXT memory_report_ci = {}; + if (supports_memory_report) { + bool already_enabled = false; + for (const char* name : enabled_extensions) { + if (strcmp(name, VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME) == 0) { + already_enabled = true; + break; + } + } + if (!already_enabled) { + enabled_extensions.push_back(VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME); + } + modified_create_info.enabledExtensionCount = static_cast(enabled_extensions.size()); + modified_create_info.ppEnabledExtensionNames = enabled_extensions.data(); + + memory_report_ci.sType = VK_STRUCTURE_TYPE_DEVICE_DEVICE_MEMORY_REPORT_CREATE_INFO_EXT; + memory_report_ci.pfnUserCallback = DeviceMemoryReport::MemoryReportCallback; + memory_report_ci.pUserData = nullptr; + memory_report_ci.pNext = modified_create_info.pNext; + modified_create_info.pNext = &memory_report_ci; + } + + VkResult result = fpCreateDevice(physicalDevice, supports_memory_report ? &modified_create_info : pCreateInfo, pAllocator, pDevice); + if (result == VK_SUCCESS) { + initDeviceTable(*pDevice, fpGetDeviceProcAddr); + DeviceMemoryReport::Get().SetHasMemoryReportCallback(*pDevice, supports_memory_report); + } + + return result; +} + +VKAPI_ATTR void VKAPI_CALL vkDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) { + dispatch_key key = get_dispatch_key(device); + device_dispatch_table(device)->DestroyDevice(device, pAllocator); + destroy_device_dispatch_table(key); +} + +// Fallback memory allocation tracking used when driver callback is unavailable. +VKAPI_ATTR VkResult VKAPI_CALL vkAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, + const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory) { + PFN_vkAllocateMemory fpAllocateMemory = (PFN_vkAllocateMemory)device_dispatch_table(device)->AllocateMemory; + if (fpAllocateMemory == NULL) { + return VK_ERROR_INITIALIZATION_FAILED; + } + VkResult result = fpAllocateMemory(device, pAllocateInfo, pAllocator, pMemory); + if (result == VK_SUCCESS && pAllocateInfo != nullptr && pMemory != nullptr && *pMemory != VK_NULL_HANDLE) { + DeviceMemoryReport::Get().OnAllocateMemory(device, *pMemory, pAllocateInfo->allocationSize); + } + return result; +} + +// Fallback memory free tracking used when driver callback is unavailable. +VKAPI_ATTR void VKAPI_CALL vkFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator) { + if (memory != VK_NULL_HANDLE) { + DeviceMemoryReport::Get().OnFreeMemory(device, memory); + } + PFN_vkFreeMemory fpFreeMemory = (PFN_vkFreeMemory)device_dispatch_table(device)->FreeMemory; + if (fpFreeMemory != NULL) { + fpFreeMemory(device, memory, pAllocator); + } +} + +EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(const char* pLayerName, + uint32_t* pPropertyCount, + VkExtensionProperties* pProperties) { + static const VkExtensionProperties instanceExtensions[] = { + {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_SPEC_VERSION}, + }; + + if (pLayerName != nullptr && strcmp(pLayerName, LAYER_NAME) == 0) { + return util_GetExtensionProperties(ARRAY_SIZE(instanceExtensions), instanceExtensions, pPropertyCount, pProperties); + } + + return util_GetExtensionProperties(0, nullptr, pPropertyCount, pProperties); +} + +EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties(uint32_t* pPropertyCount, + VkLayerProperties* pProperties) { + static const VkLayerProperties layerProperties[] = {{ + LAYER_NAME, + VK_MAKE_VERSION(1, 4, VK_HEADER_VERSION), // specVersion + VK_MAKE_VERSION(0, 1, 0), // implementationVersion + LAYER_DESCRIPTION, + }}; + + return util_GetLayerProperties(ARRAY_SIZE(layerProperties), layerProperties, pPropertyCount, pProperties); +} + +EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties(VkPhysicalDevice physicalDevice, + uint32_t* pPropertyCount, + VkLayerProperties* pProperties) { + static const VkLayerProperties layerProperties[] = {{ + LAYER_NAME, + VK_MAKE_VERSION(1, 4, VK_HEADER_VERSION), + VK_MAKE_VERSION(0, 1, 0), + LAYER_DESCRIPTION, + }}; + + return util_GetLayerProperties(ARRAY_SIZE(layerProperties), layerProperties, pPropertyCount, pProperties); +} + +EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, + const char* pLayerName, + uint32_t* pPropertyCount, + VkExtensionProperties* pProperties) { + static const VkExtensionProperties deviceExtensions[] = { + {VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME, VK_EXT_DEVICE_MEMORY_REPORT_SPEC_VERSION}, + {VK_EXT_DEBUG_MARKER_EXTENSION_NAME, VK_EXT_DEBUG_MARKER_SPEC_VERSION}, + }; + + if (pLayerName != nullptr && strcmp(pLayerName, LAYER_NAME) == 0) { + return util_GetExtensionProperties(ARRAY_SIZE(deviceExtensions), deviceExtensions, pPropertyCount, pProperties); + } + + if (physicalDevice == nullptr) { + return VK_SUCCESS; + } + + VkInstance vk_instance = DeviceMemoryReport::Get().GetVkInstance(physicalDevice); + if (vk_instance == VK_NULL_HANDLE) { + return VK_SUCCESS; + } + + // Manually append device extension. This should not be necessary, but the Android Vulkan + // loader does not expose extensions from implicit layer. + if (pProperties == nullptr) { + VkResult res = instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); + if (res == VK_SUCCESS) { + (*pPropertyCount) += ARRAY_SIZE(deviceExtensions); + } + return res; + } + + if (*pPropertyCount > 0) { + uint32_t requestedCount = *pPropertyCount; + VkResult res = instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); + if (res == VK_SUCCESS) { + uint32_t originalCount = *pPropertyCount; + uint32_t additionalCount = 0; + + for (uint32_t i = 0; i < ARRAY_SIZE(deviceExtensions); ++i) { + bool found = false; + for (uint32_t j = 0; j < originalCount; ++j) { + if (strcmp(pProperties[j].extensionName, deviceExtensions[i].extensionName) == 0) { + found = true; + break; + } + } + if (!found) { + if (originalCount + additionalCount < requestedCount) { + pProperties[originalCount + additionalCount] = deviceExtensions[i]; + } + additionalCount++; + } + } + *pPropertyCount = originalCount + additionalCount; + if (*pPropertyCount > requestedCount) { + *pPropertyCount = requestedCount; + } + } + return res; + } + return VK_SUCCESS; +} + +// Intercept memory binding to correlate buffer object handles with device memory allocations. +VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset) { + if (buffer != VK_NULL_HANDLE && memory != VK_NULL_HANDLE) { + DeviceMemoryReport::Get().OnBindBufferMemory(reinterpret_cast(buffer), reinterpret_cast(memory)); + } + if (device_dispatch_table(device)->BindBufferMemory) { + return device_dispatch_table(device)->BindBufferMemory(device, buffer, memory, memoryOffset); + } + return VK_SUCCESS; +} + +// Intercept memory binding to correlate image object handles with device memory allocations. +VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset) { + if (image != VK_NULL_HANDLE && memory != VK_NULL_HANDLE) { + DeviceMemoryReport::Get().OnBindImageMemory(reinterpret_cast(image), reinterpret_cast(memory)); + } + if (device_dispatch_table(device)->BindImageMemory) { + return device_dispatch_table(device)->BindImageMemory(device, image, memory, memoryOffset); + } + return VK_SUCCESS; +} + +// Intercept image creation to track image usage category flags. +VKAPI_ATTR VkResult VKAPI_CALL vkCreateImage(VkDevice device, const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage) { + PFN_vkCreateImage fpCreateImage = (PFN_vkCreateImage)device_dispatch_table(device)->CreateImage; + if (fpCreateImage == NULL) { + return VK_ERROR_INITIALIZATION_FAILED; + } + VkResult result = fpCreateImage(device, pCreateInfo, pAllocator, pImage); + if (result == VK_SUCCESS && pCreateInfo != nullptr && pImage != nullptr && *pImage != VK_NULL_HANDLE) { + DeviceMemoryReport::Get().OnCreateImage(reinterpret_cast(*pImage), pCreateInfo->usage); + } + return result; +} + +// Intercept image destruction to clean up tracked handle state. +VKAPI_ATTR void VKAPI_CALL vkDestroyImage(VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator) { + if (image != VK_NULL_HANDLE) { + DeviceMemoryReport::Get().OnDestroyObject(reinterpret_cast(image)); + } + PFN_vkDestroyImage fpDestroyImage = (PFN_vkDestroyImage)device_dispatch_table(device)->DestroyImage; + if (fpDestroyImage != NULL) { + fpDestroyImage(device, image, pAllocator); + } +} + +// Intercept buffer creation to track buffer usage category flags. +VKAPI_ATTR VkResult VKAPI_CALL vkCreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer) { + PFN_vkCreateBuffer fpCreateBuffer = (PFN_vkCreateBuffer)device_dispatch_table(device)->CreateBuffer; + if (fpCreateBuffer == NULL) { + return VK_ERROR_INITIALIZATION_FAILED; + } + VkResult result = fpCreateBuffer(device, pCreateInfo, pAllocator, pBuffer); + if (result == VK_SUCCESS && pCreateInfo != nullptr && pBuffer != nullptr && *pBuffer != VK_NULL_HANDLE) { + DeviceMemoryReport::Get().OnCreateBuffer(reinterpret_cast(*pBuffer), pCreateInfo->usage); + } + return result; +} + +// Intercept buffer destruction to clean up tracked handle state. +VKAPI_ATTR void VKAPI_CALL vkDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator) { + if (buffer != VK_NULL_HANDLE) { + DeviceMemoryReport::Get().OnDestroyObject(reinterpret_cast(buffer)); + } + PFN_vkDestroyBuffer fpDestroyBuffer = (PFN_vkDestroyBuffer)device_dispatch_table(device)->DestroyBuffer; + if (fpDestroyBuffer != NULL) { + fpDestroyBuffer(device, buffer, pAllocator); + } +} + +} // extern "C" diff --git a/layersvt/device_memory_report/device_memory_report_perfetto.cpp b/layersvt/device_memory_report/device_memory_report_perfetto.cpp new file mode 100644 index 0000000000..22cec9bb9a --- /dev/null +++ b/layersvt/device_memory_report/device_memory_report_perfetto.cpp @@ -0,0 +1,33 @@ +/* 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 "device_memory_report_perfetto.h" + +PERFETTO_TRACK_EVENT_STATIC_STORAGE(); + +void InitializeDeviceMemoryReportPerfetto() { + static bool initialized = false; + if (initialized) return; + initialized = true; + + perfetto::TracingInitArgs args; + args.backends = perfetto::kSystemBackend; + perfetto::Tracing::Initialize(args); + perfetto::TrackEvent::Register(); +} + +perfetto::CounterTrack GetCounterTrack(const char* name) { + return perfetto::CounterTrack(perfetto::DynamicString(name)); +} diff --git a/layersvt/device_memory_report/device_memory_report_perfetto.h b/layersvt/device_memory_report/device_memory_report_perfetto.h new file mode 100644 index 0000000000..43739643de --- /dev/null +++ b/layersvt/device_memory_report/device_memory_report_perfetto.h @@ -0,0 +1,29 @@ +/* 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. + */ + +#ifndef LAYERSVT_DEVICE_MEMORY_REPORT_PERFETTO_H +#define LAYERSVT_DEVICE_MEMORY_REPORT_PERFETTO_H + +#include "perfetto/perfetto.h" + +// Define categories used for Perfetto tracing in DeviceMemoryReport layer. +PERFETTO_DEFINE_CATEGORIES( + perfetto::Category("vulkan").SetDescription("Vulkan Device Memory Report Counters") +); + +void InitializeDeviceMemoryReportPerfetto(); +perfetto::CounterTrack GetCounterTrack(const char* name); + +#endif // LAYERSVT_DEVICE_MEMORY_REPORT_PERFETTO_H diff --git a/layersvt/test/CMakeLists.txt b/layersvt/test/CMakeLists.txt index ee14406ea3..6f7bdbd646 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -37,6 +37,9 @@ function(LayerTest NAME) if (${NAME} STREQUAL "DebugMarker") target_sources(${TEST_NAME} PRIVATE ../debug_marker/debug_marker.cpp ../debug_marker/debug_marker_perfetto.cpp ../perfetto/perfetto.cc) target_include_directories(${TEST_NAME} PRIVATE .. ../debug_marker) + elseif (${NAME} STREQUAL "DeviceMemoryReport") + target_sources(${TEST_NAME} PRIVATE ../device_memory_report/device_memory_report.cpp ../device_memory_report/device_memory_report_perfetto.cpp ../perfetto/perfetto.cc) + target_include_directories(${TEST_NAME} PRIVATE .. ../device_memory_report) endif() target_compile_definitions(${TEST_NAME} PUBLIC LAYER_BINARY_PATH="$") add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) @@ -48,7 +51,7 @@ function(LayerTest NAME) endif() endfunction() -set(LAYER_TEST_FILES api_dump monitor screenshot CPUTiming DebugMarker) +set(LAYER_TEST_FILES api_dump monitor screenshot CPUTiming DebugMarker DeviceMemoryReport) foreach(test_item ${LAYER_TEST_FILES}) # If the target doesn't exist continue. diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp new file mode 100644 index 0000000000..de6e1a63ef --- /dev/null +++ b/layersvt/test/test_devicememoryreport.cpp @@ -0,0 +1,186 @@ +/* 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 "layer_test_helper.h" +#include "device_memory_report.h" +#include "device_memory_report_perfetto.h" + +#include + +#include + +static const char* kLayerName = "VK_LAYER_GOOGLE_DeviceMemoryReport"; + +class DeviceMemoryReportTests : public VkTestFramework { + public: + ~DeviceMemoryReportTests(){}; + + static void SetUpTestSuite() {} + static void TearDownTestSuite(){}; +}; + +TEST_F(DeviceMemoryReportTests, InitLayer) { + TEST_DESCRIPTION("Test Creating a Vulkan Instance with DeviceMemoryReport layer"); + + layer_test::VulkanInstanceBuilder inst_builder; + VkResult err = inst_builder.Init(kLayerName); + EXPECT_EQ(err, VK_SUCCESS); + + VkInstance instance = inst_builder.GetInstance(); + EXPECT_NE(instance, VK_NULL_HANDLE); + + inst_builder.Reset(); +} + +TEST_F(DeviceMemoryReportTests, EmitEventsAndSubCounters) { + TEST_DESCRIPTION("Test calling OnMemoryReportEvent with object types and allocating/freeing memory"); + + InitializeDeviceMemoryReportPerfetto(); + + VkDeviceMemoryReportCallbackDataEXT cb_data = {}; + cb_data.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; + cb_data.flags = 0; // Application memory + cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + cb_data.memoryObjectId = 0x1000; + cb_data.size = 1024 * 1024; + cb_data.objectType = VK_OBJECT_TYPE_IMAGE; + cb_data.objectHandle = 0x5000; + + DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + + cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + cb_data.size = 512 * 1024; + DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + + cb_data.flags = VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT; // Driver memory + cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + cb_data.objectType = VK_OBJECT_TYPE_BUFFER; + cb_data.size = 2048; + DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + + // Test direct allocate/free fallbacks + VkDevice dummy_device = reinterpret_cast(0x1234); + VkDeviceMemory dummy_memory = reinterpret_cast(0x5678); + DeviceMemoryReport::Get().OnAllocateMemory(dummy_device, dummy_memory, 4096); + DeviceMemoryReport::Get().OnFreeMemory(dummy_device, dummy_memory); + + EXPECT_TRUE(true); +} + +TEST_F(DeviceMemoryReportTests, BufferImageBindingAndCallbackSuppression) { + TEST_DESCRIPTION("Test buffer/image memory binding, IMPORT/UNIMPORT events, and fallback suppression when callback is enabled"); + + InitializeDeviceMemoryReportPerfetto(); + + // Test buffer and image memory binding + uint64_t buffer_handle = 0x8000; + uint64_t image_handle = 0x8001; + uint64_t memory_handle = 0x9000; + DeviceMemoryReport::Get().OnBindBufferMemory(buffer_handle, memory_handle); + DeviceMemoryReport::Get().OnBindImageMemory(image_handle, memory_handle); + + // Test IMPORT_EXT and UNIMPORT_EXT events + VkDeviceMemoryReportCallbackDataEXT cb_data = {}; + cb_data.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; + cb_data.flags = 0; + cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_IMPORT_EXT; + cb_data.memoryObjectId = 0x3000; + cb_data.size = 4096; + cb_data.objectType = VK_OBJECT_TYPE_DEVICE_MEMORY; + cb_data.objectHandle = memory_handle; + DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + + cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_UNIMPORT_EXT; + DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + + // Test callback suppression: when callback is registered on VkDevice, fallback allocations should be ignored + VkDevice test_device = reinterpret_cast(0xABCD); + VkDeviceMemory test_memory = reinterpret_cast(0xEF01); + + DeviceMemoryReport::Get().SetHasMemoryReportCallback(test_device, true); + DeviceMemoryReport::Get().OnAllocateMemory(test_device, test_memory, 8192); + DeviceMemoryReport::Get().OnFreeMemory(test_device, test_memory); + + // Enable fallback by turning off callback flag and verify allocate/free work + DeviceMemoryReport::Get().SetHasMemoryReportCallback(test_device, false); + DeviceMemoryReport::Get().OnAllocateMemory(test_device, test_memory, 8192); + DeviceMemoryReport::Get().OnFreeMemory(test_device, test_memory); + + EXPECT_TRUE(true); +} + +TEST_F(DeviceMemoryReportTests, UsageTypeBreakdown) { + TEST_DESCRIPTION("Test automatic usage type classification for granular image and buffer usage categories"); + + InitializeDeviceMemoryReportPerfetto(); + + uint64_t color_img = 0xA001; + uint64_t depth_img = 0xA002; + uint64_t sampled_img = 0xA005; + + uint64_t vtx_buf = 0xB001; + uint64_t idx_buf = 0xB002; + uint64_t ubo_buf = 0xB003; + uint64_t staging_buf = 0xB006; + + uint64_t mem_handle = 0xC001; + + // Register images + DeviceMemoryReport::Get().OnCreateImage(color_img, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); + DeviceMemoryReport::Get().OnCreateImage(depth_img, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT); + DeviceMemoryReport::Get().OnCreateImage(sampled_img, VK_IMAGE_USAGE_SAMPLED_BIT); + + // Register buffers + DeviceMemoryReport::Get().OnCreateBuffer(vtx_buf, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); + DeviceMemoryReport::Get().OnCreateBuffer(idx_buf, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); + DeviceMemoryReport::Get().OnCreateBuffer(ubo_buf, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + DeviceMemoryReport::Get().OnCreateBuffer(staging_buf, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); + + VkDeviceMemoryReportCallbackDataEXT cb_data = {}; + cb_data.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; + cb_data.flags = 0; + cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + cb_data.memoryObjectId = 0x5000; + cb_data.size = 1048576; + cb_data.objectType = VK_OBJECT_TYPE_DEVICE_MEMORY; + cb_data.objectHandle = mem_handle; + DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + + // Bind images and buffers to test usage categorization and track transitions + DeviceMemoryReport::Get().OnBindImageMemory(color_img, mem_handle); + DeviceMemoryReport::Get().OnBindImageMemory(depth_img, mem_handle); + DeviceMemoryReport::Get().OnBindImageMemory(sampled_img, mem_handle); + + DeviceMemoryReport::Get().OnBindBufferMemory(vtx_buf, mem_handle); + DeviceMemoryReport::Get().OnBindBufferMemory(idx_buf, mem_handle); + DeviceMemoryReport::Get().OnBindBufferMemory(ubo_buf, mem_handle); + DeviceMemoryReport::Get().OnBindBufferMemory(staging_buf, mem_handle); + + // Free memory + cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + + // Clean up objects + DeviceMemoryReport::Get().OnDestroyObject(color_img); + DeviceMemoryReport::Get().OnDestroyObject(depth_img); + DeviceMemoryReport::Get().OnDestroyObject(sampled_img); + + DeviceMemoryReport::Get().OnDestroyObject(vtx_buf); + DeviceMemoryReport::Get().OnDestroyObject(idx_buf); + DeviceMemoryReport::Get().OnDestroyObject(ubo_buf); + DeviceMemoryReport::Get().OnDestroyObject(staging_buf); + + EXPECT_TRUE(true); +} From 2ef181fae3ec65bf7dbb8b6de4d19da2ccfe1db7 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 11 Aug 2026 15:01:00 +0000 Subject: [PATCH 02/16] Set Perfetto counter unit to bytes for memory allocations --- .../device_memory_report/device_memory_report_perfetto.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/layersvt/device_memory_report/device_memory_report_perfetto.cpp b/layersvt/device_memory_report/device_memory_report_perfetto.cpp index 22cec9bb9a..b84eaf28bd 100644 --- a/layersvt/device_memory_report/device_memory_report_perfetto.cpp +++ b/layersvt/device_memory_report/device_memory_report_perfetto.cpp @@ -29,5 +29,6 @@ void InitializeDeviceMemoryReportPerfetto() { } perfetto::CounterTrack GetCounterTrack(const char* name) { - return perfetto::CounterTrack(perfetto::DynamicString(name)); + return perfetto::CounterTrack(perfetto::DynamicString(name)) + .set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES); } From 86e0cfa4fecaabde35e208dd61047554ffa87bdb Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 12 Aug 2026 15:53:03 +0000 Subject: [PATCH 03/16] Correctly account for memory bound in a shared allocation. --- .../device_memory_report.cpp | 223 +++++++++++++----- .../device_memory_report.h | 98 +++++--- ...ice_memory_report_handwritten_dispatch.cpp | 31 ++- ...vice_memory_report_handwritten_functions.h | 176 +++++++++++--- layersvt/test/test_devicememoryreport.cpp | 174 +++++++++++++- 5 files changed, 556 insertions(+), 146 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index c1160dc233..53550e8338 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -16,8 +16,10 @@ #include "device_memory_report.h" #include "device_memory_report_perfetto.h" #include "perfetto/perfetto.h" +#include #include #include +#include DeviceMemoryReport& DeviceMemoryReport::Get() { static DeviceMemoryReport instance; @@ -56,6 +58,13 @@ static const char* GetUsageCategoryName(bool is_image, uint32_t usage_flags) { return "buffer"; } +// Construct a Perfetto track name for visualizing memory usage by category in the UI. +static std::string GetUsageTrackName(bool is_driver, std::string_view usage) { + std::string track = is_driver ? "vulkan.mem.driver.usage." : "vulkan.mem.app.usage."; + track += usage; + return track; +} + void DeviceMemoryReport::AddCounterBytes(const std::string& track, uint64_t size) { uint64_t& bytes = usage_memory_bytes_[track]; bytes += size; @@ -68,89 +77,185 @@ void DeviceMemoryReport::SubtractCounterBytes(const std::string& track, uint64_t TRACE_COUNTER("vulkan", GetCounterTrack(track.c_str()), bytes); } -void DeviceMemoryReport::ApplyUsageToHandle(uint64_t handle, const std::string& usage_str) { - auto alloc_it = object_alloc_size_map_.find(handle); - if (alloc_it == object_alloc_size_map_.end() || alloc_it->second == 0) return; +void DeviceMemoryReport::UpdateAllocationUnboundCounter(uint64_t memory_handle) { + auto allocation_it = memory_allocations_.find(memory_handle); + if (allocation_it == memory_allocations_.end()) return; + auto& allocation = allocation_it->second; - bool is_driver = object_is_driver_map_[handle]; - std::string new_usage_track = (is_driver ? "vulkan.mem.driver.usage." : "vulkan.mem.app.usage.") + usage_str; + uint64_t bound_size = 0; + std::vector> intervals; + intervals.reserve(allocation.sub_allocations.size()); + for (const auto& suballocation : allocation.sub_allocations) { + if (suballocation.size > 0) { + VkDeviceSize end = (suballocation.offset + suballocation.size < suballocation.offset) ? UINT64_MAX : (suballocation.offset + suballocation.size); + intervals.emplace_back(suballocation.offset, end); + } + } + if (!intervals.empty()) { + std::sort(intervals.begin(), intervals.end()); - auto& old_usage_track = object_usage_applied_map_[handle]; - if (old_usage_track == new_usage_track) return; + // Overlapping and adjacent intervals are merged: + // bound_size is calculated as the mathematical union (distinct physical footprint) of all intervals. + VkDeviceSize current_start = intervals[0].first; + VkDeviceSize current_end = intervals[0].second; + for (size_t i = 1; i < intervals.size(); ++i) { + if (intervals[i].first <= current_end) { + // Overlapping or adjacent interval: extend current merged range + current_end = std::max(current_end, intervals[i].second); + } else { + // Disjoint interval: add previous merged interval size and begin next range + bound_size += (current_end - current_start); + current_start = intervals[i].first; + current_end = intervals[i].second; + } + } + bound_size += (current_end - current_start); + } + + uint64_t new_unbound = (allocation.total_size > bound_size) ? (allocation.total_size - bound_size) : 0; + + std::string track_name = "unbound_memory"; + auto res_it = resources_.find(allocation.object_handle); + // If the memory object has an associated resource with a specific usage, use it as the track name. + if (res_it != resources_.end() && !res_it->second.usage.empty()) { + track_name = res_it->second.usage; + } + std::string new_unbound_track = GetUsageTrackName(allocation.is_driver, track_name); - if (!old_usage_track.empty()) { - SubtractCounterBytes(old_usage_track, alloc_it->second); + // If the unbound memory usage track name or the number of unbound bytes has changed, + // update the global counters by subtracting the old bytes from the old track + // and adding the new bytes to the new track. + if (allocation.unbound_usage_track != new_unbound_track || allocation.applied_unbound_bytes != new_unbound) { + if (allocation.applied_unbound_bytes > 0) { + SubtractCounterBytes(allocation.unbound_usage_track, allocation.applied_unbound_bytes); + } + if (new_unbound > 0) { + AddCounterBytes(new_unbound_track, new_unbound); + } } - old_usage_track = new_usage_track; - AddCounterBytes(new_usage_track, alloc_it->second); + allocation.unbound_usage_track = new_unbound_track; + allocation.applied_unbound_bytes = new_unbound; } -void DeviceMemoryReport::RemoveUsageFromHandle(uint64_t handle, uint64_t free_size) { - auto it = object_usage_applied_map_.find(handle); - if (it != object_usage_applied_map_.end()) { - if (!it->second.empty()) { - SubtractCounterBytes(it->second, free_size); +void DeviceMemoryReport::RemoveResourceBinding(uint64_t resource_handle) { + auto mem_it = resource_to_memory_map_.find(resource_handle); + if (mem_it == resource_to_memory_map_.end()) return; + + uint64_t memory_handle = mem_it->second; + resource_to_memory_map_.erase(mem_it); + + auto allocation_it = memory_allocations_.find(memory_handle); + if (allocation_it != memory_allocations_.end()) { + auto& suballocations = allocation_it->second.sub_allocations; + // Search by resource handle to identify which specific suballocation to remove, + // since a single memory block can have multiple resources bound to it. + for (auto it = suballocations.begin(); it != suballocations.end(); ++it) { + if (it->resource_handle == resource_handle) { + SubtractCounterBytes(it->usage_track, it->size); + suballocations.erase(it); + break; + } } - object_usage_applied_map_.erase(it); + UpdateAllocationUnboundCounter(memory_handle); } } -void DeviceMemoryReport::OnBindBufferMemory(uint64_t buffer_handle, uint64_t memory_handle) { +void DeviceMemoryReport::OnBindBufferMemory(uint64_t buffer_handle, uint64_t memory_handle, VkDeviceSize memory_offset) { std::lock_guard lock(counter_mutex_); - auto usage_it = object_usage_map_.find(buffer_handle); - if (usage_it != object_usage_map_.end() && !usage_it->second.empty()) { - object_usage_map_[memory_handle] = usage_it->second; - ApplyUsageToHandle(memory_handle, usage_it->second); + auto res_it = resources_.find(buffer_handle); + if (res_it == resources_.end() || res_it->second.usage.empty() || res_it->second.size == 0) return; + + // If the same resource handle is passed more than once, remove stale bindings first. + RemoveResourceBinding(buffer_handle); + + auto& allocation = memory_allocations_[memory_handle]; + VkDeviceSize res_size = res_it->second.size; + std::string new_usage_track = GetUsageTrackName(allocation.is_driver, res_it->second.usage); + + // Suballocations represent individual resources (like buffers or images) that are bound + // to specific offset regions within a single large memory allocation. + // We add a record here to track this specific resource's footprint within the larger memory block. + allocation.sub_allocations.push_back({ buffer_handle, memory_offset, res_size, new_usage_track }); + resource_to_memory_map_[buffer_handle] = memory_handle; + + // Each distinct virtual resource handle adds its virtual size to its specific category track upon binding. + AddCounterBytes(new_usage_track, res_size); + UpdateAllocationUnboundCounter(memory_handle); +} + +void DeviceMemoryReport::RemoveAllocationTracking(uint64_t memory_handle) { + auto allocation_it = memory_allocations_.find(memory_handle); + if (allocation_it == memory_allocations_.end()) return; + + auto& allocation = allocation_it->second; + for (const auto& suballocation : allocation.sub_allocations) { + SubtractCounterBytes(suballocation.usage_track, suballocation.size); + resource_to_memory_map_.erase(suballocation.resource_handle); + } + if (allocation.applied_unbound_bytes > 0) { + SubtractCounterBytes(allocation.unbound_usage_track, allocation.applied_unbound_bytes); } + memory_allocations_.erase(allocation_it); } -void DeviceMemoryReport::OnBindImageMemory(uint64_t image_handle, uint64_t memory_handle) { - OnBindBufferMemory(image_handle, memory_handle); +void DeviceMemoryReport::OnBindImageMemory(uint64_t image_handle, uint64_t memory_handle, VkDeviceSize memory_offset) { + OnBindBufferMemory(image_handle, memory_handle, memory_offset); +} + +void DeviceMemoryReport::OnRecordResourceSize(uint64_t resource_handle, VkDeviceSize size) { + std::lock_guard lock(counter_mutex_); + resources_[resource_handle].size = size; +} + +VkDeviceSize DeviceMemoryReport::GetRecordedResourceSize(uint64_t resource_handle) { + std::lock_guard lock(counter_mutex_); + auto it = resources_.find(resource_handle); + return it != resources_.end() ? it->second.size : 0; } void DeviceMemoryReport::OnCreateImage(uint64_t image_handle, VkImageUsageFlags usage) { std::lock_guard lock(counter_mutex_); - object_usage_map_[image_handle] = GetUsageCategoryName(true, usage); + resources_[image_handle].usage = GetUsageCategoryName(true, usage); + for (const auto& pair : memory_allocations_) { + if (pair.second.object_handle == image_handle) { + UpdateAllocationUnboundCounter(pair.first); + } + } } -void DeviceMemoryReport::OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFlags usage) { +void DeviceMemoryReport::OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFlags usage, VkDeviceSize size) { std::lock_guard lock(counter_mutex_); - object_usage_map_[buffer_handle] = GetUsageCategoryName(false, usage); + resources_[buffer_handle] = { GetUsageCategoryName(false, usage), size }; + for (const auto& pair : memory_allocations_) { + if (pair.second.object_handle == buffer_handle) { + UpdateAllocationUnboundCounter(pair.first); + } + } } void DeviceMemoryReport::OnDestroyObject(uint64_t object_handle) { std::lock_guard lock(counter_mutex_); - object_usage_map_.erase(object_handle); + RemoveResourceBinding(object_handle); + resources_.erase(object_handle); } void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackDataEXT* pCallbackData) { std::lock_guard lock(counter_mutex_); - bool is_driver = (pCallbackData->flags & VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT) != 0; + // For internal driver allocations, a single object (e.g. VkImage) might have multiple distinct memory allocations. + // We must use memoryObjectId as the key so each allocation is tracked separately and can be individually freed. + // For device memory allocations, objectHandle is the VkDeviceMemory handle, which we use as the key for compatibility. + uint64_t key = (pCallbackData->objectType == VK_OBJECT_TYPE_DEVICE_MEMORY) ? pCallbackData->objectHandle : pCallbackData->memoryObjectId; if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT || pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_IMPORT_EXT) { - object_alloc_size_map_[pCallbackData->objectHandle] += pCallbackData->size; - object_is_driver_map_[pCallbackData->objectHandle] = is_driver; - - auto usage_it = object_usage_map_.find(pCallbackData->objectHandle); - const std::string& usage = (usage_it != object_usage_map_.end() && !usage_it->second.empty()) - ? usage_it->second - : "unbound_memory"; - ApplyUsageToHandle(pCallbackData->objectHandle, usage); + auto& allocation = memory_allocations_[key]; + allocation.total_size = pCallbackData->size; + allocation.is_driver = (pCallbackData->flags & VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT) != 0; + allocation.object_handle = pCallbackData->objectHandle; + UpdateAllocationUnboundCounter(key); } else if (pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT || pCallbackData->type == VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_UNIMPORT_EXT) { - uint64_t free_size = pCallbackData->size; - auto alloc_it = object_alloc_size_map_.find(pCallbackData->objectHandle); - if (free_size == 0 && alloc_it != object_alloc_size_map_.end()) { - free_size = alloc_it->second; - } - - RemoveUsageFromHandle(pCallbackData->objectHandle, free_size); - - if (alloc_it != object_alloc_size_map_.end()) { - object_alloc_size_map_.erase(alloc_it); - object_is_driver_map_.erase(pCallbackData->objectHandle); - } + RemoveAllocationTracking(key); } } @@ -162,25 +267,15 @@ void DeviceMemoryReport::SetHasMemoryReportCallback(VkDevice device, bool has_ca void DeviceMemoryReport::OnAllocateMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize size) { std::lock_guard lock(counter_mutex_); if (has_callback_map_[device]) return; - memory_size_map_[memory] = size; - uint64_t handle = reinterpret_cast(memory); - object_alloc_size_map_[handle] = size; - object_is_driver_map_[handle] = false; - ApplyUsageToHandle(handle, "unbound_memory"); + auto& allocation = memory_allocations_[handle]; + allocation.total_size = size; + allocation.is_driver = false; + UpdateAllocationUnboundCounter(handle); } void DeviceMemoryReport::OnFreeMemory(VkDevice device, VkDeviceMemory memory) { std::lock_guard lock(counter_mutex_); if (has_callback_map_[device]) return; - auto it = memory_size_map_.find(memory); - if (it != memory_size_map_.end()) { - VkDeviceSize size = it->second; - memory_size_map_.erase(it); - - uint64_t handle = reinterpret_cast(memory); - RemoveUsageFromHandle(handle, size); - object_alloc_size_map_.erase(handle); - object_is_driver_map_.erase(handle); - } + RemoveAllocationTracking(reinterpret_cast(memory)); } diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index e181eabed6..5a2ce91eaf 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -19,6 +19,7 @@ #include #include #include +#include #ifndef VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT #define VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT 0x00000001 @@ -35,7 +36,7 @@ * The layer intercepts Vulkan memory allocation and object creation events, using either * VK_EXT_device_memory_report callbacks (when supported by the underlying driver) or falling back * to direct allocation intercepts (vkAllocateMemory/vkFreeMemory). - * Object bindings (vkBindBufferMemory, vkBindImageMemory) are tracked to attribute memory allocations to usage categories. + * Object bindings (vkBindBufferMemory, vkBindImageMemory, vkBindBufferMemory2, vkBindImageMemory2) are tracked to attribute memory allocations to usage categories. * * Track Categories: * Memory usage counters are reported to Perfetto under: @@ -105,15 +106,31 @@ class DeviceMemoryReport { * @brief Tracks binding of buffer memory to correlate buffer usage with memory allocations. * @param buffer_handle The 64-bit handle of the Vulkan buffer. * @param memory_handle The 64-bit handle of the Vulkan device memory. + * @param memory_offset Offset into device memory where buffer is bound. */ - void OnBindBufferMemory(uint64_t buffer_handle, uint64_t memory_handle); + void OnBindBufferMemory(uint64_t buffer_handle, uint64_t memory_handle, VkDeviceSize memory_offset); /** * @brief Tracks binding of image memory to correlate image usage with memory allocations. * @param image_handle The 64-bit handle of the Vulkan image. * @param memory_handle The 64-bit handle of the Vulkan device memory. + * @param memory_offset Offset into device memory where image is bound. */ - void OnBindImageMemory(uint64_t image_handle, uint64_t memory_handle); + void OnBindImageMemory(uint64_t image_handle, uint64_t memory_handle, VkDeviceSize memory_offset); + + /** + * @brief Records the size of a virtual resource (buffer or image) in bytes. + * @param resource_handle The 64-bit handle of the Vulkan object. + * @param size Size in bytes from memory requirements or create info. + */ + void OnRecordResourceSize(uint64_t resource_handle, VkDeviceSize size); + + /** + * @brief Retrieves the recorded size of a virtual resource (for testing). + * @param resource_handle The 64-bit handle of the Vulkan object. + * @return Size in bytes, or 0 if not tracked. + */ + VkDeviceSize GetRecordedResourceSize(uint64_t resource_handle); /** * @brief Tracks creation of a Vulkan image and its usage flags. @@ -123,11 +140,12 @@ class DeviceMemoryReport { void OnCreateImage(uint64_t image_handle, VkImageUsageFlags usage); /** - * @brief Tracks creation of a Vulkan buffer and its usage flags. + * @brief Tracks creation of a Vulkan buffer, its usage flags, and requested size. * @param buffer_handle The 64-bit handle of the Vulkan buffer. * @param usage Usage flags for the created buffer. + * @param size Size in bytes of the buffer allocation. */ - void OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFlags usage); + void OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFlags usage, VkDeviceSize size); /** * @brief Handles destruction of a Vulkan object, cleaning up tracked usage state. @@ -137,18 +155,50 @@ class DeviceMemoryReport { private: /** - * @brief Applies a usage category track counter update for an object allocation handle. - * @param handle The 64-bit handle of the object or memory allocation. - * @param usage_str The usage category string. + * @brief Represents a sub-allocation of a Vulkan resource (buffer or image) bound within a physical memory allocation. + */ + struct SubAllocation { + uint64_t resource_handle; /**< Handle of the bound Vulkan resource (buffer or image). */ + VkDeviceSize offset; /**< Offset in bytes within the physical memory allocation where the resource is bound. */ + VkDeviceSize size; /**< Size in bytes of the sub-allocated resource. */ + std::string usage_track; /**< Name of the memory usage trace counter category for this sub-allocation. */ + }; + + /** + * @brief Tracks state and sub-allocations for a physical device memory allocation. + */ + struct MemoryAllocation { + VkDeviceSize total_size = 0; + VkDeviceSize applied_unbound_bytes = 0; + bool is_driver = false; + std::vector sub_allocations; + std::string unbound_usage_track; + uint64_t object_handle = 0; + }; + + /** + * @brief Tracks metadata for a virtual resource (buffer or image). */ - void ApplyUsageToHandle(uint64_t handle, const std::string& usage_str); + struct Resource { + std::string usage; + VkDeviceSize size = 0; + }; /** - * @brief Removes a usage category track counter update for a freed object allocation handle. - * @param handle The 64-bit handle of the object or memory allocation. - * @param free_size The number of bytes being freed. + * @brief Updates unbound memory category counter for a physical memory slab. + * Unbound memory is allocated device memory not currently bound to any active resource (e.g. buffer or image). */ - void RemoveUsageFromHandle(uint64_t handle, uint64_t free_size); + void UpdateAllocationUnboundCounter(uint64_t memory_handle); + + /** + * @brief Removes a sub-allocation of a resource from a physical memory slab if bound. + */ + void RemoveResourceBinding(uint64_t resource_handle); + + /** + * @brief Removes all sub-allocations and tracking for a memory slab being freed. + */ + void RemoveAllocationTracking(uint64_t memory_handle); /** * @brief Increments trace counter for a memory track. @@ -181,29 +231,19 @@ class DeviceMemoryReport { std::unordered_map has_callback_map_; /** - * @brief Maps a device memory handle to its allocation size in bytes for fallback tracking. - */ - std::unordered_map memory_size_map_; - - /** - * @brief Maps an object handle to its total allocated memory size in bytes. - */ - std::unordered_map object_alloc_size_map_; - - /** - * @brief Maps an object handle to a boolean indicating if it is a driver-internal allocation. + * @brief Maps a virtual resource handle to its metadata. */ - std::unordered_map object_is_driver_map_; + std::unordered_map resources_; /** - * @brief Maps an object handle to its determined usage category string. + * @brief Maps a virtual resource handle to the physical memory handle it is bound to. */ - std::unordered_map object_usage_map_; + std::unordered_map resource_to_memory_map_; /** - * @brief Maps an object handle to the usage track string currently applied to it. + * @brief Maps a physical memory handle to its allocation details and sub-allocations. */ - std::unordered_map object_usage_applied_map_; + std::unordered_map memory_allocations_; /** * @brief Maps a usage track name to its current total memory usage in bytes. diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp index c60d034a41..2cfa666673 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp +++ b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp @@ -40,10 +40,20 @@ static PFN_vkVoidFunction devmemreport_known_device_functions(const char* pName) if (strcmp(pName, "vkFreeMemory") == 0) return reinterpret_cast(vkFreeMemory); if (strcmp(pName, "vkBindBufferMemory") == 0) return reinterpret_cast(vkBindBufferMemory); if (strcmp(pName, "vkBindImageMemory") == 0) return reinterpret_cast(vkBindImageMemory); + if (strcmp(pName, "vkBindBufferMemory2") == 0) return reinterpret_cast(vkBindBufferMemory2); + if (strcmp(pName, "vkBindImageMemory2") == 0) return reinterpret_cast(vkBindImageMemory2); + if (strcmp(pName, "vkBindBufferMemory2KHR") == 0) return reinterpret_cast(vkBindBufferMemory2KHR); + if (strcmp(pName, "vkBindImageMemory2KHR") == 0) return reinterpret_cast(vkBindImageMemory2KHR); if (strcmp(pName, "vkCreateImage") == 0) return reinterpret_cast(vkCreateImage); if (strcmp(pName, "vkDestroyImage") == 0) return reinterpret_cast(vkDestroyImage); if (strcmp(pName, "vkCreateBuffer") == 0) return reinterpret_cast(vkCreateBuffer); if (strcmp(pName, "vkDestroyBuffer") == 0) return reinterpret_cast(vkDestroyBuffer); + if (strcmp(pName, "vkGetImageMemoryRequirements") == 0) return reinterpret_cast(vkGetImageMemoryRequirements); + if (strcmp(pName, "vkGetImageMemoryRequirements2") == 0) return reinterpret_cast(vkGetImageMemoryRequirements2); + if (strcmp(pName, "vkGetImageMemoryRequirements2KHR") == 0) return reinterpret_cast(vkGetImageMemoryRequirements2KHR); + if (strcmp(pName, "vkGetBufferMemoryRequirements") == 0) return reinterpret_cast(vkGetBufferMemoryRequirements); + if (strcmp(pName, "vkGetBufferMemoryRequirements2") == 0) return reinterpret_cast(vkGetBufferMemoryRequirements2); + if (strcmp(pName, "vkGetBufferMemoryRequirements2KHR") == 0) return reinterpret_cast(vkGetBufferMemoryRequirements2KHR); return nullptr; } @@ -72,11 +82,6 @@ EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(V } EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device, const char* pName) { - PFN_vkVoidFunction func = devmemreport_known_device_functions(pName); - if (func) { - return func; - } - if (device == nullptr) { return nullptr; } @@ -85,7 +90,21 @@ EXPORT_FUNCTION VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkD return nullptr; } - return device_dispatch_table(device)->GetDeviceProcAddr(device, pName); + // We must verify that the underlying device actually supports the function. + // Returning an interceptor for an unsupported function violates the Vulkan spec and can cause + // applications to erroneously think an extension is supported, leading to crashes when called. + PFN_vkVoidFunction down_func = device_dispatch_table(device)->GetDeviceProcAddr(device, pName); + if (down_func == nullptr) { + return nullptr; + } + + // Only return the intercepted function if the device supports the command. + PFN_vkVoidFunction func = devmemreport_known_device_functions(pName); + if (func) { + return func; + } + + return down_func; } } // extern "C" diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index 9ec1ae6eb2..42b43af7ff 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -69,9 +69,6 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* pCre PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; assert(fpGetInstanceProcAddr != 0); PFN_vkCreateInstance fpCreateInstance = (PFN_vkCreateInstance)fpGetInstanceProcAddr(NULL, "vkCreateInstance"); - if (fpCreateInstance == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } // Call the function and create the dispatch table chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; @@ -85,9 +82,6 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* pCre // Intercept physical device enumeration to store physical device to instance mapping. VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDevices(VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices) { - if (instance_dispatch_table(instance)->EnumeratePhysicalDevices == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } VkResult result = instance_dispatch_table(instance)->EnumeratePhysicalDevices(instance, pPhysicalDeviceCount, pPhysicalDevices); @@ -101,9 +95,6 @@ VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDevices(VkInstance instance, u // Intercept physical device group enumeration to store physical device to instance mapping. VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t* pPhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties* pPhysicalDeviceGroupProperties) { - if (instance_dispatch_table(instance)->EnumeratePhysicalDeviceGroups == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } VkResult result = instance_dispatch_table(instance)->EnumeratePhysicalDeviceGroups(instance, pPhysicalDeviceGroupCount, pPhysicalDeviceGroupProperties); @@ -134,9 +125,6 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, c PFN_vkGetDeviceProcAddr fpGetDeviceProcAddr = chain_info->u.pLayerInfo->pfnNextGetDeviceProcAddr; VkInstance vk_instance = DeviceMemoryReport::Get().GetVkInstance(physicalDevice); PFN_vkCreateDevice fpCreateDevice = (PFN_vkCreateDevice)fpGetInstanceProcAddr(vk_instance, "vkCreateDevice"); - if (fpCreateDevice == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } // Call the function and create the dispatch table chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; @@ -206,9 +194,6 @@ VKAPI_ATTR void VKAPI_CALL vkDestroyDevice(VkDevice device, const VkAllocationCa VKAPI_ATTR VkResult VKAPI_CALL vkAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory) { PFN_vkAllocateMemory fpAllocateMemory = (PFN_vkAllocateMemory)device_dispatch_table(device)->AllocateMemory; - if (fpAllocateMemory == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } VkResult result = fpAllocateMemory(device, pAllocateInfo, pAllocator, pMemory); if (result == VK_SUCCESS && pAllocateInfo != nullptr && pMemory != nullptr && *pMemory != VK_NULL_HANDLE) { DeviceMemoryReport::Get().OnAllocateMemory(device, *pMemory, pAllocateInfo->allocationSize); @@ -332,35 +317,89 @@ EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionPropert // Intercept memory binding to correlate buffer object handles with device memory allocations. VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset) { - if (buffer != VK_NULL_HANDLE && memory != VK_NULL_HANDLE) { - DeviceMemoryReport::Get().OnBindBufferMemory(reinterpret_cast(buffer), reinterpret_cast(memory)); - } - if (device_dispatch_table(device)->BindBufferMemory) { - return device_dispatch_table(device)->BindBufferMemory(device, buffer, memory, memoryOffset); + VkResult result = device_dispatch_table(device)->BindBufferMemory(device, buffer, memory, memoryOffset); + if (result == VK_SUCCESS && buffer != VK_NULL_HANDLE && memory != VK_NULL_HANDLE) { + DeviceMemoryReport::Get().OnBindBufferMemory(reinterpret_cast(buffer), reinterpret_cast(memory), memoryOffset); } - return VK_SUCCESS; + return result; } // Intercept memory binding to correlate image object handles with device memory allocations. VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset) { - if (image != VK_NULL_HANDLE && memory != VK_NULL_HANDLE) { - DeviceMemoryReport::Get().OnBindImageMemory(reinterpret_cast(image), reinterpret_cast(memory)); + VkResult result = device_dispatch_table(device)->BindImageMemory(device, image, memory, memoryOffset); + if (result == VK_SUCCESS && image != VK_NULL_HANDLE && memory != VK_NULL_HANDLE) { + DeviceMemoryReport::Get().OnBindImageMemory(reinterpret_cast(image), reinterpret_cast(memory), memoryOffset); } - if (device_dispatch_table(device)->BindImageMemory) { - return device_dispatch_table(device)->BindImageMemory(device, image, memory, memoryOffset); + return result; +} + +static void RecordBufferBindings(uint32_t bindInfoCount, const VkBindBufferMemoryInfo* pBindInfos) { + for (uint32_t i = 0; i < bindInfoCount; ++i) { + if (pBindInfos[i].buffer != VK_NULL_HANDLE && pBindInfos[i].memory != VK_NULL_HANDLE) { + DeviceMemoryReport::Get().OnBindBufferMemory(reinterpret_cast(pBindInfos[i].buffer), reinterpret_cast(pBindInfos[i].memory), pBindInfos[i].memoryOffset); + } } - return VK_SUCCESS; +} + +// Intercept memory binding via vkBindBufferMemory2 to correlate buffer object handles with device memory allocations. +VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindBufferMemoryInfo* pBindInfos) { + VkResult result = device_dispatch_table(device)->BindBufferMemory2(device, bindInfoCount, pBindInfos); + if (result == VK_SUCCESS && pBindInfos != nullptr) { + RecordBufferBindings(bindInfoCount, pBindInfos); + } + return result; +} + +// Intercept memory binding via vkBindBufferMemory2KHR to correlate buffer object handles with device memory allocations. +VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory2KHR(VkDevice device, uint32_t bindInfoCount, const VkBindBufferMemoryInfo* pBindInfos) { + VkResult result = device_dispatch_table(device)->BindBufferMemory2KHR(device, bindInfoCount, pBindInfos); + if (result == VK_SUCCESS && pBindInfos != nullptr) { + RecordBufferBindings(bindInfoCount, pBindInfos); + } + return result; +} + +static void RecordImageBinds(uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos) { + for (uint32_t i = 0; i < bindInfoCount; ++i) { + if (pBindInfos[i].image != VK_NULL_HANDLE && pBindInfos[i].memory != VK_NULL_HANDLE) { + DeviceMemoryReport::Get().OnBindImageMemory(reinterpret_cast(pBindInfos[i].image), reinterpret_cast(pBindInfos[i].memory), pBindInfos[i].memoryOffset); + } + } +} + +// Intercept memory binding via vkBindImageMemory2 to correlate image object handles with device memory allocations. +VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos) { + VkResult result = device_dispatch_table(device)->BindImageMemory2(device, bindInfoCount, pBindInfos); + if (result == VK_SUCCESS && pBindInfos != nullptr) { + RecordImageBinds(bindInfoCount, pBindInfos); + } + return result; +} + +// Intercept memory binding via vkBindImageMemory2KHR to correlate image object handles with device memory allocations. +VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory2KHR(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos) { + VkResult result = device_dispatch_table(device)->BindImageMemory2KHR(device, bindInfoCount, pBindInfos); + if (result == VK_SUCCESS && pBindInfos != nullptr) { + RecordImageBinds(bindInfoCount, pBindInfos); + } + return result; } // Intercept image creation to track image usage category flags. VKAPI_ATTR VkResult VKAPI_CALL vkCreateImage(VkDevice device, const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage) { PFN_vkCreateImage fpCreateImage = (PFN_vkCreateImage)device_dispatch_table(device)->CreateImage; - if (fpCreateImage == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } VkResult result = fpCreateImage(device, pCreateInfo, pAllocator, pImage); if (result == VK_SUCCESS && pCreateInfo != nullptr && pImage != nullptr && *pImage != VK_NULL_HANDLE) { DeviceMemoryReport::Get().OnCreateImage(reinterpret_cast(*pImage), pCreateInfo->usage); + + // Query and record memory requirements right after creation. + // This is needed because Vulkan 1.3 applications might use vkGetDeviceImageMemoryRequirements + // *before* creation, and subsequently skip calling vkGetImageMemoryRequirements, which would leave the tracked size as 0. + if (device_dispatch_table(device)->GetImageMemoryRequirements && (pCreateInfo->flags & VK_IMAGE_CREATE_DISJOINT_BIT) == 0) { + VkMemoryRequirements mem_reqs; + device_dispatch_table(device)->GetImageMemoryRequirements(device, *pImage, &mem_reqs); + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(*pImage), mem_reqs.size); + } } return result; } @@ -376,15 +415,20 @@ VKAPI_ATTR void VKAPI_CALL vkDestroyImage(VkDevice device, VkImage image, const } } -// Intercept buffer creation to track buffer usage category flags. +// Intercept buffer creation to track buffer usage category flags and requested size. VKAPI_ATTR VkResult VKAPI_CALL vkCreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer) { PFN_vkCreateBuffer fpCreateBuffer = (PFN_vkCreateBuffer)device_dispatch_table(device)->CreateBuffer; - if (fpCreateBuffer == NULL) { - return VK_ERROR_INITIALIZATION_FAILED; - } VkResult result = fpCreateBuffer(device, pCreateInfo, pAllocator, pBuffer); if (result == VK_SUCCESS && pCreateInfo != nullptr && pBuffer != nullptr && *pBuffer != VK_NULL_HANDLE) { - DeviceMemoryReport::Get().OnCreateBuffer(reinterpret_cast(*pBuffer), pCreateInfo->usage); + DeviceMemoryReport::Get().OnCreateBuffer(reinterpret_cast(*pBuffer), pCreateInfo->usage, pCreateInfo->size); + + // Query buffer memory requirements to record accurate actual bound size, + // in case the app relies on vkGetDeviceBufferMemoryRequirements. + if (device_dispatch_table(device)->GetBufferMemoryRequirements) { + VkMemoryRequirements mem_reqs; + device_dispatch_table(device)->GetBufferMemoryRequirements(device, *pBuffer, &mem_reqs); + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(*pBuffer), mem_reqs.size); + } } return result; } @@ -400,4 +444,68 @@ VKAPI_ATTR void VKAPI_CALL vkDestroyBuffer(VkDevice device, VkBuffer buffer, con } } +// Intercept image memory requirements query to record virtual resource size. +VKAPI_ATTR void VKAPI_CALL vkGetImageMemoryRequirements(VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements) { + if (device_dispatch_table(device)->GetImageMemoryRequirements) { + device_dispatch_table(device)->GetImageMemoryRequirements(device, image, pMemoryRequirements); + if (image != VK_NULL_HANDLE && pMemoryRequirements != nullptr) { + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(image), pMemoryRequirements->size); + } + } +} + +static void RecordImageRequirements(const VkImageMemoryRequirementsInfo2* pInfo, const VkMemoryRequirements2* pMemoryRequirements) { + if (pInfo != nullptr && pInfo->image != VK_NULL_HANDLE && pMemoryRequirements != nullptr) { + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(pInfo->image), pMemoryRequirements->memoryRequirements.size); + } +} + +// Intercept image memory requirements 2 query to record virtual resource size. +VKAPI_ATTR void VKAPI_CALL vkGetImageMemoryRequirements2(VkDevice device, const VkImageMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements) { + if (device_dispatch_table(device)->GetImageMemoryRequirements2) { + device_dispatch_table(device)->GetImageMemoryRequirements2(device, pInfo, pMemoryRequirements); + RecordImageRequirements(pInfo, pMemoryRequirements); + } +} + +// Intercept image memory requirements 2 KHR query to record virtual resource size. +VKAPI_ATTR void VKAPI_CALL vkGetImageMemoryRequirements2KHR(VkDevice device, const VkImageMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements) { + if (device_dispatch_table(device)->GetImageMemoryRequirements2KHR) { + device_dispatch_table(device)->GetImageMemoryRequirements2KHR(device, pInfo, pMemoryRequirements); + RecordImageRequirements(pInfo, pMemoryRequirements); + } +} + +// Intercept buffer memory requirements query to record virtual resource size. +VKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer, VkMemoryRequirements* pMemoryRequirements) { + if (device_dispatch_table(device)->GetBufferMemoryRequirements) { + device_dispatch_table(device)->GetBufferMemoryRequirements(device, buffer, pMemoryRequirements); + if (buffer != VK_NULL_HANDLE && pMemoryRequirements != nullptr) { + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(buffer), pMemoryRequirements->size); + } + } +} + +static void RecordBufferRequirements2(const VkBufferMemoryRequirementsInfo2* pInfo, const VkMemoryRequirements2* pMemoryRequirements) { + if (pInfo != nullptr && pInfo->buffer != VK_NULL_HANDLE && pMemoryRequirements != nullptr) { + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(pInfo->buffer), pMemoryRequirements->memoryRequirements.size); + } +} + +// Intercept buffer memory requirements 2 query to record virtual resource size. +VKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements2(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements) { + if (device_dispatch_table(device)->GetBufferMemoryRequirements2) { + device_dispatch_table(device)->GetBufferMemoryRequirements2(device, pInfo, pMemoryRequirements); + RecordBufferRequirements2(pInfo, pMemoryRequirements); + } +} + +// Intercept buffer memory requirements 2 KHR query to record virtual resource size. +VKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements2KHR(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements) { + if (device_dispatch_table(device)->GetBufferMemoryRequirements2KHR) { + device_dispatch_table(device)->GetBufferMemoryRequirements2KHR(device, pInfo, pMemoryRequirements); + RecordBufferRequirements2(pInfo, pMemoryRequirements); + } +} + } // extern "C" diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index de6e1a63ef..65334959f9 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -88,8 +88,11 @@ TEST_F(DeviceMemoryReportTests, BufferImageBindingAndCallbackSuppression) { uint64_t buffer_handle = 0x8000; uint64_t image_handle = 0x8001; uint64_t memory_handle = 0x9000; - DeviceMemoryReport::Get().OnBindBufferMemory(buffer_handle, memory_handle); - DeviceMemoryReport::Get().OnBindImageMemory(image_handle, memory_handle); + DeviceMemoryReport::Get().OnCreateBuffer(buffer_handle, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 1024); + DeviceMemoryReport::Get().OnCreateImage(image_handle, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); + DeviceMemoryReport::Get().OnRecordResourceSize(image_handle, 2048); + DeviceMemoryReport::Get().OnBindBufferMemory(buffer_handle, memory_handle, 0); + DeviceMemoryReport::Get().OnBindImageMemory(image_handle, memory_handle, 1024); // Test IMPORT_EXT and UNIMPORT_EXT events VkDeviceMemoryReportCallbackDataEXT cb_data = {}; @@ -142,11 +145,15 @@ TEST_F(DeviceMemoryReportTests, UsageTypeBreakdown) { DeviceMemoryReport::Get().OnCreateImage(depth_img, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT); DeviceMemoryReport::Get().OnCreateImage(sampled_img, VK_IMAGE_USAGE_SAMPLED_BIT); + DeviceMemoryReport::Get().OnRecordResourceSize(color_img, 65536); + DeviceMemoryReport::Get().OnRecordResourceSize(depth_img, 65536); + DeviceMemoryReport::Get().OnRecordResourceSize(sampled_img, 65536); + // Register buffers - DeviceMemoryReport::Get().OnCreateBuffer(vtx_buf, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); - DeviceMemoryReport::Get().OnCreateBuffer(idx_buf, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); - DeviceMemoryReport::Get().OnCreateBuffer(ubo_buf, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - DeviceMemoryReport::Get().OnCreateBuffer(staging_buf, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); + DeviceMemoryReport::Get().OnCreateBuffer(vtx_buf, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 4096); + DeviceMemoryReport::Get().OnCreateBuffer(idx_buf, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, 4096); + DeviceMemoryReport::Get().OnCreateBuffer(ubo_buf, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, 4096); + DeviceMemoryReport::Get().OnCreateBuffer(staging_buf, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, 4096); VkDeviceMemoryReportCallbackDataEXT cb_data = {}; cb_data.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; @@ -159,14 +166,14 @@ TEST_F(DeviceMemoryReportTests, UsageTypeBreakdown) { DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); // Bind images and buffers to test usage categorization and track transitions - DeviceMemoryReport::Get().OnBindImageMemory(color_img, mem_handle); - DeviceMemoryReport::Get().OnBindImageMemory(depth_img, mem_handle); - DeviceMemoryReport::Get().OnBindImageMemory(sampled_img, mem_handle); + DeviceMemoryReport::Get().OnBindImageMemory(color_img, mem_handle, 0); + DeviceMemoryReport::Get().OnBindImageMemory(depth_img, mem_handle, 65536); + DeviceMemoryReport::Get().OnBindImageMemory(sampled_img, mem_handle, 131072); - DeviceMemoryReport::Get().OnBindBufferMemory(vtx_buf, mem_handle); - DeviceMemoryReport::Get().OnBindBufferMemory(idx_buf, mem_handle); - DeviceMemoryReport::Get().OnBindBufferMemory(ubo_buf, mem_handle); - DeviceMemoryReport::Get().OnBindBufferMemory(staging_buf, mem_handle); + DeviceMemoryReport::Get().OnBindBufferMemory(vtx_buf, mem_handle, 196608); + DeviceMemoryReport::Get().OnBindBufferMemory(idx_buf, mem_handle, 200704); + DeviceMemoryReport::Get().OnBindBufferMemory(ubo_buf, mem_handle, 204800); + DeviceMemoryReport::Get().OnBindBufferMemory(staging_buf, mem_handle, 208896); // Free memory cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; @@ -184,3 +191,144 @@ TEST_F(DeviceMemoryReportTests, UsageTypeBreakdown) { EXPECT_TRUE(true); } + +TEST_F(DeviceMemoryReportTests, MemoryAliasingAndOverlap) { + TEST_DESCRIPTION("Test memory aliasing where overlapping virtual resources occupy the same physical memory slab"); + + InitializeDeviceMemoryReportPerfetto(); + + uint64_t mem_handle = 0xD001; + uint64_t image_a = 0xD101; + uint64_t image_b = 0xD102; + uint64_t buffer_c = 0xD103; + + // Step 1: Allocate a 10,000-byte continuous physical memory slab (VkDeviceMemory). + // Initial state: total = 10,000 B, bound = 0 B, unbound_memory = 10,000 B. + VkDeviceMemoryReportCallbackDataEXT cb_data = {}; + cb_data.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; + cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; + cb_data.memoryObjectId = 0x6000; + cb_data.size = 10000; + cb_data.objectType = VK_OBJECT_TYPE_DEVICE_MEMORY; + cb_data.objectHandle = mem_handle; + DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + + // Step 2: Bind Resource A (Color Attachment Image) to range [0, 4000) (size = 4000 B). + // - vulkan.mem.app.usage.color_attachment += 4,000 B + // - Merged intervals: [0, 4000) -> bound_size = 4,000 B, unbound_memory = 6,000 B + DeviceMemoryReport::Get().OnCreateImage(image_a, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); + DeviceMemoryReport::Get().OnRecordResourceSize(image_a, 4000); + DeviceMemoryReport::Get().OnBindImageMemory(image_a, mem_handle, 0); + + // Step 3: Bind Resource B (Sampled Texture Image) to range [2000, 6000) (size = 4000 B). + // This overlaps / aliases Resource A on the physical sub-range [2000, 4000). + // - Each virtual resource adds its full virtual size to its specific category track: + // vulkan.mem.app.usage.texture += 4,000 B (both A and B report active virtual capacity). + // - Overlapping intervals [0, 4000) and [2000, 6000) are merged into union [0, 6000). + // - Physical slab bound_size = 6,000 B (overlapping physical region is NOT double-counted). + // - Remaining unbound headroom: unbound_memory = 10,000 - 6,000 = 4,000 B. + DeviceMemoryReport::Get().OnCreateImage(image_b, VK_IMAGE_USAGE_SAMPLED_BIT); + DeviceMemoryReport::Get().OnRecordResourceSize(image_b, 4000); + DeviceMemoryReport::Get().OnBindImageMemory(image_b, mem_handle, 2000); + + // Step 4: Bind Resource C (Vertex Buffer) to disjoint range [8000, 9500) (size = 1500 B). + // - vulkan.mem.app.usage.vertex_buffer += 1,500 B + // - Interval union: [0, 6000) U [8000, 9500) -> bound_size = 6,000 + 1,500 = 7,500 B. + // - Remaining unbound headroom: unbound_memory = 10,000 - 7,500 = 2,500 B. + DeviceMemoryReport::Get().OnCreateBuffer(buffer_c, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 1500); + DeviceMemoryReport::Get().OnBindBufferMemory(buffer_c, mem_handle, 8000); + + // Step 5: Destroy Resource A. + // - vulkan.mem.app.usage.color_attachment -= 4,000 B + // - Interval [0, 4000) is removed. Remaining intervals: [2000, 6000) U [8000, 9500). + // - Recalculated bound_size = 4,000 + 1,500 = 5,500 B. + // - Updated unbound headroom: unbound_memory = 10,000 - 5,500 = 4,500 B. + DeviceMemoryReport::Get().OnDestroyObject(image_a); + + // Step 6: Free physical memory slab. + // - All remaining sub-allocations on this slab are cleaned up and unbound counter is reset. + cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; + DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + + // Step 7: Clean up remaining virtual resource object handles. + DeviceMemoryReport::Get().OnDestroyObject(image_b); + DeviceMemoryReport::Get().OnDestroyObject(buffer_c); + + EXPECT_TRUE(true); +} + +TEST_F(DeviceMemoryReportTests, ProactiveMemoryRequirementsQuery) { + TEST_DESCRIPTION("Test that the layer proactively queries memory requirements when creating images and buffers"); + + layer_test::VulkanInstanceBuilder inst_builder; + VkResult err = inst_builder.Init(kLayerName); + EXPECT_EQ(err, VK_SUCCESS); + + VkPhysicalDevice phys_dev = VK_NULL_HANDLE; + inst_builder.GetPhysicalDevice(&phys_dev); + if (phys_dev == VK_NULL_HANDLE) { + GTEST_SKIP() << "No physical device found, skipping test."; + } + + // Create a logical device + float queue_priority = 1.0f; + VkDeviceQueueCreateInfo queue_info = {}; + queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_info.queueFamilyIndex = 0; + queue_info.queueCount = 1; + queue_info.pQueuePriorities = &queue_priority; + + VkDeviceCreateInfo dev_info = {}; + dev_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + dev_info.queueCreateInfoCount = 1; + dev_info.pQueueCreateInfos = &queue_info; + dev_info.enabledExtensionCount = 0; + + VkDevice device = VK_NULL_HANDLE; + err = vkCreateDevice(phys_dev, &dev_info, nullptr, &device); + if (err != VK_SUCCESS) { + GTEST_SKIP() << "Failed to create logical device, skipping test."; + } + + // Create an image + VkImageCreateInfo img_info = {}; + img_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + img_info.imageType = VK_IMAGE_TYPE_2D; + img_info.format = VK_FORMAT_R8G8B8A8_UNORM; + img_info.extent = {64, 64, 1}; + img_info.mipLevels = 1; + img_info.arrayLayers = 1; + img_info.samples = VK_SAMPLE_COUNT_1_BIT; + img_info.tiling = VK_IMAGE_TILING_OPTIMAL; + img_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + img_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + img_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VkImage image = VK_NULL_HANDLE; + err = vkCreateImage(device, &img_info, nullptr, &image); + ASSERT_EQ(err, VK_SUCCESS); + + // The interceptor should have called OnRecordResourceSize. + // Verify that the recorded size is > 0. + VkDeviceSize img_size = DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(image)); + EXPECT_GT(img_size, 0); + + // Create a buffer + VkBufferCreateInfo buf_info = {}; + buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buf_info.size = 1024; + buf_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + buf_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VkBuffer buffer = VK_NULL_HANDLE; + err = vkCreateBuffer(device, &buf_info, nullptr, &buffer); + ASSERT_EQ(err, VK_SUCCESS); + + // The interceptor should have called OnRecordResourceSize. + VkDeviceSize buf_size = DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(buffer)); + EXPECT_GT(buf_size, 0); + + vkDestroyImage(device, image, nullptr); + vkDestroyBuffer(device, buffer, nullptr); + vkDestroyDevice(device, nullptr); +} From aa4278f246908153ff87dc518ebd474ccbe023bc Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 25 Aug 2026 14:32:39 +0000 Subject: [PATCH 04/16] Enable the memory report only on Android. Also ensure the Vulkan layers would be built if only the memory report was enabled. --- CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e16790f3a6..3787aa7618 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,6 @@ if (BUILD_WERROR) endif() option(BUILD_TESTS "Build tests") -option(BUILD_DEVICEMEMORYREPORT "Build DeviceMemoryReport layer" ON) option(RUN_ON_CI "Build only tests that can run on C.I." ON) if(BUILD_TESTS) @@ -96,6 +95,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Windows|Linux|BSD") if(NOT WIN32) option(BUILD_CPUTIMING "Build CPUTiming layer" ON) option(BUILD_DEBUGMARKER "Build DebugMarker layer" ON) + option(BUILD_DEVICEMEMORYREPORT "Build DeviceMemoryReport layer" OFF) endif() option(BUILD_LAYERMGR "Build Vulkan Configurator" ON) set(SDK_VERSION "" CACHE STRING "Vulkan SDK version") @@ -109,6 +109,7 @@ elseif(ANDROID) option(BUILD_SCREENSHOT "Build screenshot layer" ON) option(BUILD_CPUTIMING "Build CPUTiming layer" ON) option(BUILD_DEBUGMARKER "Build DebugMarker layer" ON) + option(BUILD_DEVICEMEMORYREPORT "Build DeviceMemoryReport layer" ON) set(BUILD_MONITOR OFF) set(BUILD_LAYERMGR OFF) @@ -128,7 +129,7 @@ if(BUILD_TESTS) add_subdirectory(tests) endif() -if(BUILD_APIDUMP OR BUILD_MONITOR OR BUILD_SCREENSHOT OR BUILD_CPUTIMING OR BUILD_DEBUGMARKER) +if(BUILD_APIDUMP OR BUILD_MONITOR OR BUILD_SCREENSHOT OR BUILD_CPUTIMING OR BUILD_DEBUGMARKER OR BUILD_DEVICEMEMORYREPORT) message(STATUS "INFO: Building Vulkan Layers") add_subdirectory(layersvt) endif() From 2c3e5202b6e19eac3594e195764a4783ea62145e Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 25 Aug 2026 14:33:43 +0000 Subject: [PATCH 05/16] Eagerly enumerate physical devices in vkCreateInstance Populate physical device to instance mapping during vkCreateInstance to ensure logical device creation succeeds even if the application creates a device without explicitly calling vkEnumeratePhysicalDevices. --- ...vice_memory_report_handwritten_functions.h | 20 ++++++++++++++++++- layersvt/test/test_devicememoryreport.cpp | 19 ++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index 42b43af7ff..ee6c39cd7c 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -28,7 +28,7 @@ // the VK_LAYER_GOOGLE_DeviceMemoryReport layer: // // Core infrastructure & lifecycle: -// - vkCreateInstance: Initializes Perfetto tracing and the instance dispatch table. +// - vkCreateInstance: Initializes Perfetto tracing, the instance dispatch table, and performs eager physical device enumeration. // - vkEnumeratePhysicalDevices / vkEnumeratePhysicalDeviceGroups: Tracks the mapping // between physical devices and instances to support dispatch table lookups. // - vkCreateDevice / vkDestroyDevice: Initializes/destroys device dispatch tables and @@ -69,12 +69,30 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* pCre PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; assert(fpGetInstanceProcAddr != 0); PFN_vkCreateInstance fpCreateInstance = (PFN_vkCreateInstance)fpGetInstanceProcAddr(NULL, "vkCreateInstance"); + if (fpCreateInstance == NULL) { + return VK_ERROR_INITIALIZATION_FAILED; + } // Call the function and create the dispatch table chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; VkResult result = fpCreateInstance(pCreateInfo, pAllocator, pInstance); if (result == VK_SUCCESS) { initInstanceTable(*pInstance, fpGetInstanceProcAddr); + + // Eagerly enumerate physical devices and map them to the instance. + // This ensures we have the mapping even if the app bypasses our enumeration hooks. + PFN_vkEnumeratePhysicalDevices fpEnumeratePhysicalDevices = (PFN_vkEnumeratePhysicalDevices)fpGetInstanceProcAddr(*pInstance, "vkEnumeratePhysicalDevices"); + if (fpEnumeratePhysicalDevices) { + uint32_t count = 0; + fpEnumeratePhysicalDevices(*pInstance, &count, nullptr); + if (count > 0) { + std::vector devices(count); + fpEnumeratePhysicalDevices(*pInstance, &count, devices.data()); + for (uint32_t i = 0; i < count; ++i) { + DeviceMemoryReport::Get().SetVkInstance(devices[i], *pInstance); + } + } + } } return result; diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index 65334959f9..dba13b4471 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -44,6 +44,25 @@ TEST_F(DeviceMemoryReportTests, InitLayer) { inst_builder.Reset(); } +TEST_F(DeviceMemoryReportTests, EagerPhysicalDeviceEnumeration) { + TEST_DESCRIPTION("Test eager physical device mapping on instance creation"); + + layer_test::VulkanInstanceBuilder inst_builder; + VkResult err = inst_builder.Init(kLayerName); + EXPECT_EQ(err, VK_SUCCESS); + + VkInstance instance = inst_builder.GetInstance(); + EXPECT_NE(instance, VK_NULL_HANDLE); + + VkPhysicalDevice phys_dev = VK_NULL_HANDLE; + inst_builder.GetPhysicalDevice(&phys_dev); + if (phys_dev != VK_NULL_HANDLE) { + EXPECT_EQ(DeviceMemoryReport::Get().GetVkInstance(phys_dev), instance); + } + + inst_builder.Reset(); +} + TEST_F(DeviceMemoryReportTests, EmitEventsAndSubCounters) { TEST_DESCRIPTION("Test calling OnMemoryReportEvent with object types and allocating/freeing memory"); From 8d29b2ea124fcf83314146f1a7c1e7d732a2200d Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 25 Aug 2026 14:34:46 +0000 Subject: [PATCH 06/16] Remove code and comments remaining in error from debug object work. --- ...vice_memory_report_handwritten_functions.h | 18 +++------- layersvt/test/test_devicememoryreport.cpp | 36 +++++++++++++++++++ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index ee6c39cd7c..f0e19c8c68 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -34,11 +34,12 @@ // - vkCreateDevice / vkDestroyDevice: Initializes/destroys device dispatch tables and // injects VK_EXT_device_memory_report callback registration into device creation. // -// Memory tracking & debugging intercepts: +// Memory tracking & resource tracking intercepts: // - vkAllocateMemory / vkFreeMemory: Tracks direct allocations/frees as fallbacks. -// - vkBindBufferMemory / vkBindImageMemory: Associates buffer/image handles with memory. -// - vkSetDebugUtilsObjectNameEXT / vkDebugMarkerSetObjectNameEXT: Associates debug names -// and markers with object handles for labeled memory reporting. +// - vkBindBufferMemory* / vkBindImageMemory*: Associates buffer/image handles with memory allocations. +// - vkCreateBuffer / vkDestroyBuffer: Tracks buffer creation, usage flags, and requested sizes. +// - vkCreateImage / vkDestroyImage: Tracks image creation and usage flags. +// - vkGetBufferMemoryRequirements* / vkGetImageMemoryRequirements*: Tracks resource memory requirements. // - vkEnumerate*ExtensionProperties / vkEnumerate*LayerProperties: Advertises the layer // and support for the VK_EXT_device_memory_report extension. @@ -233,14 +234,6 @@ VKAPI_ATTR void VKAPI_CALL vkFreeMemory(VkDevice device, VkDeviceMemory memory, EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties) { - static const VkExtensionProperties instanceExtensions[] = { - {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_SPEC_VERSION}, - }; - - if (pLayerName != nullptr && strcmp(pLayerName, LAYER_NAME) == 0) { - return util_GetExtensionProperties(ARRAY_SIZE(instanceExtensions), instanceExtensions, pPropertyCount, pProperties); - } - return util_GetExtensionProperties(0, nullptr, pPropertyCount, pProperties); } @@ -275,7 +268,6 @@ EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionPropert VkExtensionProperties* pProperties) { static const VkExtensionProperties deviceExtensions[] = { {VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME, VK_EXT_DEVICE_MEMORY_REPORT_SPEC_VERSION}, - {VK_EXT_DEBUG_MARKER_EXTENSION_NAME, VK_EXT_DEBUG_MARKER_SPEC_VERSION}, }; if (pLayerName != nullptr && strcmp(pLayerName, LAYER_NAME) == 0) { diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index dba13b4471..aff6a7e319 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -63,6 +63,42 @@ TEST_F(DeviceMemoryReportTests, EagerPhysicalDeviceEnumeration) { inst_builder.Reset(); } +TEST_F(DeviceMemoryReportTests, ExtensionProperties) { + TEST_DESCRIPTION("Test extension enumeration for instance and device"); + + layer_test::VulkanInstanceBuilder inst_builder; + VkResult err = inst_builder.Init(kLayerName); + EXPECT_EQ(err, VK_SUCCESS); + + VkInstance instance = inst_builder.GetInstance(); + EXPECT_NE(instance, VK_NULL_HANDLE); + + // Test instance extension properties advertised by the layer + uint32_t inst_ext_count = 0; + EXPECT_EQ(vkEnumerateInstanceExtensionProperties(kLayerName, &inst_ext_count, nullptr), VK_SUCCESS); + EXPECT_EQ(inst_ext_count, 0u); + + VkPhysicalDevice phys_dev = VK_NULL_HANDLE; + inst_builder.GetPhysicalDevice(&phys_dev); + if (phys_dev != VK_NULL_HANDLE) { + // Test device extension properties advertised by the layer + uint32_t dev_ext_count = 0; + EXPECT_EQ(vkEnumerateDeviceExtensionProperties(phys_dev, kLayerName, &dev_ext_count, nullptr), VK_SUCCESS); + EXPECT_GE(dev_ext_count, 1u); + std::vector dev_exts(dev_ext_count); + EXPECT_EQ(vkEnumerateDeviceExtensionProperties(phys_dev, kLayerName, &dev_ext_count, dev_exts.data()), VK_SUCCESS); + bool found_mem_report = false; + for (const auto& ext : dev_exts) { + if (strcmp(ext.extensionName, VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME) == 0) { + found_mem_report = true; + } + } + EXPECT_TRUE(found_mem_report); + } + + inst_builder.Reset(); +} + TEST_F(DeviceMemoryReportTests, EmitEventsAndSubCounters) { TEST_DESCRIPTION("Test calling OnMemoryReportEvent with object types and allocating/freeing memory"); From e8e74f2afb4a006a1e20f62f8dfa7d27721a2a3c Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 25 Aug 2026 14:35:34 +0000 Subject: [PATCH 07/16] Use physicalDevice directly for instance dispatch table lookup Query extension properties via instance_dispatch_table(physicalDevice) directly rather than relying on instance singleton map lookup. --- ...evice_memory_report_handwritten_functions.h | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index f0e19c8c68..8dd8c3fcda 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -144,6 +144,9 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, c PFN_vkGetDeviceProcAddr fpGetDeviceProcAddr = chain_info->u.pLayerInfo->pfnNextGetDeviceProcAddr; VkInstance vk_instance = DeviceMemoryReport::Get().GetVkInstance(physicalDevice); PFN_vkCreateDevice fpCreateDevice = (PFN_vkCreateDevice)fpGetInstanceProcAddr(vk_instance, "vkCreateDevice"); + if (fpCreateDevice == NULL) { + return VK_ERROR_INITIALIZATION_FAILED; + } // Call the function and create the dispatch table chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; @@ -151,10 +154,10 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, c // Check if the underlying driver supports VK_EXT_device_memory_report bool supports_memory_report = false; uint32_t ext_count = 0; - if (vk_instance != VK_NULL_HANDLE && instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties) { - if (instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties(physicalDevice, nullptr, &ext_count, nullptr) == VK_SUCCESS && ext_count > 0) { + if (instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties) { + if (instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, nullptr, &ext_count, nullptr) == VK_SUCCESS && ext_count > 0) { std::vector exts(ext_count); - if (instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties(physicalDevice, nullptr, &ext_count, exts.data()) == VK_SUCCESS) { + if (instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, nullptr, &ext_count, exts.data()) == VK_SUCCESS) { for (const auto& ext : exts) { if (strcmp(ext.extensionName, VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME) == 0) { supports_memory_report = true; @@ -278,15 +281,10 @@ EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionPropert return VK_SUCCESS; } - VkInstance vk_instance = DeviceMemoryReport::Get().GetVkInstance(physicalDevice); - if (vk_instance == VK_NULL_HANDLE) { - return VK_SUCCESS; - } - // Manually append device extension. This should not be necessary, but the Android Vulkan // loader does not expose extensions from implicit layer. if (pProperties == nullptr) { - VkResult res = instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); + VkResult res = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); if (res == VK_SUCCESS) { (*pPropertyCount) += ARRAY_SIZE(deviceExtensions); } @@ -295,7 +293,7 @@ EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionPropert if (*pPropertyCount > 0) { uint32_t requestedCount = *pPropertyCount; - VkResult res = instance_dispatch_table(vk_instance)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); + VkResult res = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); if (res == VK_SUCCESS) { uint32_t originalCount = *pPropertyCount; uint32_t additionalCount = 0; From 5bc1f65fa84ad04ce652cd38290087754debeff9 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 26 Aug 2026 10:59:16 +0000 Subject: [PATCH 08/16] Make Perfetto SDK initialization thread-safe using std::call_once Move std::call_once inside InitializeDeviceMemoryReportPerfetto to ensure thread-safe Perfetto initialization and remove the static once_flag from the header. --- .../device_memory_report_handwritten_functions.h | 6 ++---- .../device_memory_report_perfetto.cpp | 15 +++++++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index 8dd8c3fcda..264350a7f8 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -56,13 +56,11 @@ #define LAYER_NAME "VK_LAYER_GOOGLE_DeviceMemoryReport" #define LAYER_DESCRIPTION "Vulkan Device Memory Report Layer" -static std::once_flag g_perfetto_init_flag; - extern "C" { VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) { - std::call_once(g_perfetto_init_flag, []() { InitializeDeviceMemoryReportPerfetto(); }); + InitializeDeviceMemoryReportPerfetto(); // Get the function pointer VkLayerInstanceCreateInfo* chain_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO); @@ -135,7 +133,7 @@ VKAPI_ATTR void VKAPI_CALL vkDestroyInstance(VkInstance instance, const VkAlloca VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { - std::call_once(g_perfetto_init_flag, []() { InitializeDeviceMemoryReportPerfetto(); }); + InitializeDeviceMemoryReportPerfetto(); // Get the function pointer VkLayerDeviceCreateInfo* chain_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO); diff --git a/layersvt/device_memory_report/device_memory_report_perfetto.cpp b/layersvt/device_memory_report/device_memory_report_perfetto.cpp index b84eaf28bd..82cc14ecc0 100644 --- a/layersvt/device_memory_report/device_memory_report_perfetto.cpp +++ b/layersvt/device_memory_report/device_memory_report_perfetto.cpp @@ -18,14 +18,13 @@ PERFETTO_TRACK_EVENT_STATIC_STORAGE(); void InitializeDeviceMemoryReportPerfetto() { - static bool initialized = false; - if (initialized) return; - initialized = true; - - perfetto::TracingInitArgs args; - args.backends = perfetto::kSystemBackend; - perfetto::Tracing::Initialize(args); - perfetto::TrackEvent::Register(); + static std::once_flag init_flag; + std::call_once(init_flag, []() { + perfetto::TracingInitArgs args; + args.backends = perfetto::kSystemBackend; + perfetto::Tracing::Initialize(args); + perfetto::TrackEvent::Register(); + }); } perfetto::CounterTrack GetCounterTrack(const char* name) { From 52c3ff70ddaa73cee3c132491738e20402b1d1da Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 26 Aug 2026 11:01:06 +0000 Subject: [PATCH 09/16] Use static lookup map for Perfetto CounterTracks to avoid dynamic allocation Pre-define static CounterTrack instances for known Vulkan memory report categories to eliminate heap allocation overhead in GetCounterTrack during tracing hot paths. --- .../device_memory_report_perfetto.cpp | 65 ++++++++++++++++++- .../device_memory_report_perfetto.h | 3 +- layersvt/test/test_devicememoryreport.cpp | 25 +++++++ 3 files changed, 90 insertions(+), 3 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report_perfetto.cpp b/layersvt/device_memory_report/device_memory_report_perfetto.cpp index 82cc14ecc0..01b4dc258e 100644 --- a/layersvt/device_memory_report/device_memory_report_perfetto.cpp +++ b/layersvt/device_memory_report/device_memory_report_perfetto.cpp @@ -14,6 +14,11 @@ */ #include "device_memory_report_perfetto.h" +#include +#include +#include +#include +#include PERFETTO_TRACK_EVENT_STATIC_STORAGE(); @@ -27,7 +32,63 @@ void InitializeDeviceMemoryReportPerfetto() { }); } -perfetto::CounterTrack GetCounterTrack(const char* name) { - return perfetto::CounterTrack(perfetto::DynamicString(name)) +namespace { +static const std::unordered_map kStaticTracks = { + // App usage tracks + {"vulkan.mem.app.usage.staging_transfer", perfetto::CounterTrack("vulkan.mem.app.usage.staging_transfer").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.app.usage.ray_tracing", perfetto::CounterTrack("vulkan.mem.app.usage.ray_tracing").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.app.usage.indirect_gpu_driven", perfetto::CounterTrack("vulkan.mem.app.usage.indirect_gpu_driven").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.app.usage.compute_storage", perfetto::CounterTrack("vulkan.mem.app.usage.compute_storage").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.app.usage.uniform_constants", perfetto::CounterTrack("vulkan.mem.app.usage.uniform_constants").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.app.usage.geometry_mesh", perfetto::CounterTrack("vulkan.mem.app.usage.geometry_mesh").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.app.usage.transform_feedback", perfetto::CounterTrack("vulkan.mem.app.usage.transform_feedback").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.app.usage.general_buffer", perfetto::CounterTrack("vulkan.mem.app.usage.general_buffer").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.app.usage.transient_memoryless", perfetto::CounterTrack("vulkan.mem.app.usage.transient_memoryless").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.app.usage.storage_compute_image", perfetto::CounterTrack("vulkan.mem.app.usage.storage_compute_image").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.app.usage.depth_stencil_target", perfetto::CounterTrack("vulkan.mem.app.usage.depth_stencil_target").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.app.usage.color_render_target", perfetto::CounterTrack("vulkan.mem.app.usage.color_render_target").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.app.usage.static_texture", perfetto::CounterTrack("vulkan.mem.app.usage.static_texture").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.app.usage.general_image", perfetto::CounterTrack("vulkan.mem.app.usage.general_image").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.app.usage.unbound_memory", perfetto::CounterTrack("vulkan.mem.app.usage.unbound_memory").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + + // Driver usage tracks + {"vulkan.mem.driver.usage.staging_transfer", perfetto::CounterTrack("vulkan.mem.driver.usage.staging_transfer").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.driver.usage.ray_tracing", perfetto::CounterTrack("vulkan.mem.driver.usage.ray_tracing").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.driver.usage.indirect_gpu_driven", perfetto::CounterTrack("vulkan.mem.driver.usage.indirect_gpu_driven").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.driver.usage.compute_storage", perfetto::CounterTrack("vulkan.mem.driver.usage.compute_storage").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.driver.usage.uniform_constants", perfetto::CounterTrack("vulkan.mem.driver.usage.uniform_constants").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.driver.usage.geometry_mesh", perfetto::CounterTrack("vulkan.mem.driver.usage.geometry_mesh").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.driver.usage.transform_feedback", perfetto::CounterTrack("vulkan.mem.driver.usage.transform_feedback").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.driver.usage.general_buffer", perfetto::CounterTrack("vulkan.mem.driver.usage.general_buffer").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.driver.usage.transient_memoryless", perfetto::CounterTrack("vulkan.mem.driver.usage.transient_memoryless").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.driver.usage.storage_compute_image", perfetto::CounterTrack("vulkan.mem.driver.usage.storage_compute_image").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.driver.usage.depth_stencil_target", perfetto::CounterTrack("vulkan.mem.driver.usage.depth_stencil_target").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.driver.usage.color_render_target", perfetto::CounterTrack("vulkan.mem.driver.usage.color_render_target").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.driver.usage.static_texture", perfetto::CounterTrack("vulkan.mem.driver.usage.static_texture").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.driver.usage.general_image", perfetto::CounterTrack("vulkan.mem.driver.usage.general_image").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, + {"vulkan.mem.driver.usage.unbound_memory", perfetto::CounterTrack("vulkan.mem.driver.usage.unbound_memory").set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES)}, +}; +} // namespace + +perfetto::CounterTrack GetCounterTrack(std::string_view name) { + auto it = kStaticTracks.find(name); + if (it != kStaticTracks.end()) { + return it->second; + } + static std::mutex dynamic_mutex; + static std::unordered_map dynamic_tracks; + static std::unordered_set dynamic_names; + std::lock_guard lock(dynamic_mutex); + auto dyn_it = dynamic_tracks.find(std::string(name)); + if (dyn_it != dynamic_tracks.end()) { + return dyn_it->second; + } + auto name_it = dynamic_names.insert(std::string(name)).first; + perfetto::CounterTrack track = perfetto::CounterTrack(perfetto::DynamicString(name_it->c_str())) .set_unit(perfetto::CounterTrack::Unit::UNIT_SIZE_BYTES); + dynamic_tracks.emplace( + std::piecewise_construct, + std::forward_as_tuple(*name_it), + std::forward_as_tuple(track)); + return track; } diff --git a/layersvt/device_memory_report/device_memory_report_perfetto.h b/layersvt/device_memory_report/device_memory_report_perfetto.h index 43739643de..fb43e7f929 100644 --- a/layersvt/device_memory_report/device_memory_report_perfetto.h +++ b/layersvt/device_memory_report/device_memory_report_perfetto.h @@ -17,6 +17,7 @@ #define LAYERSVT_DEVICE_MEMORY_REPORT_PERFETTO_H #include "perfetto/perfetto.h" +#include // Define categories used for Perfetto tracing in DeviceMemoryReport layer. PERFETTO_DEFINE_CATEGORIES( @@ -24,6 +25,6 @@ PERFETTO_DEFINE_CATEGORIES( ); void InitializeDeviceMemoryReportPerfetto(); -perfetto::CounterTrack GetCounterTrack(const char* name); +perfetto::CounterTrack GetCounterTrack(std::string_view name); #endif // LAYERSVT_DEVICE_MEMORY_REPORT_PERFETTO_H diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index aff6a7e319..9750d168a7 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -387,3 +387,28 @@ TEST_F(DeviceMemoryReportTests, ProactiveMemoryRequirementsQuery) { vkDestroyBuffer(device, buffer, nullptr); vkDestroyDevice(device, nullptr); } + +TEST_F(DeviceMemoryReportTests, StaticCounterTrackLookup) { + TEST_DESCRIPTION("Test static counter track lookup and dynamic fallback track creation"); + + perfetto::CounterTrack track_staging = GetCounterTrack("vulkan.mem.app.usage.staging_transfer"); + EXPECT_EQ(track_staging.Serialize().counter().unit(), perfetto::protos::gen::CounterDescriptor::UNIT_SIZE_BYTES); + EXPECT_NE(track_staging.uuid, 0u); + + perfetto::CounterTrack track_storage = GetCounterTrack("vulkan.mem.app.usage.compute_storage"); + EXPECT_EQ(track_storage.Serialize().counter().unit(), perfetto::protos::gen::CounterDescriptor::UNIT_SIZE_BYTES); + EXPECT_NE(track_storage.uuid, 0u); + + perfetto::CounterTrack track_unbound = GetCounterTrack("vulkan.mem.driver.usage.unbound_memory"); + EXPECT_EQ(track_unbound.Serialize().counter().unit(), perfetto::protos::gen::CounterDescriptor::UNIT_SIZE_BYTES); + EXPECT_NE(track_unbound.uuid, 0u); + + perfetto::CounterTrack dynamic_track = GetCounterTrack("vulkan.mem.app.custom_track"); + EXPECT_EQ(dynamic_track.Serialize().counter().unit(), perfetto::protos::gen::CounterDescriptor::UNIT_SIZE_BYTES); + EXPECT_NE(dynamic_track.uuid, 0u); + + // Re-query the dynamic track to ensure persistent registry returns consistent track + perfetto::CounterTrack dynamic_track_again = GetCounterTrack("vulkan.mem.app.custom_track"); + EXPECT_EQ(dynamic_track.uuid, dynamic_track_again.uuid); + EXPECT_EQ(dynamic_track_again.Serialize().counter().unit(), perfetto::protos::gen::CounterDescriptor::UNIT_SIZE_BYTES); +} From bda7e1fea6658bc22f5eedebfd50c7e4a5173d62 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 26 Aug 2026 13:20:08 +0000 Subject: [PATCH 10/16] Remove redundant vkEnumerateDeviceExtensionProperties layer intercept Remove the handwritten implementation and export of vkEnumerateDeviceExtensionProperties from the DeviceMemoryReport layer, allowing extension enumeration to be handled natively via the loader dispatch table and layer manifest. --- .../VkLayer_DeviceMemoryReport.def | 1 - ...ice_memory_report_handwritten_dispatch.cpp | 1 - ...vice_memory_report_handwritten_functions.h | 58 ------------------- 3 files changed, 60 deletions(-) diff --git a/layersvt/device_memory_report/VkLayer_DeviceMemoryReport.def b/layersvt/device_memory_report/VkLayer_DeviceMemoryReport.def index 12e17d232b..7e64580f96 100644 --- a/layersvt/device_memory_report/VkLayer_DeviceMemoryReport.def +++ b/layersvt/device_memory_report/VkLayer_DeviceMemoryReport.def @@ -18,5 +18,4 @@ vkGetInstanceProcAddr vkGetDeviceProcAddr vkEnumerateInstanceExtensionProperties vkEnumerateInstanceLayerProperties -vkEnumerateDeviceExtensionProperties vkEnumerateDeviceLayerProperties diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp index 2cfa666673..0c160a209d 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp +++ b/layersvt/device_memory_report/device_memory_report_handwritten_dispatch.cpp @@ -35,7 +35,6 @@ static PFN_vkVoidFunction devmemreport_known_device_functions(const char* pName) if (strcmp(pName, "vkCreateDevice") == 0) return reinterpret_cast(vkCreateDevice); if (strcmp(pName, "vkDestroyDevice") == 0) return reinterpret_cast(vkDestroyDevice); if (strcmp(pName, "vkEnumerateDeviceLayerProperties") == 0) return reinterpret_cast(vkEnumerateDeviceLayerProperties); - if (strcmp(pName, "vkEnumerateDeviceExtensionProperties") == 0) return reinterpret_cast(vkEnumerateDeviceExtensionProperties); if (strcmp(pName, "vkAllocateMemory") == 0) return reinterpret_cast(vkAllocateMemory); if (strcmp(pName, "vkFreeMemory") == 0) return reinterpret_cast(vkFreeMemory); if (strcmp(pName, "vkBindBufferMemory") == 0) return reinterpret_cast(vkBindBufferMemory); diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index 264350a7f8..5ab9d547a9 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -79,7 +79,6 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* pCre initInstanceTable(*pInstance, fpGetInstanceProcAddr); // Eagerly enumerate physical devices and map them to the instance. - // This ensures we have the mapping even if the app bypasses our enumeration hooks. PFN_vkEnumeratePhysicalDevices fpEnumeratePhysicalDevices = (PFN_vkEnumeratePhysicalDevices)fpGetInstanceProcAddr(*pInstance, "vkEnumeratePhysicalDevices"); if (fpEnumeratePhysicalDevices) { uint32_t count = 0; @@ -263,63 +262,6 @@ EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( return util_GetLayerProperties(ARRAY_SIZE(layerProperties), layerProperties, pPropertyCount, pProperties); } -EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, - const char* pLayerName, - uint32_t* pPropertyCount, - VkExtensionProperties* pProperties) { - static const VkExtensionProperties deviceExtensions[] = { - {VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME, VK_EXT_DEVICE_MEMORY_REPORT_SPEC_VERSION}, - }; - - if (pLayerName != nullptr && strcmp(pLayerName, LAYER_NAME) == 0) { - return util_GetExtensionProperties(ARRAY_SIZE(deviceExtensions), deviceExtensions, pPropertyCount, pProperties); - } - - if (physicalDevice == nullptr) { - return VK_SUCCESS; - } - - // Manually append device extension. This should not be necessary, but the Android Vulkan - // loader does not expose extensions from implicit layer. - if (pProperties == nullptr) { - VkResult res = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); - if (res == VK_SUCCESS) { - (*pPropertyCount) += ARRAY_SIZE(deviceExtensions); - } - return res; - } - - if (*pPropertyCount > 0) { - uint32_t requestedCount = *pPropertyCount; - VkResult res = instance_dispatch_table(physicalDevice)->EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pPropertyCount, pProperties); - if (res == VK_SUCCESS) { - uint32_t originalCount = *pPropertyCount; - uint32_t additionalCount = 0; - - for (uint32_t i = 0; i < ARRAY_SIZE(deviceExtensions); ++i) { - bool found = false; - for (uint32_t j = 0; j < originalCount; ++j) { - if (strcmp(pProperties[j].extensionName, deviceExtensions[i].extensionName) == 0) { - found = true; - break; - } - } - if (!found) { - if (originalCount + additionalCount < requestedCount) { - pProperties[originalCount + additionalCount] = deviceExtensions[i]; - } - additionalCount++; - } - } - *pPropertyCount = originalCount + additionalCount; - if (*pPropertyCount > requestedCount) { - *pPropertyCount = requestedCount; - } - } - return res; - } - return VK_SUCCESS; -} // Intercept memory binding to correlate buffer object handles with device memory allocations. VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset) { From 53c993aea2770676256d1c331e8e9986a9471b57 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 26 Aug 2026 13:22:14 +0000 Subject: [PATCH 11/16] Add GetBufferCluster and GetImageCluster memory classification functions Introduce helper functions to classify Vulkan buffer and image usage flags and physical memory property flags into refined memory usage clusters (staging_transfer, ray_tracing, indirect_gpu_driven, compute_storage, uniform_constants, geometry_mesh, transient_memoryless, etc.) with unit tests covering classification precedence. --- .../device_memory_report.cpp | 76 +++++++++++++++++++ .../device_memory_report.h | 16 ++++ layersvt/test/test_devicememoryreport.cpp | 68 +++++++++++++++++ 3 files changed, 160 insertions(+) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 53550e8338..3ab46a8e02 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -42,6 +42,82 @@ void VKAPI_PTR DeviceMemoryReport::MemoryReportCallback(const VkDeviceMemoryRepo DeviceMemoryReport::Get().OnMemoryReportEvent(pCallbackData); } +const char* GetBufferCluster(VkBufferUsageFlags usage, VkMemoryPropertyFlags memFlags) { + const VkBufferUsageFlags kFunctionalBufferUsageFlags = + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_INDEX_BUFFER_BIT | + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | + VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | + VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | + VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | + VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR | + VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT | + VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT | + VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT; + + // 1. Host staging: Must be host-visible and not a primary GPU functional buffer + if ((memFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) && + (usage & (VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)) && + !(usage & kFunctionalBufferUsageFlags)) { + return "staging_transfer"; + } + // 2. Ray tracing acceleration structures & shader binding tables + if (usage & (VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | + VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR)) { + return "ray_tracing"; + } + // 3. Indirect draw / dispatch commands & conditional rendering + if (usage & (VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT)) { + return "indirect_gpu_driven"; + } + // 4. Compute storage buffers (SSBOs & Storage Texel Buffers) + if (usage & (VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT)) { + return "compute_storage"; + } + // 5. Uniform buffers & uniform texel buffers + if (usage & (VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT)) { + return "uniform_constants"; + } + // 6. Geometry & mesh data (Vertex + Index) + if (usage & (VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT)) { + return "geometry_mesh"; + } + // 7. Transform feedback stream-out + if (usage & (VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT | VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT)) { + return "transform_feedback"; + } + return "general_buffer"; +} + +const char* GetImageCluster(VkImageUsageFlags usage, VkMemoryPropertyFlags memFlags) { + // 1. Mobile TBDR on-chip tile memoryless attachments + if ((usage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT) || + (memFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT)) { + return "transient_memoryless"; + } + // 2. Compute storage write targets (UAVs) + if (usage & VK_IMAGE_USAGE_STORAGE_BIT) { + return "storage_compute_image"; + } + // 3. Depth / stencil render targets + if (usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) { + return "depth_stencil_target"; + } + // 4. Color render targets + if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) { + return "color_render_target"; + } + // 5. Sampled textures + if (usage & VK_IMAGE_USAGE_SAMPLED_BIT) { + return "static_texture"; + } + return "general_image"; +} + // Maps Vulkan image or buffer usage flags to a Perfetto memory track usage category name. static const char* GetUsageCategoryName(bool is_image, uint32_t usage_flags) { if (is_image) { diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index 5a2ce91eaf..be5e2ae5ab 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -25,6 +25,22 @@ #define VK_DEVICE_MEMORY_REPORT_FLAG_INTERNAL_OBJECT_BIT_EXT 0x00000001 #endif +/** + * @brief Categorizes buffer usage flags and memory property flags into a cluster category name. + * @param usage The Vulkan buffer usage flags. + * @param memFlags The physical memory property flags. + * @return The name of the cluster category for buffer memory tracking. + */ +const char* GetBufferCluster(VkBufferUsageFlags usage, VkMemoryPropertyFlags memFlags = 0); + +/** + * @brief Categorizes image usage flags and memory property flags into a cluster category name. + * @param usage The Vulkan image usage flags. + * @param memFlags The physical memory property flags. + * @return The name of the cluster category for image memory tracking. + */ +const char* GetImageCluster(VkImageUsageFlags usage, VkMemoryPropertyFlags memFlags = 0); + /** * The DeviceMemoryReport class is responsible for tracking Vulkan device memory * allocations and object associations, sending live memory usage counters to Perfetto traces. diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index 9750d168a7..74ff713caf 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -388,6 +388,74 @@ TEST_F(DeviceMemoryReportTests, ProactiveMemoryRequirementsQuery) { vkDestroyDevice(device, nullptr); } +TEST_F(DeviceMemoryReportTests, ClusterClassificationFunctions) { + TEST_DESCRIPTION("Test cluster classification for buffer and image usages with memory properties"); + + // Buffer clusters + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT), "staging_transfer"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT), "staging_transfer"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT), "staging_transfer"); + // Staging without host-visible property falls through to general_buffer + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 0), "general_buffer"); + // Host-visible functional buffers should NOT be misclassified as staging + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT), "geometry_mesh"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT), "geometry_mesh"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT), "uniform_constants"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT), "uniform_constants"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT), "compute_storage"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT), "compute_storage"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT), "indirect_gpu_driven"); + + // Ray tracing + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR, 0), "ray_tracing"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, 0), "ray_tracing"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, 0), "ray_tracing"); + + // Indirect GPU driven + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, 0), "indirect_gpu_driven"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT, 0), "indirect_gpu_driven"); + + // Compute storage + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, 0), "compute_storage"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT, 0), "compute_storage"); + + // Uniform constants + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, 0), "uniform_constants"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT, 0), "uniform_constants"); + + // Geometry mesh + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 0), "geometry_mesh"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, 0), "geometry_mesh"); + + // Transform feedback + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT, 0), "transform_feedback"); + EXPECT_STREQ(GetBufferCluster(VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT, 0), "transform_feedback"); + + // General buffer + EXPECT_STREQ(GetBufferCluster(0, 0), "general_buffer"); + + // Image clusters + // Transient memoryless + EXPECT_STREQ(GetImageCluster(VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT, 0), "transient_memoryless"); + EXPECT_STREQ(GetImageCluster(0, VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT), "transient_memoryless"); + + // Storage compute image + EXPECT_STREQ(GetImageCluster(VK_IMAGE_USAGE_STORAGE_BIT, 0), "storage_compute_image"); + + // Depth stencil target + EXPECT_STREQ(GetImageCluster(VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, 0), "depth_stencil_target"); + + // Color render target + EXPECT_STREQ(GetImageCluster(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, 0), "color_render_target"); + EXPECT_STREQ(GetImageCluster(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 0), "color_render_target"); + + // Static texture + EXPECT_STREQ(GetImageCluster(VK_IMAGE_USAGE_SAMPLED_BIT, 0), "static_texture"); + + // General image + EXPECT_STREQ(GetImageCluster(0, 0), "general_image"); +} + TEST_F(DeviceMemoryReportTests, StaticCounterTrackLookup) { TEST_DESCRIPTION("Test static counter track lookup and dynamic fallback track creation"); From 0ce16a84846f6373d3691c7c54552bd7981688a3 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Wed, 26 Aug 2026 13:23:14 +0000 Subject: [PATCH 12/16] Integrate memory properties capture and cluster classification into resource tracking Capture and cache VkPhysicalDeviceMemoryProperties on vkCreateDevice, resolve memory property flags via memoryTypeIndex in vkAllocateMemory, and refactor Resource metadata to resolve usage clusters dynamically during memory binding and unbound headroom updates. --- .../device_memory_report.cpp | 123 ++++++++---- .../device_memory_report.h | 46 ++++- ...vice_memory_report_handwritten_functions.h | 9 +- layersvt/test/test_devicememoryreport.cpp | 189 ++++++++++-------- 4 files changed, 241 insertions(+), 126 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 3ab46a8e02..72c66c54a8 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -32,12 +32,50 @@ void DeviceMemoryReport::SetVkInstance(VkPhysicalDevice phys_dev, VkInstance ins } VkInstance DeviceMemoryReport::GetVkInstance(VkPhysicalDevice phys_dev) { + if (phys_dev == VK_NULL_HANDLE) return VK_NULL_HANDLE; std::lock_guard lock(map_mutex_); auto it = vk_instance_map_.find(phys_dev); if (it != vk_instance_map_.end()) return it->second; + + struct LoaderPhysDevTramp { + void* disp; + void* this_instance; + uint64_t magic; + VkPhysicalDevice unwrapped_phys_dev; + }; + const uint64_t kPhysTrampMagicNumber = 0x10ADED020210ADEDUL; + auto* tramp = reinterpret_cast(phys_dev); + if (tramp && tramp->magic == kPhysTrampMagicNumber) { + auto it_unwrapped = vk_instance_map_.find(tramp->unwrapped_phys_dev); + if (it_unwrapped != vk_instance_map_.end()) return it_unwrapped->second; + } + + if (vk_instance_map_.size() == 1) { + return vk_instance_map_.begin()->second; + } return VK_NULL_HANDLE; } +void DeviceMemoryReport::SetDeviceMemoryProperties(VkDevice device, const VkPhysicalDeviceMemoryProperties& props) { + std::lock_guard lock(counter_mutex_); + device_memory_properties_map_[device] = props; +} + +void DeviceMemoryReport::SetAllocationMemoryProperties(uint64_t memory_handle, VkMemoryPropertyFlags property_flags) { + std::lock_guard lock(counter_mutex_); + auto& allocation = memory_allocations_[memory_handle]; + allocation.mem_flags = property_flags; + if (allocation.total_size > 0) { + UpdateAllocationUnboundCounter(memory_handle); + } +} + +void DeviceMemoryReport::OnDestroyDevice(VkDevice device) { + std::lock_guard lock(counter_mutex_); + has_callback_map_.erase(device); + device_memory_properties_map_.erase(device); +} + void VKAPI_PTR DeviceMemoryReport::MemoryReportCallback(const VkDeviceMemoryReportCallbackDataEXT* pCallbackData, void* pUserData) { DeviceMemoryReport::Get().OnMemoryReportEvent(pCallbackData); } @@ -118,22 +156,6 @@ const char* GetImageCluster(VkImageUsageFlags usage, VkMemoryPropertyFlags memFl return "general_image"; } -// Maps Vulkan image or buffer usage flags to a Perfetto memory track usage category name. -static const char* GetUsageCategoryName(bool is_image, uint32_t usage_flags) { - if (is_image) { - if (usage_flags & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) return "depth_stencil_attachment"; - if (usage_flags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) return "color_attachment"; - if (usage_flags & VK_IMAGE_USAGE_SAMPLED_BIT) return "texture"; - if (usage_flags & (VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT)) return "transfer_image"; - return "image"; - } - if (usage_flags & VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) return "vertex_buffer"; - if (usage_flags & VK_BUFFER_USAGE_INDEX_BUFFER_BIT) return "index_buffer"; - if (usage_flags & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) return "uniform_buffer"; - if (usage_flags & (VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)) return "staging_buffer"; - return "buffer"; -} - // Construct a Perfetto track name for visualizing memory usage by category in the UI. static std::string GetUsageTrackName(bool is_driver, std::string_view usage) { std::string track = is_driver ? "vulkan.mem.driver.usage." : "vulkan.mem.app.usage."; @@ -144,13 +166,13 @@ static std::string GetUsageTrackName(bool is_driver, std::string_view usage) { void DeviceMemoryReport::AddCounterBytes(const std::string& track, uint64_t size) { uint64_t& bytes = usage_memory_bytes_[track]; bytes += size; - TRACE_COUNTER("vulkan", GetCounterTrack(track.c_str()), bytes); + TRACE_COUNTER("vulkan", GetCounterTrack(track), bytes); } void DeviceMemoryReport::SubtractCounterBytes(const std::string& track, uint64_t size) { uint64_t& bytes = usage_memory_bytes_[track]; bytes = (bytes >= size) ? (bytes - size) : 0; - TRACE_COUNTER("vulkan", GetCounterTrack(track.c_str()), bytes); + TRACE_COUNTER("vulkan", GetCounterTrack(track), bytes); } void DeviceMemoryReport::UpdateAllocationUnboundCounter(uint64_t memory_handle) { @@ -193,8 +215,8 @@ void DeviceMemoryReport::UpdateAllocationUnboundCounter(uint64_t memory_handle) std::string track_name = "unbound_memory"; auto res_it = resources_.find(allocation.object_handle); // If the memory object has an associated resource with a specific usage, use it as the track name. - if (res_it != resources_.end() && !res_it->second.usage.empty()) { - track_name = res_it->second.usage; + if (res_it != resources_.end()) { + track_name = res_it->second.GetCluster(allocation.mem_flags); } std::string new_unbound_track = GetUsageTrackName(allocation.is_driver, track_name); @@ -236,29 +258,37 @@ void DeviceMemoryReport::RemoveResourceBinding(uint64_t resource_handle) { } } -void DeviceMemoryReport::OnBindBufferMemory(uint64_t buffer_handle, uint64_t memory_handle, VkDeviceSize memory_offset) { +void DeviceMemoryReport::BindResourceMemory(uint64_t resource_handle, uint64_t memory_handle, VkDeviceSize memory_offset) { std::lock_guard lock(counter_mutex_); - auto res_it = resources_.find(buffer_handle); - if (res_it == resources_.end() || res_it->second.usage.empty() || res_it->second.size == 0) return; + auto res_it = resources_.find(resource_handle); + if (res_it == resources_.end() || res_it->second.size == 0) return; // If the same resource handle is passed more than once, remove stale bindings first. - RemoveResourceBinding(buffer_handle); + RemoveResourceBinding(resource_handle); auto& allocation = memory_allocations_[memory_handle]; VkDeviceSize res_size = res_it->second.size; - std::string new_usage_track = GetUsageTrackName(allocation.is_driver, res_it->second.usage); + std::string new_usage_track = GetUsageTrackName(allocation.is_driver, res_it->second.GetCluster(allocation.mem_flags)); // Suballocations represent individual resources (like buffers or images) that are bound // to specific offset regions within a single large memory allocation. // We add a record here to track this specific resource's footprint within the larger memory block. - allocation.sub_allocations.push_back({ buffer_handle, memory_offset, res_size, new_usage_track }); - resource_to_memory_map_[buffer_handle] = memory_handle; + allocation.sub_allocations.push_back({ resource_handle, memory_offset, res_size, new_usage_track }); + resource_to_memory_map_[resource_handle] = memory_handle; // Each distinct virtual resource handle adds its virtual size to its specific category track upon binding. AddCounterBytes(new_usage_track, res_size); UpdateAllocationUnboundCounter(memory_handle); } +void DeviceMemoryReport::OnBindBufferMemory(uint64_t buffer_handle, uint64_t memory_handle, VkDeviceSize memory_offset) { + BindResourceMemory(buffer_handle, memory_handle, memory_offset); +} + +void DeviceMemoryReport::OnBindImageMemory(uint64_t image_handle, uint64_t memory_handle, VkDeviceSize memory_offset) { + BindResourceMemory(image_handle, memory_handle, memory_offset); +} + void DeviceMemoryReport::RemoveAllocationTracking(uint64_t memory_handle) { auto allocation_it = memory_allocations_.find(memory_handle); if (allocation_it == memory_allocations_.end()) return; @@ -274,10 +304,6 @@ void DeviceMemoryReport::RemoveAllocationTracking(uint64_t memory_handle) { memory_allocations_.erase(allocation_it); } -void DeviceMemoryReport::OnBindImageMemory(uint64_t image_handle, uint64_t memory_handle, VkDeviceSize memory_offset) { - OnBindBufferMemory(image_handle, memory_handle, memory_offset); -} - void DeviceMemoryReport::OnRecordResourceSize(uint64_t resource_handle, VkDeviceSize size) { std::lock_guard lock(counter_mutex_); resources_[resource_handle].size = size; @@ -291,7 +317,9 @@ VkDeviceSize DeviceMemoryReport::GetRecordedResourceSize(uint64_t resource_handl void DeviceMemoryReport::OnCreateImage(uint64_t image_handle, VkImageUsageFlags usage) { std::lock_guard lock(counter_mutex_); - resources_[image_handle].usage = GetUsageCategoryName(true, usage); + auto& res = resources_[image_handle]; + res.is_image = true; + res.image_usage = usage; for (const auto& pair : memory_allocations_) { if (pair.second.object_handle == image_handle) { UpdateAllocationUnboundCounter(pair.first); @@ -301,7 +329,10 @@ void DeviceMemoryReport::OnCreateImage(uint64_t image_handle, VkImageUsageFlags void DeviceMemoryReport::OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFlags usage, VkDeviceSize size) { std::lock_guard lock(counter_mutex_); - resources_[buffer_handle] = { GetUsageCategoryName(false, usage), size }; + auto& res = resources_[buffer_handle]; + res.is_image = false; + res.buffer_usage = usage; + res.size = size; for (const auto& pair : memory_allocations_) { if (pair.second.object_handle == buffer_handle) { UpdateAllocationUnboundCounter(pair.first); @@ -340,14 +371,30 @@ void DeviceMemoryReport::SetHasMemoryReportCallback(VkDevice device, bool has_ca has_callback_map_[device] = has_callback; } -void DeviceMemoryReport::OnAllocateMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize size) { +void DeviceMemoryReport::OnAllocateMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize size, uint32_t memory_type_index, VkMemoryPropertyFlags property_flags) { std::lock_guard lock(counter_mutex_); - if (has_callback_map_[device]) return; uint64_t handle = reinterpret_cast(memory); auto& allocation = memory_allocations_[handle]; - allocation.total_size = size; - allocation.is_driver = false; - UpdateAllocationUnboundCounter(handle); + + VkMemoryPropertyFlags mem_flags = property_flags; + if (mem_flags == 0 && memory_type_index != UINT32_MAX) { + auto prop_it = device_memory_properties_map_.find(device); + if (prop_it != device_memory_properties_map_.end() && memory_type_index < prop_it->second.memoryTypeCount) { + mem_flags = prop_it->second.memoryTypes[memory_type_index].propertyFlags; + } + } + bool mem_flags_changed = (allocation.mem_flags != mem_flags); + allocation.mem_flags = mem_flags; + + if (has_callback_map_[device]) { + if (mem_flags_changed && allocation.total_size > 0) { + UpdateAllocationUnboundCounter(handle); + } + } else { + allocation.total_size = size; + allocation.is_driver = false; + UpdateAllocationUnboundCounter(handle); + } } void DeviceMemoryReport::OnFreeMemory(VkDevice device, VkDeviceMemory memory) { diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index be5e2ae5ab..22514516aa 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -83,6 +83,26 @@ class DeviceMemoryReport { */ VkInstance GetVkInstance(VkPhysicalDevice phys_dev); + /** + * @brief Stores physical device memory properties for a logical device. + * @param device The Vulkan device handle. + * @param props The memory properties of the physical device. + */ + void SetDeviceMemoryProperties(VkDevice device, const VkPhysicalDeviceMemoryProperties& props); + + /** + * @brief Explicitly sets memory property flags for an allocation handle (useful for testing or overrides). + * @param memory_handle The 64-bit handle of the Vulkan device memory. + * @param property_flags The memory property flags. + */ + void SetAllocationMemoryProperties(uint64_t memory_handle, VkMemoryPropertyFlags property_flags); + + /** + * @brief Cleans up device-specific tracking state upon device destruction. + * @param device The Vulkan device handle. + */ + void OnDestroyDevice(VkDevice device); + /** * @brief Static callback invoked by the VK_EXT_device_memory_report extension. * @param pCallbackData Pointer to the memory report callback data structure. @@ -108,8 +128,10 @@ class DeviceMemoryReport { * @param device The Vulkan device handle. * @param memory The VkDeviceMemory handle being allocated. * @param size The size of the allocation in bytes. + * @param memory_type_index The memory type index from VkMemoryAllocateInfo. + * @param property_flags Optional explicit memory property flags (for testing or overrides). */ - void OnAllocateMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize size); + void OnAllocateMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize size, uint32_t memory_type_index = UINT32_MAX, VkMemoryPropertyFlags property_flags = 0); /** * @brief Handles fallback memory free tracking when driver callback is unavailable. @@ -187,6 +209,7 @@ class DeviceMemoryReport { VkDeviceSize total_size = 0; VkDeviceSize applied_unbound_bytes = 0; bool is_driver = false; + VkMemoryPropertyFlags mem_flags = 0; std::vector sub_allocations; std::string unbound_usage_track; uint64_t object_handle = 0; @@ -196,10 +219,24 @@ class DeviceMemoryReport { * @brief Tracks metadata for a virtual resource (buffer or image). */ struct Resource { - std::string usage; + bool is_image = false; + VkImageUsageFlags image_usage = 0; + VkBufferUsageFlags buffer_usage = 0; VkDeviceSize size = 0; + + const char* GetCluster(VkMemoryPropertyFlags mem_flags) const { + if (is_image) { + return GetImageCluster(image_usage, mem_flags); + } + return GetBufferCluster(buffer_usage, mem_flags); + } }; + /** + * @brief Helper to bind resource memory (buffers and images). + */ + void BindResourceMemory(uint64_t resource_handle, uint64_t memory_handle, VkDeviceSize memory_offset); + /** * @brief Updates unbound memory category counter for a physical memory slab. * Unbound memory is allocated device memory not currently bound to any active resource (e.g. buffer or image). @@ -246,6 +283,11 @@ class DeviceMemoryReport { */ std::unordered_map has_callback_map_; + /** + * @brief Maps a Vulkan device handle to its physical device memory properties. + */ + std::unordered_map device_memory_properties_map_; + /** * @brief Maps a virtual resource handle to its metadata. */ diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index 5ab9d547a9..c122838337 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -198,12 +198,19 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, c if (result == VK_SUCCESS) { initDeviceTable(*pDevice, fpGetDeviceProcAddr); DeviceMemoryReport::Get().SetHasMemoryReportCallback(*pDevice, supports_memory_report); + + VkPhysicalDeviceMemoryProperties mem_props = {}; + if (instance_dispatch_table(physicalDevice)->GetPhysicalDeviceMemoryProperties) { + instance_dispatch_table(physicalDevice)->GetPhysicalDeviceMemoryProperties(physicalDevice, &mem_props); + DeviceMemoryReport::Get().SetDeviceMemoryProperties(*pDevice, mem_props); + } } return result; } VKAPI_ATTR void VKAPI_CALL vkDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) { + DeviceMemoryReport::Get().OnDestroyDevice(device); dispatch_key key = get_dispatch_key(device); device_dispatch_table(device)->DestroyDevice(device, pAllocator); destroy_device_dispatch_table(key); @@ -215,7 +222,7 @@ VKAPI_ATTR VkResult VKAPI_CALL vkAllocateMemory(VkDevice device, const VkMemoryA PFN_vkAllocateMemory fpAllocateMemory = (PFN_vkAllocateMemory)device_dispatch_table(device)->AllocateMemory; VkResult result = fpAllocateMemory(device, pAllocateInfo, pAllocator, pMemory); if (result == VK_SUCCESS && pAllocateInfo != nullptr && pMemory != nullptr && *pMemory != VK_NULL_HANDLE) { - DeviceMemoryReport::Get().OnAllocateMemory(device, *pMemory, pAllocateInfo->allocationSize); + DeviceMemoryReport::Get().OnAllocateMemory(device, *pMemory, pAllocateInfo->allocationSize, pAllocateInfo->memoryTypeIndex); } return result; } diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index 74ff713caf..1d83467d41 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -187,11 +187,15 @@ TEST_F(DeviceMemoryReportTests, UsageTypeBreakdown) { uint64_t color_img = 0xA001; uint64_t depth_img = 0xA002; uint64_t sampled_img = 0xA005; + uint64_t storage_img = 0xA006; + uint64_t transient_img = 0xA007; uint64_t vtx_buf = 0xB001; uint64_t idx_buf = 0xB002; uint64_t ubo_buf = 0xB003; uint64_t staging_buf = 0xB006; + uint64_t storage_buf = 0xB007; + uint64_t indirect_buf = 0xB008; uint64_t mem_handle = 0xC001; @@ -199,36 +203,47 @@ TEST_F(DeviceMemoryReportTests, UsageTypeBreakdown) { DeviceMemoryReport::Get().OnCreateImage(color_img, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); DeviceMemoryReport::Get().OnCreateImage(depth_img, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT); DeviceMemoryReport::Get().OnCreateImage(sampled_img, VK_IMAGE_USAGE_SAMPLED_BIT); + DeviceMemoryReport::Get().OnCreateImage(storage_img, VK_IMAGE_USAGE_STORAGE_BIT); + DeviceMemoryReport::Get().OnCreateImage(transient_img, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT); DeviceMemoryReport::Get().OnRecordResourceSize(color_img, 65536); DeviceMemoryReport::Get().OnRecordResourceSize(depth_img, 65536); DeviceMemoryReport::Get().OnRecordResourceSize(sampled_img, 65536); + DeviceMemoryReport::Get().OnRecordResourceSize(storage_img, 65536); + DeviceMemoryReport::Get().OnRecordResourceSize(transient_img, 65536); // Register buffers DeviceMemoryReport::Get().OnCreateBuffer(vtx_buf, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 4096); DeviceMemoryReport::Get().OnCreateBuffer(idx_buf, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, 4096); DeviceMemoryReport::Get().OnCreateBuffer(ubo_buf, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, 4096); DeviceMemoryReport::Get().OnCreateBuffer(staging_buf, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, 4096); + DeviceMemoryReport::Get().OnCreateBuffer(storage_buf, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, 4096); + DeviceMemoryReport::Get().OnCreateBuffer(indirect_buf, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, 4096); VkDeviceMemoryReportCallbackDataEXT cb_data = {}; cb_data.sType = VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT; cb_data.flags = 0; cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT; cb_data.memoryObjectId = 0x5000; - cb_data.size = 1048576; + cb_data.size = 2097152; cb_data.objectType = VK_OBJECT_TYPE_DEVICE_MEMORY; cb_data.objectHandle = mem_handle; DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); + DeviceMemoryReport::Get().SetAllocationMemoryProperties(mem_handle, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); // Bind images and buffers to test usage categorization and track transitions DeviceMemoryReport::Get().OnBindImageMemory(color_img, mem_handle, 0); DeviceMemoryReport::Get().OnBindImageMemory(depth_img, mem_handle, 65536); DeviceMemoryReport::Get().OnBindImageMemory(sampled_img, mem_handle, 131072); + DeviceMemoryReport::Get().OnBindImageMemory(storage_img, mem_handle, 196608); + DeviceMemoryReport::Get().OnBindImageMemory(transient_img, mem_handle, 262144); - DeviceMemoryReport::Get().OnBindBufferMemory(vtx_buf, mem_handle, 196608); - DeviceMemoryReport::Get().OnBindBufferMemory(idx_buf, mem_handle, 200704); - DeviceMemoryReport::Get().OnBindBufferMemory(ubo_buf, mem_handle, 204800); - DeviceMemoryReport::Get().OnBindBufferMemory(staging_buf, mem_handle, 208896); + DeviceMemoryReport::Get().OnBindBufferMemory(vtx_buf, mem_handle, 327680); + DeviceMemoryReport::Get().OnBindBufferMemory(idx_buf, mem_handle, 331776); + DeviceMemoryReport::Get().OnBindBufferMemory(ubo_buf, mem_handle, 335872); + DeviceMemoryReport::Get().OnBindBufferMemory(staging_buf, mem_handle, 339968); + DeviceMemoryReport::Get().OnBindBufferMemory(storage_buf, mem_handle, 344064); + DeviceMemoryReport::Get().OnBindBufferMemory(indirect_buf, mem_handle, 348160); // Free memory cb_data.type = VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT; @@ -238,11 +253,15 @@ TEST_F(DeviceMemoryReportTests, UsageTypeBreakdown) { DeviceMemoryReport::Get().OnDestroyObject(color_img); DeviceMemoryReport::Get().OnDestroyObject(depth_img); DeviceMemoryReport::Get().OnDestroyObject(sampled_img); + DeviceMemoryReport::Get().OnDestroyObject(storage_img); + DeviceMemoryReport::Get().OnDestroyObject(transient_img); DeviceMemoryReport::Get().OnDestroyObject(vtx_buf); DeviceMemoryReport::Get().OnDestroyObject(idx_buf); DeviceMemoryReport::Get().OnDestroyObject(ubo_buf); DeviceMemoryReport::Get().OnDestroyObject(staging_buf); + DeviceMemoryReport::Get().OnDestroyObject(storage_buf); + DeviceMemoryReport::Get().OnDestroyObject(indirect_buf); EXPECT_TRUE(true); } @@ -269,7 +288,7 @@ TEST_F(DeviceMemoryReportTests, MemoryAliasingAndOverlap) { DeviceMemoryReport::MemoryReportCallback(&cb_data, nullptr); // Step 2: Bind Resource A (Color Attachment Image) to range [0, 4000) (size = 4000 B). - // - vulkan.mem.app.usage.color_attachment += 4,000 B + // - vulkan.mem.app.usage.color_render_target += 4,000 B // - Merged intervals: [0, 4000) -> bound_size = 4,000 B, unbound_memory = 6,000 B DeviceMemoryReport::Get().OnCreateImage(image_a, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); DeviceMemoryReport::Get().OnRecordResourceSize(image_a, 4000); @@ -278,7 +297,7 @@ TEST_F(DeviceMemoryReportTests, MemoryAliasingAndOverlap) { // Step 3: Bind Resource B (Sampled Texture Image) to range [2000, 6000) (size = 4000 B). // This overlaps / aliases Resource A on the physical sub-range [2000, 4000). // - Each virtual resource adds its full virtual size to its specific category track: - // vulkan.mem.app.usage.texture += 4,000 B (both A and B report active virtual capacity). + // vulkan.mem.app.usage.static_texture += 4,000 B (both A and B report active virtual capacity). // - Overlapping intervals [0, 4000) and [2000, 6000) are merged into union [0, 6000). // - Physical slab bound_size = 6,000 B (overlapping physical region is NOT double-counted). // - Remaining unbound headroom: unbound_memory = 10,000 - 6,000 = 4,000 B. @@ -287,14 +306,14 @@ TEST_F(DeviceMemoryReportTests, MemoryAliasingAndOverlap) { DeviceMemoryReport::Get().OnBindImageMemory(image_b, mem_handle, 2000); // Step 4: Bind Resource C (Vertex Buffer) to disjoint range [8000, 9500) (size = 1500 B). - // - vulkan.mem.app.usage.vertex_buffer += 1,500 B + // - vulkan.mem.app.usage.geometry_mesh += 1,500 B // - Interval union: [0, 6000) U [8000, 9500) -> bound_size = 6,000 + 1,500 = 7,500 B. // - Remaining unbound headroom: unbound_memory = 10,000 - 7,500 = 2,500 B. DeviceMemoryReport::Get().OnCreateBuffer(buffer_c, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 1500); DeviceMemoryReport::Get().OnBindBufferMemory(buffer_c, mem_handle, 8000); // Step 5: Destroy Resource A. - // - vulkan.mem.app.usage.color_attachment -= 4,000 B + // - vulkan.mem.app.usage.color_render_target -= 4,000 B // - Interval [0, 4000) is removed. Remaining intervals: [2000, 6000) U [8000, 9500). // - Recalculated bound_size = 4,000 + 1,500 = 5,500 B. // - Updated unbound headroom: unbound_memory = 10,000 - 5,500 = 4,500 B. @@ -312,82 +331,6 @@ TEST_F(DeviceMemoryReportTests, MemoryAliasingAndOverlap) { EXPECT_TRUE(true); } -TEST_F(DeviceMemoryReportTests, ProactiveMemoryRequirementsQuery) { - TEST_DESCRIPTION("Test that the layer proactively queries memory requirements when creating images and buffers"); - - layer_test::VulkanInstanceBuilder inst_builder; - VkResult err = inst_builder.Init(kLayerName); - EXPECT_EQ(err, VK_SUCCESS); - - VkPhysicalDevice phys_dev = VK_NULL_HANDLE; - inst_builder.GetPhysicalDevice(&phys_dev); - if (phys_dev == VK_NULL_HANDLE) { - GTEST_SKIP() << "No physical device found, skipping test."; - } - - // Create a logical device - float queue_priority = 1.0f; - VkDeviceQueueCreateInfo queue_info = {}; - queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queue_info.queueFamilyIndex = 0; - queue_info.queueCount = 1; - queue_info.pQueuePriorities = &queue_priority; - - VkDeviceCreateInfo dev_info = {}; - dev_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - dev_info.queueCreateInfoCount = 1; - dev_info.pQueueCreateInfos = &queue_info; - dev_info.enabledExtensionCount = 0; - - VkDevice device = VK_NULL_HANDLE; - err = vkCreateDevice(phys_dev, &dev_info, nullptr, &device); - if (err != VK_SUCCESS) { - GTEST_SKIP() << "Failed to create logical device, skipping test."; - } - - // Create an image - VkImageCreateInfo img_info = {}; - img_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - img_info.imageType = VK_IMAGE_TYPE_2D; - img_info.format = VK_FORMAT_R8G8B8A8_UNORM; - img_info.extent = {64, 64, 1}; - img_info.mipLevels = 1; - img_info.arrayLayers = 1; - img_info.samples = VK_SAMPLE_COUNT_1_BIT; - img_info.tiling = VK_IMAGE_TILING_OPTIMAL; - img_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; - img_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - img_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - - VkImage image = VK_NULL_HANDLE; - err = vkCreateImage(device, &img_info, nullptr, &image); - ASSERT_EQ(err, VK_SUCCESS); - - // The interceptor should have called OnRecordResourceSize. - // Verify that the recorded size is > 0. - VkDeviceSize img_size = DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(image)); - EXPECT_GT(img_size, 0); - - // Create a buffer - VkBufferCreateInfo buf_info = {}; - buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - buf_info.size = 1024; - buf_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - buf_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - - VkBuffer buffer = VK_NULL_HANDLE; - err = vkCreateBuffer(device, &buf_info, nullptr, &buffer); - ASSERT_EQ(err, VK_SUCCESS); - - // The interceptor should have called OnRecordResourceSize. - VkDeviceSize buf_size = DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(buffer)); - EXPECT_GT(buf_size, 0); - - vkDestroyImage(device, image, nullptr); - vkDestroyBuffer(device, buffer, nullptr); - vkDestroyDevice(device, nullptr); -} - TEST_F(DeviceMemoryReportTests, ClusterClassificationFunctions) { TEST_DESCRIPTION("Test cluster classification for buffer and image usages with memory properties"); @@ -480,3 +423,79 @@ TEST_F(DeviceMemoryReportTests, StaticCounterTrackLookup) { EXPECT_EQ(dynamic_track.uuid, dynamic_track_again.uuid); EXPECT_EQ(dynamic_track_again.Serialize().counter().unit(), perfetto::protos::gen::CounterDescriptor::UNIT_SIZE_BYTES); } + +TEST_F(DeviceMemoryReportTests, ProactiveMemoryRequirementsQuery) { + TEST_DESCRIPTION("Test that the layer proactively queries memory requirements when creating images and buffers"); + + layer_test::VulkanInstanceBuilder inst_builder; + VkResult err = inst_builder.Init(kLayerName); + EXPECT_EQ(err, VK_SUCCESS); + + VkPhysicalDevice phys_dev = VK_NULL_HANDLE; + inst_builder.GetPhysicalDevice(&phys_dev); + if (phys_dev == VK_NULL_HANDLE) { + GTEST_SKIP() << "No physical device found, skipping test."; + } + + // Create a logical device + float queue_priority = 1.0f; + VkDeviceQueueCreateInfo queue_info = {}; + queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_info.queueFamilyIndex = 0; + queue_info.queueCount = 1; + queue_info.pQueuePriorities = &queue_priority; + + VkDeviceCreateInfo dev_info = {}; + dev_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + dev_info.queueCreateInfoCount = 1; + dev_info.pQueueCreateInfos = &queue_info; + dev_info.enabledExtensionCount = 0; + + VkDevice device = VK_NULL_HANDLE; + err = vkCreateDevice(phys_dev, &dev_info, nullptr, &device); + if (err != VK_SUCCESS) { + GTEST_SKIP() << "Failed to create logical device, skipping test."; + } + + // Create an image + VkImageCreateInfo img_info = {}; + img_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + img_info.imageType = VK_IMAGE_TYPE_2D; + img_info.format = VK_FORMAT_R8G8B8A8_UNORM; + img_info.extent = {64, 64, 1}; + img_info.mipLevels = 1; + img_info.arrayLayers = 1; + img_info.samples = VK_SAMPLE_COUNT_1_BIT; + img_info.tiling = VK_IMAGE_TILING_OPTIMAL; + img_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + img_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + img_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VkImage image = VK_NULL_HANDLE; + err = vkCreateImage(device, &img_info, nullptr, &image); + ASSERT_EQ(err, VK_SUCCESS); + + // The interceptor should have called OnRecordResourceSize. + // Verify that the recorded size is > 0. + VkDeviceSize img_size = DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(image)); + EXPECT_GT(img_size, 0); + + // Create a buffer + VkBufferCreateInfo buf_info = {}; + buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buf_info.size = 1024; + buf_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + buf_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VkBuffer buffer = VK_NULL_HANDLE; + err = vkCreateBuffer(device, &buf_info, nullptr, &buffer); + ASSERT_EQ(err, VK_SUCCESS); + + // The interceptor should have called OnRecordResourceSize. + VkDeviceSize buf_size = DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(buffer)); + EXPECT_GT(buf_size, 0); + + vkDestroyImage(device, image, nullptr); + vkDestroyBuffer(device, buffer, nullptr); + vkDestroyDevice(device, nullptr); +} From 75beb154b2db990a8a3bf5c2e97736d781725d1b Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Thu, 27 Aug 2026 12:18:15 +0000 Subject: [PATCH 13/16] Rename Perfetto category to VulkanDeviceMemoryReport --- layersvt/device_memory_report/device_memory_report.cpp | 4 ++-- layersvt/device_memory_report/device_memory_report_perfetto.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 72c66c54a8..9dfb3ede2c 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -166,13 +166,13 @@ static std::string GetUsageTrackName(bool is_driver, std::string_view usage) { void DeviceMemoryReport::AddCounterBytes(const std::string& track, uint64_t size) { uint64_t& bytes = usage_memory_bytes_[track]; bytes += size; - TRACE_COUNTER("vulkan", GetCounterTrack(track), bytes); + TRACE_COUNTER("VulkanDeviceMemoryReport", GetCounterTrack(track), bytes); } void DeviceMemoryReport::SubtractCounterBytes(const std::string& track, uint64_t size) { uint64_t& bytes = usage_memory_bytes_[track]; bytes = (bytes >= size) ? (bytes - size) : 0; - TRACE_COUNTER("vulkan", GetCounterTrack(track), bytes); + TRACE_COUNTER("VulkanDeviceMemoryReport", GetCounterTrack(track), bytes); } void DeviceMemoryReport::UpdateAllocationUnboundCounter(uint64_t memory_handle) { diff --git a/layersvt/device_memory_report/device_memory_report_perfetto.h b/layersvt/device_memory_report/device_memory_report_perfetto.h index fb43e7f929..b3a7645668 100644 --- a/layersvt/device_memory_report/device_memory_report_perfetto.h +++ b/layersvt/device_memory_report/device_memory_report_perfetto.h @@ -21,7 +21,7 @@ // Define categories used for Perfetto tracing in DeviceMemoryReport layer. PERFETTO_DEFINE_CATEGORIES( - perfetto::Category("vulkan").SetDescription("Vulkan Device Memory Report Counters") + perfetto::Category("VulkanDeviceMemoryReport").SetDescription("Vulkan Device Memory Report Counters") ); void InitializeDeviceMemoryReportPerfetto(); From 7a2c1b57932ef7d45b4cd1efe4b7307763141d7f Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Thu, 27 Aug 2026 12:18:41 +0000 Subject: [PATCH 14/16] Add comments explaining physical device trampoline in GetVkInstance --- .../device_memory_report/device_memory_report.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 9dfb3ede2c..e2516d71a6 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -37,19 +37,27 @@ VkInstance DeviceMemoryReport::GetVkInstance(VkPhysicalDevice phys_dev) { auto it = vk_instance_map_.find(phys_dev); if (it != vk_instance_map_.end()) return it->second; + // In Vulkan, physical device handles may be passed to layers either as unwrapped ICD handles + // or wrapped in a loader trampoline object (struct loader_phys_dev_tramp in loader.h). + // When physical devices are enumerated during instance creation, the handles stored in + // vk_instance_map_ may be the unwrapped handles. If vkCreateDevice later receives a loader + // trampoline handle, we inspect the trampoline structure to safely extract the underlying + // unwrapped VkPhysicalDevice handle. struct LoaderPhysDevTramp { void* disp; void* this_instance; - uint64_t magic; + uint64_t magic; // LOADER_PHYS_DEV_TRAMP_MAGIC VkPhysicalDevice unwrapped_phys_dev; }; - const uint64_t kPhysTrampMagicNumber = 0x10ADED020210ADEDUL; + const uint64_t kPhysTrampMagicNumber = 0x10ADED020210ADEDUL; // "LOADED" in hex speak auto* tramp = reinterpret_cast(phys_dev); if (tramp && tramp->magic == kPhysTrampMagicNumber) { auto it_unwrapped = vk_instance_map_.find(tramp->unwrapped_phys_dev); if (it_unwrapped != vk_instance_map_.end()) return it_unwrapped->second; } + // Fallback: If only a single VkInstance exists in the process, any valid physical device + // must belong to that instance. if (vk_instance_map_.size() == 1) { return vk_instance_map_.begin()->second; } From 89f3a5ab6c169365172c4d7863d92ab15a70806c Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Thu, 27 Aug 2026 12:18:59 +0000 Subject: [PATCH 15/16] Document total_size logic in SetAllocationMemoryProperties --- layersvt/device_memory_report/device_memory_report.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index e2516d71a6..1a6710eb57 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -73,6 +73,14 @@ void DeviceMemoryReport::SetAllocationMemoryProperties(uint64_t memory_handle, V std::lock_guard lock(counter_mutex_); auto& allocation = memory_allocations_[memory_handle]; allocation.mem_flags = property_flags; + // An allocation with total_size > 0 represents an active physical memory allocation that + // has already been recorded and contributed to Perfetto counter tracks. + // Changing its memory property flags (e.g., HOST_VISIBLE or LAZILY_ALLOCATED) can alter + // how its unbound memory headroom is classified into usage categories (such as staging + // vs general buffer or transient memoryless). We must update the unbound counter to move + // the reported bytes from the old usage track to the newly classified track. + // If total_size == 0, the allocation has not yet been instantiated (or is being configured + // prior to allocation), so no counter bytes have been emitted to Perfetto yet. if (allocation.total_size > 0) { UpdateAllocationUnboundCounter(memory_handle); } From 88e7ae21776a3662ee038fdc0955819651f8151b Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Thu, 27 Aug 2026 13:59:12 +0000 Subject: [PATCH 16/16] Remove eager physical device enumeration and simplify GetVkInstance --- .../device_memory_report.cpp | 19 ------------------- ...vice_memory_report_handwritten_functions.h | 16 +--------------- layersvt/test/test_devicememoryreport.cpp | 12 +++++------- 3 files changed, 6 insertions(+), 41 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 1a6710eb57..68fbd77cdf 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -37,25 +37,6 @@ VkInstance DeviceMemoryReport::GetVkInstance(VkPhysicalDevice phys_dev) { auto it = vk_instance_map_.find(phys_dev); if (it != vk_instance_map_.end()) return it->second; - // In Vulkan, physical device handles may be passed to layers either as unwrapped ICD handles - // or wrapped in a loader trampoline object (struct loader_phys_dev_tramp in loader.h). - // When physical devices are enumerated during instance creation, the handles stored in - // vk_instance_map_ may be the unwrapped handles. If vkCreateDevice later receives a loader - // trampoline handle, we inspect the trampoline structure to safely extract the underlying - // unwrapped VkPhysicalDevice handle. - struct LoaderPhysDevTramp { - void* disp; - void* this_instance; - uint64_t magic; // LOADER_PHYS_DEV_TRAMP_MAGIC - VkPhysicalDevice unwrapped_phys_dev; - }; - const uint64_t kPhysTrampMagicNumber = 0x10ADED020210ADEDUL; // "LOADED" in hex speak - auto* tramp = reinterpret_cast(phys_dev); - if (tramp && tramp->magic == kPhysTrampMagicNumber) { - auto it_unwrapped = vk_instance_map_.find(tramp->unwrapped_phys_dev); - if (it_unwrapped != vk_instance_map_.end()) return it_unwrapped->second; - } - // Fallback: If only a single VkInstance exists in the process, any valid physical device // must belong to that instance. if (vk_instance_map_.size() == 1) { diff --git a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h index c122838337..78dc41d8d4 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -28,7 +28,7 @@ // the VK_LAYER_GOOGLE_DeviceMemoryReport layer: // // Core infrastructure & lifecycle: -// - vkCreateInstance: Initializes Perfetto tracing, the instance dispatch table, and performs eager physical device enumeration. +// - vkCreateInstance: Initializes Perfetto tracing and the instance dispatch table. // - vkEnumeratePhysicalDevices / vkEnumeratePhysicalDeviceGroups: Tracks the mapping // between physical devices and instances to support dispatch table lookups. // - vkCreateDevice / vkDestroyDevice: Initializes/destroys device dispatch tables and @@ -77,20 +77,6 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* pCre VkResult result = fpCreateInstance(pCreateInfo, pAllocator, pInstance); if (result == VK_SUCCESS) { initInstanceTable(*pInstance, fpGetInstanceProcAddr); - - // Eagerly enumerate physical devices and map them to the instance. - PFN_vkEnumeratePhysicalDevices fpEnumeratePhysicalDevices = (PFN_vkEnumeratePhysicalDevices)fpGetInstanceProcAddr(*pInstance, "vkEnumeratePhysicalDevices"); - if (fpEnumeratePhysicalDevices) { - uint32_t count = 0; - fpEnumeratePhysicalDevices(*pInstance, &count, nullptr); - if (count > 0) { - std::vector devices(count); - fpEnumeratePhysicalDevices(*pInstance, &count, devices.data()); - for (uint32_t i = 0; i < count; ++i) { - DeviceMemoryReport::Get().SetVkInstance(devices[i], *pInstance); - } - } - } } return result; diff --git a/layersvt/test/test_devicememoryreport.cpp b/layersvt/test/test_devicememoryreport.cpp index 1d83467d41..6fc6c639b9 100644 --- a/layersvt/test/test_devicememoryreport.cpp +++ b/layersvt/test/test_devicememoryreport.cpp @@ -44,8 +44,8 @@ TEST_F(DeviceMemoryReportTests, InitLayer) { inst_builder.Reset(); } -TEST_F(DeviceMemoryReportTests, EagerPhysicalDeviceEnumeration) { - TEST_DESCRIPTION("Test eager physical device mapping on instance creation"); +TEST_F(DeviceMemoryReportTests, PhysicalDeviceEnumeration) { + TEST_DESCRIPTION("Test physical device to instance mapping"); layer_test::VulkanInstanceBuilder inst_builder; VkResult err = inst_builder.Init(kLayerName); @@ -54,11 +54,9 @@ TEST_F(DeviceMemoryReportTests, EagerPhysicalDeviceEnumeration) { VkInstance instance = inst_builder.GetInstance(); EXPECT_NE(instance, VK_NULL_HANDLE); - VkPhysicalDevice phys_dev = VK_NULL_HANDLE; - inst_builder.GetPhysicalDevice(&phys_dev); - if (phys_dev != VK_NULL_HANDLE) { - EXPECT_EQ(DeviceMemoryReport::Get().GetVkInstance(phys_dev), instance); - } + VkPhysicalDevice phys_dev = reinterpret_cast(0x1234); + DeviceMemoryReport::Get().SetVkInstance(phys_dev, instance); + EXPECT_EQ(DeviceMemoryReport::Get().GetVkInstance(phys_dev), instance); inst_builder.Reset(); }