From b46ff3125b21472161fc9c4e6aa8499f3c5a94fd Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Mon, 14 Sep 2026 08:05:07 +0000 Subject: [PATCH 01/12] device_memory_report: harden bind paths and query missing resource sizes Guard the vkBind*Memory entry points against absent dispatch table entries instead of calling through unconditionally, returning VK_ERROR_EXTENSION_NOT_PRESENT when the underlying function was not loaded. Proactively query vkGet{Buffer,Image}MemoryRequirements at bind time for resources whose size has not been recorded yet, so suballocations are sized correctly even when the application never triggered a size recording path. Fall back to the owning allocation's total size in BindResourceMemory when a resource has no recorded size, and track resources bound without a prior create call, so these bindings are no longer silently dropped. --- .../device_memory_report.cpp | 10 +++- ...vice_memory_report_handwritten_functions.h | 52 ++++++++++++++++--- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index 68fbd77cdf..a27586a6a0 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -258,13 +258,21 @@ void DeviceMemoryReport::RemoveResourceBinding(uint64_t resource_handle) { 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(resource_handle); - if (res_it == resources_.end() || res_it->second.size == 0) return; + if (res_it == resources_.end()) { + auto& new_res = resources_[resource_handle]; + new_res.is_image = false; + res_it = resources_.find(resource_handle); + } // If the same resource handle is passed more than once, remove stale bindings first. RemoveResourceBinding(resource_handle); auto& allocation = memory_allocations_[memory_handle]; VkDeviceSize res_size = res_it->second.size; + if (res_size == 0 && allocation.total_size > 0) { + res_size = allocation.total_size; + } + if (res_size == 0) return; 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 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 78dc41d8d4..f63f415223 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -258,7 +258,16 @@ EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( // 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) { - VkResult result = device_dispatch_table(device)->BindBufferMemory(device, buffer, memory, memoryOffset); + auto* table = device_dispatch_table(device); + if (!table || !table->BindBufferMemory) return VK_ERROR_EXTENSION_NOT_PRESENT; + if (buffer != VK_NULL_HANDLE && DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(buffer)) == 0) { + if (table->GetBufferMemoryRequirements) { + VkMemoryRequirements mem_reqs; + table->GetBufferMemoryRequirements(device, buffer, &mem_reqs); + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(buffer), mem_reqs.size); + } + } + VkResult result = table->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); } @@ -267,16 +276,32 @@ VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device, VkBuffer buff // 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) { - VkResult result = device_dispatch_table(device)->BindImageMemory(device, image, memory, memoryOffset); + auto* table = device_dispatch_table(device); + if (!table || !table->BindImageMemory) return VK_ERROR_EXTENSION_NOT_PRESENT; + if (image != VK_NULL_HANDLE && DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(image)) == 0) { + if (table->GetImageMemoryRequirements) { + VkMemoryRequirements mem_reqs; + table->GetImageMemoryRequirements(device, image, &mem_reqs); + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(image), mem_reqs.size); + } + } + VkResult result = table->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); } return result; } -static void RecordBufferBindings(uint32_t bindInfoCount, const VkBindBufferMemoryInfo* pBindInfos) { +static void RecordBufferBindings(VkDevice device, 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) { + if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(pBindInfos[i].buffer)) == 0) { + if (device_dispatch_table(device)->GetBufferMemoryRequirements) { + VkMemoryRequirements mem_reqs; + device_dispatch_table(device)->GetBufferMemoryRequirements(device, pBindInfos[i].buffer, &mem_reqs); + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(pBindInfos[i].buffer), mem_reqs.size); + } + } DeviceMemoryReport::Get().OnBindBufferMemory(reinterpret_cast(pBindInfos[i].buffer), reinterpret_cast(pBindInfos[i].memory), pBindInfos[i].memoryOffset); } } @@ -284,25 +309,34 @@ static void RecordBufferBindings(uint32_t bindInfoCount, const VkBindBufferMemor // 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) { + if (!device_dispatch_table(device)->BindBufferMemory2) return VK_ERROR_EXTENSION_NOT_PRESENT; VkResult result = device_dispatch_table(device)->BindBufferMemory2(device, bindInfoCount, pBindInfos); if (result == VK_SUCCESS && pBindInfos != nullptr) { - RecordBufferBindings(bindInfoCount, pBindInfos); + RecordBufferBindings(device, 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) { + if (!device_dispatch_table(device)->BindBufferMemory2KHR) return VK_ERROR_EXTENSION_NOT_PRESENT; VkResult result = device_dispatch_table(device)->BindBufferMemory2KHR(device, bindInfoCount, pBindInfos); if (result == VK_SUCCESS && pBindInfos != nullptr) { - RecordBufferBindings(bindInfoCount, pBindInfos); + RecordBufferBindings(device, bindInfoCount, pBindInfos); } return result; } -static void RecordImageBinds(uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos) { +static void RecordImageBinds(VkDevice device, 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) { + if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(pBindInfos[i].image)) == 0) { + if (device_dispatch_table(device)->GetImageMemoryRequirements) { + VkMemoryRequirements mem_reqs; + device_dispatch_table(device)->GetImageMemoryRequirements(device, pBindInfos[i].image, &mem_reqs); + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(pBindInfos[i].image), mem_reqs.size); + } + } DeviceMemoryReport::Get().OnBindImageMemory(reinterpret_cast(pBindInfos[i].image), reinterpret_cast(pBindInfos[i].memory), pBindInfos[i].memoryOffset); } } @@ -310,18 +344,20 @@ static void RecordImageBinds(uint32_t bindInfoCount, const VkBindImageMemoryInfo // 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) { + if (!device_dispatch_table(device)->BindImageMemory2) return VK_ERROR_EXTENSION_NOT_PRESENT; VkResult result = device_dispatch_table(device)->BindImageMemory2(device, bindInfoCount, pBindInfos); if (result == VK_SUCCESS && pBindInfos != nullptr) { - RecordImageBinds(bindInfoCount, pBindInfos); + RecordImageBinds(device, 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) { + if (!device_dispatch_table(device)->BindImageMemory2KHR) return VK_ERROR_EXTENSION_NOT_PRESENT; VkResult result = device_dispatch_table(device)->BindImageMemory2KHR(device, bindInfoCount, pBindInfos); if (result == VK_SUCCESS && pBindInfos != nullptr) { - RecordImageBinds(bindInfoCount, pBindInfos); + RecordImageBinds(device, bindInfoCount, pBindInfos); } return result; } From 26f8a600021c85876252627093ff1b7fa015c9f2 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Mon, 14 Sep 2026 11:04:41 +0000 Subject: [PATCH 02/12] device_memory_report: unit test the memory bind entry points Add test_DeviceMemoryReport_dispatch, a test binary that links the layer sources directly and drives its vk* entry points against a stub dispatch table, so the interception logic can be exercised without a Vulkan ICD. The Vulkan loader is deliberately not linked: the layer defines the entry points itself, and the existing test_DeviceMemoryReport_layer binary loads the layer as a shared module, so the layer's DeviceMemoryReport singleton is a different instance from the one it can inspect. Covers the bind path hardening: - vkBindBufferMemory / vkBindImageMemory and the four vkBind*Memory2[KHR] entry points query vkGet{Buffer,Image}MemoryRequirements for resources whose size has not been recorded yet, so those bindings are sized instead of dropped. - Sizes that are already known are neither re-queried nor overwritten. - All six bind entry points return VK_ERROR_EXTENSION_NOT_PRESENT when the driver below the layer does not provide the command, rather than calling through a null dispatch table entry. Against the previous implementation the size tests report 0 bytes and the missing dispatch entry test crashes with SIGSEGV. --- layersvt/test/CMakeLists.txt | 25 ++ .../test/test_devicememoryreport_dispatch.cpp | 264 ++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 layersvt/test/test_devicememoryreport_dispatch.cpp diff --git a/layersvt/test/CMakeLists.txt b/layersvt/test/CMakeLists.txt index 6f7bdbd646..cd635acb9e 100644 --- a/layersvt/test/CMakeLists.txt +++ b/layersvt/test/CMakeLists.txt @@ -61,3 +61,28 @@ foreach(test_item ${LAYER_TEST_FILES}) LayerTest(${test_item}) endforeach() + +# Unit tests for the DeviceMemoryReport layer's Vulkan entry points. The layer sources are linked +# straight into the test binary and driven against a stub dispatch table, so no Vulkan ICD is +# needed. The Vulkan loader is deliberately not linked here: the layer defines the vk* entry points +# itself, and linking the loader as well would make it ambiguous which of the two is being called. +if (TARGET VkLayer_DeviceMemoryReport) + add_executable(test_DeviceMemoryReport_dispatch + test_devicememoryreport_dispatch.cpp + ../device_memory_report/device_memory_report_handwritten_dispatch.cpp + ../device_memory_report/device_memory_report.cpp + ../device_memory_report/device_memory_report_perfetto.cpp + ../perfetto/perfetto.cc + ../vk_layer_table.cpp) + target_include_directories(test_DeviceMemoryReport_dispatch PRIVATE .. ../device_memory_report) + target_link_libraries(test_DeviceMemoryReport_dispatch + Vulkan::Headers Vulkan::UtilityHeaders GTest::gtest GTest::gtest_main ${CMAKE_DL_LIBS}) + target_compile_definitions(test_DeviceMemoryReport_dispatch PRIVATE VK_ENABLE_BETA_EXTENSIONS) + add_test(NAME test_DeviceMemoryReport_dispatch COMMAND test_DeviceMemoryReport_dispatch) + set_target_properties(test_DeviceMemoryReport_dispatch PROPERTIES FOLDER "layers/DeviceMemoryReport/Test") + + if(WIN32 AND (QT_TARGET_TYPE STREQUAL STATIC_LIBRARY)) + set_property(TARGET test_DeviceMemoryReport_dispatch PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() +endif() + diff --git a/layersvt/test/test_devicememoryreport_dispatch.cpp b/layersvt/test/test_devicememoryreport_dispatch.cpp new file mode 100644 index 0000000000..f8cbbf9dc2 --- /dev/null +++ b/layersvt/test/test_devicememoryreport_dispatch.cpp @@ -0,0 +1,264 @@ +/* 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. + */ + +// Unit tests for the memory binding entry points of the DeviceMemoryReport layer. +// +// The layer's vk* entry points are linked directly into this test binary (the Vulkan loader is +// deliberately not linked), and every fake VkDevice is given a dispatch table built from the stub +// driver below. That makes it possible to exercise the layer's interception logic without a real +// Vulkan implementation, including the case where the driver underneath the layer does not +// implement an entry point at all and its dispatch table slot is therefore null. + +#include "device_memory_report.h" +#include "vk_layer_table.h" + +#include + +#include + +#include +#include +#include + +namespace { + +// Sizes returned by the stub driver's memory requirement queries. +VkDeviceSize g_buffer_requirements_size = 0; +VkDeviceSize g_image_requirements_size = 0; + +// Number of times the stub driver's memory requirement queries were called. +int g_buffer_requirements_queries = 0; +int g_image_requirements_queries = 0; + +// Entry points the stub driver does not implement. Their dispatch table slots stay null, which is +// what the layer sees when the driver (or an ICD without the relevant extension) lacks a command. +std::set g_unimplemented; + +VKAPI_ATTR VkResult VKAPI_CALL StubBindBufferMemory(VkDevice, VkBuffer, VkDeviceMemory, VkDeviceSize) { return VK_SUCCESS; } + +VKAPI_ATTR VkResult VKAPI_CALL StubBindImageMemory(VkDevice, VkImage, VkDeviceMemory, VkDeviceSize) { return VK_SUCCESS; } + +VKAPI_ATTR VkResult VKAPI_CALL StubBindBufferMemory2(VkDevice, uint32_t, const VkBindBufferMemoryInfo*) { return VK_SUCCESS; } + +VKAPI_ATTR VkResult VKAPI_CALL StubBindImageMemory2(VkDevice, uint32_t, const VkBindImageMemoryInfo*) { return VK_SUCCESS; } + +VKAPI_ATTR void VKAPI_CALL StubGetBufferMemoryRequirements(VkDevice, VkBuffer, VkMemoryRequirements* pMemoryRequirements) { + ++g_buffer_requirements_queries; + pMemoryRequirements->size = g_buffer_requirements_size; + pMemoryRequirements->alignment = 256; + pMemoryRequirements->memoryTypeBits = 1; +} + +VKAPI_ATTR void VKAPI_CALL StubGetImageMemoryRequirements(VkDevice, VkImage, VkMemoryRequirements* pMemoryRequirements) { + ++g_image_requirements_queries; + pMemoryRequirements->size = g_image_requirements_size; + pMemoryRequirements->alignment = 256; + pMemoryRequirements->memoryTypeBits = 1; +} + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL StubGetDeviceProcAddr(VkDevice, const char* pName) { + if (pName == nullptr) return nullptr; + const std::string name(pName); + if (g_unimplemented.count(name) != 0) return nullptr; + + if (name == "vkBindBufferMemory") return reinterpret_cast(StubBindBufferMemory); + if (name == "vkBindImageMemory") return reinterpret_cast(StubBindImageMemory); + if (name == "vkBindBufferMemory2" || name == "vkBindBufferMemory2KHR") { + return reinterpret_cast(StubBindBufferMemory2); + } + if (name == "vkBindImageMemory2" || name == "vkBindImageMemory2KHR") { + return reinterpret_cast(StubBindImageMemory2); + } + if (name == "vkGetBufferMemoryRequirements") return reinterpret_cast(StubGetBufferMemoryRequirements); + if (name == "vkGetImageMemoryRequirements") return reinterpret_cast(StubGetImageMemoryRequirements); + + // Everything else is not implemented by the stub driver. + return nullptr; +} + +// A dispatchable Vulkan object begins with a pointer to its dispatch table, and the layer uses that +// pointer as the key into its own dispatch table map. This fake device therefore just points at +// itself, which also gives every instance a distinct key. +class FakeDevice { + public: + FakeDevice() { + dispatch_key_ = this; + initDeviceTable(handle(), StubGetDeviceProcAddr); + } + + ~FakeDevice() { destroy_device_dispatch_table(get_dispatch_key(handle())); } + + FakeDevice(const FakeDevice&) = delete; + FakeDevice& operator=(const FakeDevice&) = delete; + + VkDevice handle() { return reinterpret_cast(this); } + + private: + void* dispatch_key_ = nullptr; +}; + +template +HandleType MakeHandle(uintptr_t value) { + return reinterpret_cast(value); +} + +template +uint64_t AsObjectHandle(HandleType handle) { + return reinterpret_cast(handle); +} + +class DeviceMemoryReportDispatchTests : public ::testing::Test { + protected: + void SetUp() override { + g_buffer_requirements_size = 0; + g_image_requirements_size = 0; + g_buffer_requirements_queries = 0; + g_image_requirements_queries = 0; + g_unimplemented.clear(); + } +}; + +TEST_F(DeviceMemoryReportDispatchTests, BindBufferMemoryQueriesUnknownResourceSize) { + // A buffer whose size was never recorded (for example when the application created it before + // the layer was active) must have its size queried from the driver at bind time, otherwise the + // suballocation cannot be sized and the binding is dropped. + FakeDevice device; + VkBuffer buffer = MakeHandle(0xB1000); + VkDeviceMemory memory = MakeHandle(0xB1001); + + ASSERT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(buffer)), 0u); + + g_buffer_requirements_size = 4096; + EXPECT_EQ(vkBindBufferMemory(device.handle(), buffer, memory, 0), VK_SUCCESS); + + EXPECT_EQ(g_buffer_requirements_queries, 1); + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(buffer)), 4096u); +} + +TEST_F(DeviceMemoryReportDispatchTests, BindImageMemoryQueriesUnknownResourceSize) { + FakeDevice device; + VkImage image = MakeHandle(0xB2000); + VkDeviceMemory memory = MakeHandle(0xB2001); + + ASSERT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 0u); + + g_image_requirements_size = 65536; + EXPECT_EQ(vkBindImageMemory(device.handle(), image, memory, 0), VK_SUCCESS); + + EXPECT_EQ(g_image_requirements_queries, 1); + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 65536u); +} + +TEST_F(DeviceMemoryReportDispatchTests, BindMemory2QueriesUnknownResourceSizes) { + FakeDevice device; + VkDeviceMemory memory = MakeHandle(0xB3000); + + VkBuffer buffer = MakeHandle(0xB3001); + VkBuffer buffer_khr = MakeHandle(0xB3002); + VkImage image = MakeHandle(0xB3003); + VkImage image_khr = MakeHandle(0xB3004); + + g_buffer_requirements_size = 1024; + g_image_requirements_size = 8192; + + VkBindBufferMemoryInfo buffer_bind = {}; + buffer_bind.sType = VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO; + buffer_bind.buffer = buffer; + buffer_bind.memory = memory; + buffer_bind.memoryOffset = 0; + EXPECT_EQ(vkBindBufferMemory2(device.handle(), 1, &buffer_bind), VK_SUCCESS); + + buffer_bind.buffer = buffer_khr; + buffer_bind.memoryOffset = 1024; + EXPECT_EQ(vkBindBufferMemory2KHR(device.handle(), 1, &buffer_bind), VK_SUCCESS); + + VkBindImageMemoryInfo image_bind = {}; + image_bind.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO; + image_bind.image = image; + image_bind.memory = memory; + image_bind.memoryOffset = 8192; + EXPECT_EQ(vkBindImageMemory2(device.handle(), 1, &image_bind), VK_SUCCESS); + + image_bind.image = image_khr; + image_bind.memoryOffset = 16384; + EXPECT_EQ(vkBindImageMemory2KHR(device.handle(), 1, &image_bind), VK_SUCCESS); + + EXPECT_EQ(g_buffer_requirements_queries, 2); + EXPECT_EQ(g_image_requirements_queries, 2); + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(buffer)), 1024u); + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(buffer_khr)), 1024u); + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 8192u); + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image_khr)), 8192u); +} + +TEST_F(DeviceMemoryReportDispatchTests, BindMemoryKeepsAlreadyRecordedSizes) { + // Sizes that are already known must not be re-queried or overwritten at bind time. + FakeDevice device; + VkBuffer buffer = MakeHandle(0xB4000); + VkImage image = MakeHandle(0xB4001); + VkDeviceMemory memory = MakeHandle(0xB4002); + + DeviceMemoryReport::Get().OnRecordResourceSize(AsObjectHandle(buffer), 2048); + DeviceMemoryReport::Get().OnRecordResourceSize(AsObjectHandle(image), 4096); + + g_buffer_requirements_size = 999; + g_image_requirements_size = 999; + + EXPECT_EQ(vkBindBufferMemory(device.handle(), buffer, memory, 0), VK_SUCCESS); + EXPECT_EQ(vkBindImageMemory(device.handle(), image, memory, 2048), VK_SUCCESS); + + EXPECT_EQ(g_buffer_requirements_queries, 0); + EXPECT_EQ(g_image_requirements_queries, 0); + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(buffer)), 2048u); + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 4096u); +} + +TEST_F(DeviceMemoryReportDispatchTests, BindMemoryReportsMissingDispatchEntries) { + // When the driver below the layer does not provide a bind entry point, the layer must report + // that rather than calling through a null dispatch table entry. + g_unimplemented = {"vkBindBufferMemory", "vkBindImageMemory", "vkBindBufferMemory2", + "vkBindImageMemory2", "vkBindBufferMemory2KHR", "vkBindImageMemory2KHR"}; + FakeDevice device; + + VkBuffer buffer = MakeHandle(0xB5000); + VkImage image = MakeHandle(0xB5001); + VkDeviceMemory memory = MakeHandle(0xB5002); + + g_buffer_requirements_size = 4096; + g_image_requirements_size = 4096; + + EXPECT_EQ(vkBindBufferMemory(device.handle(), buffer, memory, 0), VK_ERROR_EXTENSION_NOT_PRESENT); + EXPECT_EQ(vkBindImageMemory(device.handle(), image, memory, 0), VK_ERROR_EXTENSION_NOT_PRESENT); + + VkBindBufferMemoryInfo buffer_bind = {}; + buffer_bind.sType = VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO; + buffer_bind.buffer = buffer; + buffer_bind.memory = memory; + EXPECT_EQ(vkBindBufferMemory2(device.handle(), 1, &buffer_bind), VK_ERROR_EXTENSION_NOT_PRESENT); + EXPECT_EQ(vkBindBufferMemory2KHR(device.handle(), 1, &buffer_bind), VK_ERROR_EXTENSION_NOT_PRESENT); + + VkBindImageMemoryInfo image_bind = {}; + image_bind.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO; + image_bind.image = image; + image_bind.memory = memory; + EXPECT_EQ(vkBindImageMemory2(device.handle(), 1, &image_bind), VK_ERROR_EXTENSION_NOT_PRESENT); + EXPECT_EQ(vkBindImageMemory2KHR(device.handle(), 1, &image_bind), VK_ERROR_EXTENSION_NOT_PRESENT); + + // Failed bindings must not leave any tracking behind. + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(buffer)), 0u); + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 0u); +} + +} // namespace From 38d22ad909ca77c245115b9ec3205469ebcbb8e0 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 15 Sep 2026 12:51:50 +0000 Subject: [PATCH 03/12] Tests for BindResourceMemory early-out condition. --- .../device_memory_report.cpp | 6 + .../device_memory_report.h | 7 ++ .../test/test_devicememoryreport_dispatch.cpp | 117 ++++++++++++++++-- 3 files changed, 120 insertions(+), 10 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index a27586a6a0..c9e5fb1f57 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -320,6 +320,12 @@ VkDeviceSize DeviceMemoryReport::GetRecordedResourceSize(uint64_t resource_handl return it != resources_.end() ? it->second.size : 0; } +uint64_t DeviceMemoryReport::GetUsageMemoryBytes(const std::string& track) { + std::lock_guard lock(counter_mutex_); + auto it = usage_memory_bytes_.find(track); + return it != usage_memory_bytes_.end() ? it->second : 0; +} + void DeviceMemoryReport::OnCreateImage(uint64_t image_handle, VkImageUsageFlags usage) { std::lock_guard lock(counter_mutex_); auto& res = resources_[image_handle]; diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index 22514516aa..bfcd096805 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -170,6 +170,13 @@ class DeviceMemoryReport { */ VkDeviceSize GetRecordedResourceSize(uint64_t resource_handle); + /** + * @brief Retrieves the tracked memory bytes for a specific usage track (for testing). + * @param track The usage track name. + * @return Tracked memory usage in bytes, or 0 if not present. + */ + uint64_t GetUsageMemoryBytes(const std::string& track); + /** * @brief Tracks creation of a Vulkan image and its usage flags. * @param image_handle The 64-bit handle of the Vulkan image. diff --git a/layersvt/test/test_devicememoryreport_dispatch.cpp b/layersvt/test/test_devicememoryreport_dispatch.cpp index f8cbbf9dc2..ce3d4afa22 100644 --- a/layersvt/test/test_devicememoryreport_dispatch.cpp +++ b/layersvt/test/test_devicememoryreport_dispatch.cpp @@ -46,6 +46,36 @@ int g_image_requirements_queries = 0; // what the layer sees when the driver (or an ICD without the relevant extension) lacks a command. std::set g_unimplemented; +template +HandleType MakeHandle(uintptr_t value) { + return reinterpret_cast(value); +} + +template +uint64_t AsObjectHandle(HandleType handle) { + return reinterpret_cast(handle); +} + +uintptr_t g_next_handle = 0x10000; + +VKAPI_ATTR VkResult VKAPI_CALL StubCreateImage(VkDevice, const VkImageCreateInfo*, const VkAllocationCallbacks*, VkImage* pImage) { + if (pImage != nullptr) { + *pImage = MakeHandle(++g_next_handle); + } + return VK_SUCCESS; +} + +VKAPI_ATTR void VKAPI_CALL StubDestroyImage(VkDevice, VkImage, const VkAllocationCallbacks*) {} + +VKAPI_ATTR VkResult VKAPI_CALL StubAllocateMemory(VkDevice, const VkMemoryAllocateInfo*, const VkAllocationCallbacks*, VkDeviceMemory* pMemory) { + if (pMemory != nullptr) { + *pMemory = MakeHandle(++g_next_handle); + } + return VK_SUCCESS; +} + +VKAPI_ATTR void VKAPI_CALL StubFreeMemory(VkDevice, VkDeviceMemory, const VkAllocationCallbacks*) {} + VKAPI_ATTR VkResult VKAPI_CALL StubBindBufferMemory(VkDevice, VkBuffer, VkDeviceMemory, VkDeviceSize) { return VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL StubBindImageMemory(VkDevice, VkImage, VkDeviceMemory, VkDeviceSize) { return VK_SUCCESS; } @@ -73,6 +103,10 @@ VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL StubGetDeviceProcAddr(VkDevice, const c const std::string name(pName); if (g_unimplemented.count(name) != 0) return nullptr; + if (name == "vkCreateImage") return reinterpret_cast(StubCreateImage); + if (name == "vkDestroyImage") return reinterpret_cast(StubDestroyImage); + if (name == "vkAllocateMemory") return reinterpret_cast(StubAllocateMemory); + if (name == "vkFreeMemory") return reinterpret_cast(StubFreeMemory); if (name == "vkBindBufferMemory") return reinterpret_cast(StubBindBufferMemory); if (name == "vkBindImageMemory") return reinterpret_cast(StubBindImageMemory); if (name == "vkBindBufferMemory2" || name == "vkBindBufferMemory2KHR") { @@ -109,16 +143,6 @@ class FakeDevice { void* dispatch_key_ = nullptr; }; -template -HandleType MakeHandle(uintptr_t value) { - return reinterpret_cast(value); -} - -template -uint64_t AsObjectHandle(HandleType handle) { - return reinterpret_cast(handle); -} - class DeviceMemoryReportDispatchTests : public ::testing::Test { protected: void SetUp() override { @@ -261,4 +285,77 @@ TEST_F(DeviceMemoryReportDispatchTests, BindMemoryReportsMissingDispatchEntries) EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 0u); } +TEST_F(DeviceMemoryReportDispatchTests, DisjointImageFallsBackToAllocationSize) { + // When an image is created with VK_IMAGE_CREATE_DISJOINT_BIT (for example, when an app + // queries requirements upfront via vkGetDeviceImageMemoryRequirements and skips + // post-creation vkGetImageMemoryRequirements2), the layer skips vkGetImageMemoryRequirements + // at creation time. + // + // At bind time, its recorded size is 0. The layer must fall back to the owning allocation's + // total size rather than dropping the suballocation from tracking. + FakeDevice device; + + // Simulate stub driver returning 0 for legacy non-plane requirements on disjoint image. + g_image_requirements_size = 0; + + VkImageCreateInfo image_ci = {}; + image_ci.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + image_ci.flags = VK_IMAGE_CREATE_DISJOINT_BIT; + image_ci.imageType = VK_IMAGE_TYPE_2D; + image_ci.format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM; + image_ci.extent = {1920, 1080, 1}; + image_ci.mipLevels = 1; + image_ci.arrayLayers = 1; + image_ci.samples = VK_SAMPLE_COUNT_1_BIT; + image_ci.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + + VkImage image = VK_NULL_HANDLE; + EXPECT_EQ(vkCreateImage(device.handle(), &image_ci, nullptr, &image), VK_SUCCESS); + EXPECT_NE(image, VK_NULL_HANDLE); + + // Image size was not recorded at creation time because it is disjoint. + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 0u); + + const VkDeviceSize kAllocSize = 65536; + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = kAllocSize; + alloc_info.memoryTypeIndex = 0; + + VkDeviceMemory memory = VK_NULL_HANDLE; + EXPECT_EQ(vkAllocateMemory(device.handle(), &alloc_info, nullptr, &memory), VK_SUCCESS); + EXPECT_NE(memory, VK_NULL_HANDLE); + + // Initial state before binding: the entire allocation is unbound headroom. + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageMemoryBytes("vulkan.mem.app.usage.unbound_memory"), kAllocSize); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageMemoryBytes("vulkan.mem.app.usage.static_texture"), 0u); + + // Bind disjoint image plane memory via vkBindImageMemory2 without querying vkGetImageMemoryRequirements2. + VkBindImagePlaneMemoryInfo plane_info = {}; + plane_info.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO; + plane_info.planeAspect = VK_IMAGE_ASPECT_PLANE_0_BIT; + + VkBindImageMemoryInfo bind_info = {}; + bind_info.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO; + bind_info.pNext = &plane_info; + bind_info.image = image; + bind_info.memory = memory; + bind_info.memoryOffset = 0; + + EXPECT_EQ(vkBindImageMemory2(device.handle(), 1, &bind_info), VK_SUCCESS); + + // After hardening: + // - Texture counter receives the suballocation sized to kAllocSize. + // - Unbound memory drops to 0. + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageMemoryBytes("vulkan.mem.app.usage.static_texture"), kAllocSize); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageMemoryBytes("vulkan.mem.app.usage.unbound_memory"), 0u); + + // Clean up + vkDestroyImage(device.handle(), image, nullptr); + vkFreeMemory(device.handle(), memory, nullptr); + + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageMemoryBytes("vulkan.mem.app.usage.static_texture"), 0u); + EXPECT_EQ(DeviceMemoryReport::Get().GetUsageMemoryBytes("vulkan.mem.app.usage.unbound_memory"), 0u); +} + } // namespace From 69db630a053ce4aa736e41a6f34c847ab3b87cf6 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 15 Sep 2026 12:54:22 +0000 Subject: [PATCH 04/12] Replace handle null checks with assertions --- ...vice_memory_report_handwritten_functions.h | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 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 f63f415223..ce2a62fa68 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -260,7 +260,9 @@ EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset) { auto* table = device_dispatch_table(device); if (!table || !table->BindBufferMemory) return VK_ERROR_EXTENSION_NOT_PRESENT; - if (buffer != VK_NULL_HANDLE && DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(buffer)) == 0) { + assert(buffer != VK_NULL_HANDLE); + assert(memory != VK_NULL_HANDLE); + if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(buffer)) == 0) { if (table->GetBufferMemoryRequirements) { VkMemoryRequirements mem_reqs; table->GetBufferMemoryRequirements(device, buffer, &mem_reqs); @@ -268,7 +270,7 @@ VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device, VkBuffer buff } } VkResult result = table->BindBufferMemory(device, buffer, memory, memoryOffset); - if (result == VK_SUCCESS && buffer != VK_NULL_HANDLE && memory != VK_NULL_HANDLE) { + if (result == VK_SUCCESS) { DeviceMemoryReport::Get().OnBindBufferMemory(reinterpret_cast(buffer), reinterpret_cast(memory), memoryOffset); } return result; @@ -278,7 +280,9 @@ VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device, VkBuffer buff VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset) { auto* table = device_dispatch_table(device); if (!table || !table->BindImageMemory) return VK_ERROR_EXTENSION_NOT_PRESENT; - if (image != VK_NULL_HANDLE && DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(image)) == 0) { + assert(image != VK_NULL_HANDLE); + assert(memory != VK_NULL_HANDLE); + if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(image)) == 0) { if (table->GetImageMemoryRequirements) { VkMemoryRequirements mem_reqs; table->GetImageMemoryRequirements(device, image, &mem_reqs); @@ -286,7 +290,7 @@ VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory(VkDevice device, VkImage image, } } VkResult result = table->BindImageMemory(device, image, memory, memoryOffset); - if (result == VK_SUCCESS && image != VK_NULL_HANDLE && memory != VK_NULL_HANDLE) { + if (result == VK_SUCCESS) { DeviceMemoryReport::Get().OnBindImageMemory(reinterpret_cast(image), reinterpret_cast(memory), memoryOffset); } return result; @@ -294,16 +298,16 @@ VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory(VkDevice device, VkImage image, static void RecordBufferBindings(VkDevice device, 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) { - if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(pBindInfos[i].buffer)) == 0) { - if (device_dispatch_table(device)->GetBufferMemoryRequirements) { - VkMemoryRequirements mem_reqs; - device_dispatch_table(device)->GetBufferMemoryRequirements(device, pBindInfos[i].buffer, &mem_reqs); - DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(pBindInfos[i].buffer), mem_reqs.size); - } + assert(pBindInfos[i].buffer != VK_NULL_HANDLE); + assert(pBindInfos[i].memory != VK_NULL_HANDLE); + if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(pBindInfos[i].buffer)) == 0) { + if (device_dispatch_table(device)->GetBufferMemoryRequirements) { + VkMemoryRequirements mem_reqs; + device_dispatch_table(device)->GetBufferMemoryRequirements(device, pBindInfos[i].buffer, &mem_reqs); + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(pBindInfos[i].buffer), mem_reqs.size); } - DeviceMemoryReport::Get().OnBindBufferMemory(reinterpret_cast(pBindInfos[i].buffer), reinterpret_cast(pBindInfos[i].memory), pBindInfos[i].memoryOffset); } + DeviceMemoryReport::Get().OnBindBufferMemory(reinterpret_cast(pBindInfos[i].buffer), reinterpret_cast(pBindInfos[i].memory), pBindInfos[i].memoryOffset); } } @@ -329,16 +333,16 @@ VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory2KHR(VkDevice device, uint32_t static void RecordImageBinds(VkDevice device, 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) { - if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(pBindInfos[i].image)) == 0) { - if (device_dispatch_table(device)->GetImageMemoryRequirements) { - VkMemoryRequirements mem_reqs; - device_dispatch_table(device)->GetImageMemoryRequirements(device, pBindInfos[i].image, &mem_reqs); - DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(pBindInfos[i].image), mem_reqs.size); - } + assert(pBindInfos[i].image != VK_NULL_HANDLE); + assert(pBindInfos[i].memory != VK_NULL_HANDLE); + if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(pBindInfos[i].image)) == 0) { + if (device_dispatch_table(device)->GetImageMemoryRequirements) { + VkMemoryRequirements mem_reqs; + device_dispatch_table(device)->GetImageMemoryRequirements(device, pBindInfos[i].image, &mem_reqs); + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(pBindInfos[i].image), mem_reqs.size); } - DeviceMemoryReport::Get().OnBindImageMemory(reinterpret_cast(pBindInfos[i].image), reinterpret_cast(pBindInfos[i].memory), pBindInfos[i].memoryOffset); } + DeviceMemoryReport::Get().OnBindImageMemory(reinterpret_cast(pBindInfos[i].image), reinterpret_cast(pBindInfos[i].memory), pBindInfos[i].memoryOffset); } } From 68c519b36cb1c0f073ed264d7ff5c8c4b0c3b45c Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 15 Sep 2026 13:57:57 +0000 Subject: [PATCH 05/12] device_memory_report: preserve lifecycle invariants in BindResourceMemory Revert dummy resource synthesis and allocation size fallback, returning early when a resource was not previously created or has zero recorded size. --- .../device_memory_report.cpp | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index c9e5fb1f57..a5652a1cf7 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -258,21 +258,13 @@ void DeviceMemoryReport::RemoveResourceBinding(uint64_t resource_handle) { 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(resource_handle); - if (res_it == resources_.end()) { - auto& new_res = resources_[resource_handle]; - new_res.is_image = false; - 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(resource_handle); auto& allocation = memory_allocations_[memory_handle]; VkDeviceSize res_size = res_it->second.size; - if (res_size == 0 && allocation.total_size > 0) { - res_size = allocation.total_size; - } - if (res_size == 0) return; 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 @@ -326,6 +318,18 @@ uint64_t DeviceMemoryReport::GetUsageMemoryBytes(const std::string& track) { return it != usage_memory_bytes_.end() ? it->second : 0; } +void DeviceMemoryReport::Reset() { + std::lock_guard lock1(map_mutex_); + std::lock_guard lock2(counter_mutex_); + vk_instance_map_.clear(); + has_callback_map_.clear(); + device_memory_properties_map_.clear(); + resources_.clear(); + resource_to_memory_map_.clear(); + memory_allocations_.clear(); + usage_memory_bytes_.clear(); +} + void DeviceMemoryReport::OnCreateImage(uint64_t image_handle, VkImageUsageFlags usage) { std::lock_guard lock(counter_mutex_); auto& res = resources_[image_handle]; From c41b3bc3e7586267f9a69ac57decceb24746b359 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 15 Sep 2026 13:58:13 +0000 Subject: [PATCH 06/12] device_memory_report: harden bind entry points and guard disjoint queries Assert dispatch table entry presence for core 1.0 vkBind*Memory commands instead of returning VK_ERROR_EXTENSION_NOT_PRESENT. Avoid calling legacy vkGetImageMemoryRequirements for multi-planar disjoint image plane bindings. --- .../device_memory_report_handwritten_functions.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 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 ce2a62fa68..1e388afb2f 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -259,7 +259,7 @@ EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( // 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) { auto* table = device_dispatch_table(device); - if (!table || !table->BindBufferMemory) return VK_ERROR_EXTENSION_NOT_PRESENT; + assert(table != nullptr && table->BindBufferMemory != nullptr); assert(buffer != VK_NULL_HANDLE); assert(memory != VK_NULL_HANDLE); if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(buffer)) == 0) { @@ -279,7 +279,7 @@ VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device, VkBuffer buff // 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) { auto* table = device_dispatch_table(device); - if (!table || !table->BindImageMemory) return VK_ERROR_EXTENSION_NOT_PRESENT; + assert(table != nullptr && table->BindImageMemory != nullptr); assert(image != VK_NULL_HANDLE); assert(memory != VK_NULL_HANDLE); if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(image)) == 0) { @@ -336,7 +336,9 @@ static void RecordImageBinds(VkDevice device, uint32_t bindInfoCount, const VkBi assert(pBindInfos[i].image != VK_NULL_HANDLE); assert(pBindInfos[i].memory != VK_NULL_HANDLE); if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(pBindInfos[i].image)) == 0) { - if (device_dispatch_table(device)->GetImageMemoryRequirements) { + const auto* plane_info = reinterpret_cast(pBindInfos[i].pNext); + bool is_plane_bind = (plane_info != nullptr && plane_info->sType == VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO); + if (!is_plane_bind && device_dispatch_table(device)->GetImageMemoryRequirements) { VkMemoryRequirements mem_reqs; device_dispatch_table(device)->GetImageMemoryRequirements(device, pBindInfos[i].image, &mem_reqs); DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(pBindInfos[i].image), mem_reqs.size); From ebc08c7f25cc0facb3425402db7f1a309144a3b4 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Tue, 15 Sep 2026 13:58:25 +0000 Subject: [PATCH 07/12] device_memory_report: reset state in unit tests for hermetic test execution Add DeviceMemoryReport::Reset() and invoke it in SetUp() and TearDown() of DeviceMemoryReportDispatchTests so tests are hermetic under shuffling and repetition. Update dispatch tests to match the hardened entry points. --- .../device_memory_report.h | 5 + .../test/test_devicememoryreport_dispatch.cpp | 97 ++----------------- 2 files changed, 14 insertions(+), 88 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index bfcd096805..ec5c5a4460 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -177,6 +177,11 @@ class DeviceMemoryReport { */ uint64_t GetUsageMemoryBytes(const std::string& track); + /** + * @brief Resets all tracked internal state (for testing). + */ + void Reset(); + /** * @brief Tracks creation of a Vulkan image and its usage flags. * @param image_handle The 64-bit handle of the Vulkan image. diff --git a/layersvt/test/test_devicememoryreport_dispatch.cpp b/layersvt/test/test_devicememoryreport_dispatch.cpp index ce3d4afa22..51b1124723 100644 --- a/layersvt/test/test_devicememoryreport_dispatch.cpp +++ b/layersvt/test/test_devicememoryreport_dispatch.cpp @@ -146,12 +146,17 @@ class FakeDevice { class DeviceMemoryReportDispatchTests : public ::testing::Test { protected: void SetUp() override { + DeviceMemoryReport::Get().Reset(); g_buffer_requirements_size = 0; g_image_requirements_size = 0; g_buffer_requirements_queries = 0; g_image_requirements_queries = 0; g_unimplemented.clear(); } + + void TearDown() override { + DeviceMemoryReport::Get().Reset(); + } }; TEST_F(DeviceMemoryReportDispatchTests, BindBufferMemoryQueriesUnknownResourceSize) { @@ -249,23 +254,16 @@ TEST_F(DeviceMemoryReportDispatchTests, BindMemoryKeepsAlreadyRecordedSizes) { EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 4096u); } -TEST_F(DeviceMemoryReportDispatchTests, BindMemoryReportsMissingDispatchEntries) { - // When the driver below the layer does not provide a bind entry point, the layer must report - // that rather than calling through a null dispatch table entry. - g_unimplemented = {"vkBindBufferMemory", "vkBindImageMemory", "vkBindBufferMemory2", - "vkBindImageMemory2", "vkBindBufferMemory2KHR", "vkBindImageMemory2KHR"}; +TEST_F(DeviceMemoryReportDispatchTests, BindMemory2ReportsMissingDispatchEntries) { + // When the driver below the layer does not provide an extension entry point, the layer must report + // VK_ERROR_EXTENSION_NOT_PRESENT. + g_unimplemented = {"vkBindBufferMemory2", "vkBindImageMemory2", "vkBindBufferMemory2KHR", "vkBindImageMemory2KHR"}; FakeDevice device; VkBuffer buffer = MakeHandle(0xB5000); VkImage image = MakeHandle(0xB5001); VkDeviceMemory memory = MakeHandle(0xB5002); - g_buffer_requirements_size = 4096; - g_image_requirements_size = 4096; - - EXPECT_EQ(vkBindBufferMemory(device.handle(), buffer, memory, 0), VK_ERROR_EXTENSION_NOT_PRESENT); - EXPECT_EQ(vkBindImageMemory(device.handle(), image, memory, 0), VK_ERROR_EXTENSION_NOT_PRESENT); - VkBindBufferMemoryInfo buffer_bind = {}; buffer_bind.sType = VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO; buffer_bind.buffer = buffer; @@ -279,83 +277,6 @@ TEST_F(DeviceMemoryReportDispatchTests, BindMemoryReportsMissingDispatchEntries) image_bind.memory = memory; EXPECT_EQ(vkBindImageMemory2(device.handle(), 1, &image_bind), VK_ERROR_EXTENSION_NOT_PRESENT); EXPECT_EQ(vkBindImageMemory2KHR(device.handle(), 1, &image_bind), VK_ERROR_EXTENSION_NOT_PRESENT); - - // Failed bindings must not leave any tracking behind. - EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(buffer)), 0u); - EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 0u); -} - -TEST_F(DeviceMemoryReportDispatchTests, DisjointImageFallsBackToAllocationSize) { - // When an image is created with VK_IMAGE_CREATE_DISJOINT_BIT (for example, when an app - // queries requirements upfront via vkGetDeviceImageMemoryRequirements and skips - // post-creation vkGetImageMemoryRequirements2), the layer skips vkGetImageMemoryRequirements - // at creation time. - // - // At bind time, its recorded size is 0. The layer must fall back to the owning allocation's - // total size rather than dropping the suballocation from tracking. - FakeDevice device; - - // Simulate stub driver returning 0 for legacy non-plane requirements on disjoint image. - g_image_requirements_size = 0; - - VkImageCreateInfo image_ci = {}; - image_ci.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - image_ci.flags = VK_IMAGE_CREATE_DISJOINT_BIT; - image_ci.imageType = VK_IMAGE_TYPE_2D; - image_ci.format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM; - image_ci.extent = {1920, 1080, 1}; - image_ci.mipLevels = 1; - image_ci.arrayLayers = 1; - image_ci.samples = VK_SAMPLE_COUNT_1_BIT; - image_ci.usage = VK_IMAGE_USAGE_SAMPLED_BIT; - - VkImage image = VK_NULL_HANDLE; - EXPECT_EQ(vkCreateImage(device.handle(), &image_ci, nullptr, &image), VK_SUCCESS); - EXPECT_NE(image, VK_NULL_HANDLE); - - // Image size was not recorded at creation time because it is disjoint. - EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 0u); - - const VkDeviceSize kAllocSize = 65536; - VkMemoryAllocateInfo alloc_info = {}; - alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - alloc_info.allocationSize = kAllocSize; - alloc_info.memoryTypeIndex = 0; - - VkDeviceMemory memory = VK_NULL_HANDLE; - EXPECT_EQ(vkAllocateMemory(device.handle(), &alloc_info, nullptr, &memory), VK_SUCCESS); - EXPECT_NE(memory, VK_NULL_HANDLE); - - // Initial state before binding: the entire allocation is unbound headroom. - EXPECT_EQ(DeviceMemoryReport::Get().GetUsageMemoryBytes("vulkan.mem.app.usage.unbound_memory"), kAllocSize); - EXPECT_EQ(DeviceMemoryReport::Get().GetUsageMemoryBytes("vulkan.mem.app.usage.static_texture"), 0u); - - // Bind disjoint image plane memory via vkBindImageMemory2 without querying vkGetImageMemoryRequirements2. - VkBindImagePlaneMemoryInfo plane_info = {}; - plane_info.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO; - plane_info.planeAspect = VK_IMAGE_ASPECT_PLANE_0_BIT; - - VkBindImageMemoryInfo bind_info = {}; - bind_info.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO; - bind_info.pNext = &plane_info; - bind_info.image = image; - bind_info.memory = memory; - bind_info.memoryOffset = 0; - - EXPECT_EQ(vkBindImageMemory2(device.handle(), 1, &bind_info), VK_SUCCESS); - - // After hardening: - // - Texture counter receives the suballocation sized to kAllocSize. - // - Unbound memory drops to 0. - EXPECT_EQ(DeviceMemoryReport::Get().GetUsageMemoryBytes("vulkan.mem.app.usage.static_texture"), kAllocSize); - EXPECT_EQ(DeviceMemoryReport::Get().GetUsageMemoryBytes("vulkan.mem.app.usage.unbound_memory"), 0u); - - // Clean up - vkDestroyImage(device.handle(), image, nullptr); - vkFreeMemory(device.handle(), memory, nullptr); - - EXPECT_EQ(DeviceMemoryReport::Get().GetUsageMemoryBytes("vulkan.mem.app.usage.static_texture"), 0u); - EXPECT_EQ(DeviceMemoryReport::Get().GetUsageMemoryBytes("vulkan.mem.app.usage.unbound_memory"), 0u); } } // namespace From 666659fc8ba84dc3b1b1143a094281165d495f4d Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Thu, 17 Sep 2026 14:22:24 +0000 Subject: [PATCH 08/12] device_memory_report: use std::scoped_lock in Reset --- layersvt/device_memory_report/device_memory_report.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index c26b32102f..7c2f6419f1 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -323,8 +323,7 @@ uint64_t DeviceMemoryReport::GetUsageCounterBytes(const std::string& track) { } void DeviceMemoryReport::Reset() { - std::lock_guard lock1(map_mutex_); - std::lock_guard lock2(counter_mutex_); + std::scoped_lock lock(map_mutex_, counter_mutex_); vk_instance_map_.clear(); has_callback_map_.clear(); device_memory_properties_map_.clear(); From b8953ccf119f9cb38e7b49e760b036b33130731d Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Thu, 17 Sep 2026 14:22:34 +0000 Subject: [PATCH 09/12] device_memory_report: drop redundant table null check in bind assertions --- .../device_memory_report_handwritten_functions.h | 4 ++-- 1 file changed, 2 insertions(+), 2 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 1e388afb2f..fbff741d1c 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -259,7 +259,7 @@ EXPORT_FUNCTION VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( // 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) { auto* table = device_dispatch_table(device); - assert(table != nullptr && table->BindBufferMemory != nullptr); + assert(table->BindBufferMemory != nullptr); assert(buffer != VK_NULL_HANDLE); assert(memory != VK_NULL_HANDLE); if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(buffer)) == 0) { @@ -279,7 +279,7 @@ VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device, VkBuffer buff // 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) { auto* table = device_dispatch_table(device); - assert(table != nullptr && table->BindImageMemory != nullptr); + assert(table->BindImageMemory != nullptr); assert(image != VK_NULL_HANDLE); assert(memory != VK_NULL_HANDLE); if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(image)) == 0) { From 4e901dc46cd0b0163a176bf2fb068d60ad881d50 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Thu, 17 Sep 2026 14:22:46 +0000 Subject: [PATCH 10/12] device_memory_report: hoist table lookup in RecordBufferBindings --- .../device_memory_report_handwritten_functions.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 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 fbff741d1c..633ddbb0d8 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -297,15 +297,14 @@ VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory(VkDevice device, VkImage image, } static void RecordBufferBindings(VkDevice device, uint32_t bindInfoCount, const VkBindBufferMemoryInfo* pBindInfos) { + auto* table = device_dispatch_table(device); for (uint32_t i = 0; i < bindInfoCount; ++i) { assert(pBindInfos[i].buffer != VK_NULL_HANDLE); assert(pBindInfos[i].memory != VK_NULL_HANDLE); if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(pBindInfos[i].buffer)) == 0) { - if (device_dispatch_table(device)->GetBufferMemoryRequirements) { - VkMemoryRequirements mem_reqs; - device_dispatch_table(device)->GetBufferMemoryRequirements(device, pBindInfos[i].buffer, &mem_reqs); - DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(pBindInfos[i].buffer), mem_reqs.size); - } + VkMemoryRequirements mem_reqs; + table->GetBufferMemoryRequirements(device, pBindInfos[i].buffer, &mem_reqs); + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(pBindInfos[i].buffer), mem_reqs.size); } DeviceMemoryReport::Get().OnBindBufferMemory(reinterpret_cast(pBindInfos[i].buffer), reinterpret_cast(pBindInfos[i].memory), pBindInfos[i].memoryOffset); } From 1aaf475144403a14066fc3294c3dd284a97e4338 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Thu, 17 Sep 2026 14:23:10 +0000 Subject: [PATCH 11/12] device_memory_report: traverse full pNext chain for disjoint image plane bind --- ...vice_memory_report_handwritten_functions.h | 15 ++++++--- .../test/test_devicememoryreport_dispatch.cpp | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+), 4 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 633ddbb0d8..32afc21448 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -331,15 +331,22 @@ VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory2KHR(VkDevice device, uint32_t } static void RecordImageBinds(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos) { + auto* table = device_dispatch_table(device); for (uint32_t i = 0; i < bindInfoCount; ++i) { assert(pBindInfos[i].image != VK_NULL_HANDLE); assert(pBindInfos[i].memory != VK_NULL_HANDLE); if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(pBindInfos[i].image)) == 0) { - const auto* plane_info = reinterpret_cast(pBindInfos[i].pNext); - bool is_plane_bind = (plane_info != nullptr && plane_info->sType == VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO); - if (!is_plane_bind && device_dispatch_table(device)->GetImageMemoryRequirements) { + bool is_plane_bind = false; + for (const auto* header = reinterpret_cast(pBindInfos[i].pNext); + header != nullptr; header = header->pNext) { + if (header->sType == VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO) { + is_plane_bind = true; + break; + } + } + if (!is_plane_bind) { VkMemoryRequirements mem_reqs; - device_dispatch_table(device)->GetImageMemoryRequirements(device, pBindInfos[i].image, &mem_reqs); + table->GetImageMemoryRequirements(device, pBindInfos[i].image, &mem_reqs); DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(pBindInfos[i].image), mem_reqs.size); } } diff --git a/layersvt/test/test_devicememoryreport_dispatch.cpp b/layersvt/test/test_devicememoryreport_dispatch.cpp index 51b1124723..04b4d0ad52 100644 --- a/layersvt/test/test_devicememoryreport_dispatch.cpp +++ b/layersvt/test/test_devicememoryreport_dispatch.cpp @@ -254,6 +254,38 @@ TEST_F(DeviceMemoryReportDispatchTests, BindMemoryKeepsAlreadyRecordedSizes) { EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 4096u); } +TEST_F(DeviceMemoryReportDispatchTests, BindImageMemory2SkipsDisjointImagePlaneBind) { + // When binding an image plane with VkBindImagePlaneMemoryInfo in the pNext chain (even if not first), + // vkGetImageMemoryRequirements must not be queried because disjoint images require + // vkGetImageMemoryRequirements2 with plane aspect specified. + FakeDevice device; + VkImage image = MakeHandle(0xB4800); + VkDeviceMemory memory = MakeHandle(0xB4801); + + ASSERT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 0u); + + VkBindImagePlaneMemoryInfo plane_info = {}; + plane_info.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO; + plane_info.pNext = nullptr; + plane_info.planeAspect = VK_IMAGE_ASPECT_PLANE_0_BIT; + + VkBindImageMemoryDeviceGroupInfo device_group_info = {}; + device_group_info.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_DEVICE_GROUP_INFO; + device_group_info.pNext = &plane_info; + + VkBindImageMemoryInfo image_bind = {}; + image_bind.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO; + image_bind.pNext = &device_group_info; + image_bind.image = image; + image_bind.memory = memory; + image_bind.memoryOffset = 0; + + EXPECT_EQ(vkBindImageMemory2(device.handle(), 1, &image_bind), VK_SUCCESS); + + EXPECT_EQ(g_image_requirements_queries, 0); + EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 0u); +} + TEST_F(DeviceMemoryReportDispatchTests, BindMemory2ReportsMissingDispatchEntries) { // When the driver below the layer does not provide an extension entry point, the layer must report // VK_ERROR_EXTENSION_NOT_PRESENT. From 531a5063a2ebb73e21cac2b81e6f8810906d2f19 Mon Sep 17 00:00:00 2001 From: Jim Blackler Date: Thu, 17 Sep 2026 14:24:19 +0000 Subject: [PATCH 12/12] device_memory_report: assert downstream dispatch presence in vkBind*Memory2 --- ...vice_memory_report_handwritten_functions.h | 20 ++++++----- .../test/test_devicememoryreport_dispatch.cpp | 36 ++----------------- 2 files changed, 14 insertions(+), 42 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 32afc21448..62ab0c7517 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -312,8 +312,9 @@ static void RecordBufferBindings(VkDevice device, uint32_t bindInfoCount, const // 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) { - if (!device_dispatch_table(device)->BindBufferMemory2) return VK_ERROR_EXTENSION_NOT_PRESENT; - VkResult result = device_dispatch_table(device)->BindBufferMemory2(device, bindInfoCount, pBindInfos); + auto* table = device_dispatch_table(device); + assert(table->BindBufferMemory2 != nullptr); + VkResult result = table->BindBufferMemory2(device, bindInfoCount, pBindInfos); if (result == VK_SUCCESS && pBindInfos != nullptr) { RecordBufferBindings(device, bindInfoCount, pBindInfos); } @@ -322,8 +323,9 @@ VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory2(VkDevice device, uint32_t bin // 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) { - if (!device_dispatch_table(device)->BindBufferMemory2KHR) return VK_ERROR_EXTENSION_NOT_PRESENT; - VkResult result = device_dispatch_table(device)->BindBufferMemory2KHR(device, bindInfoCount, pBindInfos); + auto* table = device_dispatch_table(device); + assert(table->BindBufferMemory2KHR != nullptr); + VkResult result = table->BindBufferMemory2KHR(device, bindInfoCount, pBindInfos); if (result == VK_SUCCESS && pBindInfos != nullptr) { RecordBufferBindings(device, bindInfoCount, pBindInfos); } @@ -356,8 +358,9 @@ static void RecordImageBinds(VkDevice device, uint32_t bindInfoCount, const VkBi // 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) { - if (!device_dispatch_table(device)->BindImageMemory2) return VK_ERROR_EXTENSION_NOT_PRESENT; - VkResult result = device_dispatch_table(device)->BindImageMemory2(device, bindInfoCount, pBindInfos); + auto* table = device_dispatch_table(device); + assert(table->BindImageMemory2 != nullptr); + VkResult result = table->BindImageMemory2(device, bindInfoCount, pBindInfos); if (result == VK_SUCCESS && pBindInfos != nullptr) { RecordImageBinds(device, bindInfoCount, pBindInfos); } @@ -366,8 +369,9 @@ VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory2(VkDevice device, uint32_t bind // 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) { - if (!device_dispatch_table(device)->BindImageMemory2KHR) return VK_ERROR_EXTENSION_NOT_PRESENT; - VkResult result = device_dispatch_table(device)->BindImageMemory2KHR(device, bindInfoCount, pBindInfos); + auto* table = device_dispatch_table(device); + assert(table->BindImageMemory2KHR != nullptr); + VkResult result = table->BindImageMemory2KHR(device, bindInfoCount, pBindInfos); if (result == VK_SUCCESS && pBindInfos != nullptr) { RecordImageBinds(device, bindInfoCount, pBindInfos); } diff --git a/layersvt/test/test_devicememoryreport_dispatch.cpp b/layersvt/test/test_devicememoryreport_dispatch.cpp index 04b4d0ad52..200117cfbf 100644 --- a/layersvt/test/test_devicememoryreport_dispatch.cpp +++ b/layersvt/test/test_devicememoryreport_dispatch.cpp @@ -18,8 +18,7 @@ // The layer's vk* entry points are linked directly into this test binary (the Vulkan loader is // deliberately not linked), and every fake VkDevice is given a dispatch table built from the stub // driver below. That makes it possible to exercise the layer's interception logic without a real -// Vulkan implementation, including the case where the driver underneath the layer does not -// implement an entry point at all and its dispatch table slot is therefore null. +// Vulkan implementation. #include "device_memory_report.h" #include "vk_layer_table.h" @@ -29,7 +28,6 @@ #include #include -#include #include namespace { @@ -42,10 +40,6 @@ VkDeviceSize g_image_requirements_size = 0; int g_buffer_requirements_queries = 0; int g_image_requirements_queries = 0; -// Entry points the stub driver does not implement. Their dispatch table slots stay null, which is -// what the layer sees when the driver (or an ICD without the relevant extension) lacks a command. -std::set g_unimplemented; - template HandleType MakeHandle(uintptr_t value) { return reinterpret_cast(value); @@ -101,7 +95,6 @@ VKAPI_ATTR void VKAPI_CALL StubGetImageMemoryRequirements(VkDevice, VkImage, VkM VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL StubGetDeviceProcAddr(VkDevice, const char* pName) { if (pName == nullptr) return nullptr; const std::string name(pName); - if (g_unimplemented.count(name) != 0) return nullptr; if (name == "vkCreateImage") return reinterpret_cast(StubCreateImage); if (name == "vkDestroyImage") return reinterpret_cast(StubDestroyImage); @@ -151,7 +144,6 @@ class DeviceMemoryReportDispatchTests : public ::testing::Test { g_image_requirements_size = 0; g_buffer_requirements_queries = 0; g_image_requirements_queries = 0; - g_unimplemented.clear(); } void TearDown() override { @@ -286,29 +278,5 @@ TEST_F(DeviceMemoryReportDispatchTests, BindImageMemory2SkipsDisjointImagePlaneB EXPECT_EQ(DeviceMemoryReport::Get().GetRecordedResourceSize(AsObjectHandle(image)), 0u); } -TEST_F(DeviceMemoryReportDispatchTests, BindMemory2ReportsMissingDispatchEntries) { - // When the driver below the layer does not provide an extension entry point, the layer must report - // VK_ERROR_EXTENSION_NOT_PRESENT. - g_unimplemented = {"vkBindBufferMemory2", "vkBindImageMemory2", "vkBindBufferMemory2KHR", "vkBindImageMemory2KHR"}; - FakeDevice device; - - VkBuffer buffer = MakeHandle(0xB5000); - VkImage image = MakeHandle(0xB5001); - VkDeviceMemory memory = MakeHandle(0xB5002); - - VkBindBufferMemoryInfo buffer_bind = {}; - buffer_bind.sType = VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO; - buffer_bind.buffer = buffer; - buffer_bind.memory = memory; - EXPECT_EQ(vkBindBufferMemory2(device.handle(), 1, &buffer_bind), VK_ERROR_EXTENSION_NOT_PRESENT); - EXPECT_EQ(vkBindBufferMemory2KHR(device.handle(), 1, &buffer_bind), VK_ERROR_EXTENSION_NOT_PRESENT); - - VkBindImageMemoryInfo image_bind = {}; - image_bind.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO; - image_bind.image = image; - image_bind.memory = memory; - EXPECT_EQ(vkBindImageMemory2(device.handle(), 1, &image_bind), VK_ERROR_EXTENSION_NOT_PRESENT); - EXPECT_EQ(vkBindImageMemory2KHR(device.handle(), 1, &image_bind), VK_ERROR_EXTENSION_NOT_PRESENT); -} - } // namespace +