Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
42 changes: 41 additions & 1 deletion layersvt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
22 changes: 22 additions & 0 deletions layersvt/device_memory_report/VkLayer_DeviceMemoryReport.def
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions layersvt/device_memory_report/VkLayer_DeviceMemoryReport.json.in
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
}
186 changes: 186 additions & 0 deletions layersvt/device_memory_report/device_memory_report.cpp
Original file line number Diff line number Diff line change
@@ -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 <string>
#include <string_view>

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

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

VkInstance DeviceMemoryReport::GetVkInstance(VkPhysicalDevice phys_dev) {
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> lock(counter_mutex_);
object_usage_map_[image_handle] = GetUsageCategoryName(true, usage);
}

void DeviceMemoryReport::OnCreateBuffer(uint64_t buffer_handle, VkBufferUsageFlags usage) {
std::lock_guard<std::mutex> lock(counter_mutex_);
object_usage_map_[buffer_handle] = GetUsageCategoryName(false, usage);
}

void DeviceMemoryReport::OnDestroyObject(uint64_t object_handle) {
std::lock_guard<std::mutex> lock(counter_mutex_);
object_usage_map_.erase(object_handle);
}

void DeviceMemoryReport::OnMemoryReportEvent(const VkDeviceMemoryReportCallbackDataEXT* pCallbackData) {
std::lock_guard<std::mutex> 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<std::mutex> lock(counter_mutex_);
has_callback_map_[device] = has_callback;
}

void DeviceMemoryReport::OnAllocateMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize size) {
std::lock_guard<std::mutex> lock(counter_mutex_);
if (has_callback_map_[device]) return;
memory_size_map_[memory] = size;

uint64_t handle = reinterpret_cast<uint64_t>(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<std::mutex> 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<uint64_t>(memory);
RemoveUsageFromHandle(handle, size);
object_alloc_size_map_.erase(handle);
object_is_driver_map_.erase(handle);
}
}
Loading