diff --git a/VkLayer_profiler_layer/profiler/profiler_memory_tracker.cpp b/VkLayer_profiler_layer/profiler/profiler_memory_tracker.cpp index 0aac8f47..98a6594e 100644 --- a/VkLayer_profiler_layer/profiler/profiler_memory_tracker.cpp +++ b/VkLayer_profiler_layer/profiler/profiler_memory_tracker.cpp @@ -241,62 +241,81 @@ namespace Profiler std::vector& bindings = std::get>( it->second.m_MemoryBindings ); + // Undbind first to avoid searching for overlapping ranges. + UnbindBufferMemoryRange( bindings, bufferOffset, size ); + if( memory.m_Handle != VK_NULL_HANDLE ) { - // New memory binding of the buffer region. - DeviceProfilerBufferMemoryBindingData& binding = bindings.emplace_back(); - binding.m_Memory = memory; - binding.m_MemoryOffset = memoryOffset; - binding.m_BufferOffset = bufferOffset; - binding.m_Size = size; - } - else - { - // If memory is null, the resource region is unbound. - // Remove all bindings that entirely cover the range and update the partially-unbound regions. - const VkDeviceSize startUnbindOffset = bufferOffset; - const VkDeviceSize endUnbindOffset = bufferOffset + size; - - for( auto binding = bindings.begin(); binding != bindings.end(); ) + // Extend existing binding adjacent to the bound region. + auto binding = bindings.begin(); + while( binding != bindings.end() ) { - const VkDeviceSize startBindingOffset = binding->m_BufferOffset; - const VkDeviceSize endBindingOffset = binding->m_BufferOffset + binding->m_Size; - - if( ( startUnbindOffset <= startBindingOffset ) && ( endUnbindOffset >= endBindingOffset ) ) - { - // Binding entirely covered by the unbound range, remove it. - binding = bindings.erase( binding ); - } - else if( ( startUnbindOffset > startBindingOffset ) && ( endUnbindOffset < endBindingOffset ) ) - { - // Buffer partially-unbound in the middle. - DeviceProfilerBufferMemoryBindingData newBinding = *binding; - newBinding.m_Size = startUnbindOffset - startBindingOffset; - binding->m_BufferOffset = endUnbindOffset; - binding->m_MemoryOffset += newBinding.m_Size + size; - binding->m_Size -= newBinding.m_Size + size; - binding = bindings.insert( binding, newBinding ); - binding++; - } - else if( ( startUnbindOffset <= startBindingOffset ) && ( endUnbindOffset > startBindingOffset ) ) + if( ( binding->m_Memory == memory ) && + ( binding->m_BufferOffset + binding->m_Size == bufferOffset ) && + ( binding->m_MemoryOffset + binding->m_Size == memoryOffset ) ) { - // Buffer partially-unbound at the start. - binding->m_BufferOffset = endUnbindOffset; - binding->m_MemoryOffset += endUnbindOffset - startBindingOffset; - binding->m_Size -= endUnbindOffset - startBindingOffset; - binding++; + // Extend existing binding at the end. + binding->m_Size += size; + + // Check if the binding can be merged with the next one. + auto nextBinding = std::next( binding ); + if( ( nextBinding != bindings.end() ) && + ( nextBinding->m_Memory == memory ) && + ( nextBinding->m_BufferOffset == bufferOffset + size ) && + ( nextBinding->m_MemoryOffset == memoryOffset + size ) ) + { + // Merge with the next binding. + binding->m_Size += nextBinding->m_Size; + binding = std::prev( bindings.erase( nextBinding ) ); + } + + break; } - else if( ( startUnbindOffset < endBindingOffset ) && ( endUnbindOffset >= endBindingOffset ) ) + + if( ( binding->m_Memory == memory ) && + ( binding->m_BufferOffset == bufferOffset + size ) && + ( binding->m_MemoryOffset == memoryOffset + size ) ) { - // Buffer partially-unbound at the end. - binding->m_Size -= endBindingOffset - startUnbindOffset; - binding++; + // Extend existing binding at the start. + binding->m_BufferOffset = bufferOffset; + binding->m_MemoryOffset = memoryOffset; + binding->m_Size += size; + + // Check if the binding can be merged with the previous one. + if( binding != bindings.begin() ) + { + auto prevBinding = std::prev( binding ); + if( ( prevBinding->m_Memory == memory ) && + ( prevBinding->m_BufferOffset + prevBinding->m_Size == bufferOffset ) && + ( prevBinding->m_MemoryOffset + prevBinding->m_Size == memoryOffset ) ) + { + // Merge with the previous binding. + prevBinding->m_Size += binding->m_Size; + binding = std::prev( bindings.erase( binding ) ); + } + } + + break; } - else + + binding++; + } + + // New memory binding of the buffer region. + if( binding == bindings.end() ) + { + // Keep the array sorted by buffer offset. + auto insertLocation = bindings.begin(); + while( ( insertLocation != bindings.end() ) && ( insertLocation->m_BufferOffset < bufferOffset ) ) { - // Binding not affected. - binding++; + insertLocation++; } + + binding = bindings.insert( insertLocation, DeviceProfilerBufferMemoryBindingData() ); + binding->m_Memory = memory; + binding->m_MemoryOffset = memoryOffset; + binding->m_BufferOffset = bufferOffset; + binding->m_Size = size; } } } @@ -304,6 +323,63 @@ namespace Profiler /***********************************************************************************\ + Function: + UnbindBufferMemoryRange + + Description: + + \***********************************************************************************/ + void DeviceProfilerMemoryTracker::UnbindBufferMemoryRange( std::vector& bindings, VkDeviceSize offset, VkDeviceSize size ) + { + const VkDeviceSize startUnbindOffset = offset; + const VkDeviceSize endUnbindOffset = offset + size; + + auto binding = bindings.begin(); + while( binding != bindings.end() ) + { + const VkDeviceSize startBindingOffset = binding->m_BufferOffset; + const VkDeviceSize endBindingOffset = binding->m_BufferOffset + binding->m_Size; + + if( ( startUnbindOffset <= startBindingOffset ) && ( endUnbindOffset >= endBindingOffset ) ) + { + // Binding entirely covered by the unbound range, remove it. + binding = bindings.erase( binding ); + } + else if( ( startUnbindOffset > startBindingOffset ) && ( endUnbindOffset < endBindingOffset ) ) + { + // Buffer partially-unbound in the middle. + DeviceProfilerBufferMemoryBindingData newBinding = *binding; + newBinding.m_Size = startUnbindOffset - startBindingOffset; + binding->m_BufferOffset = endUnbindOffset; + binding->m_MemoryOffset += newBinding.m_Size + size; + binding->m_Size -= newBinding.m_Size + size; + binding = bindings.insert( binding, newBinding ); + binding++; + } + else if( ( startUnbindOffset <= startBindingOffset ) && ( endUnbindOffset > startBindingOffset ) ) + { + // Buffer partially-unbound at the start. + binding->m_BufferOffset = endUnbindOffset; + binding->m_MemoryOffset += endUnbindOffset - startBindingOffset; + binding->m_Size -= endUnbindOffset - startBindingOffset; + binding++; + } + else if( ( startUnbindOffset < endBindingOffset ) && ( endUnbindOffset >= endBindingOffset ) ) + { + // Buffer partially-unbound at the end. + binding->m_Size -= endBindingOffset - startUnbindOffset; + binding++; + } + else + { + // Binding not affected. + binding++; + } + } + } + + /***********************************************************************************\ + Function: RegisterImage diff --git a/VkLayer_profiler_layer/profiler/profiler_memory_tracker.h b/VkLayer_profiler_layer/profiler/profiler_memory_tracker.h index 8513919c..81d56159 100644 --- a/VkLayer_profiler_layer/profiler/profiler_memory_tracker.h +++ b/VkLayer_profiler_layer/profiler/profiler_memory_tracker.h @@ -73,5 +73,7 @@ namespace Profiler ConcurrentMap, DeviceProfilerImageMemoryData> m_Images; void ResetMemoryData(); + + void UnbindBufferMemoryRange( std::vector& bindings, VkDeviceSize offset, VkDeviceSize size ); }; } diff --git a/VkLayer_profiler_layer/profiler_tests/profiler_memory_tests.cpp b/VkLayer_profiler_layer/profiler_tests/profiler_memory_tests.cpp index c2e3ac0c..b1bc284f 100644 --- a/VkLayer_profiler_layer/profiler_tests/profiler_memory_tests.cpp +++ b/VkLayer_profiler_layer/profiler_tests/profiler_memory_tests.cpp @@ -747,10 +747,10 @@ namespace Profiler existing memory bindings works correctly. First the entire resource is bound at offset 0 to memory at offset 0, with - a separate binding for each [alignment] bytes, resulting in [count] bindings. + a separate binding for each [alignment] bytes, resulting in 1 binding. Then, the last [2 x alignment] bytes of the resource are unbound. - The expected result is that the number of reported memory bindings decreases by 2, - and the first [count - 2] bindings remain unchanged. + The expected result is that the number of reported memory bindings remains + the same, and the only binding size decreases by [2 x alignment]. Requires sparseBinding and sparseResidencyBuffer features to be supported. @@ -795,19 +795,87 @@ namespace Profiler std::shared_ptr pData = Prof->GetData(); const DeviceProfilerBufferMemoryData& bufferData = pData->m_Memory.m_Buffers.at( Prof->GetObjectHandle( buffer ) ); - ASSERT_EQ( count - 2, bufferData.GetMemoryBindingCount() ); + ASSERT_EQ( 1, bufferData.GetMemoryBindingCount() ); - for( uint32_t i = 0; i < count - 2; ++i ) - { - const DeviceProfilerBufferMemoryBindingData& bindingData = bufferData.GetMemoryBindings()[i]; - EXPECT_EQ( deviceMemory, bindingData.m_Memory ); - EXPECT_EQ( memoryRequirements.alignment, bindingData.m_Size ); - EXPECT_EQ( memoryRequirements.alignment * i, bindingData.m_BufferOffset ); - EXPECT_EQ( memoryRequirements.alignment * i, bindingData.m_MemoryOffset ); - } + const DeviceProfilerBufferMemoryBindingData& bindingData = *bufferData.GetMemoryBindings(); + EXPECT_EQ( deviceMemory, bindingData.m_Memory ); + EXPECT_EQ( memoryRequirements.size - 2 * memoryRequirements.alignment, bindingData.m_Size ); + EXPECT_EQ( 0, bindingData.m_BufferOffset ); + EXPECT_EQ( 0, bindingData.m_MemoryOffset ); + } + + vkDestroyBuffer( Vk->Device, buffer, nullptr ); + vkFreeMemory( Vk->Device, deviceMemory, nullptr ); + } + + /***********************************************************************************\ + + Test: + SparseBinding_RebindResource + + Description: + This test verifies that rebinding a sparse resource to a different memory + works correctly. + + First the entire resource is bound at offset 0 to memory at offset 0, with + a single separate binding. Then, first [alignment] bytes of the resource are + bound to a different memory. The expected result is that the number of reported + memory bindings increases to 2, the first binding points to the new memory, and + the second binding points to the original memory with size reduced by [alignment]. + + Requires sparseBinding and sparseResidencyBuffer features to be supported. + + \***********************************************************************************/ + TEST_F( DeviceProfilerMemoryULT, SparseBinding_RebindResource ) + { + SkipIfUnsupported( sparseBindingFeature ); + + VkBuffer buffer = {}; + VkDeviceMemory deviceMemory = {}; + VkDeviceMemory deviceMemory2 = {}; + VkMemoryRequirements memoryRequirements = {}; + ASSERT_EQ( VK_SUCCESS, CreateSparseBufferResource( 256 * 1024, &buffer, &deviceMemory, &memoryRequirements ) ); + ASSERT_GT( memoryRequirements.size, memoryRequirements.alignment ); + + { // Allocate memory for the new binding + VkMemoryAllocateInfo memoryAllocateInfo = {}; + memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memoryAllocateInfo.memoryTypeIndex = FindMemoryType( 0, memoryRequirements.memoryTypeBits ); + memoryAllocateInfo.allocationSize = memoryRequirements.alignment; + ASSERT_EQ( VK_SUCCESS, vkAllocateMemory( Vk->Device, &memoryAllocateInfo, nullptr, &deviceMemory2 ) ); + } + + { // Bind the resource to the new memory + VkSparseMemoryBind sparseMemoryBind = {}; + sparseMemoryBind.memory = deviceMemory2; + sparseMemoryBind.memoryOffset = 0; + sparseMemoryBind.resourceOffset = 0; + sparseMemoryBind.size = memoryRequirements.alignment; + ASSERT_EQ( VK_SUCCESS, BindSparseBufferResource( buffer, sparseMemoryBind ) ); + } + + { // Collect and post-process data + Prof->FinishFrame(); + + std::shared_ptr pData = Prof->GetData(); + const DeviceProfilerBufferMemoryData& bufferData = pData->m_Memory.m_Buffers.at( Prof->GetObjectHandle( buffer ) ); + + ASSERT_EQ( 2, bufferData.GetMemoryBindingCount() ); + const DeviceProfilerBufferMemoryBindingData* pBindings = bufferData.GetMemoryBindings(); + + EXPECT_EQ( deviceMemory2, pBindings[0].m_Memory ); + EXPECT_EQ( memoryRequirements.alignment, pBindings[0].m_Size ); + EXPECT_EQ( 0, pBindings[0].m_BufferOffset ); + EXPECT_EQ( 0, pBindings[0].m_MemoryOffset ); + + EXPECT_EQ( deviceMemory, pBindings[1].m_Memory ); + EXPECT_EQ( memoryRequirements.size - memoryRequirements.alignment, pBindings[1].m_Size ); + EXPECT_EQ( memoryRequirements.alignment, pBindings[1].m_BufferOffset ); + EXPECT_EQ( memoryRequirements.alignment, pBindings[1].m_MemoryOffset ); } vkDestroyBuffer( Vk->Device, buffer, nullptr ); vkFreeMemory( Vk->Device, deviceMemory, nullptr ); + vkFreeMemory( Vk->Device, deviceMemory2, nullptr ); } }