From 18520361942e24c02bf35266d2a94655c9080e53 Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Fri, 24 Aug 2018 16:28:28 +0200 Subject: [PATCH 01/30] Added VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT, WORST_FIT, FIRST_FIT, and aliases: VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT, MIN_TIME, MIN_FRAGMENTATION. Deleted VMA_BEST_FIT macro. --- src/vk_mem_alloc.h | 241 ++++++++++++++++++++++++++++++++------------- 1 file changed, 170 insertions(+), 71 deletions(-) diff --git a/src/vk_mem_alloc.h b/src/vk_mem_alloc.h index 7dd8ff2..d3be4dd 100644 --- a/src/vk_mem_alloc.h +++ b/src/vk_mem_alloc.h @@ -1806,6 +1806,29 @@ typedef enum VmaAllocationCreateFlagBits { */ VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT = 0x00000040, + /** Allocation strategy that chooses smallest possible free range for the + allocation. + */ + VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT = 0x00010000, + /** Allocation strategy that chooses biggest possible free range for the + allocation. + */ + VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT = 0x00020000, + /** Allocation strategy that chooses first suitable free range for the + allocation. + */ + VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT = 0x00040000, + + /** Allocation strategy that tries to minimize memory usage. + */ + VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT = VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT, + /** Allocation strategy that tries to minimize allocation time. + */ + VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT = VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT, + /** Allocation strategy that tries to minimize memory fragmentation. + */ + VMA_ALLOCATION_CREATE_STRATEGY_MIN_FRAGMENTATION_BIT = VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT, + VMA_ALLOCATION_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VmaAllocationCreateFlagBits; typedef VkFlags VmaAllocationCreateFlags; @@ -2793,22 +2816,6 @@ If providing your own implementation, you need to implement a subset of std::ato #define VMA_ATOMIC_UINT32 std::atomic #endif -#ifndef VMA_BEST_FIT - /** - Main parameter for function assessing how good is a free suballocation for a new - allocation request. - - - Set to 1 to use Best-Fit algorithm - prefer smaller blocks, as close to the - size of requested allocations as possible. - - Set to 0 to use Worst-Fit algorithm - prefer larger blocks, as large as - possible. - - Experiments in special testing environment showed that Best-Fit algorithm is - better. - */ - #define VMA_BEST_FIT (1) -#endif - #ifndef VMA_DEBUG_ALWAYS_DEDICATED_MEMORY /** Every allocation will have its own memory block. @@ -4536,6 +4543,7 @@ public: bool upperAddress, VmaSuballocationType allocType, bool canMakeOtherLost, + uint32_t strategy, // Always one of VMA_ALLOCATION_CREATE_STRATEGY_* flags. VmaAllocationRequest* pAllocationRequest) = 0; virtual bool MakeRequestedAllocationsLost( @@ -4608,6 +4616,7 @@ public: bool upperAddress, VmaSuballocationType allocType, bool canMakeOtherLost, + uint32_t strategy, VmaAllocationRequest* pAllocationRequest); virtual bool MakeRequestedAllocationsLost( @@ -4776,6 +4785,7 @@ public: bool upperAddress, VmaSuballocationType allocType, bool canMakeOtherLost, + uint32_t strategy, VmaAllocationRequest* pAllocationRequest); virtual bool MakeRequestedAllocationsLost( @@ -5036,6 +5046,7 @@ private: VmaAllocationCreateFlags allocFlags, void* pUserData, VmaSuballocationType suballocType, + uint32_t strategy, VmaAllocation* pAllocation); VkResult CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIndex); @@ -6576,16 +6587,6 @@ void VmaBlockMetadata_Generic::PrintDetailedMap(class VmaJsonWriter& json) const #endif // #if VMA_STATS_STRING_ENABLED -/* -How many suitable free suballocations to analyze before choosing best one. -- Set to 1 to use First-Fit algorithm - first suitable free suballocation will - be chosen. -- Set to UINT32_MAX to use Best-Fit/Worst-Fit algorithm - all suitable free - suballocations will be analized and best one will be chosen. -- Any other value is also acceptable. -*/ -//static const uint32_t MAX_SUITABLE_SUBALLOCATIONS_TO_CHECK = 8; - bool VmaBlockMetadata_Generic::CreateAllocationRequest( uint32_t currentFrameIndex, uint32_t frameInUseCount, @@ -6595,6 +6596,7 @@ bool VmaBlockMetadata_Generic::CreateAllocationRequest( bool upperAddress, VmaSuballocationType allocType, bool canMakeOtherLost, + uint32_t strategy, VmaAllocationRequest* pAllocationRequest) { VMA_ASSERT(allocSize > 0); @@ -6604,7 +6606,8 @@ bool VmaBlockMetadata_Generic::CreateAllocationRequest( VMA_HEAVY_ASSERT(Validate()); // There is not enough total free space in this block to fullfill the request: Early return. - if(canMakeOtherLost == false && m_SumFreeSize < allocSize + 2 * VMA_DEBUG_MARGIN) + if(canMakeOtherLost == false && + m_SumFreeSize < allocSize + 2 * VMA_DEBUG_MARGIN) { return false; } @@ -6613,7 +6616,7 @@ bool VmaBlockMetadata_Generic::CreateAllocationRequest( const size_t freeSuballocCount = m_FreeSuballocationsBySize.size(); if(freeSuballocCount > 0) { - if(VMA_BEST_FIT) + if(strategy == VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT) { // Find first free suballocation with size not less than allocSize + 2 * VMA_DEBUG_MARGIN. VmaSuballocationList::iterator* const it = VmaBinaryFindFirstNotLess( @@ -6643,7 +6646,7 @@ bool VmaBlockMetadata_Generic::CreateAllocationRequest( } } } - else + else // WORST_FIT, FIRST_FIT { // Search staring from biggest suballocations. for(size_t index = freeSuballocCount; index--; ) @@ -6700,7 +6703,8 @@ bool VmaBlockMetadata_Generic::CreateAllocationRequest( { tmpAllocRequest.item = suballocIt; - if(tmpAllocRequest.CalcCost() < pAllocationRequest->CalcCost()) + if(tmpAllocRequest.CalcCost() < pAllocationRequest->CalcCost() || + strategy == VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT) { *pAllocationRequest = tmpAllocRequest; } @@ -8306,6 +8310,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest( bool upperAddress, VmaSuballocationType allocType, bool canMakeOtherLost, + uint32_t strategy, VmaAllocationRequest* pAllocationRequest) { VMA_ASSERT(allocSize > 0); @@ -9444,6 +9449,10 @@ VkResult VmaBlockVector::Allocate( const bool canCreateNewBlock = ((createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) == 0) && (m_Blocks.size() < m_MaxBlockCount); + uint32_t strategy = createInfo.flags & ( + VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT | + VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT | + VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT); // If linearAlgorithm is used, canMakeOtherLost is available only when used as ring buffer. // Which in turn is available only when maxBlockCount = 1. @@ -9459,6 +9468,20 @@ VkResult VmaBlockVector::Allocate( return VK_ERROR_FEATURE_NOT_PRESENT; } + // Validate strategy. + switch(strategy) + { + case 0: + strategy = VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT; + break; + case VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT: + case VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT: + case VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT: + break; + default: + return VK_ERROR_FEATURE_NOT_PRESENT; + } + // Early reject: requested allocation size is larger that maximum block size for this block vector. if(size + 2 * VMA_DEBUG_MARGIN > m_PreferredBlockSize) { @@ -9494,6 +9517,7 @@ VkResult VmaBlockVector::Allocate( allocFlagsCopy, createInfo.pUserData, suballocType, + strategy, pAllocation); if(res == VK_SUCCESS) { @@ -9504,25 +9528,54 @@ VkResult VmaBlockVector::Allocate( } else { - // Forward order in m_Blocks - prefer blocks with smallest amount of free space. - for(size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex ) + if(strategy == VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT) { - VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex]; - VMA_ASSERT(pCurrBlock); - VkResult res = AllocateFromBlock( - pCurrBlock, - hCurrentPool, - currentFrameIndex, - size, - alignment, - allocFlagsCopy, - createInfo.pUserData, - suballocType, - pAllocation); - if(res == VK_SUCCESS) + // Forward order in m_Blocks - prefer blocks with smallest amount of free space. + for(size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex ) { - VMA_DEBUG_LOG(" Returned from existing block #%u", (uint32_t)blockIndex); - return VK_SUCCESS; + VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex]; + VMA_ASSERT(pCurrBlock); + VkResult res = AllocateFromBlock( + pCurrBlock, + hCurrentPool, + currentFrameIndex, + size, + alignment, + allocFlagsCopy, + createInfo.pUserData, + suballocType, + strategy, + pAllocation); + if(res == VK_SUCCESS) + { + VMA_DEBUG_LOG(" Returned from existing block #%u", (uint32_t)blockIndex); + return VK_SUCCESS; + } + } + } + else // WORST_FIT, FIRST_FIT + { + // Backward order in m_Blocks - prefer blocks with largest amount of free space. + for(size_t blockIndex = m_Blocks.size(); blockIndex--; ) + { + VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex]; + VMA_ASSERT(pCurrBlock); + VkResult res = AllocateFromBlock( + pCurrBlock, + hCurrentPool, + currentFrameIndex, + size, + alignment, + allocFlagsCopy, + createInfo.pUserData, + suballocType, + strategy, + pAllocation); + if(res == VK_SUCCESS) + { + VMA_DEBUG_LOG(" Returned from existing block #%u", (uint32_t)blockIndex); + return VK_SUCCESS; + } } } } @@ -9589,6 +9642,7 @@ VkResult VmaBlockVector::Allocate( allocFlagsCopy, createInfo.pUserData, suballocType, + strategy, pAllocation); if(res == VK_SUCCESS) { @@ -9615,34 +9669,76 @@ VkResult VmaBlockVector::Allocate( VkDeviceSize bestRequestCost = VK_WHOLE_SIZE; // 1. Search existing allocations. - // Forward order in m_Blocks - prefer blocks with smallest amount of free space. - for(size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex ) + if(strategy == VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT) { - VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex]; - VMA_ASSERT(pCurrBlock); - VmaAllocationRequest currRequest = {}; - if(pCurrBlock->m_pMetadata->CreateAllocationRequest( - currentFrameIndex, - m_FrameInUseCount, - m_BufferImageGranularity, - size, - alignment, - (createInfo.flags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0, - suballocType, - canMakeOtherLost, - &currRequest)) + // Forward order in m_Blocks - prefer blocks with smallest amount of free space. + for(size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex ) { - const VkDeviceSize currRequestCost = currRequest.CalcCost(); - if(pBestRequestBlock == VMA_NULL || - currRequestCost < bestRequestCost) + VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex]; + VMA_ASSERT(pCurrBlock); + VmaAllocationRequest currRequest = {}; + if(pCurrBlock->m_pMetadata->CreateAllocationRequest( + currentFrameIndex, + m_FrameInUseCount, + m_BufferImageGranularity, + size, + alignment, + (createInfo.flags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0, + suballocType, + canMakeOtherLost, + strategy, + &currRequest)) { - pBestRequestBlock = pCurrBlock; - bestRequest = currRequest; - bestRequestCost = currRequestCost; - - if(bestRequestCost == 0) + const VkDeviceSize currRequestCost = currRequest.CalcCost(); + if(pBestRequestBlock == VMA_NULL || + currRequestCost < bestRequestCost) { - break; + pBestRequestBlock = pCurrBlock; + bestRequest = currRequest; + bestRequestCost = currRequestCost; + + if(bestRequestCost == 0) + { + break; + } + } + } + } + } + else // WORST_FIT, FIRST_FIT + { + // Backward order in m_Blocks - prefer blocks with largest amount of free space. + for(size_t blockIndex = m_Blocks.size(); blockIndex--; ) + { + VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex]; + VMA_ASSERT(pCurrBlock); + VmaAllocationRequest currRequest = {}; + if(pCurrBlock->m_pMetadata->CreateAllocationRequest( + currentFrameIndex, + m_FrameInUseCount, + m_BufferImageGranularity, + size, + alignment, + (createInfo.flags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0, + suballocType, + canMakeOtherLost, + strategy, + &currRequest)) + { + const VkDeviceSize currRequestCost = currRequest.CalcCost(); + if(pBestRequestBlock == VMA_NULL || + currRequestCost < bestRequestCost || + strategy == VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT) + { + pBestRequestBlock = pCurrBlock; + bestRequest = currRequest; + bestRequestCost = currRequestCost; + + if(bestRequestCost == 0 || + strategy == VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT) + { + break; + } } } } @@ -9835,6 +9931,7 @@ VkResult VmaBlockVector::AllocateFromBlock( VmaAllocationCreateFlags allocFlags, void* pUserData, VmaSuballocationType suballocType, + uint32_t strategy, VmaAllocation* pAllocation) { VMA_ASSERT((allocFlags & VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT) == 0); @@ -9852,6 +9949,7 @@ VkResult VmaBlockVector::AllocateFromBlock( isUpperAddress, suballocType, false, // canMakeOtherLost + strategy, &currRequest)) { // Allocate from pCurrBlock. @@ -10262,6 +10360,7 @@ VkResult VmaDefragmentator::DefragmentRound( false, // upperAddress suballocType, false, // canMakeOtherLost + VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT, &dstAllocRequest) && MoveMakesSense( dstBlockIndex, dstAllocRequest.offset, srcBlockIndex, srcOffset)) From 0667e33bdd955326477d3404f5466c9ea0d2fef7 Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Fri, 24 Aug 2018 17:26:44 +0200 Subject: [PATCH 02/30] Added allocation strategy to main benchmark. --- src/Tests.cpp | 81 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/src/Tests.cpp b/src/Tests.cpp index 842fba2..d04045d 100644 --- a/src/Tests.cpp +++ b/src/Tests.cpp @@ -21,10 +21,10 @@ static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_SMALL; enum class FREE_ORDER { FORWARD, BACKWARD, RANDOM, COUNT }; -static const wchar_t* FREE_ORDER_NAMES[] = { - L"FORWARD", - L"BACKWARD", - L"RANDOM", +static const char* FREE_ORDER_NAMES[] = { + "FORWARD", + "BACKWARD", + "RANDOM", }; struct AllocationSize @@ -45,6 +45,7 @@ struct Config uint32_t ThreadCount; uint32_t ThreadsUsingCommonAllocationsProbabilityPercent; FREE_ORDER FreeOrder; + VmaAllocationCreateFlags AllocationStrategy; // For VMA_ALLOCATION_CREATE_STRATEGY_* }; struct Result @@ -264,6 +265,7 @@ VkResult MainTest(Result& outResult, const Config& config) VmaAllocationCreateInfo memReq = {}; memReq.usage = (VmaMemoryUsage)(VMA_MEMORY_USAGE_GPU_ONLY + memUsageIndex); + memReq.flags |= config.AllocationStrategy; Allocation allocation = {}; VmaAllocationInfo allocationInfo; @@ -1002,7 +1004,7 @@ void TestDefragmentationFull() for(size_t i = 0; i < allocations.size(); ++i) ValidateAllocationData(allocations[i]); - SaveAllocatorStatsToFile(L"Before.csv"); + //SaveAllocatorStatsToFile(L"Before.csv"); { std::vector vmaAllocations(allocations.size()); @@ -1051,9 +1053,9 @@ void TestDefragmentationFull() for(size_t i = 0; i < allocations.size(); ++i) ValidateAllocationData(allocations[i]); - wchar_t fileName[MAX_PATH]; - swprintf(fileName, MAX_PATH, L"After_%02u.csv", defragIndex); - SaveAllocatorStatsToFile(fileName); + //wchar_t fileName[MAX_PATH]; + //swprintf(fileName, MAX_PATH, L"After_%02u.csv", defragIndex); + //SaveAllocatorStatsToFile(fileName); } } @@ -2210,9 +2212,9 @@ static void BenchmarkLinearAllocatorCase(bool linear, bool empty, FREE_ORDER fre vmaDestroyPool(g_hAllocator, pool); - wprintf(L" LinearAlgorithm=%u %s FreeOrder=%s: allocations %g s, free %g s\n", + printf(" LinearAlgorithm=%u %s FreeOrder=%s: allocations %g s, free %g s\n", linear ? 1 : 0, - empty ? L"Empty" : L"Not empty", + empty ? "Empty" : "Not empty", FREE_ORDER_NAMES[(size_t)freeOrder], ToFloatSeconds(allocTotalDuration), ToFloatSeconds(freeTotalDuration)); @@ -3351,12 +3353,12 @@ static void WriteMainTestResult( fprintf(file, "%s,%s,%s," - "BeginBytesToAllocate=%I64u MaxBytesToAllocate=%I64u AdditionalOperationCount=%u ThreadCount=%u FreeOrder=%d," + "BeginBytesToAllocate=%I64u MaxBytesToAllocate=%I64u AdditionalOperationCount=%u ThreadCount=%u FreeOrder=%s," "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u\n", codeDescription, testDescription, timeStr, - config.BeginBytesToAllocate, config.MaxBytesToAllocate, config.AdditionalOperationCount, config.ThreadCount, (uint32_t)config.FreeOrder, + config.BeginBytesToAllocate, config.MaxBytesToAllocate, config.AdditionalOperationCount, config.ThreadCount, FREE_ORDER_NAMES[(uint32_t)config.FreeOrder], totalTimeSeconds * 1e6f, allocationTimeMinSeconds * 1e6f, allocationTimeAvgSeconds * 1e6f, @@ -3447,6 +3449,7 @@ static void PerformCustomMainTest(FILE* file) config.FreeOrder = FREE_ORDER::FORWARD; config.ThreadCount = 16; config.ThreadsUsingCommonAllocationsProbabilityPercent = 50; + config.AllocationStrategy = 0; // Buffers //config.AllocationSizes.push_back({4, 16, 1024}); @@ -3522,6 +3525,18 @@ static void PerformMainTests(FILE* file) case CONFIG_TYPE_MAXIMUM: threadCountCount = 7; break; default: assert(0); } + + size_t strategyCount = 0; + switch(ConfigType) + { + case CONFIG_TYPE_MINIMUM: strategyCount = 1; break; + case CONFIG_TYPE_SMALL: strategyCount = 1; break; + case CONFIG_TYPE_AVERAGE: strategyCount = 2; break; + case CONFIG_TYPE_LARGE: strategyCount = 2; break; + case CONFIG_TYPE_MAXIMUM: strategyCount = 3; break; + default: assert(0); + } + for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex) { std::string desc1; @@ -3718,16 +3733,38 @@ static void PerformMainTests(FILE* file) assert(0); } - const char* testDescription = desc5.c_str(); - - for(size_t repeat = 0; repeat < repeatCount; ++repeat) + for(size_t strategyIndex = 0; strategyIndex < strategyCount; ++strategyIndex) { - printf("%s Repeat %u\n", testDescription, (uint32_t)repeat); + std::string desc6 = desc5; + switch(strategyIndex) + { + case 0: + desc6 += " BestFit"; + config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT; + break; + case 1: + desc6 += " WorstFit"; + config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT; + break; + case 2: + desc6 += " FirstFit"; + config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT; + break; + default: + assert(0); + } - Result result{}; - VkResult res = MainTest(result, config); - assert(res == VK_SUCCESS); - WriteMainTestResult(file, CODE_DESCRIPTION, testDescription, config, result); + const char* testDescription = desc6.c_str(); + + for(size_t repeat = 0; repeat < repeatCount; ++repeat) + { + printf("%s Repeat %u\n", testDescription, (uint32_t)repeat); + + Result result{}; + VkResult res = MainTest(result, config); + assert(res == VK_SUCCESS); + WriteMainTestResult(file, CODE_DESCRIPTION, testDescription, config, result); + } } } } @@ -3978,6 +4015,7 @@ BenchmarkLinearAllocator(); // # Simple tests +#if 0 TestBasics(); #if VMA_DEBUG_MARGIN TestDebugMargin(); @@ -3996,6 +4034,7 @@ BenchmarkLinearAllocator(); BenchmarkLinearAllocator(); TestDefragmentationSimple(); TestDefragmentationFull(); +#endif // # Detailed tests FILE* file; @@ -4006,8 +4045,10 @@ BenchmarkLinearAllocator(); PerformMainTests(file); //PerformCustomMainTest(file); +#if 0 WritePoolTestResultHeader(file); PerformPoolTests(file); +#endif //PerformCustomPoolTest(file); fclose(file); From 740b08f6ebe6df544ca236d31f8e080485ac12f9 Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Mon, 27 Aug 2018 13:42:07 +0200 Subject: [PATCH 03/30] Testing environment: Improved formatting of CSV faile with results of main benchmark. --- src/Tests.cpp | 61 ++++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/Tests.cpp b/src/Tests.cpp index d04045d..010d1c2 100644 --- a/src/Tests.cpp +++ b/src/Tests.cpp @@ -3318,8 +3318,8 @@ static void TestMappingMultithreaded() static void WriteMainTestResultHeader(FILE* file) { fprintf(file, - "Code,Test,Time," - "Config," + "Code,Time," + "Threads,Buffers and images,Sizes,Operations,Allocation strategy,Free order," "Total Time (us)," "Allocation Time Min (us)," "Allocation Time Avg (us)," @@ -3353,12 +3353,10 @@ static void WriteMainTestResult( fprintf(file, "%s,%s,%s," - "BeginBytesToAllocate=%I64u MaxBytesToAllocate=%I64u AdditionalOperationCount=%u ThreadCount=%u FreeOrder=%s," "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u\n", codeDescription, - testDescription, timeStr, - config.BeginBytesToAllocate, config.MaxBytesToAllocate, config.AdditionalOperationCount, config.ThreadCount, FREE_ORDER_NAMES[(uint32_t)config.FreeOrder], + testDescription, totalTimeSeconds * 1e6f, allocationTimeMinSeconds * 1e6f, allocationTimeAvgSeconds * 1e6f, @@ -3590,9 +3588,9 @@ static void PerformMainTests(FILE* file) std::string desc2 = desc1; switch(buffersVsImagesIndex) { - case 0: desc2 += " Buffers"; break; - case 1: desc2 += " Images"; break; - case 2: desc2 += " Buffers+Images"; break; + case 0: desc2 += ",Buffers"; break; + case 1: desc2 += ",Images"; break; + case 2: desc2 += ",Buffers+Images"; break; default: assert(0); } @@ -3604,9 +3602,9 @@ static void PerformMainTests(FILE* file) std::string desc3 = desc2; switch(smallVsLargeIndex) { - case 0: desc3 += " Small"; break; - case 1: desc3 += " Large"; break; - case 2: desc3 += " Small+Large"; break; + case 0: desc3 += ",Small"; break; + case 1: desc3 += ",Large"; break; + case 2: desc3 += ",Small+Large"; break; default: assert(0); } @@ -3710,22 +3708,22 @@ static void PerformMainTests(FILE* file) switch(beginBytesToAllocateIndex) { case 0: - desc5 += " Allocate_100%"; + desc5 += ",Allocate_100%"; config.BeginBytesToAllocate = config.MaxBytesToAllocate; config.AdditionalOperationCount = 0; break; case 1: - desc5 += " Allocate_50%+Operations"; + desc5 += ",Allocate_50%+Operations"; config.BeginBytesToAllocate = config.MaxBytesToAllocate * 50 / 100; config.AdditionalOperationCount = 1024; break; case 2: - desc5 += " Allocate_5%+Operations"; + desc5 += ",Allocate_5%+Operations"; config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100; config.AdditionalOperationCount = 1024; break; case 3: - desc5 += " Allocate_95%+Operations"; + desc5 += ",Allocate_95%+Operations"; config.BeginBytesToAllocate = config.MaxBytesToAllocate * 95 / 100; config.AdditionalOperationCount = 1024; break; @@ -3739,31 +3737,42 @@ static void PerformMainTests(FILE* file) switch(strategyIndex) { case 0: - desc6 += " BestFit"; + desc6 += ",BestFit"; config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT; break; case 1: - desc6 += " WorstFit"; + desc6 += ",WorstFit"; config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT; break; case 2: - desc6 += " FirstFit"; + desc6 += ",FirstFit"; config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT; break; default: assert(0); } - const char* testDescription = desc6.c_str(); + switch(config.FreeOrder) + { + case FREE_ORDER::FORWARD: desc6 += ",Forward"; break; + case FREE_ORDER::BACKWARD: desc6 += ",Backward"; break; + case FREE_ORDER::RANDOM: desc6 += ",Random"; break; + default: assert(0); + } + + const char* testDescription = desc6.c_str(); for(size_t repeat = 0; repeat < repeatCount; ++repeat) { - printf("%s Repeat %u\n", testDescription, (uint32_t)repeat); + printf("%s #%u\n", testDescription, (uint32_t)repeat); Result result{}; VkResult res = MainTest(result, config); assert(res == VK_SUCCESS); - WriteMainTestResult(file, CODE_DESCRIPTION, testDescription, config, result); + if(file) + { + WriteMainTestResult(file, CODE_DESCRIPTION, testDescription, config, result); + } } } } @@ -3984,7 +3993,7 @@ static void PerformPoolTests(FILE* file) for(size_t repeat = 0; repeat < repeatCount; ++repeat) { - printf("%s Repeat %u\n", testDescription, (uint32_t)repeat); + printf("%s #%u\n", testDescription, (uint32_t)repeat); PoolTestResult result{}; g_MemoryAliasingWarningEnabled = false; @@ -4006,16 +4015,11 @@ void Test() if(false) { // # Temporarily insert custom tests here -TestLinearAllocator(); -ManuallyTestLinearAllocator(); -TestLinearAllocatorMultiBlock(); -BenchmarkLinearAllocator(); return; } // # Simple tests -#if 0 TestBasics(); #if VMA_DEBUG_MARGIN TestDebugMargin(); @@ -4034,7 +4038,6 @@ BenchmarkLinearAllocator(); BenchmarkLinearAllocator(); TestDefragmentationSimple(); TestDefragmentationFull(); -#endif // # Detailed tests FILE* file; @@ -4045,10 +4048,8 @@ BenchmarkLinearAllocator(); PerformMainTests(file); //PerformCustomMainTest(file); -#if 0 WritePoolTestResultHeader(file); PerformPoolTests(file); -#endif //PerformCustomPoolTest(file); fclose(file); From 33d2ce744b715fefc685f9626d64eab56875fe4d Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Mon, 27 Aug 2018 13:59:13 +0200 Subject: [PATCH 04/30] Added writing results of linear allocator benchmark to file "LinearAllocator.csv". --- src/Tests.cpp | 84 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/src/Tests.cpp b/src/Tests.cpp index 010d1c2..a508fb0 100644 --- a/src/Tests.cpp +++ b/src/Tests.cpp @@ -7,6 +7,8 @@ #ifdef _WIN32 +static const char* CODE_DESCRIPTION = "Foo"; + enum CONFIG_TYPE { CONFIG_TYPE_MINIMUM, CONFIG_TYPE_SMALL, @@ -213,6 +215,15 @@ public: } }; +static void CurrentTimeToStr(std::string& out) +{ + time_t rawTime; time(&rawTime); + struct tm timeInfo; localtime_s(&timeInfo, &rawTime); + char timeStr[128]; + strftime(timeStr, _countof(timeStr), "%c", &timeInfo); + out = timeStr; +} + VkResult MainTest(Result& outResult, const Config& config) { assert(config.ThreadCount > 0); @@ -2093,7 +2104,7 @@ static void ManuallyTestLinearAllocator() vmaDestroyPool(g_hAllocator, pool); } -static void BenchmarkLinearAllocatorCase(bool linear, bool empty, FREE_ORDER freeOrder) +static void BenchmarkLinearAllocatorCase(FILE* file, bool linear, bool empty, FREE_ORDER freeOrder) { RandomNumberGenerator rand{16223}; @@ -2212,18 +2223,43 @@ static void BenchmarkLinearAllocatorCase(bool linear, bool empty, FREE_ORDER fre vmaDestroyPool(g_hAllocator, pool); + const float allocTotalSeconds = ToFloatSeconds(allocTotalDuration); + const float freeTotalSeconds = ToFloatSeconds(freeTotalDuration); + printf(" LinearAlgorithm=%u %s FreeOrder=%s: allocations %g s, free %g s\n", linear ? 1 : 0, empty ? "Empty" : "Not empty", FREE_ORDER_NAMES[(size_t)freeOrder], - ToFloatSeconds(allocTotalDuration), - ToFloatSeconds(freeTotalDuration)); + allocTotalSeconds, + freeTotalSeconds); + + if(file) + { + std::string currTime; + CurrentTimeToStr(currTime); + + fprintf(file, "%s,%s,%u,%u,%s,%g,%g\n", + CODE_DESCRIPTION, currTime.c_str(), + linear ? 1 : 0, + empty ? 1 : 0, + FREE_ORDER_NAMES[(uint32_t)freeOrder], + allocTotalSeconds, + freeTotalSeconds); + } } -static void BenchmarkLinearAllocator() +static void BenchmarkLinearAllocator(FILE* file) { wprintf(L"Benchmark linear allocator\n"); + if(file) + { + fprintf(file, + "Code,Time," + "Linear,Empty,Free order," + "Allocation time (s),Deallocation time (s)\n"); + } + uint32_t freeOrderCount = 1; if(ConfigType >= CONFIG_TYPE::CONFIG_TYPE_LARGE) freeOrderCount = 3; @@ -2248,6 +2284,7 @@ static void BenchmarkLinearAllocator() for(uint32_t linearIndex = 0; linearIndex < 2; ++linearIndex) { BenchmarkLinearAllocatorCase( + file, linearIndex ? 1 : 0, // linear emptyIndex ? 0 : 1, // empty freeOrder); // freeOrder @@ -3346,16 +3383,14 @@ static void WriteMainTestResult( float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg); float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax); - time_t rawTime; time(&rawTime); - struct tm timeInfo; localtime_s(&timeInfo, &rawTime); - char timeStr[128]; - strftime(timeStr, _countof(timeStr), "%c", &timeInfo); + std::string currTime; + CurrentTimeToStr(currTime); fprintf(file, "%s,%s,%s," "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u\n", codeDescription, - timeStr, + currTime.c_str(), testDescription, totalTimeSeconds * 1e6f, allocationTimeMinSeconds * 1e6f, @@ -3402,10 +3437,8 @@ static void WritePoolTestResult( float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg); float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax); - time_t rawTime; time(&rawTime); - struct tm timeInfo; localtime_s(&timeInfo, &rawTime); - char timeStr[128]; - strftime(timeStr, _countof(timeStr), "%c", &timeInfo); + std::string currTime; + CurrentTimeToStr(currTime); fprintf(file, "%s,%s,%s," @@ -3414,7 +3447,7 @@ static void WritePoolTestResult( // General codeDescription, testDescription, - timeStr, + currTime.c_str(), // Config config.ThreadCount, (unsigned long long)config.PoolSize, @@ -3501,8 +3534,6 @@ static void PerformCustomPoolTest(FILE* file) WritePoolTestResult(file, "Code desc", "Test desc", config, result); } -static const char* CODE_DESCRIPTION = "Foo"; - static void PerformMainTests(FILE* file) { uint32_t repeatCount = 1; @@ -3752,13 +3783,8 @@ static void PerformMainTests(FILE* file) assert(0); } - switch(config.FreeOrder) - { - case FREE_ORDER::FORWARD: desc6 += ",Forward"; break; - case FREE_ORDER::BACKWARD: desc6 += ",Backward"; break; - case FREE_ORDER::RANDOM: desc6 += ",Random"; break; - default: assert(0); - } + desc6 += ','; + desc6 += FREE_ORDER_NAMES[(uint32_t)config.FreeOrder]; const char* testDescription = desc6.c_str(); @@ -4035,7 +4061,17 @@ void Test() TestLinearAllocator(); ManuallyTestLinearAllocator(); TestLinearAllocatorMultiBlock(); - BenchmarkLinearAllocator(); + + { + FILE* file; + fopen_s(&file, "LinearAllocator.csv", "w"); + assert(file != NULL); + + BenchmarkLinearAllocator(file); + + fclose(file); + } + TestDefragmentationSimple(); TestDefragmentationFull(); From 0a3fb6ca6029553dd1bd49ad1f5079861505d484 Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Mon, 27 Aug 2018 14:40:27 +0200 Subject: [PATCH 05/30] Tests: benchmark of linear allocator now compares to various allocation strategies. --- src/Tests.cpp | 85 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 19 deletions(-) diff --git a/src/Tests.cpp b/src/Tests.cpp index a508fb0..c5cf05b 100644 --- a/src/Tests.cpp +++ b/src/Tests.cpp @@ -130,6 +130,33 @@ struct BufferInfo VmaAllocation Allocation = VK_NULL_HANDLE; }; +static uint32_t GetAllocationStrategyCount() +{ + uint32_t strategyCount = 0; + switch(ConfigType) + { + case CONFIG_TYPE_MINIMUM: strategyCount = 1; break; + case CONFIG_TYPE_SMALL: strategyCount = 1; break; + case CONFIG_TYPE_AVERAGE: strategyCount = 2; break; + case CONFIG_TYPE_LARGE: strategyCount = 2; break; + case CONFIG_TYPE_MAXIMUM: strategyCount = 3; break; + default: assert(0); + } + return strategyCount; +} + +static const char* GetAllocationStrategyName(VmaAllocationCreateFlags allocStrategy) +{ + switch(allocStrategy) + { + case VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT: return "BEST_FIT"; break; + case VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT: return "WORST_FIT"; break; + case VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT: return "FIRST_FIT"; break; + case 0: return "Default"; break; + default: assert(0); return ""; + } +} + static void InitResult(Result& outResult) { outResult.TotalTime = duration::zero(); @@ -2104,7 +2131,11 @@ static void ManuallyTestLinearAllocator() vmaDestroyPool(g_hAllocator, pool); } -static void BenchmarkLinearAllocatorCase(FILE* file, bool linear, bool empty, FREE_ORDER freeOrder) +static void BenchmarkLinearAllocatorCase(FILE* file, + bool linear, + bool empty, + VmaAllocationCreateFlags allocStrategy, + FREE_ORDER freeOrder) { RandomNumberGenerator rand{16223}; @@ -2145,6 +2176,7 @@ static void BenchmarkLinearAllocatorCase(FILE* file, bool linear, bool empty, FR VmaAllocationCreateInfo allocCreateInfo = {}; allocCreateInfo.pool = pool; + allocCreateInfo.flags = allocStrategy; VmaAllocation alloc; std::vector baseAllocations; @@ -2226,9 +2258,10 @@ static void BenchmarkLinearAllocatorCase(FILE* file, bool linear, bool empty, FR const float allocTotalSeconds = ToFloatSeconds(allocTotalDuration); const float freeTotalSeconds = ToFloatSeconds(freeTotalDuration); - printf(" LinearAlgorithm=%u %s FreeOrder=%s: allocations %g s, free %g s\n", + printf(" LinearAlgorithm=%u %s Allocation=%s FreeOrder=%s: allocations %g s, free %g s\n", linear ? 1 : 0, empty ? "Empty" : "Not empty", + GetAllocationStrategyName(allocStrategy), FREE_ORDER_NAMES[(size_t)freeOrder], allocTotalSeconds, freeTotalSeconds); @@ -2238,10 +2271,11 @@ static void BenchmarkLinearAllocatorCase(FILE* file, bool linear, bool empty, FR std::string currTime; CurrentTimeToStr(currTime); - fprintf(file, "%s,%s,%u,%u,%s,%g,%g\n", + fprintf(file, "%s,%s,%u,%u,%s,%s,%g,%g\n", CODE_DESCRIPTION, currTime.c_str(), linear ? 1 : 0, empty ? 1 : 0, + GetAllocationStrategyName(allocStrategy), FREE_ORDER_NAMES[(uint32_t)freeOrder], allocTotalSeconds, freeTotalSeconds); @@ -2256,7 +2290,7 @@ static void BenchmarkLinearAllocator(FILE* file) { fprintf(file, "Code,Time," - "Linear,Empty,Free order," + "Linear,Empty,Allocation strategy,Free order," "Allocation time (s),Deallocation time (s)\n"); } @@ -2267,6 +2301,7 @@ static void BenchmarkLinearAllocator(FILE* file) freeOrderCount = 2; const uint32_t emptyCount = ConfigType >= CONFIG_TYPE::CONFIG_TYPE_SMALL ? 2 : 1; + const uint32_t allocStrategyCount = GetAllocationStrategyCount(); for(uint32_t freeOrderIndex = 0; freeOrderIndex < freeOrderCount; ++freeOrderIndex) { @@ -2283,11 +2318,30 @@ static void BenchmarkLinearAllocator(FILE* file) { for(uint32_t linearIndex = 0; linearIndex < 2; ++linearIndex) { - BenchmarkLinearAllocatorCase( - file, - linearIndex ? 1 : 0, // linear - emptyIndex ? 0 : 1, // empty - freeOrder); // freeOrder + const bool linear = linearIndex ? 1 : 0; + + uint32_t currAllocStrategyCount = linear ? 1 : allocStrategyCount; + for(uint32_t allocStrategyIndex = 0; allocStrategyIndex < currAllocStrategyCount; ++allocStrategyIndex) + { + VmaAllocatorCreateFlags strategy = 0; + if(!linear) + { + switch(allocStrategyIndex) + { + case 0: strategy = VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT; break; + case 1: strategy = VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT; break; + case 2: strategy = VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT; break; + default: assert(0); + } + } + + BenchmarkLinearAllocatorCase( + file, + linear, // linear + emptyIndex ? 0 : 1, // empty + strategy, + freeOrder); // freeOrder + } } } } @@ -3555,16 +3609,7 @@ static void PerformMainTests(FILE* file) default: assert(0); } - size_t strategyCount = 0; - switch(ConfigType) - { - case CONFIG_TYPE_MINIMUM: strategyCount = 1; break; - case CONFIG_TYPE_SMALL: strategyCount = 1; break; - case CONFIG_TYPE_AVERAGE: strategyCount = 2; break; - case CONFIG_TYPE_LARGE: strategyCount = 2; break; - case CONFIG_TYPE_MAXIMUM: strategyCount = 3; break; - default: assert(0); - } + const size_t strategyCount = GetAllocationStrategyCount(); for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex) { @@ -4041,6 +4086,8 @@ void Test() if(false) { // # Temporarily insert custom tests here + // ######################################## + // ######################################## return; } From 6d9d7183432f25bb8b64006d9227a10648ff7da4 Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Tue, 28 Aug 2018 13:09:27 +0200 Subject: [PATCH 06/30] TEMP started coding buddy algorithm. --- src/vk_mem_alloc.h | 378 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 349 insertions(+), 29 deletions(-) diff --git a/src/vk_mem_alloc.h b/src/vk_mem_alloc.h index 758e9f6..299b866 100644 --- a/src/vk_mem_alloc.h +++ b/src/vk_mem_alloc.h @@ -1836,6 +1836,13 @@ typedef enum VmaAllocationCreateFlagBits { */ VMA_ALLOCATION_CREATE_STRATEGY_MIN_FRAGMENTATION_BIT = VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT, + /** A bit mask to extract only `STRATEGY` bits from entire set of flags. + */ + VMA_ALLOCATION_CREATE_STRATEGY_MASK = + VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT | + VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT | + VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT, + VMA_ALLOCATION_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VmaAllocationCreateFlagBits; typedef VkFlags VmaAllocationCreateFlags; @@ -1977,6 +1984,15 @@ When using this flag, you must specify VmaPoolCreateInfo::maxBlockCount == 1 (or */ VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT = 0x00000004, + /** TODO */ + VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT = 0x00000008, + + /** Bit mask to extract only `ALGORITHM` bits from entire set of flags. + */ + VMA_POOL_CREATE_ALGORITHM_MASK = + VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT | + VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT, + VMA_POOL_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VmaPoolCreateFlagBits; typedef VkFlags VmaPoolCreateFlags; @@ -2940,16 +2956,45 @@ static inline T VmaAlignDown(T val, T align) // Division with mathematical rounding to nearest number. template -inline T VmaRoundDiv(T x, T y) +static inline T VmaRoundDiv(T x, T y) { return (x + (y / (T)2)) / y; } +// Returns smallest power of 2 greater or equal to v. +static inline uint32_t VmaNextPow2(uint32_t v) +{ + v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; +} +// Returns biggest power of 2 less or equal to v. +/* +static inline uint32_t VmaPrevPow2(uint32_t v) +{ + +} +*/ + static inline bool VmaStrIsEmpty(const char* pStr) { return pStr == VMA_NULL || *pStr == '\0'; } +static const char* VmaAlgorithmToStr(uint32_t algorithm) +{ + switch(algorithm) + { + case VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT: + return "Linear"; + case VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT: + return "Buddy"; + case 0: + return "Default"; + default: + VMA_ASSERT(0); + return ""; + } +} + #ifndef VMA_SORT template @@ -4869,6 +4914,96 @@ private: void CleanupAfterFree(); }; +class VmaBlockMetadata_Buddy : public VmaBlockMetadata +{ + VMA_CLASS_NO_COPY(VmaBlockMetadata_Buddy) +public: + VmaBlockMetadata_Buddy(VmaAllocator hAllocator); + virtual ~VmaBlockMetadata_Buddy(); + virtual void Init(VkDeviceSize size); + + virtual bool Validate() const; + virtual size_t GetAllocationCount() const; + virtual VkDeviceSize GetSumFreeSize() const; + virtual VkDeviceSize GetUnusedRangeSizeMax() const; + virtual bool IsEmpty() const; + + virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const; + virtual void AddPoolStats(VmaPoolStats& inoutStats) const; + +#if VMA_STATS_STRING_ENABLED + virtual void PrintDetailedMap(class VmaJsonWriter& json) const; +#endif + + virtual bool CreateAllocationRequest( + uint32_t currentFrameIndex, + uint32_t frameInUseCount, + VkDeviceSize bufferImageGranularity, + VkDeviceSize allocSize, + VkDeviceSize allocAlignment, + bool upperAddress, + VmaSuballocationType allocType, + bool canMakeOtherLost, + uint32_t strategy, + VmaAllocationRequest* pAllocationRequest); + + virtual bool MakeRequestedAllocationsLost( + uint32_t currentFrameIndex, + uint32_t frameInUseCount, + VmaAllocationRequest* pAllocationRequest); + + virtual uint32_t MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount); + + virtual VkResult CheckCorruption(const void* pBlockData); + + virtual void Alloc( + const VmaAllocationRequest& request, + VmaSuballocationType type, + VkDeviceSize allocSize, + bool upperAddress, + VmaAllocation hAllocation); + + virtual void Free(const VmaAllocation allocation); + virtual void FreeAtOffset(VkDeviceSize offset); + +private: + static const size_t MAX_LEVELS = 30; + + struct Node + { + enum TYPE + { + TYPE_FREE, + TYPE_ALLOCATION, + TYPE_SPLIT, + TYPE_COUNT + } type; + Node* parent; + Node* buddy; + union + { + struct + { + Node* nextFree; + } free; + struct + { + VmaAllocation alloc; + } allocation; + struct + { + Node* leftChild; + } split; + }; + }; + + Node* m_Root; + Node* m_FreeList[MAX_LEVELS]; + + void DeleteNode(Node* node); + bool ValidateNode(const Node* parent, const Node* curr) const; +}; + /* Represents a single block of device memory (`VkDeviceMemory`) with all the data about its regions (aka suballocations, #VmaAllocation), assigned and free. @@ -4896,7 +5031,7 @@ public: VkDeviceMemory newMemory, VkDeviceSize newSize, uint32_t id, - bool linearAlgorithm); + uint32_t algorithm); // Always call before destruction. void Destroy(VmaAllocator allocator); @@ -4968,7 +5103,7 @@ public: uint32_t frameInUseCount, bool isCustomPool, bool explicitBlockSize, - bool linearAlgorithm); + uint32_t algorithm); ~VmaBlockVector(); VkResult CreateMinBlocks(); @@ -4977,7 +5112,7 @@ public: VkDeviceSize GetPreferredBlockSize() const { return m_PreferredBlockSize; } VkDeviceSize GetBufferImageGranularity() const { return m_BufferImageGranularity; } uint32_t GetFrameInUseCount() const { return m_FrameInUseCount; } - bool UsesLinearAlgorithm() const { return m_LinearAlgorithm; } + uint32_t GetAlgorithm() const { return m_Algorithm; } void GetPoolStats(VmaPoolStats* pStats); @@ -5031,7 +5166,7 @@ private: const uint32_t m_FrameInUseCount; const bool m_IsCustomPool; const bool m_ExplicitBlockSize; - const bool m_LinearAlgorithm; + const uint32_t m_Algorithm; bool m_HasEmptyBlock; VMA_MUTEX m_Mutex; // Incrementally sorted by sumFreeSize, ascending. @@ -9089,6 +9224,190 @@ void VmaBlockMetadata_Linear::CleanupAfterFree() } +//////////////////////////////////////////////////////////////////////////////// +// class VmaBlockMetadata_Buddy + +VmaBlockMetadata_Buddy::VmaBlockMetadata_Buddy(VmaAllocator hAllocator) : + m_Root(VMA_NULL) +{ + memset(m_FreeList, 0, sizeof(m_FreeList)); +} + +VmaBlockMetadata_Buddy::~VmaBlockMetadata_Buddy() +{ + DeleteNode(m_Root); +} + +void VmaBlockMetadata_Buddy::Init(VkDeviceSize size) +{ + VmaBlockMetadata::Init(size); + + Node* rootNode = new Node(); + rootNode->type = Node::TYPE_FREE; + rootNode->parent = VMA_NULL; + rootNode->buddy = VMA_NULL; + rootNode->free.nextFree = VMA_NULL; + + m_FreeList[0] = rootNode; +} + +bool VmaBlockMetadata_Buddy::Validate() const +{ + if(!ValidateNode(VMA_NULL, m_Root)) + { + return false; + } + + return true; +} + +size_t VmaBlockMetadata_Buddy::GetAllocationCount() const +{ + return 0; // TODO +} + +VkDeviceSize VmaBlockMetadata_Buddy::GetSumFreeSize() const +{ + return 0; // TODO +} + +VkDeviceSize VmaBlockMetadata_Buddy::VmaBlockMetadata_Buddy::GetUnusedRangeSizeMax() const +{ + return 0; // TODO +} + +bool VmaBlockMetadata_Buddy::IsEmpty() const +{ + return m_Root->type == Node::TYPE_FREE; +} + +void VmaBlockMetadata_Buddy::CalcAllocationStatInfo(VmaStatInfo& outInfo) const +{ + // TODO +} + +void VmaBlockMetadata_Buddy::AddPoolStats(VmaPoolStats& inoutStats) const +{ + // TODO +} + +#if VMA_STATS_STRING_ENABLED + +void VmaBlockMetadata_Buddy::PrintDetailedMap(class VmaJsonWriter& json) const +{ + // TODO +} + +#endif // #if VMA_STATS_STRING_ENABLED + +bool VmaBlockMetadata_Buddy::CreateAllocationRequest( + uint32_t currentFrameIndex, + uint32_t frameInUseCount, + VkDeviceSize bufferImageGranularity, + VkDeviceSize allocSize, + VkDeviceSize allocAlignment, + bool upperAddress, + VmaSuballocationType allocType, + bool canMakeOtherLost, + uint32_t strategy, + VmaAllocationRequest* pAllocationRequest) +{ + // TODO + return false; +} + +bool VmaBlockMetadata_Buddy::MakeRequestedAllocationsLost( + uint32_t currentFrameIndex, + uint32_t frameInUseCount, + VmaAllocationRequest* pAllocationRequest) +{ + return false; // TODO +} + +uint32_t VmaBlockMetadata_Buddy::MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount) +{ + return 0; // TODO +} + +VkResult VmaBlockMetadata_Buddy::CheckCorruption(const void* pBlockData) +{ + return VK_SUCCESS; // TODO +} + +void VmaBlockMetadata_Buddy::Alloc( + const VmaAllocationRequest& request, + VmaSuballocationType type, + VkDeviceSize allocSize, + bool upperAddress, + VmaAllocation hAllocation) +{ +} + +void VmaBlockMetadata_Buddy::Free(const VmaAllocation allocation) +{ +} + +void VmaBlockMetadata_Buddy::FreeAtOffset(VkDeviceSize offset) +{ +} + +void VmaBlockMetadata_Buddy::DeleteNode(Node* node) +{ + if(node->type == Node::TYPE_SPLIT) + { + DeleteNode(node->split.leftChild->buddy); + DeleteNode(node->split.leftChild); + } + + delete node; +} + +bool VmaBlockMetadata_Buddy::ValidateNode(const Node* parent, const Node* curr) const +{ + if(curr->parent != parent) + { + return false; + } + if((curr->buddy == VMA_NULL) != (parent == VMA_NULL)) + { + return false; + } + if(curr->buddy != VMA_NULL && curr->buddy->buddy != curr) + { + return false; + } + switch(curr->type) + { + case Node::TYPE_FREE: + break; + case Node::TYPE_ALLOCATION: + if(curr->allocation.alloc == VK_NULL_HANDLE) + { + return false; + } + break; + case Node::TYPE_SPLIT: + if(curr->split.leftChild == VMA_NULL) + { + return false; + } + if(!ValidateNode(curr, curr->split.leftChild)) + { + return false; + } + if(!ValidateNode(curr, curr->split.leftChild->buddy)) + { + return false; + } + break; + default: + return false; + } + + return true; +} + + //////////////////////////////////////////////////////////////////////////////// // class VmaDeviceMemoryBlock @@ -9108,7 +9427,7 @@ void VmaDeviceMemoryBlock::Init( VkDeviceMemory newMemory, VkDeviceSize newSize, uint32_t id, - bool linearAlgorithm) + uint32_t algorithm) { VMA_ASSERT(m_hMemory == VK_NULL_HANDLE); @@ -9116,12 +9435,18 @@ void VmaDeviceMemoryBlock::Init( m_Id = id; m_hMemory = newMemory; - if(linearAlgorithm) + switch(algorithm) { + case VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT: m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_Linear)(hAllocator); - } - else - { + break; + case VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT: + m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_Buddy)(hAllocator); + break; + default: + VMA_ASSERT(0); + // Fall-through. + case 0: m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_Generic)(hAllocator); } m_pMetadata->Init(newSize); @@ -9351,7 +9676,7 @@ VmaPool_T::VmaPool_T( createInfo.frameInUseCount, true, // isCustomPool createInfo.blockSize != 0, // explicitBlockSize - (createInfo.flags & VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT) != 0), // linearAlgorithm + createInfo.flags & VMA_POOL_CREATE_ALGORITHM_MASK), // algorithm m_Id(0) { } @@ -9374,7 +9699,7 @@ VmaBlockVector::VmaBlockVector( uint32_t frameInUseCount, bool isCustomPool, bool explicitBlockSize, - bool linearAlgorithm) : + uint32_t algorithm) : m_hAllocator(hAllocator), m_MemoryTypeIndex(memoryTypeIndex), m_PreferredBlockSize(preferredBlockSize), @@ -9384,7 +9709,7 @@ VmaBlockVector::VmaBlockVector( m_FrameInUseCount(frameInUseCount), m_IsCustomPool(isCustomPool), m_ExplicitBlockSize(explicitBlockSize), - m_LinearAlgorithm(linearAlgorithm), + m_Algorithm(algorithm), m_Blocks(VmaStlAllocator(hAllocator->GetAllocationCallbacks())), m_HasEmptyBlock(false), m_pDefragmentator(VMA_NULL), @@ -9464,21 +9789,18 @@ VkResult VmaBlockVector::Allocate( const bool canCreateNewBlock = ((createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) == 0) && (m_Blocks.size() < m_MaxBlockCount); - uint32_t strategy = createInfo.flags & ( - VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT | - VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT | - VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT); + uint32_t strategy = createInfo.flags & VMA_ALLOCATION_CREATE_STRATEGY_MASK; // If linearAlgorithm is used, canMakeOtherLost is available only when used as ring buffer. // Which in turn is available only when maxBlockCount = 1. - if(m_LinearAlgorithm && m_MaxBlockCount > 1) + if(m_Algorithm == VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT && m_MaxBlockCount > 1) { canMakeOtherLost = false; } // Upper address can only be used with linear allocator and within single memory block. if(isUpperAddress && - (!m_LinearAlgorithm || m_MaxBlockCount > 1)) + (m_Algorithm != VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT || m_MaxBlockCount > 1)) { return VK_ERROR_FEATURE_NOT_PRESENT; } @@ -9516,7 +9838,7 @@ VkResult VmaBlockVector::Allocate( VmaAllocationCreateFlags allocFlagsCopy = createInfo.flags; allocFlagsCopy &= ~VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT; - if(m_LinearAlgorithm) + if(m_Algorithm == VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT) { // Use only last block. if(!m_Blocks.empty()) @@ -9923,7 +10245,7 @@ void VmaBlockVector::Remove(VmaDeviceMemoryBlock* pBlock) void VmaBlockVector::IncrementallySortBlocks() { - if(!m_LinearAlgorithm) + if(m_Algorithm != VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT) { // Bubble sort only until first swap. for(size_t i = 1; i < m_Blocks.size(); ++i) @@ -10034,7 +10356,7 @@ VkResult VmaBlockVector::CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIn mem, allocInfo.allocationSize, m_NextBlockId++, - m_LinearAlgorithm); + m_Algorithm); m_Blocks.push_back(pBlock); if(pNewBlockIndex != VMA_NULL) @@ -10083,10 +10405,10 @@ void VmaBlockVector::PrintDetailedMap(class VmaJsonWriter& json) json.WriteNumber(m_FrameInUseCount); } - if(m_LinearAlgorithm) + if(m_Algorithm != 0) { - json.WriteString("LinearAlgorithm"); - json.WriteBool(true); + json.WriteString("Algorithm"); + json.WriteString(VmaAlgorithmToStr(m_Algorithm)); } } else @@ -10267,7 +10589,7 @@ VmaDefragmentator::VmaDefragmentator( m_Allocations(VmaStlAllocator(hAllocator->GetAllocationCallbacks())), m_Blocks(VmaStlAllocator(hAllocator->GetAllocationCallbacks())) { - VMA_ASSERT(!pBlockVector->UsesLinearAlgorithm()); + VMA_ASSERT(pBlockVector->GetAlgorithm() == 0); } VmaDefragmentator::~VmaDefragmentator() @@ -11782,7 +12104,7 @@ VkResult VmaAllocator_T::Defragment( if(hAllocPool != VK_NULL_HANDLE) { // Pools with linear algorithm are not defragmented. - if(!hAllocPool->m_BlockVector.UsesLinearAlgorithm()) + if(hAllocPool->m_BlockVector.GetAlgorithm() == 0) { pAllocBlockVector = &hAllocPool->m_BlockVector; } @@ -11988,8 +12310,6 @@ VkResult VmaAllocator_T::CreatePool(const VmaPoolCreateInfo* pCreateInfo, VmaPoo { VMA_DEBUG_LOG(" CreatePool: MemoryTypeIndex=%u, flags=%u", pCreateInfo->memoryTypeIndex, pCreateInfo->flags); - const bool isLinearAlgorithm = (pCreateInfo->flags & VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT) != 0; - VmaPoolCreateInfo newCreateInfo = *pCreateInfo; if(newCreateInfo.maxBlockCount == 0) From a83793a63eeaf09ddd5b0d39c14c8f61e7365ec0 Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Mon, 3 Sep 2018 13:40:42 +0200 Subject: [PATCH 07/30] Buddy allocator - more coding. --- src/Tests.cpp | 69 +++++++++- src/vk_mem_alloc.h | 317 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 362 insertions(+), 24 deletions(-) diff --git a/src/Tests.cpp b/src/Tests.cpp index c5cf05b..21b82f2 100644 --- a/src/Tests.cpp +++ b/src/Tests.cpp @@ -4079,15 +4079,82 @@ static void PerformPoolTests(FILE* file) } } +static void BasicTestBuddyAllocator() +{ + wprintf(L"Basic test buddy allocator\n"); + + RandomNumberGenerator rand{76543}; + + VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + sampleBufCreateInfo.size = 1024; // Whatever. + sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + VmaAllocationCreateInfo sampleAllocCreateInfo = {}; + sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + VmaPoolCreateInfo poolCreateInfo = {}; + VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); + assert(res == VK_SUCCESS); + + poolCreateInfo.blockSize = 1024 * 1024; + poolCreateInfo.flags = VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT; + poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1; + + VmaPool pool = nullptr; + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + assert(res == VK_SUCCESS); + + VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = pool; + + std::vector bufInfo; + BufferInfo newBufInfo; + VmaAllocationInfo allocInfo; + + bufCreateInfo.size = 1024 * 256; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + assert(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + + bufCreateInfo.size = 1024 * 512; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + assert(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + + bufCreateInfo.size = 1024 * 128; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + assert(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + + SaveAllocatorStatsToFile(L"BuddyTest01.json"); + + // Destroy the buffers in random order. + while(!bufInfo.empty()) + { + const size_t indexToDestroy = rand.Generate() % bufInfo.size(); + const BufferInfo& currBufInfo = bufInfo[indexToDestroy]; + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.erase(bufInfo.begin() + indexToDestroy); + } + + vmaDestroyPool(g_hAllocator, pool); +} + void Test() { wprintf(L"TESTING:\n"); - if(false) + if(true) { // # Temporarily insert custom tests here // ######################################## // ######################################## + BasicTestBuddyAllocator(); return; } diff --git a/src/vk_mem_alloc.h b/src/vk_mem_alloc.h index 299b866..af6b08a 100644 --- a/src/vk_mem_alloc.h +++ b/src/vk_mem_alloc.h @@ -2964,15 +2964,27 @@ static inline T VmaRoundDiv(T x, T y) // Returns smallest power of 2 greater or equal to v. static inline uint32_t VmaNextPow2(uint32_t v) { - v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; } -// Returns biggest power of 2 less or equal to v. -/* -static inline uint32_t VmaPrevPow2(uint32_t v) +static inline uint64_t VmaNextPow2(uint64_t v) { - + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v |= v >> 32; + v++; + return v; } -*/ static inline bool VmaStrIsEmpty(const char* pStr) { @@ -4556,6 +4568,7 @@ struct VmaAllocationRequest VkDeviceSize sumItemSize; // Sum size of items to make lost that overlap with proposed allocation. VmaSuballocationList::iterator item; size_t itemsToMakeLostCount; + void* customData; VkDeviceSize CalcCost() const { @@ -4967,10 +4980,11 @@ public: virtual void FreeAtOffset(VkDeviceSize offset); private: - static const size_t MAX_LEVELS = 30; + static const size_t MAX_LEVELS = 30; // TODO struct Node { + VkDeviceSize offset; enum TYPE { TYPE_FREE, @@ -5001,7 +5015,16 @@ private: Node* m_FreeList[MAX_LEVELS]; void DeleteNode(Node* node); - bool ValidateNode(const Node* parent, const Node* curr) const; + bool ValidateNode(const Node* parent, const Node* curr, uint32_t level, VkDeviceSize levelNodeSize) const; + uint32_t AllocSizeToLevel(VkDeviceSize allocSize) const; + VkDeviceSize LevelToNodeSize(uint32_t level) const; + // Alloc passed just for validation. Can be null. + void FreeAtOffset(VmaAllocation alloc, VkDeviceSize offset); + void CalcAllocationStatInfoNode(VmaStatInfo& outInfo, const Node* node, VkDeviceSize levelNodeSize) const; + +#if VMA_STATS_STRING_ENABLED + void PrintDetailedMapNode(class VmaJsonWriter& json, const Node* node, VkDeviceSize levelNodeSize) const; +#endif }; /* @@ -9243,21 +9266,36 @@ void VmaBlockMetadata_Buddy::Init(VkDeviceSize size) VmaBlockMetadata::Init(size); Node* rootNode = new Node(); + rootNode->offset = 0; rootNode->type = Node::TYPE_FREE; rootNode->parent = VMA_NULL; rootNode->buddy = VMA_NULL; rootNode->free.nextFree = VMA_NULL; + m_Root = rootNode; m_FreeList[0] = rootNode; } bool VmaBlockMetadata_Buddy::Validate() const { - if(!ValidateNode(VMA_NULL, m_Root)) + if(!ValidateNode(VMA_NULL, m_Root, 0, GetSize())) { return false; } + for(uint32_t level = 0; level < MAX_LEVELS; ++level) + { + for(Node* freeNode = m_FreeList[level]; + freeNode != VMA_NULL; + freeNode = freeNode->free.nextFree) + { + if(freeNode->type != Node::TYPE_FREE) + { + return false; + } + } + } + return true; } @@ -9283,7 +9321,16 @@ bool VmaBlockMetadata_Buddy::IsEmpty() const void VmaBlockMetadata_Buddy::CalcAllocationStatInfo(VmaStatInfo& outInfo) const { - // TODO + outInfo.blockCount = 1; + + outInfo.allocationCount = outInfo.unusedRangeCount = 0; + outInfo.unusedBytes = outInfo.unusedBytes = 0; + + outInfo.allocationSizeMax = outInfo.unusedRangeSizeMax = 0; + outInfo.allocationSizeMin = outInfo.unusedRangeSizeMin = UINT64_MAX; + outInfo.allocationSizeAvg = outInfo.unusedRangeSizeAvg = 0; // Unused. + + CalcAllocationStatInfoNode(outInfo, m_Root, GetSize()); } void VmaBlockMetadata_Buddy::AddPoolStats(VmaPoolStats& inoutStats) const @@ -9295,7 +9342,19 @@ void VmaBlockMetadata_Buddy::AddPoolStats(VmaPoolStats& inoutStats) const void VmaBlockMetadata_Buddy::PrintDetailedMap(class VmaJsonWriter& json) const { - // TODO + // TODO optimize + VmaStatInfo stat; + CalcAllocationStatInfo(stat); + + PrintDetailedMap_Begin( + json, + stat.unusedBytes, + stat.allocationCount, + stat.unusedRangeCount); + + PrintDetailedMapNode(json, m_Root, GetSize()); + + PrintDetailedMap_End(json); } #endif // #if VMA_STATS_STRING_ENABLED @@ -9312,8 +9371,81 @@ bool VmaBlockMetadata_Buddy::CreateAllocationRequest( uint32_t strategy, VmaAllocationRequest* pAllocationRequest) { + VMA_ASSERT(!upperAddress && "VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT can be used only with linear algorithm."); + + const VkDeviceSize size = GetSize(); + if(allocSize > size) + { + return false; + } + + const uint32_t targetLevel = AllocSizeToLevel(allocSize); + + // No free node with intended size. + if(m_FreeList[targetLevel] == VMA_NULL) + { + // Go up until we find free node with larget size. + uint32_t level = targetLevel; + while(m_FreeList[level] == VMA_NULL) + { + if(level == 0) + { + return false; + } + --level; + } + + // Go down, splitting free nodes. + while(level < targetLevel) + { + // Get first free node at current level. + Node* node = m_FreeList[level]; + // Remove it from list of free nodes at this level. + m_FreeList[level] = node->free.nextFree; + + const uint32_t childrenLevel = level + 1; + + // Create two free sub-nodes. + Node* leftChild = new Node(); + Node* rightChild = new Node(); + + leftChild->offset = node->offset; + leftChild->type = Node::TYPE_FREE; + leftChild->parent = node; + leftChild->buddy = rightChild; + leftChild->free.nextFree = VMA_NULL; + + rightChild->offset = node->offset + LevelToNodeSize(childrenLevel); + rightChild->type = Node::TYPE_FREE; + rightChild->parent = node; + rightChild->buddy = leftChild; + rightChild->free.nextFree = VMA_NULL; + + // Convert current node to split type. + node->type = Node::TYPE_SPLIT; + node->split.leftChild = leftChild; + + // Add child nodes to free list. + leftChild->free.nextFree = m_FreeList[childrenLevel]; + m_FreeList[childrenLevel] = leftChild; + + rightChild->free.nextFree = m_FreeList[childrenLevel]; + m_FreeList[childrenLevel] = rightChild; + + ++level; + } + } + + Node* freeNode = m_FreeList[targetLevel]; + VMA_ASSERT(freeNode != VMA_NULL); + + pAllocationRequest->offset = freeNode->offset; // TODO - return false; + pAllocationRequest->sumFreeSize = 0; + pAllocationRequest->sumItemSize = 0; + pAllocationRequest->itemsToMakeLostCount = 0; + pAllocationRequest->customData = (void*)(uintptr_t)targetLevel; + return true; } bool VmaBlockMetadata_Buddy::MakeRequestedAllocationsLost( @@ -9341,14 +9473,27 @@ void VmaBlockMetadata_Buddy::Alloc( bool upperAddress, VmaAllocation hAllocation) { + const uint32_t targetLevel = (uint32_t)(uintptr_t)request.customData; + VMA_ASSERT(m_FreeList[targetLevel] != VMA_NULL); + Node* node = m_FreeList[targetLevel]; + VMA_ASSERT(node->type == Node::TYPE_FREE); + + // Remove from free list. + m_FreeList[targetLevel] = node->free.nextFree; + + // Convert to allocation node. + node->type = Node::TYPE_ALLOCATION; + node->allocation.alloc = hAllocation; } void VmaBlockMetadata_Buddy::Free(const VmaAllocation allocation) { + FreeAtOffset(allocation, allocation->GetOffset()); } void VmaBlockMetadata_Buddy::FreeAtOffset(VkDeviceSize offset) { + FreeAtOffset(VMA_NULL, offset); } void VmaBlockMetadata_Buddy::DeleteNode(Node* node) @@ -9362,7 +9507,7 @@ void VmaBlockMetadata_Buddy::DeleteNode(Node* node) delete node; } -bool VmaBlockMetadata_Buddy::ValidateNode(const Node* parent, const Node* curr) const +bool VmaBlockMetadata_Buddy::ValidateNode(const Node* parent, const Node* curr, uint32_t level, VkDeviceSize levelNodeSize) const { if(curr->parent != parent) { @@ -9387,17 +9532,31 @@ bool VmaBlockMetadata_Buddy::ValidateNode(const Node* parent, const Node* curr) } break; case Node::TYPE_SPLIT: - if(curr->split.leftChild == VMA_NULL) { - return false; - } - if(!ValidateNode(curr, curr->split.leftChild)) - { - return false; - } - if(!ValidateNode(curr, curr->split.leftChild->buddy)) - { - return false; + const uint32_t childrenLevel = level + 1; + const VkDeviceSize childrenLevelNodeSize = levelNodeSize / 2; + const Node* const leftChild = curr->split.leftChild; + if(leftChild == VMA_NULL) + { + return false; + } + if(leftChild->offset != curr->offset) + { + return false; + } + if(!ValidateNode(curr, leftChild, childrenLevel, childrenLevelNodeSize)) + { + return false; + } + const Node* const rightChild = leftChild->buddy; + if(rightChild->offset != curr->offset + levelNodeSize) + { + return false; + } + if(!ValidateNode(curr, rightChild, childrenLevel, childrenLevelNodeSize)) + { + return false; + } } break; default: @@ -9407,6 +9566,118 @@ bool VmaBlockMetadata_Buddy::ValidateNode(const Node* parent, const Node* curr) return true; } +uint32_t VmaBlockMetadata_Buddy::AllocSizeToLevel(VkDeviceSize allocSize) const +{ + // TODO optimize + uint32_t level = 0; + VkDeviceSize currLevelNodeSize = GetSize(); + VkDeviceSize nextLevelNodeSize = currLevelNodeSize / 2; + while(allocSize <= nextLevelNodeSize && level + 1 < MAX_LEVELS) + { + ++level; + currLevelNodeSize = nextLevelNodeSize; + nextLevelNodeSize = currLevelNodeSize / 2; + } + return level; +} + +VkDeviceSize VmaBlockMetadata_Buddy::LevelToNodeSize(uint32_t level) const +{ + // TODO optimize + VkDeviceSize result = GetSize(); + for(uint32_t i = 0; i < level; ++i) + { + result /= 2; + } + return result; +} + +void VmaBlockMetadata_Buddy::FreeAtOffset(VmaAllocation alloc, VkDeviceSize offset) +{ + Node* node = m_Root; + uint32_t level = 0; + VkDeviceSize levelNodeSize = GetSize(); + while(node->type == Node::TYPE_SPLIT) + { + Node* leftChild = node->split.leftChild; + Node* rightChild = leftChild->buddy; + if(offset < rightChild->offset) // TODO could be calculated + { + node = leftChild; + } + else + { + node = rightChild; + } + ++level; + } + + VMA_ASSERT(node != VMA_NULL && node->type == Node::TYPE_ALLOCATION); + VMA_ASSERT(alloc == VK_NULL_HANDLE || node->allocation.alloc == alloc); + + node->type = Node::TYPE_FREE; + node->free.nextFree = m_FreeList[level]; + m_FreeList[level] = node; + + // TODO join free nodes if possible. +} + +void VmaBlockMetadata_Buddy::CalcAllocationStatInfoNode(VmaStatInfo& outInfo, const Node* node, VkDeviceSize levelNodeSize) const +{ + switch(node->type) + { + case Node::TYPE_FREE: + ++outInfo.unusedRangeCount; + outInfo.unusedBytes += levelNodeSize; + outInfo.unusedRangeSizeMax = VMA_MAX(outInfo.unusedRangeSizeMax, levelNodeSize); + outInfo.unusedRangeSizeMin = VMA_MAX(outInfo.unusedRangeSizeMin, levelNodeSize); + break; + case Node::TYPE_ALLOCATION: + ++outInfo.allocationCount; + outInfo.usedBytes += levelNodeSize; + outInfo.allocationSizeMax = VMA_MAX(outInfo.allocationSizeMax, levelNodeSize); + outInfo.allocationSizeMin = VMA_MAX(outInfo.allocationSizeMin, levelNodeSize); + break; + case Node::TYPE_SPLIT: + { + const VkDeviceSize childrenNodeSize = levelNodeSize / 2; + const Node* const leftChild = node->split.leftChild; + CalcAllocationStatInfoNode(outInfo, leftChild, childrenNodeSize); + const Node* const rightChild = leftChild->buddy; + CalcAllocationStatInfoNode(outInfo, rightChild, childrenNodeSize); + } + break; + default: + VMA_ASSERT(0); + } +} + +#if VMA_STATS_STRING_ENABLED +void VmaBlockMetadata_Buddy::PrintDetailedMapNode(class VmaJsonWriter& json, const Node* node, VkDeviceSize levelNodeSize) const +{ + switch(node->type) + { + case Node::TYPE_FREE: + PrintDetailedMap_UnusedRange(json, node->offset, levelNodeSize); + break; + case Node::TYPE_ALLOCATION: + PrintDetailedMap_Allocation(json, node->offset, node->allocation.alloc); + break; + case Node::TYPE_SPLIT: + { + const VkDeviceSize childrenNodeSize = levelNodeSize / 2; + const Node* const leftChild = node->split.leftChild; + PrintDetailedMapNode(json, leftChild, childrenNodeSize); + const Node* const rightChild = leftChild->buddy; + PrintDetailedMapNode(json, rightChild, childrenNodeSize); + } + break; + default: + VMA_ASSERT(0); + } +} +#endif // #if VMA_STATS_STRING_ENABLED + //////////////////////////////////////////////////////////////////////////////// // class VmaDeviceMemoryBlock From bf1a931a2db260191657cfbcc6b7e4416dd769e1 Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Thu, 6 Sep 2018 17:04:32 +0200 Subject: [PATCH 08/30] Next small step: moved split logic from VmaBlockMetadata_Buddy::CreateAllocationRequest to VmaBlockMetadata_Buddy::Alloc. --- src/vk_mem_alloc.h | 130 +++++++++++++++++++++------------------------ 1 file changed, 62 insertions(+), 68 deletions(-) diff --git a/src/vk_mem_alloc.h b/src/vk_mem_alloc.h index af6b08a..8d05441 100644 --- a/src/vk_mem_alloc.h +++ b/src/vk_mem_alloc.h @@ -4927,6 +4927,9 @@ private: void CleanupAfterFree(); }; +/* +Level 0 has block size = GetSize(). Level 1 has block size = GetSize() / 2 and so on... +*/ class VmaBlockMetadata_Buddy : public VmaBlockMetadata { VMA_CLASS_NO_COPY(VmaBlockMetadata_Buddy) @@ -9380,72 +9383,20 @@ bool VmaBlockMetadata_Buddy::CreateAllocationRequest( } const uint32_t targetLevel = AllocSizeToLevel(allocSize); - - // No free node with intended size. - if(m_FreeList[targetLevel] == VMA_NULL) + for(uint32_t level = targetLevel + 1; level--; ) { - // Go up until we find free node with larget size. - uint32_t level = targetLevel; - while(m_FreeList[level] == VMA_NULL) + if(m_FreeList[level] != VMA_NULL) { - if(level == 0) - { - return false; - } - --level; - } - - // Go down, splitting free nodes. - while(level < targetLevel) - { - // Get first free node at current level. - Node* node = m_FreeList[level]; - // Remove it from list of free nodes at this level. - m_FreeList[level] = node->free.nextFree; - - const uint32_t childrenLevel = level + 1; - - // Create two free sub-nodes. - Node* leftChild = new Node(); - Node* rightChild = new Node(); - - leftChild->offset = node->offset; - leftChild->type = Node::TYPE_FREE; - leftChild->parent = node; - leftChild->buddy = rightChild; - leftChild->free.nextFree = VMA_NULL; - - rightChild->offset = node->offset + LevelToNodeSize(childrenLevel); - rightChild->type = Node::TYPE_FREE; - rightChild->parent = node; - rightChild->buddy = leftChild; - rightChild->free.nextFree = VMA_NULL; - - // Convert current node to split type. - node->type = Node::TYPE_SPLIT; - node->split.leftChild = leftChild; - - // Add child nodes to free list. - leftChild->free.nextFree = m_FreeList[childrenLevel]; - m_FreeList[childrenLevel] = leftChild; - - rightChild->free.nextFree = m_FreeList[childrenLevel]; - m_FreeList[childrenLevel] = rightChild; - - ++level; + pAllocationRequest->offset = m_FreeList[level]->offset; + pAllocationRequest->sumFreeSize = LevelToNodeSize(level); + pAllocationRequest->sumItemSize = 0; + pAllocationRequest->itemsToMakeLostCount = 0; + pAllocationRequest->customData = (void*)(uintptr_t)level; + return true; } } - Node* freeNode = m_FreeList[targetLevel]; - VMA_ASSERT(freeNode != VMA_NULL); - - pAllocationRequest->offset = freeNode->offset; - // TODO - pAllocationRequest->sumFreeSize = 0; - pAllocationRequest->sumItemSize = 0; - pAllocationRequest->itemsToMakeLostCount = 0; - pAllocationRequest->customData = (void*)(uintptr_t)targetLevel; - return true; + return false; } bool VmaBlockMetadata_Buddy::MakeRequestedAllocationsLost( @@ -9473,17 +9424,60 @@ void VmaBlockMetadata_Buddy::Alloc( bool upperAddress, VmaAllocation hAllocation) { - const uint32_t targetLevel = (uint32_t)(uintptr_t)request.customData; - VMA_ASSERT(m_FreeList[targetLevel] != VMA_NULL); - Node* node = m_FreeList[targetLevel]; - VMA_ASSERT(node->type == Node::TYPE_FREE); + const uint32_t targetLevel = AllocSizeToLevel(allocSize); + uint32_t currLevel = (uint32_t)(uintptr_t)request.customData; + VMA_ASSERT(m_FreeList[currLevel] != VMA_NULL); + Node* currNode = m_FreeList[currLevel]; + VMA_ASSERT(currNode->type == Node::TYPE_FREE); + VMA_ASSERT(currNode->offset == request.offset); + // Go down, splitting free nodes. + while(currLevel < targetLevel) + { + // currNode is already first free node at currLevel. + // Remove it from list of free nodes at this currLevel. + m_FreeList[currLevel] = currNode->free.nextFree; + + const uint32_t childrenLevel = currLevel + 1; + + // Create two free sub-nodes. + Node* leftChild = new Node(); + Node* rightChild = new Node(); + + leftChild->offset = currNode->offset; + leftChild->type = Node::TYPE_FREE; + leftChild->parent = currNode; + leftChild->buddy = rightChild; + leftChild->free.nextFree = VMA_NULL; + + rightChild->offset = currNode->offset + LevelToNodeSize(childrenLevel); + rightChild->type = Node::TYPE_FREE; + rightChild->parent = currNode; + rightChild->buddy = leftChild; + rightChild->free.nextFree = VMA_NULL; + + // Convert current currNode to split type. + currNode->type = Node::TYPE_SPLIT; + currNode->split.leftChild = leftChild; + + // Add child nodes to free list. + rightChild->free.nextFree = m_FreeList[childrenLevel]; + m_FreeList[childrenLevel] = rightChild; + + leftChild->free.nextFree = m_FreeList[childrenLevel]; + m_FreeList[childrenLevel] = leftChild; + + ++currLevel; + currNode = m_FreeList[currLevel]; + } + // Remove from free list. - m_FreeList[targetLevel] = node->free.nextFree; + VMA_ASSERT(currLevel == targetLevel && currNode != VMA_NULL && currNode->type == Node::TYPE_FREE); + m_FreeList[targetLevel] = currNode->free.nextFree; // Convert to allocation node. - node->type = Node::TYPE_ALLOCATION; - node->allocation.alloc = hAllocation; + currNode->type = Node::TYPE_ALLOCATION; + currNode->allocation.alloc = hAllocation; } void VmaBlockMetadata_Buddy::Free(const VmaAllocation allocation) From 24c4f45abf382004bd9682560f732c795246ca05 Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Thu, 6 Sep 2018 17:39:11 +0200 Subject: [PATCH 09/30] Changed VmaBlockMetadata_Buddy::m_FreeList into a doubly linked list. Implemented merging of free blocks. Buddy allocation algorithm now works. --- src/vk_mem_alloc.h | 148 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 121 insertions(+), 27 deletions(-) diff --git a/src/vk_mem_alloc.h b/src/vk_mem_alloc.h index 8d05441..3a27dc9 100644 --- a/src/vk_mem_alloc.h +++ b/src/vk_mem_alloc.h @@ -5001,7 +5001,8 @@ private: { struct { - Node* nextFree; + Node* prev; + Node* next; } free; struct { @@ -5015,7 +5016,10 @@ private: }; Node* m_Root; - Node* m_FreeList[MAX_LEVELS]; + struct { + Node* front; + Node* back; + } m_FreeList[MAX_LEVELS]; void DeleteNode(Node* node); bool ValidateNode(const Node* parent, const Node* curr, uint32_t level, VkDeviceSize levelNodeSize) const; @@ -5024,6 +5028,14 @@ private: // Alloc passed just for validation. Can be null. void FreeAtOffset(VmaAllocation alloc, VkDeviceSize offset); void CalcAllocationStatInfoNode(VmaStatInfo& outInfo, const Node* node, VkDeviceSize levelNodeSize) const; + // Adds node to the front of FreeList at given level. + // node->type must be FREE. + // node->free.prev, next can be undefined. + void AddToFreeListFront(uint32_t level, Node* node); + // Removes node from FreeList at given level. + // node->type must be FREE. + // node->free.prev, next stay untouched. + void RemoveFromFreeList(uint32_t level, Node* node); #if VMA_STATS_STRING_ENABLED void PrintDetailedMapNode(class VmaJsonWriter& json, const Node* node, VkDeviceSize levelNodeSize) const; @@ -9273,29 +9285,51 @@ void VmaBlockMetadata_Buddy::Init(VkDeviceSize size) rootNode->type = Node::TYPE_FREE; rootNode->parent = VMA_NULL; rootNode->buddy = VMA_NULL; - rootNode->free.nextFree = VMA_NULL; m_Root = rootNode; - m_FreeList[0] = rootNode; + AddToFreeListFront(0, rootNode); } bool VmaBlockMetadata_Buddy::Validate() const { + // Validate tree. if(!ValidateNode(VMA_NULL, m_Root, 0, GetSize())) { return false; } + // Validate free node lists. for(uint32_t level = 0; level < MAX_LEVELS; ++level) { - for(Node* freeNode = m_FreeList[level]; - freeNode != VMA_NULL; - freeNode = freeNode->free.nextFree) + if(m_FreeList[level].front != VMA_NULL && + m_FreeList[level].front->free.prev != VMA_NULL) { - if(freeNode->type != Node::TYPE_FREE) + return false; + } + + for(Node* node = m_FreeList[level].front; + node != VMA_NULL; + node = node->free.next) + { + if(node->type != Node::TYPE_FREE) { return false; } + + if(node->free.next == VMA_NULL) + { + if(m_FreeList[level].back != node) + { + return false; + } + } + else + { + if(node->free.next->free.prev != node) + { + return false; + } + } } } @@ -9385,9 +9419,9 @@ bool VmaBlockMetadata_Buddy::CreateAllocationRequest( const uint32_t targetLevel = AllocSizeToLevel(allocSize); for(uint32_t level = targetLevel + 1; level--; ) { - if(m_FreeList[level] != VMA_NULL) + if(m_FreeList[level].front != VMA_NULL) { - pAllocationRequest->offset = m_FreeList[level]->offset; + pAllocationRequest->offset = m_FreeList[level].front->offset; pAllocationRequest->sumFreeSize = LevelToNodeSize(level); pAllocationRequest->sumItemSize = 0; pAllocationRequest->itemsToMakeLostCount = 0; @@ -9426,8 +9460,8 @@ void VmaBlockMetadata_Buddy::Alloc( { const uint32_t targetLevel = AllocSizeToLevel(allocSize); uint32_t currLevel = (uint32_t)(uintptr_t)request.customData; - VMA_ASSERT(m_FreeList[currLevel] != VMA_NULL); - Node* currNode = m_FreeList[currLevel]; + VMA_ASSERT(m_FreeList[currLevel].front != VMA_NULL); + Node* currNode = m_FreeList[currLevel].front; VMA_ASSERT(currNode->type == Node::TYPE_FREE); VMA_ASSERT(currNode->offset == request.offset); @@ -9436,7 +9470,7 @@ void VmaBlockMetadata_Buddy::Alloc( { // currNode is already first free node at currLevel. // Remove it from list of free nodes at this currLevel. - m_FreeList[currLevel] = currNode->free.nextFree; + RemoveFromFreeList(currLevel, currNode); const uint32_t childrenLevel = currLevel + 1; @@ -9448,32 +9482,27 @@ void VmaBlockMetadata_Buddy::Alloc( leftChild->type = Node::TYPE_FREE; leftChild->parent = currNode; leftChild->buddy = rightChild; - leftChild->free.nextFree = VMA_NULL; rightChild->offset = currNode->offset + LevelToNodeSize(childrenLevel); rightChild->type = Node::TYPE_FREE; rightChild->parent = currNode; rightChild->buddy = leftChild; - rightChild->free.nextFree = VMA_NULL; // Convert current currNode to split type. currNode->type = Node::TYPE_SPLIT; currNode->split.leftChild = leftChild; - // Add child nodes to free list. - rightChild->free.nextFree = m_FreeList[childrenLevel]; - m_FreeList[childrenLevel] = rightChild; - - leftChild->free.nextFree = m_FreeList[childrenLevel]; - m_FreeList[childrenLevel] = leftChild; + // Add child nodes to free list. Order is important! + AddToFreeListFront(childrenLevel, rightChild); + AddToFreeListFront(childrenLevel, leftChild); ++currLevel; - currNode = m_FreeList[currLevel]; + currNode = m_FreeList[currLevel].front; } // Remove from free list. VMA_ASSERT(currLevel == targetLevel && currNode != VMA_NULL && currNode->type == Node::TYPE_FREE); - m_FreeList[targetLevel] = currNode->free.nextFree; + RemoveFromFreeList(currLevel, currNode); // Convert to allocation node. currNode->type = Node::TYPE_ALLOCATION; @@ -9588,9 +9617,9 @@ VkDeviceSize VmaBlockMetadata_Buddy::LevelToNodeSize(uint32_t level) const void VmaBlockMetadata_Buddy::FreeAtOffset(VmaAllocation alloc, VkDeviceSize offset) { + // Find node and level. Node* node = m_Root; uint32_t level = 0; - VkDeviceSize levelNodeSize = GetSize(); while(node->type == Node::TYPE_SPLIT) { Node* leftChild = node->split.leftChild; @@ -9610,10 +9639,22 @@ void VmaBlockMetadata_Buddy::FreeAtOffset(VmaAllocation alloc, VkDeviceSize offs VMA_ASSERT(alloc == VK_NULL_HANDLE || node->allocation.alloc == alloc); node->type = Node::TYPE_FREE; - node->free.nextFree = m_FreeList[level]; - m_FreeList[level] = node; - // TODO join free nodes if possible. + // Join free nodes if possible. + while(level > 0 && node->buddy->type == Node::TYPE_FREE) + { + RemoveFromFreeList(level, node->buddy); + Node* const parent = node->parent; + + delete node->buddy; + delete node; + parent->type = Node::TYPE_FREE; + + node = parent; + --level; + } + + AddToFreeListFront(level, node); } void VmaBlockMetadata_Buddy::CalcAllocationStatInfoNode(VmaStatInfo& outInfo, const Node* node, VkDeviceSize levelNodeSize) const @@ -9646,6 +9687,59 @@ void VmaBlockMetadata_Buddy::CalcAllocationStatInfoNode(VmaStatInfo& outInfo, co } } +void VmaBlockMetadata_Buddy::AddToFreeListFront(uint32_t level, Node* node) +{ + VMA_ASSERT(node->type == Node::TYPE_FREE); + + // List is empty. + Node* const frontNode = m_FreeList[level].front; + if(frontNode == VMA_NULL) + { + VMA_ASSERT(m_FreeList[level].back == VMA_NULL); + node->free.prev = node->free.next = VMA_NULL; + m_FreeList[level].front = m_FreeList[level].back = node; + } + else + { + VMA_ASSERT(frontNode->free.prev == VMA_NULL); + node->free.prev = VMA_NULL; + node->free.next = frontNode; + frontNode->free.prev = node; + m_FreeList[level].front = node; + } +} + +void VmaBlockMetadata_Buddy::RemoveFromFreeList(uint32_t level, Node* node) +{ + VMA_ASSERT(m_FreeList[level].front != VMA_NULL); + + // It is at the front. + if(node->free.prev == VMA_NULL) + { + VMA_ASSERT(m_FreeList[level].front == node); + m_FreeList[level].front = node->free.next; + } + else + { + Node* const prevFreeNode = node->free.prev; + VMA_ASSERT(prevFreeNode->free.next == node); + prevFreeNode->free.next = node->free.next; + } + + // It is at the back. + if(node->free.next == VMA_NULL) + { + VMA_ASSERT(m_FreeList[level].back == node); + m_FreeList[level].back = node->free.prev; + } + else + { + Node* const nextFreeNode = node->free.next; + VMA_ASSERT(nextFreeNode->free.prev == node); + nextFreeNode->free.prev = node->free.prev; + } +} + #if VMA_STATS_STRING_ENABLED void VmaBlockMetadata_Buddy::PrintDetailedMapNode(class VmaJsonWriter& json, const Node* node, VkDeviceSize levelNodeSize) const { From a70e05dbc554641f516fa00306279e6344400f26 Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Fri, 7 Sep 2018 12:36:38 +0200 Subject: [PATCH 10/30] . --- src/VmaReplay/VmaUsage.h | 2 +- src/vk_mem_alloc.h | 36 +++++++++++++----------------------- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/VmaReplay/VmaUsage.h b/src/VmaReplay/VmaUsage.h index 182132b..cf93a33 100644 --- a/src/VmaReplay/VmaUsage.h +++ b/src/VmaReplay/VmaUsage.h @@ -9,7 +9,7 @@ //#define VMA_USE_STL_CONTAINERS 1 -//#define VMA_HEAVY_ASSERT(expr) assert(expr) +#define VMA_HEAVY_ASSERT(expr) assert(expr) //#define VMA_DEDICATED_ALLOCATION 0 diff --git a/src/vk_mem_alloc.h b/src/vk_mem_alloc.h index 3a27dc9..9d21090 100644 --- a/src/vk_mem_alloc.h +++ b/src/vk_mem_alloc.h @@ -4942,7 +4942,7 @@ public: virtual size_t GetAllocationCount() const; virtual VkDeviceSize GetSumFreeSize() const; virtual VkDeviceSize GetUnusedRangeSizeMax() const; - virtual bool IsEmpty() const; + virtual bool IsEmpty() const { return m_Root->type == Node::TYPE_FREE; } virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const; virtual void AddPoolStats(VmaPoolStats& inoutStats) const; @@ -4979,8 +4979,8 @@ public: bool upperAddress, VmaAllocation hAllocation); - virtual void Free(const VmaAllocation allocation); - virtual void FreeAtOffset(VkDeviceSize offset); + virtual void Free(const VmaAllocation allocation) { FreeAtOffset(allocation, allocation->GetOffset()); } + virtual void FreeAtOffset(VkDeviceSize offset) { FreeAtOffset(VMA_NULL, offset); } private: static const size_t MAX_LEVELS = 30; // TODO @@ -4997,6 +4997,7 @@ private: } type; Node* parent; Node* buddy; + union { struct @@ -9351,11 +9352,6 @@ VkDeviceSize VmaBlockMetadata_Buddy::VmaBlockMetadata_Buddy::GetUnusedRangeSizeM return 0; // TODO } -bool VmaBlockMetadata_Buddy::IsEmpty() const -{ - return m_Root->type == Node::TYPE_FREE; -} - void VmaBlockMetadata_Buddy::CalcAllocationStatInfo(VmaStatInfo& outInfo) const { outInfo.blockCount = 1; @@ -9509,16 +9505,6 @@ void VmaBlockMetadata_Buddy::Alloc( currNode->allocation.alloc = hAllocation; } -void VmaBlockMetadata_Buddy::Free(const VmaAllocation allocation) -{ - FreeAtOffset(allocation, allocation->GetOffset()); -} - -void VmaBlockMetadata_Buddy::FreeAtOffset(VkDeviceSize offset) -{ - FreeAtOffset(VMA_NULL, offset); -} - void VmaBlockMetadata_Buddy::DeleteNode(Node* node) { if(node->type == Node::TYPE_SPLIT) @@ -9547,6 +9533,7 @@ bool VmaBlockMetadata_Buddy::ValidateNode(const Node* parent, const Node* curr, switch(curr->type) { case Node::TYPE_FREE: + // curr->free.prev, next are validated separately. break; case Node::TYPE_ALLOCATION: if(curr->allocation.alloc == VK_NULL_HANDLE) @@ -9619,20 +9606,23 @@ void VmaBlockMetadata_Buddy::FreeAtOffset(VmaAllocation alloc, VkDeviceSize offs { // Find node and level. Node* node = m_Root; + VkDeviceSize nodeOffset = 0; uint32_t level = 0; + VkDeviceSize levelSize = GetSize(); while(node->type == Node::TYPE_SPLIT) { - Node* leftChild = node->split.leftChild; - Node* rightChild = leftChild->buddy; - if(offset < rightChild->offset) // TODO could be calculated + const VkDeviceSize nextLevelSize = levelSize / 2; + if(offset < nodeOffset + nextLevelSize) { - node = leftChild; + node = node->split.leftChild; } else { - node = rightChild; + node = node->split.leftChild->buddy; + nodeOffset += nextLevelSize; } ++level; + levelSize = nextLevelSize; } VMA_ASSERT(node != VMA_NULL && node->type == Node::TYPE_ALLOCATION); From 4338f6667de03afd1a2a3cef62f4a9ff43df365b Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Fri, 7 Sep 2018 14:12:37 +0200 Subject: [PATCH 11/30] Added internal function VmaIsPow2 and asserts checking if various alignment parameters are power of 2. --- src/Tests.cpp | 4 +++- src/VmaReplay/VmaUsage.h | 2 +- src/vk_mem_alloc.h | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Tests.cpp b/src/Tests.cpp index 21b82f2..bc2b964 100644 --- a/src/Tests.cpp +++ b/src/Tests.cpp @@ -4149,7 +4149,7 @@ void Test() { wprintf(L"TESTING:\n"); - if(true) + if(false) { // # Temporarily insert custom tests here // ######################################## @@ -4176,6 +4176,8 @@ void Test() ManuallyTestLinearAllocator(); TestLinearAllocatorMultiBlock(); + BasicTestBuddyAllocator(); + { FILE* file; fopen_s(&file, "LinearAllocator.csv", "w"); diff --git a/src/VmaReplay/VmaUsage.h b/src/VmaReplay/VmaUsage.h index cf93a33..182132b 100644 --- a/src/VmaReplay/VmaUsage.h +++ b/src/VmaReplay/VmaUsage.h @@ -9,7 +9,7 @@ //#define VMA_USE_STL_CONTAINERS 1 -#define VMA_HEAVY_ASSERT(expr) assert(expr) +//#define VMA_HEAVY_ASSERT(expr) assert(expr) //#define VMA_DEDICATED_ALLOCATION 0 diff --git a/src/vk_mem_alloc.h b/src/vk_mem_alloc.h index 9d21090..956e58d 100644 --- a/src/vk_mem_alloc.h +++ b/src/vk_mem_alloc.h @@ -2961,6 +2961,17 @@ static inline T VmaRoundDiv(T x, T y) return (x + (y / (T)2)) / y; } +/* +Returns true if given number is a power of two. +T must be unsigned integer number or signed integer but always nonnegative. +For 0 returns true. +*/ +template +inline bool VmaIsPow2(T x) +{ + return (x & (x-1)) == 0; +} + // Returns smallest power of 2 greater or equal to v. static inline uint32_t VmaNextPow2(uint32_t v) { @@ -11749,6 +11760,11 @@ VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) : (*m_VulkanFunctions.vkGetPhysicalDeviceProperties)(m_PhysicalDevice, &m_PhysicalDeviceProperties); (*m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties)(m_PhysicalDevice, &m_MemProps); + VMA_ASSERT(VmaIsPow2(VMA_DEBUG_ALIGNMENT)); + VMA_ASSERT(VmaIsPow2(VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY)); + VMA_ASSERT(VmaIsPow2(m_PhysicalDeviceProperties.limits.bufferImageGranularity)); + VMA_ASSERT(VmaIsPow2(m_PhysicalDeviceProperties.limits.nonCoherentAtomSize)); + m_PreferredLargeHeapBlockSize = (pCreateInfo->preferredLargeHeapBlockSize != 0) ? pCreateInfo->preferredLargeHeapBlockSize : static_cast(VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE); @@ -12196,6 +12212,8 @@ VkResult VmaAllocator_T::AllocateMemory( VmaSuballocationType suballocType, VmaAllocation* pAllocation) { + VMA_ASSERT(VmaIsPow2(vkMemReq.alignment)); + if((createInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) != 0 && (createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) != 0) { From ca5db0b8a532ae86305fd86e665a5324c0d8228f Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Fri, 7 Sep 2018 14:58:49 +0200 Subject: [PATCH 12/30] Rebuilt Doxygen documentation after upgrading Doxygen version. --- docs/html/allocation_annotation.html | 11 +- docs/html/annotated.html | 11 +- docs/html/choosing_memory_type.html | 11 +- docs/html/classes.html | 11 +- docs/html/configuration.html | 11 +- docs/html/custom_memory_pools.html | 11 +- docs/html/debugging_memory_usage.html | 11 +- docs/html/defragmentation.html | 11 +- docs/html/doxygen.css | 2 +- docs/html/dynsections.js | 33 +++++- docs/html/files.html | 11 +- docs/html/functions.html | 11 +- docs/html/functions_vars.html | 11 +- docs/html/general_considerations.html | 11 +- docs/html/globals.html | 38 +++++- docs/html/globals_defs.html | 11 +- docs/html/globals_enum.html | 11 +- docs/html/globals_eval.html | 42 ++++++- docs/html/globals_func.html | 11 +- docs/html/globals_type.html | 11 +- docs/html/index.html | 11 +- docs/html/jquery.js | 32 ++++- docs/html/lost_allocations.html | 13 ++- docs/html/memory_mapping.html | 11 +- docs/html/menu.js | 24 ++++ docs/html/menudata.js | 26 ++++- docs/html/pages.html | 11 +- docs/html/quick_start.html | 11 +- docs/html/record_and_replay.html | 11 +- docs/html/search/all_0.html | 6 +- docs/html/search/all_1.html | 6 +- docs/html/search/all_2.html | 6 +- docs/html/search/all_3.html | 6 +- docs/html/search/all_4.html | 6 +- docs/html/search/all_5.html | 6 +- docs/html/search/all_6.html | 6 +- docs/html/search/all_7.html | 6 +- docs/html/search/all_8.html | 6 +- docs/html/search/all_9.html | 6 +- docs/html/search/all_a.html | 6 +- docs/html/search/all_b.html | 6 +- docs/html/search/all_c.html | 6 +- docs/html/search/all_d.html | 6 +- docs/html/search/all_e.html | 6 +- docs/html/search/all_f.html | 6 +- docs/html/search/all_f.js | 9 ++ docs/html/search/classes_0.html | 6 +- docs/html/search/defines_0.html | 6 +- docs/html/search/enums_0.html | 6 +- docs/html/search/enumvalues_0.html | 6 +- docs/html/search/enumvalues_0.js | 9 ++ docs/html/search/files_0.html | 6 +- docs/html/search/functions_0.html | 6 +- docs/html/search/pages_0.html | 6 +- docs/html/search/pages_1.html | 6 +- docs/html/search/pages_2.html | 6 +- docs/html/search/pages_3.html | 6 +- docs/html/search/pages_4.html | 6 +- docs/html/search/pages_5.html | 6 +- docs/html/search/pages_6.html | 6 +- docs/html/search/pages_7.html | 6 +- docs/html/search/pages_8.html | 6 +- docs/html/search/pages_9.html | 6 +- docs/html/search/search.js | 25 +++- docs/html/search/typedefs_0.html | 6 +- docs/html/search/typedefs_1.html | 6 +- docs/html/search/variables_0.html | 6 +- docs/html/search/variables_1.html | 6 +- docs/html/search/variables_2.html | 6 +- docs/html/search/variables_3.html | 6 +- docs/html/search/variables_4.html | 6 +- docs/html/search/variables_5.html | 6 +- docs/html/search/variables_6.html | 6 +- docs/html/search/variables_7.html | 6 +- docs/html/search/variables_8.html | 6 +- docs/html/search/variables_9.html | 6 +- docs/html/search/variables_a.html | 6 +- docs/html/search/variables_b.html | 6 +- docs/html/statistics.html | 11 +- docs/html/struct_vma_allocation.html | 11 +- ...ct_vma_allocation_create_info-members.html | 11 +- .../struct_vma_allocation_create_info.html | 11 +- .../struct_vma_allocation_info-members.html | 11 +- docs/html/struct_vma_allocation_info.html | 11 +- docs/html/struct_vma_allocator.html | 11 +- ...uct_vma_allocator_create_info-members.html | 11 +- .../struct_vma_allocator_create_info.html | 11 +- ...ruct_vma_defragmentation_info-members.html | 11 +- .../html/struct_vma_defragmentation_info.html | 11 +- ...uct_vma_defragmentation_stats-members.html | 11 +- .../struct_vma_defragmentation_stats.html | 11 +- ...t_vma_device_memory_callbacks-members.html | 11 +- .../struct_vma_device_memory_callbacks.html | 11 +- docs/html/struct_vma_pool.html | 11 +- .../struct_vma_pool_create_info-members.html | 11 +- docs/html/struct_vma_pool_create_info.html | 11 +- docs/html/struct_vma_pool_stats-members.html | 11 +- docs/html/struct_vma_pool_stats.html | 11 +- .../struct_vma_record_settings-members.html | 11 +- docs/html/struct_vma_record_settings.html | 11 +- docs/html/struct_vma_stat_info-members.html | 11 +- docs/html/struct_vma_stat_info.html | 11 +- docs/html/struct_vma_stats-members.html | 11 +- docs/html/struct_vma_stats.html | 11 +- .../struct_vma_vulkan_functions-members.html | 11 +- docs/html/struct_vma_vulkan_functions.html | 11 +- docs/html/usage_patterns.html | 11 +- docs/html/vk__mem__alloc_8h.html | 63 +++++++--- docs/html/vk__mem__alloc_8h_source.html | 110 ++++++++++-------- docs/html/vk_khr_dedicated_allocation.html | 11 +- 110 files changed, 926 insertions(+), 337 deletions(-) diff --git a/docs/html/allocation_annotation.html b/docs/html/allocation_annotation.html index e9cd214..dc60c81 100644 --- a/docs/html/allocation_annotation.html +++ b/docs/html/allocation_annotation.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: Allocation names and user data @@ -29,18 +29,21 @@ - + +/* @license-end */
diff --git a/docs/html/annotated.html b/docs/html/annotated.html index 37ef74d..5cca84d 100644 --- a/docs/html/annotated.html +++ b/docs/html/annotated.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: Class List @@ -29,18 +29,21 @@
- + +/* @license-end */ @@ -86,7 +89,7 @@ $(function() { diff --git a/docs/html/choosing_memory_type.html b/docs/html/choosing_memory_type.html index b713e77..0c78cf2 100644 --- a/docs/html/choosing_memory_type.html +++ b/docs/html/choosing_memory_type.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: Choosing memory type @@ -29,18 +29,21 @@ - + +/* @license-end */
diff --git a/docs/html/classes.html b/docs/html/classes.html index 099f7e3..d922236 100644 --- a/docs/html/classes.html +++ b/docs/html/classes.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: Class Index @@ -29,18 +29,21 @@
- + +/* @license-end */ @@ -77,7 +80,7 @@ $(function() { diff --git a/docs/html/configuration.html b/docs/html/configuration.html index 42ac113..14930b9 100644 --- a/docs/html/configuration.html +++ b/docs/html/configuration.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: Configuration @@ -29,18 +29,21 @@ - + +/* @license-end */
diff --git a/docs/html/custom_memory_pools.html b/docs/html/custom_memory_pools.html index 35197c6..89132d1 100644 --- a/docs/html/custom_memory_pools.html +++ b/docs/html/custom_memory_pools.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: Custom memory pools @@ -29,18 +29,21 @@
- + +/* @license-end */
diff --git a/docs/html/debugging_memory_usage.html b/docs/html/debugging_memory_usage.html index 83e8d35..0163a92 100644 --- a/docs/html/debugging_memory_usage.html +++ b/docs/html/debugging_memory_usage.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: Debugging incorrect memory usage @@ -29,18 +29,21 @@
- + +/* @license-end */
diff --git a/docs/html/defragmentation.html b/docs/html/defragmentation.html index 6df573c..41bf4ca 100644 --- a/docs/html/defragmentation.html +++ b/docs/html/defragmentation.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: Defragmentation @@ -29,18 +29,21 @@
- + +/* @license-end */ - + +/* @license-end */ @@ -72,7 +75,7 @@ $(function() { diff --git a/docs/html/functions.html b/docs/html/functions.html index efb1f35..508e7c6 100644 --- a/docs/html/functions.html +++ b/docs/html/functions.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: Class Members @@ -29,18 +29,21 @@ - + +/* @license-end */ @@ -314,7 +317,7 @@ $(function() { diff --git a/docs/html/functions_vars.html b/docs/html/functions_vars.html index bb5c24c..6e0c81b 100644 --- a/docs/html/functions_vars.html +++ b/docs/html/functions_vars.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: Class Members - Variables @@ -29,18 +29,21 @@ - + +/* @license-end */ @@ -314,7 +317,7 @@ $(function() { diff --git a/docs/html/general_considerations.html b/docs/html/general_considerations.html index f631ac8..2777914 100644 --- a/docs/html/general_considerations.html +++ b/docs/html/general_considerations.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: General considerations @@ -29,18 +29,21 @@ - + +/* @license-end */
diff --git a/docs/html/globals.html b/docs/html/globals.html index 73bdc76..4062170 100644 --- a/docs/html/globals.html +++ b/docs/html/globals.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: File Members @@ -29,18 +29,21 @@
- + +/* @license-end */ @@ -89,6 +92,27 @@ $(function() {
  • VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT : vk_mem_alloc.h
  • +
  • VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT +: vk_mem_alloc.h +
  • +
  • VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT +: vk_mem_alloc.h +
  • +
  • VMA_ALLOCATION_CREATE_STRATEGY_MASK +: vk_mem_alloc.h +
  • +
  • VMA_ALLOCATION_CREATE_STRATEGY_MIN_FRAGMENTATION_BIT +: vk_mem_alloc.h +
  • +
  • VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT +: vk_mem_alloc.h +
  • +
  • VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT +: vk_mem_alloc.h +
  • +
  • VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT +: vk_mem_alloc.h +
  • VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT : vk_mem_alloc.h
  • @@ -125,6 +149,12 @@ $(function() {
  • VMA_MEMORY_USAGE_UNKNOWN : vk_mem_alloc.h
  • +
  • VMA_POOL_CREATE_ALGORITHM_MASK +: vk_mem_alloc.h +
  • +
  • VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT +: vk_mem_alloc.h +
  • VMA_POOL_CREATE_FLAG_BITS_MAX_ENUM : vk_mem_alloc.h
  • @@ -326,7 +356,7 @@ $(function() { diff --git a/docs/html/globals_defs.html b/docs/html/globals_defs.html index ca5de3f..48832a6 100644 --- a/docs/html/globals_defs.html +++ b/docs/html/globals_defs.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: File Members @@ -29,18 +29,21 @@ - + +/* @license-end */ @@ -74,7 +77,7 @@ $(function() { diff --git a/docs/html/globals_enum.html b/docs/html/globals_enum.html index d295ccd..ba9dcc7 100644 --- a/docs/html/globals_enum.html +++ b/docs/html/globals_enum.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: File Members @@ -29,18 +29,21 @@ - + +/* @license-end */ @@ -80,7 +83,7 @@ $(function() { diff --git a/docs/html/globals_eval.html b/docs/html/globals_eval.html index 38d7955..7a471e0 100644 --- a/docs/html/globals_eval.html +++ b/docs/html/globals_eval.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: File Members @@ -29,18 +29,21 @@ - + +/* @license-end */ @@ -58,7 +61,9 @@ $(function() {
      +  + +

      - v -

      • VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT : vk_mem_alloc.h
      • @@ -77,6 +82,27 @@ $(function() {
      • VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT : vk_mem_alloc.h
      • +
      • VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT +: vk_mem_alloc.h +
      • +
      • VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT +: vk_mem_alloc.h +
      • +
      • VMA_ALLOCATION_CREATE_STRATEGY_MASK +: vk_mem_alloc.h +
      • +
      • VMA_ALLOCATION_CREATE_STRATEGY_MIN_FRAGMENTATION_BIT +: vk_mem_alloc.h +
      • +
      • VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT +: vk_mem_alloc.h +
      • +
      • VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT +: vk_mem_alloc.h +
      • +
      • VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT +: vk_mem_alloc.h +
      • VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT : vk_mem_alloc.h
      • @@ -110,6 +136,12 @@ $(function() {
      • VMA_MEMORY_USAGE_UNKNOWN : vk_mem_alloc.h
      • +
      • VMA_POOL_CREATE_ALGORITHM_MASK +: vk_mem_alloc.h +
      • +
      • VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT +: vk_mem_alloc.h +
      • VMA_POOL_CREATE_FLAG_BITS_MAX_ENUM : vk_mem_alloc.h
      • @@ -131,7 +163,7 @@ $(function() { diff --git a/docs/html/globals_func.html b/docs/html/globals_func.html index a31da7d..bcad79b 100644 --- a/docs/html/globals_func.html +++ b/docs/html/globals_func.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: File Members @@ -29,18 +29,21 @@
    - + +/* @license-end */ @@ -178,7 +181,7 @@ $(function() { diff --git a/docs/html/globals_type.html b/docs/html/globals_type.html index 68106a1..e0c4903 100644 --- a/docs/html/globals_type.html +++ b/docs/html/globals_type.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: File Members @@ -29,18 +29,21 @@ - + +/* @license-end */ @@ -134,7 +137,7 @@ $(function() { diff --git a/docs/html/index.html b/docs/html/index.html index 298454e..01b0a08 100644 --- a/docs/html/index.html +++ b/docs/html/index.html @@ -3,7 +3,7 @@ - + Vulkan Memory Allocator: Vulkan Memory Allocator @@ -29,18 +29,21 @@ - + +/* @license-end */ @@ -154,7 +157,7 @@ See also diff --git a/docs/html/jquery.js b/docs/html/jquery.js index f5343ed..2771c74 100644 --- a/docs/html/jquery.js +++ b/docs/html/jquery.js @@ -1,3 +1,31 @@ +/* + @licstart The following is the entire license notice for the + JavaScript code in this file. + + Copyright (C) 1997-2017 by Dimitri van Heesch + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + @licend The above is the entire license notice + for the JavaScript code in this file + */ /*! * jQuery JavaScript Library v1.7.1 * http://jquery.com/ @@ -53,7 +81,7 @@ (function(b,c){var a=false;b(document).mouseup(function(d){a=false});b.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var d=this;this.element.bind("mousedown."+this.widgetName,function(e){return d._mouseDown(e)}).bind("click."+this.widgetName,function(e){if(true===b.data(e.target,d.widgetName+".preventClickEvent")){b.removeData(e.target,d.widgetName+".preventClickEvent");e.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName)},_mouseDown:function(f){if(a){return}(this._mouseStarted&&this._mouseUp(f));this._mouseDownEvent=f;var e=this,g=(f.which==1),d=(typeof this.options.cancel=="string"&&f.target.nodeName?b(f.target).closest(this.options.cancel).length:false);if(!g||d||!this._mouseCapture(f)){return true}this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet){this._mouseDelayTimer=setTimeout(function(){e.mouseDelayMet=true},this.options.delay)}if(this._mouseDistanceMet(f)&&this._mouseDelayMet(f)){this._mouseStarted=(this._mouseStart(f)!==false);if(!this._mouseStarted){f.preventDefault();return true}}if(true===b.data(f.target,this.widgetName+".preventClickEvent")){b.removeData(f.target,this.widgetName+".preventClickEvent")}this._mouseMoveDelegate=function(h){return e._mouseMove(h)};this._mouseUpDelegate=function(h){return e._mouseUp(h)};b(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);f.preventDefault();a=true;return true},_mouseMove:function(d){if(b.browser.msie&&!(document.documentMode>=9)&&!d.button){return this._mouseUp(d)}if(this._mouseStarted){this._mouseDrag(d);return d.preventDefault()}if(this._mouseDistanceMet(d)&&this._mouseDelayMet(d)){this._mouseStarted=(this._mouseStart(this._mouseDownEvent,d)!==false);(this._mouseStarted?this._mouseDrag(d):this._mouseUp(d))}return !this._mouseStarted},_mouseUp:function(d){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;if(d.target==this._mouseDownEvent.target){b.data(d.target,this.widgetName+".preventClickEvent",true)}this._mouseStop(d)}return false},_mouseDistanceMet:function(d){return(Math.max(Math.abs(this._mouseDownEvent.pageX-d.pageX),Math.abs(this._mouseDownEvent.pageY-d.pageY))>=this.options.distance)},_mouseDelayMet:function(d){return this.mouseDelayMet},_mouseStart:function(d){},_mouseDrag:function(d){},_mouseStop:function(d){},_mouseCapture:function(d){return true}})})(jQuery);(function(c,d){c.widget("ui.resizable",c.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,containment:false,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1000},_create:function(){var f=this,k=this.options;this.element.addClass("ui-resizable");c.extend(this,{_aspectRatio:!!(k.aspectRatio),aspectRatio:k.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:k.helper||k.ghost||k.animate?k.helper||"ui-resizable-helper":null});if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)){this.element.wrap(c('
    ').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle=this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=k.handles||(!c(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all"){this.handles="n,e,s,w,se,sw,ne,nw"}var l=this.handles.split(",");this.handles={};for(var g=0;g');if(/sw|se|ne|nw/.test(j)){h.css({zIndex:++k.zIndex})}if("se"==j){h.addClass("ui-icon ui-icon-gripsmall-diagonal-se")}this.handles[j]=".ui-resizable-"+j;this.element.append(h)}}this._renderAxis=function(q){q=q||this.element;for(var n in this.handles){if(this.handles[n].constructor==String){this.handles[n]=c(this.handles[n],this.element).show()}if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var o=c(this.handles[n],this.element),p=0;p=/sw|ne|nw|se|n|s/.test(n)?o.outerHeight():o.outerWidth();var m=["padding",/ne|nw|n/.test(n)?"Top":/se|sw|s/.test(n)?"Bottom":/^e$/.test(n)?"Right":"Left"].join("");q.css(m,p);this._proportionallyResize()}if(!c(this.handles[n]).length){continue}}};this._renderAxis(this.element);this._handles=c(".ui-resizable-handle",this.element).disableSelection();this._handles.mouseover(function(){if(!f.resizing){if(this.className){var i=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)}f.axis=i&&i[1]?i[1]:"se"}});if(k.autoHide){this._handles.hide();c(this.element).addClass("ui-resizable-autohide").hover(function(){if(k.disabled){return}c(this).removeClass("ui-resizable-autohide");f._handles.show()},function(){if(k.disabled){return}if(!f.resizing){c(this).addClass("ui-resizable-autohide");f._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();var e=function(g){c(g).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){e(this.element);var f=this.element;f.after(this.originalElement.css({position:f.css("position"),width:f.outerWidth(),height:f.outerHeight(),top:f.css("top"),left:f.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);e(this.originalElement);return this},_mouseCapture:function(f){var g=false;for(var e in this.handles){if(c(this.handles[e])[0]==f.target){g=true}}return !this.options.disabled&&g},_mouseStart:function(g){var j=this.options,f=this.element.position(),e=this.element;this.resizing=true;this.documentScroll={top:c(document).scrollTop(),left:c(document).scrollLeft()};if(e.is(".ui-draggable")||(/absolute/).test(e.css("position"))){e.css({position:"absolute",top:f.top,left:f.left})}this._renderProxy();var k=b(this.helper.css("left")),h=b(this.helper.css("top"));if(j.containment){k+=c(j.containment).scrollLeft()||0;h+=c(j.containment).scrollTop()||0}this.offset=this.helper.offset();this.position={left:k,top:h};this.size=this._helper?{width:e.outerWidth(),height:e.outerHeight()}:{width:e.width(),height:e.height()};this.originalSize=this._helper?{width:e.outerWidth(),height:e.outerHeight()}:{width:e.width(),height:e.height()};this.originalPosition={left:k,top:h};this.sizeDiff={width:e.outerWidth()-e.width(),height:e.outerHeight()-e.height()};this.originalMousePosition={left:g.pageX,top:g.pageY};this.aspectRatio=(typeof j.aspectRatio=="number")?j.aspectRatio:((this.originalSize.width/this.originalSize.height)||1);var i=c(".ui-resizable-"+this.axis).css("cursor");c("body").css("cursor",i=="auto"?this.axis+"-resize":i);e.addClass("ui-resizable-resizing");this._propagate("start",g);return true},_mouseDrag:function(e){var h=this.helper,g=this.options,m={},q=this,j=this.originalMousePosition,n=this.axis;var r=(e.pageX-j.left)||0,p=(e.pageY-j.top)||0;var i=this._change[n];if(!i){return false}var l=i.apply(this,[e,r,p]),k=c.browser.msie&&c.browser.version<7,f=this.sizeDiff;this._updateVirtualBoundaries(e.shiftKey);if(this._aspectRatio||e.shiftKey){l=this._updateRatio(l,e)}l=this._respectSize(l,e);this._propagate("resize",e);h.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});if(!this._helper&&this._proportionallyResizeElements.length){this._proportionallyResize()}this._updateCache(l);this._trigger("resize",e,this.ui());return false},_mouseStop:function(h){this.resizing=false;var i=this.options,m=this;if(this._helper){var g=this._proportionallyResizeElements,e=g.length&&(/textarea/i).test(g[0].nodeName),f=e&&c.ui.hasScroll(g[0],"left")?0:m.sizeDiff.height,k=e?0:m.sizeDiff.width;var n={width:(m.helper.width()-k),height:(m.helper.height()-f)},j=(parseInt(m.element.css("left"),10)+(m.position.left-m.originalPosition.left))||null,l=(parseInt(m.element.css("top"),10)+(m.position.top-m.originalPosition.top))||null;if(!i.animate){this.element.css(c.extend(n,{top:l,left:j}))}m.helper.height(m.size.height);m.helper.width(m.size.width);if(this._helper&&!i.animate){this._proportionallyResize()}}c("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop",h);if(this._helper){this.helper.remove()}return false},_updateVirtualBoundaries:function(g){var j=this.options,i,h,f,k,e;e={minWidth:a(j.minWidth)?j.minWidth:0,maxWidth:a(j.maxWidth)?j.maxWidth:Infinity,minHeight:a(j.minHeight)?j.minHeight:0,maxHeight:a(j.maxHeight)?j.maxHeight:Infinity};if(this._aspectRatio||g){i=e.minHeight*this.aspectRatio;f=e.minWidth/this.aspectRatio;h=e.maxHeight*this.aspectRatio;k=e.maxWidth/this.aspectRatio;if(i>e.minWidth){e.minWidth=i}if(f>e.minHeight){e.minHeight=f}if(hl.width),s=a(l.height)&&i.minHeight&&(i.minHeight>l.height);if(h){l.width=i.minWidth}if(s){l.height=i.minHeight}if(t){l.width=i.maxWidth}if(m){l.height=i.maxHeight}var f=this.originalPosition.left+this.originalSize.width,p=this.position.top+this.size.height;var k=/sw|nw|w/.test(q),e=/nw|ne|n/.test(q);if(h&&k){l.left=f-i.minWidth}if(t&&k){l.left=f-i.maxWidth}if(s&&e){l.top=p-i.minHeight}if(m&&e){l.top=p-i.maxHeight}var n=!l.width&&!l.height;if(n&&!l.left&&l.top){l.top=null}else{if(n&&!l.top&&l.left){l.left=null}}return l},_proportionallyResize:function(){var k=this.options;if(!this._proportionallyResizeElements.length){return}var g=this.helper||this.element;for(var f=0;f');var e=c.browser.msie&&c.browser.version<7,g=(e?1:0),h=(e?2:-1);this.helper.addClass(this._helper).css({width:this.element.outerWidth()+h,height:this.element.outerHeight()+h,position:"absolute",left:this.elementOffset.left-g+"px",top:this.elementOffset.top-g+"px",zIndex:++i.zIndex});this.helper.appendTo("body").disableSelection()}else{this.helper=this.element}},_change:{e:function(g,f,e){return{width:this.originalSize.width+f}},w:function(h,f,e){var j=this.options,g=this.originalSize,i=this.originalPosition;return{left:i.left+f,width:g.width-f}},n:function(h,f,e){var j=this.options,g=this.originalSize,i=this.originalPosition;return{top:i.top+e,height:g.height-e}},s:function(g,f,e){return{height:this.originalSize.height+e}},se:function(g,f,e){return c.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[g,f,e]))},sw:function(g,f,e){return c.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[g,f,e]))},ne:function(g,f,e){return c.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[g,f,e]))},nw:function(g,f,e){return c.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[g,f,e]))}},_propagate:function(f,e){c.ui.plugin.call(this,f,[e,this.ui()]);(f!="resize"&&this._trigger(f,e,this.ui()))},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});c.extend(c.ui.resizable,{version:"1.8.18"});c.ui.plugin.add("resizable","alsoResize",{start:function(f,g){var e=c(this).data("resizable"),i=e.options;var h=function(j){c(j).each(function(){var k=c(this);k.data("resizable-alsoresize",{width:parseInt(k.width(),10),height:parseInt(k.height(),10),left:parseInt(k.css("left"),10),top:parseInt(k.css("top"),10)})})};if(typeof(i.alsoResize)=="object"&&!i.alsoResize.parentNode){if(i.alsoResize.length){i.alsoResize=i.alsoResize[0];h(i.alsoResize)}else{c.each(i.alsoResize,function(j){h(j)})}}else{h(i.alsoResize)}},resize:function(g,i){var f=c(this).data("resizable"),j=f.options,h=f.originalSize,l=f.originalPosition;var k={height:(f.size.height-h.height)||0,width:(f.size.width-h.width)||0,top:(f.position.top-l.top)||0,left:(f.position.left-l.left)||0},e=function(m,n){c(m).each(function(){var q=c(this),r=c(this).data("resizable-alsoresize"),p={},o=n&&n.length?n:q.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];c.each(o,function(s,u){var t=(r[u]||0)+(k[u]||0);if(t&&t>=0){p[u]=t||null}});q.css(p)})};if(typeof(j.alsoResize)=="object"&&!j.alsoResize.nodeType){c.each(j.alsoResize,function(m,n){e(m,n)})}else{e(j.alsoResize)}},stop:function(e,f){c(this).removeData("resizable-alsoresize")}});c.ui.plugin.add("resizable","animate",{stop:function(i,n){var p=c(this).data("resizable"),j=p.options;var h=p._proportionallyResizeElements,e=h.length&&(/textarea/i).test(h[0].nodeName),f=e&&c.ui.hasScroll(h[0],"left")?0:p.sizeDiff.height,l=e?0:p.sizeDiff.width;var g={width:(p.size.width-l),height:(p.size.height-f)},k=(parseInt(p.element.css("left"),10)+(p.position.left-p.originalPosition.left))||null,m=(parseInt(p.element.css("top"),10)+(p.position.top-p.originalPosition.top))||null;p.element.animate(c.extend(g,m&&k?{top:m,left:k}:{}),{duration:j.animateDuration,easing:j.animateEasing,step:function(){var o={width:parseInt(p.element.css("width"),10),height:parseInt(p.element.css("height"),10),top:parseInt(p.element.css("top"),10),left:parseInt(p.element.css("left"),10)};if(h&&h.length){c(h[0]).css({width:o.width,height:o.height})}p._updateCache(o);p._propagate("resize",i)}})}});c.ui.plugin.add("resizable","containment",{start:function(f,r){var t=c(this).data("resizable"),j=t.options,l=t.element;var g=j.containment,k=(g instanceof c)?g.get(0):(/parent/.test(g))?l.parent().get(0):g;if(!k){return}t.containerElement=c(k);if(/document/.test(g)||g==document){t.containerOffset={left:0,top:0};t.containerPosition={left:0,top:0};t.parentData={element:c(document),left:0,top:0,width:c(document).width(),height:c(document).height()||document.body.parentNode.scrollHeight}}else{var n=c(k),i=[];c(["Top","Right","Left","Bottom"]).each(function(p,o){i[p]=b(n.css("padding"+o))});t.containerOffset=n.offset();t.containerPosition=n.position();t.containerSize={height:(n.innerHeight()-i[3]),width:(n.innerWidth()-i[1])};var q=t.containerOffset,e=t.containerSize.height,m=t.containerSize.width,h=(c.ui.hasScroll(k,"left")?k.scrollWidth:m),s=(c.ui.hasScroll(k)?k.scrollHeight:e);t.parentData={element:k,left:q.left,top:q.top,width:h,height:s}}},resize:function(g,q){var t=c(this).data("resizable"),i=t.options,f=t.containerSize,p=t.containerOffset,m=t.size,n=t.position,r=t._aspectRatio||g.shiftKey,e={top:0,left:0},h=t.containerElement;if(h[0]!=document&&(/static/).test(h.css("position"))){e=p}if(n.left<(t._helper?p.left:0)){t.size.width=t.size.width+(t._helper?(t.position.left-p.left):(t.position.left-e.left));if(r){t.size.height=t.size.width/i.aspectRatio}t.position.left=i.helper?p.left:0}if(n.top<(t._helper?p.top:0)){t.size.height=t.size.height+(t._helper?(t.position.top-p.top):t.position.top);if(r){t.size.width=t.size.height*i.aspectRatio}t.position.top=t._helper?p.top:0}t.offset.left=t.parentData.left+t.position.left;t.offset.top=t.parentData.top+t.position.top;var l=Math.abs((t._helper?t.offset.left-e.left:(t.offset.left-e.left))+t.sizeDiff.width),s=Math.abs((t._helper?t.offset.top-e.top:(t.offset.top-p.top))+t.sizeDiff.height);var k=t.containerElement.get(0)==t.element.parent().get(0),j=/relative|absolute/.test(t.containerElement.css("position"));if(k&&j){l-=t.parentData.left}if(l+t.size.width>=t.parentData.width){t.size.width=t.parentData.width-l;if(r){t.size.height=t.size.width/t.aspectRatio}}if(s+t.size.height>=t.parentData.height){t.size.height=t.parentData.height-s;if(r){t.size.width=t.size.height*t.aspectRatio}}},stop:function(f,n){var q=c(this).data("resizable"),g=q.options,l=q.position,m=q.containerOffset,e=q.containerPosition,i=q.containerElement;var j=c(q.helper),r=j.offset(),p=j.outerWidth()-q.sizeDiff.width,k=j.outerHeight()-q.sizeDiff.height;if(q._helper&&!g.animate&&(/relative/).test(i.css("position"))){c(this).css({left:r.left-e.left-m.left,width:p,height:k})}if(q._helper&&!g.animate&&(/static/).test(i.css("position"))){c(this).css({left:r.left-e.left-m.left,width:p,height:k})}}});c.ui.plugin.add("resizable","ghost",{start:function(g,h){var e=c(this).data("resizable"),i=e.options,f=e.size;e.ghost=e.originalElement.clone();e.ghost.css({opacity:0.25,display:"block",position:"relative",height:f.height,width:f.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof i.ghost=="string"?i.ghost:"");e.ghost.appendTo(e.helper)},resize:function(f,g){var e=c(this).data("resizable"),h=e.options;if(e.ghost){e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})}},stop:function(f,g){var e=c(this).data("resizable"),h=e.options;if(e.ghost&&e.helper){e.helper.get(0).removeChild(e.ghost.get(0))}}});c.ui.plugin.add("resizable","grid",{resize:function(e,m){var p=c(this).data("resizable"),h=p.options,k=p.size,i=p.originalSize,j=p.originalPosition,n=p.axis,l=h._aspectRatio||e.shiftKey;h.grid=typeof h.grid=="number"?[h.grid,h.grid]:h.grid;var g=Math.round((k.width-i.width)/(h.grid[0]||1))*(h.grid[0]||1),f=Math.round((k.height-i.height)/(h.grid[1]||1))*(h.grid[1]||1);if(/^(se|s|e)$/.test(n)){p.size.width=i.width+g;p.size.height=i.height+f}else{if(/^(ne)$/.test(n)){p.size.width=i.width+g;p.size.height=i.height+f;p.position.top=j.top-f}else{if(/^(sw)$/.test(n)){p.size.width=i.width+g;p.size.height=i.height+f;p.position.left=j.left-g}else{p.size.width=i.width+g;p.size.height=i.height+f;p.position.top=j.top-f;p.position.left=j.left-g}}}}});var b=function(e){return parseInt(e,10)||0};var a=function(e){return !isNaN(parseInt(e,10))}})(jQuery);/*! * jQuery hashchange event - v1.3 - 7/21/2010 * http://benalman.com/projects/jquery-hashchange-plugin/ - * + * * Copyright (c) 2010 "Cowboy" Ben Alman * Dual licensed under the MIT and GPL licenses. * http://benalman.com/about/license/ @@ -84,4 +112,4 @@ * * Licensed MIT */ -(function(a){if(typeof define==="function"&&define.amd){define(["jquery"],a)}else{if(typeof module==="object"&&typeof module.exports==="object"){module.exports=a(require("jquery"))}else{a(jQuery)}}}(function(a){var b=[],e=!!window.createPopup,f=false,d="ontouchstart" in window,h=false,g=window.requestAnimationFrame||function(l){return setTimeout(l,1000/60)},c=window.cancelAnimationFrame||function(l){clearTimeout(l)};function k(m){var n=".smartmenus_mouse";if(!h&&!m){var o=true,l=null;a(document).bind(i([["mousemove",function(s){var t={x:s.pageX,y:s.pageY,timeStamp:new Date().getTime()};if(l){var q=Math.abs(l.x-t.x),p=Math.abs(l.y-t.y);if((q>0||p>0)&&q<=2&&p<=2&&t.timeStamp-l.timeStamp<=300){f=true;if(o){var r=a(s.target).closest("a");if(r.is("a")){a.each(b,function(){if(a.contains(this.$root[0],r[0])){this.itemEnter({currentTarget:r[0]});return false}})}o=false}}}l=t}],[d?"touchstart":"pointerover pointermove pointerout MSPointerOver MSPointerMove MSPointerOut",function(p){if(j(p.originalEvent)){f=false}}]],n));h=true}else{if(h&&m){a(document).unbind(n);h=false}}}function j(l){return !/^(4|mouse)$/.test(l.pointerType)}function i(l,n){if(!n){n=""}var m={};a.each(l,function(o,p){m[p[0].split(" ").join(n+" ")+n]=p[1]});return m}a.SmartMenus=function(m,l){this.$root=a(m);this.opts=l;this.rootId="";this.accessIdPrefix="";this.$subArrow=null;this.activatedItems=[];this.visibleSubMenus=[];this.showTimeout=0;this.hideTimeout=0;this.scrollTimeout=0;this.clickActivated=false;this.focusActivated=false;this.zIndexInc=0;this.idInc=0;this.$firstLink=null;this.$firstSub=null;this.disabled=false;this.$disableOverlay=null;this.$touchScrollingSub=null;this.cssTransforms3d="perspective" in m.style||"webkitPerspective" in m.style;this.wasCollapsible=false;this.init()};a.extend(a.SmartMenus,{hideAll:function(){a.each(b,function(){this.menuHideAll()})},destroy:function(){while(b.length){b[0].destroy()}k(true)},prototype:{init:function(n){var l=this;if(!n){b.push(this);this.rootId=(new Date().getTime()+Math.random()+"").replace(/\D/g,"");this.accessIdPrefix="sm-"+this.rootId+"-";if(this.$root.hasClass("sm-rtl")){this.opts.rightToLeftSubMenus=true}var r=".smartmenus";this.$root.data("smartmenus",this).attr("data-smartmenus-id",this.rootId).dataSM("level",1).bind(i([["mouseover focusin",a.proxy(this.rootOver,this)],["mouseout focusout",a.proxy(this.rootOut,this)],["keydown",a.proxy(this.rootKeyDown,this)]],r)).delegate("a",i([["mouseenter",a.proxy(this.itemEnter,this)],["mouseleave",a.proxy(this.itemLeave,this)],["mousedown",a.proxy(this.itemDown,this)],["focus",a.proxy(this.itemFocus,this)],["blur",a.proxy(this.itemBlur,this)],["click",a.proxy(this.itemClick,this)]],r));r+=this.rootId;if(this.opts.hideOnClick){a(document).bind(i([["touchstart",a.proxy(this.docTouchStart,this)],["touchmove",a.proxy(this.docTouchMove,this)],["touchend",a.proxy(this.docTouchEnd,this)],["click",a.proxy(this.docClick,this)]],r))}a(window).bind(i([["resize orientationchange",a.proxy(this.winResize,this)]],r));if(this.opts.subIndicators){this.$subArrow=a("").addClass("sub-arrow");if(this.opts.subIndicatorsText){this.$subArrow.html(this.opts.subIndicatorsText)}}k()}this.$firstSub=this.$root.find("ul").each(function(){l.menuInit(a(this))}).eq(0);this.$firstLink=this.$root.find("a").eq(0);if(this.opts.markCurrentItem){var p=/(index|default)\.[^#\?\/]*/i,m=/#.*/,q=window.location.href.replace(p,""),o=q.replace(m,"");this.$root.find("a").each(function(){var s=this.href.replace(p,""),t=a(this);if(s==q||s==o){t.addClass("current");if(l.opts.markCurrentTree){t.parentsUntil("[data-smartmenus-id]","ul").each(function(){a(this).dataSM("parent-a").addClass("current")})}}})}this.wasCollapsible=this.isCollapsible()},destroy:function(m){if(!m){var n=".smartmenus";this.$root.removeData("smartmenus").removeAttr("data-smartmenus-id").removeDataSM("level").unbind(n).undelegate(n);n+=this.rootId;a(document).unbind(n);a(window).unbind(n);if(this.opts.subIndicators){this.$subArrow=null}}this.menuHideAll();var l=this;this.$root.find("ul").each(function(){var o=a(this);if(o.dataSM("scroll-arrows")){o.dataSM("scroll-arrows").remove()}if(o.dataSM("shown-before")){if(l.opts.subMenusMinWidth||l.opts.subMenusMaxWidth){o.css({width:"",minWidth:"",maxWidth:""}).removeClass("sm-nowrap")}if(o.dataSM("scroll-arrows")){o.dataSM("scroll-arrows").remove()}o.css({zIndex:"",top:"",left:"",marginLeft:"",marginTop:"",display:""})}if((o.attr("id")||"").indexOf(l.accessIdPrefix)==0){o.removeAttr("id")}}).removeDataSM("in-mega").removeDataSM("shown-before").removeDataSM("ie-shim").removeDataSM("scroll-arrows").removeDataSM("parent-a").removeDataSM("level").removeDataSM("beforefirstshowfired").removeAttr("role").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeAttr("aria-expanded");this.$root.find("a.has-submenu").each(function(){var o=a(this);if(o.attr("id").indexOf(l.accessIdPrefix)==0){o.removeAttr("id")}}).removeClass("has-submenu").removeDataSM("sub").removeAttr("aria-haspopup").removeAttr("aria-controls").removeAttr("aria-expanded").closest("li").removeDataSM("sub");if(this.opts.subIndicators){this.$root.find("span.sub-arrow").remove()}if(this.opts.markCurrentItem){this.$root.find("a.current").removeClass("current")}if(!m){this.$root=null;this.$firstLink=null;this.$firstSub=null;if(this.$disableOverlay){this.$disableOverlay.remove();this.$disableOverlay=null}b.splice(a.inArray(this,b),1)}},disable:function(l){if(!this.disabled){this.menuHideAll();if(!l&&!this.opts.isPopup&&this.$root.is(":visible")){var m=this.$root.offset();this.$disableOverlay=a('
    ').css({position:"absolute",top:m.top,left:m.left,width:this.$root.outerWidth(),height:this.$root.outerHeight(),zIndex:this.getStartZIndex(true),opacity:0}).appendTo(document.body)}this.disabled=true}},docClick:function(l){if(this.$touchScrollingSub){this.$touchScrollingSub=null;return}if(this.visibleSubMenus.length&&!a.contains(this.$root[0],l.target)||a(l.target).is("a")){this.menuHideAll()}},docTouchEnd:function(m){if(!this.lastTouch){return}if(this.visibleSubMenus.length&&(this.lastTouch.x2===undefined||this.lastTouch.x1==this.lastTouch.x2)&&(this.lastTouch.y2===undefined||this.lastTouch.y1==this.lastTouch.y2)&&(!this.lastTouch.target||!a.contains(this.$root[0],this.lastTouch.target))){if(this.hideTimeout){clearTimeout(this.hideTimeout);this.hideTimeout=0}var l=this;this.hideTimeout=setTimeout(function(){l.menuHideAll()},350)}this.lastTouch=null},docTouchMove:function(m){if(!this.lastTouch){return}var l=m.originalEvent.touches[0];this.lastTouch.x2=l.pageX;this.lastTouch.y2=l.pageY},docTouchStart:function(m){var l=m.originalEvent.touches[0];this.lastTouch={x1:l.pageX,y1:l.pageY,target:l.target}},enable:function(){if(this.disabled){if(this.$disableOverlay){this.$disableOverlay.remove();this.$disableOverlay=null}this.disabled=false}},getClosestMenu:function(m){var l=a(m).closest("ul");while(l.dataSM("in-mega")){l=l.parent().closest("ul")}return l[0]||null},getHeight:function(l){return this.getOffset(l,true)},getOffset:function(n,l){var m;if(n.css("display")=="none"){m={position:n[0].style.position,visibility:n[0].style.visibility};n.css({position:"absolute",visibility:"hidden"}).show()}var o=n[0].getBoundingClientRect&&n[0].getBoundingClientRect(),p=o&&(l?o.height||o.bottom-o.top:o.width||o.right-o.left);if(!p&&p!==0){p=l?n[0].offsetHeight:n[0].offsetWidth}if(m){n.hide().css(m)}return p},getStartZIndex:function(l){var m=parseInt(this[l?"$root":"$firstSub"].css("z-index"));if(!l&&isNaN(m)){m=parseInt(this.$root.css("z-index"))}return !isNaN(m)?m:1},getTouchPoint:function(l){return l.touches&&l.touches[0]||l.changedTouches&&l.changedTouches[0]||l},getViewport:function(l){var m=l?"Height":"Width",o=document.documentElement["client"+m],n=window["inner"+m];if(n){o=Math.min(o,n)}return o},getViewportHeight:function(){return this.getViewport(true)},getViewportWidth:function(){return this.getViewport()},getWidth:function(l){return this.getOffset(l)},handleEvents:function(){return !this.disabled&&this.isCSSOn()},handleItemEvents:function(l){return this.handleEvents()&&!this.isLinkInMegaMenu(l)},isCollapsible:function(){return this.$firstSub.css("position")=="static"},isCSSOn:function(){return this.$firstLink.css("display")=="block"},isFixed:function(){var l=this.$root.css("position")=="fixed";if(!l){this.$root.parentsUntil("body").each(function(){if(a(this).css("position")=="fixed"){l=true;return false}})}return l},isLinkInMegaMenu:function(l){return a(this.getClosestMenu(l[0])).hasClass("mega-menu")},isTouchMode:function(){return !f||this.opts.noMouseOver||this.isCollapsible()},itemActivate:function(p,l){var n=p.closest("ul"),q=n.dataSM("level");if(q>1&&(!this.activatedItems[q-2]||this.activatedItems[q-2][0]!=n.dataSM("parent-a")[0])){var m=this;a(n.parentsUntil("[data-smartmenus-id]","ul").get().reverse()).add(n).each(function(){m.itemActivate(a(this).dataSM("parent-a"))})}if(!this.isCollapsible()||l){this.menuHideSubMenus(!this.activatedItems[q-1]||this.activatedItems[q-1][0]!=p[0]?q-1:q)}this.activatedItems[q-1]=p;if(this.$root.triggerHandler("activate.smapi",p[0])===false){return}var o=p.dataSM("sub");if(o&&(this.isTouchMode()||(!this.opts.showOnClick||this.clickActivated))){this.menuShow(o)}},itemBlur:function(m){var l=a(m.currentTarget);if(!this.handleItemEvents(l)){return}this.$root.triggerHandler("blur.smapi",l[0])},itemClick:function(o){var n=a(o.currentTarget);if(!this.handleItemEvents(n)){return}if(this.$touchScrollingSub&&this.$touchScrollingSub[0]==n.closest("ul")[0]){this.$touchScrollingSub=null;o.stopPropagation();return false}if(this.$root.triggerHandler("click.smapi",n[0])===false){return false}var p=a(o.target).is("span.sub-arrow"),m=n.dataSM("sub"),l=m?m.dataSM("level")==2:false;if(m&&!m.is(":visible")){if(this.opts.showOnClick&&l){this.clickActivated=true}this.itemActivate(n);if(m.is(":visible")){this.focusActivated=true;return false}}else{if(this.isCollapsible()&&p){this.itemActivate(n);this.menuHide(m);return false}}if(this.opts.showOnClick&&l||n.hasClass("disabled")||this.$root.triggerHandler("select.smapi",n[0])===false){return false}},itemDown:function(m){var l=a(m.currentTarget);if(!this.handleItemEvents(l)){return}l.dataSM("mousedown",true)},itemEnter:function(n){var m=a(n.currentTarget);if(!this.handleItemEvents(m)){return}if(!this.isTouchMode()){if(this.showTimeout){clearTimeout(this.showTimeout);this.showTimeout=0}var l=this;this.showTimeout=setTimeout(function(){l.itemActivate(m)},this.opts.showOnClick&&m.closest("ul").dataSM("level")==1?1:this.opts.showTimeout)}this.$root.triggerHandler("mouseenter.smapi",m[0])},itemFocus:function(m){var l=a(m.currentTarget);if(!this.handleItemEvents(l)){return}if(this.focusActivated&&(!this.isTouchMode()||!l.dataSM("mousedown"))&&(!this.activatedItems.length||this.activatedItems[this.activatedItems.length-1][0]!=l[0])){this.itemActivate(l,true)}this.$root.triggerHandler("focus.smapi",l[0])},itemLeave:function(m){var l=a(m.currentTarget);if(!this.handleItemEvents(l)){return}if(!this.isTouchMode()){l[0].blur();if(this.showTimeout){clearTimeout(this.showTimeout);this.showTimeout=0}}l.removeDataSM("mousedown");this.$root.triggerHandler("mouseleave.smapi",l[0])},menuHide:function(m){if(this.$root.triggerHandler("beforehide.smapi",m[0])===false){return}m.stop(true,true);if(m.css("display")!="none"){var l=function(){m.css("z-index","")};if(this.isCollapsible()){if(this.opts.collapsibleHideFunction){this.opts.collapsibleHideFunction.call(this,m,l)}else{m.hide(this.opts.collapsibleHideDuration,l)}}else{if(this.opts.hideFunction){this.opts.hideFunction.call(this,m,l)}else{m.hide(this.opts.hideDuration,l)}}if(m.dataSM("ie-shim")){m.dataSM("ie-shim").remove().css({"-webkit-transform":"",transform:""})}if(m.dataSM("scroll")){this.menuScrollStop(m);m.css({"touch-action":"","-ms-touch-action":"","-webkit-transform":"",transform:""}).unbind(".smartmenus_scroll").removeDataSM("scroll").dataSM("scroll-arrows").hide()}m.dataSM("parent-a").removeClass("highlighted").attr("aria-expanded","false");m.attr({"aria-expanded":"false","aria-hidden":"true"});var n=m.dataSM("level");this.activatedItems.splice(n-1,1);this.visibleSubMenus.splice(a.inArray(m,this.visibleSubMenus),1);this.$root.triggerHandler("hide.smapi",m[0])}},menuHideAll:function(){if(this.showTimeout){clearTimeout(this.showTimeout);this.showTimeout=0}var m=this.opts.isPopup?1:0;for(var l=this.visibleSubMenus.length-1;l>=m;l--){this.menuHide(this.visibleSubMenus[l])}if(this.opts.isPopup){this.$root.stop(true,true);if(this.$root.is(":visible")){if(this.opts.hideFunction){this.opts.hideFunction.call(this,this.$root)}else{this.$root.hide(this.opts.hideDuration)}if(this.$root.dataSM("ie-shim")){this.$root.dataSM("ie-shim").remove()}}}this.activatedItems=[];this.visibleSubMenus=[];this.clickActivated=false;this.focusActivated=false;this.zIndexInc=0;this.$root.triggerHandler("hideAll.smapi")},menuHideSubMenus:function(n){for(var l=this.activatedItems.length-1;l>=n;l--){var m=this.activatedItems[l].dataSM("sub");if(m){this.menuHide(m)}}},menuIframeShim:function(l){if(e&&this.opts.overlapControlsInIE&&!l.dataSM("ie-shim")){l.dataSM("ie-shim",a("