diff --git a/layersvt/device_memory_report/device_memory_report.cpp b/layersvt/device_memory_report/device_memory_report.cpp index cac4cea1ee..2f797f42cb 100644 --- a/layersvt/device_memory_report/device_memory_report.cpp +++ b/layersvt/device_memory_report/device_memory_report.cpp @@ -393,6 +393,16 @@ uint64_t DeviceMemoryReport::GetUsageCounterBytes(const std::string& track) { return it != usage_memory_bytes_.end() ? it->second : 0; } +void DeviceMemoryReport::Reset() { + std::scoped_lock lock(map_mutex_, 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]; diff --git a/layersvt/device_memory_report/device_memory_report.h b/layersvt/device_memory_report/device_memory_report.h index 236a9a5c35..43890fe195 100644 --- a/layersvt/device_memory_report/device_memory_report.h +++ b/layersvt/device_memory_report/device_memory_report.h @@ -170,6 +170,11 @@ class DeviceMemoryReport { */ VkDeviceSize GetRecordedResourceSize(uint64_t resource_handle); + /** + * @brief Resets all tracked internal state (for testing). + */ + void Reset(); + /** * @brief Retrieves the current total memory bytes recorded on a usage track (for testing). * @param track The full usage track name (e.g. "vulkan.mem.app.usage.unbound_memory"). 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 6761dc3dbe..62ab0c7517 100644 --- a/layersvt/device_memory_report/device_memory_report_handwritten_functions.h +++ b/layersvt/device_memory_report/device_memory_report_handwritten_functions.h @@ -258,8 +258,19 @@ 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); - if (result == VK_SUCCESS && buffer != VK_NULL_HANDLE && memory != VK_NULL_HANDLE) { + auto* table = device_dispatch_table(device); + assert(table->BindBufferMemory != nullptr); + 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); + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(buffer), mem_reqs.size); + } + } + VkResult result = table->BindBufferMemory(device, buffer, memory, memoryOffset); + if (result == VK_SUCCESS) { DeviceMemoryReport::Get().OnBindBufferMemory(reinterpret_cast(buffer), reinterpret_cast(memory), memoryOffset); } return result; @@ -267,63 +278,102 @@ 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); - if (result == VK_SUCCESS && image != VK_NULL_HANDLE && memory != VK_NULL_HANDLE) { + auto* table = device_dispatch_table(device); + assert(table->BindImageMemory != nullptr); + 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); + DeviceMemoryReport::Get().OnRecordResourceSize(reinterpret_cast(image), mem_reqs.size); + } + } + VkResult result = table->BindImageMemory(device, image, memory, memoryOffset); + if (result == VK_SUCCESS) { 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) { + auto* table = device_dispatch_table(device); 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); + assert(pBindInfos[i].buffer != VK_NULL_HANDLE); + assert(pBindInfos[i].memory != VK_NULL_HANDLE); + if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(pBindInfos[i].buffer)) == 0) { + 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); } } // 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); + auto* table = device_dispatch_table(device); + assert(table->BindBufferMemory2 != nullptr); + VkResult result = table->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) { - assert(device_dispatch_table(device)->BindBufferMemory2KHR != nullptr); - 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(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) { + auto* table = device_dispatch_table(device); 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); + assert(pBindInfos[i].image != VK_NULL_HANDLE); + assert(pBindInfos[i].memory != VK_NULL_HANDLE); + if (DeviceMemoryReport::Get().GetRecordedResourceSize(reinterpret_cast(pBindInfos[i].image)) == 0) { + 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; + table->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); } } // 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); + auto* table = device_dispatch_table(device); + assert(table->BindImageMemory2 != nullptr); + VkResult result = table->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) { - assert(device_dispatch_table(device)->BindImageMemory2KHR != nullptr); - 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(bindInfoCount, pBindInfos); + RecordImageBinds(device, bindInfoCount, pBindInfos); } return result; } 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..200117cfbf --- /dev/null +++ b/layersvt/test/test_devicememoryreport_dispatch.cpp @@ -0,0 +1,282 @@ +/* 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. + +#include "device_memory_report.h" +#include "vk_layer_table.h" + +#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; + +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; } + +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 (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") { + 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; +}; + +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; + } + + void TearDown() override { + DeviceMemoryReport::Get().Reset(); + } +}; + +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, 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); +} + +} // namespace +