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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 123 additions & 47 deletions VkLayer_profiler_layer/profiler/profiler_memory_tracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,69 +241,145 @@ namespace Profiler
std::vector<DeviceProfilerBufferMemoryBindingData>& bindings =
std::get<std::vector<DeviceProfilerBufferMemoryBindingData>>( 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;
}
}
}
}

/***********************************************************************************\

Function:
UnbindBufferMemoryRange

Description:

\***********************************************************************************/
void DeviceProfilerMemoryTracker::UnbindBufferMemoryRange( std::vector<DeviceProfilerBufferMemoryBindingData>& 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

Expand Down
2 changes: 2 additions & 0 deletions VkLayer_profiler_layer/profiler/profiler_memory_tracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,7 @@ namespace Profiler
ConcurrentMap<VkObjectHandle<VkImage>, DeviceProfilerImageMemoryData> m_Images;

void ResetMemoryData();

void UnbindBufferMemoryRange( std::vector<DeviceProfilerBufferMemoryBindingData>& bindings, VkDeviceSize offset, VkDeviceSize size );
};
}
92 changes: 80 additions & 12 deletions VkLayer_profiler_layer/profiler_tests/profiler_memory_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -795,19 +795,87 @@ namespace Profiler
std::shared_ptr<DeviceProfilerFrameData> 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<DeviceProfilerFrameData> 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 );
}
}