diff --git a/bin/SparseBindingTest.comp.spv b/bin/SparseBindingTest.comp.spv new file mode 100644 index 0000000..d56e0a9 Binary files /dev/null and b/bin/SparseBindingTest.comp.spv differ diff --git a/docs/Recording file format.md b/docs/Recording file format.md index 8fb17ea..e2d83f1 100644 --- a/docs/Recording file format.md +++ b/docs/Recording file format.md @@ -152,6 +152,10 @@ No parameters. - allocation : pointer +**vmaFreeMemoryPages** (min format version: 1.5) + +- allocations : list of pointers + **vmaCreateLostAllocation** (min format version 1.2) - allocation (output) : pointer @@ -170,6 +174,20 @@ No parameters. - allocation (output) : pointer - allocationCreateInfo.pUserData : string (may contain additional commas) +**vmaAllocateMemoryPages** (min format version 1.5) + +- vkMemoryRequirements.size : uint64 +- vkMemoryRequirements.alignment : uint64 +- vkMemoryRequirements.memoryTypeBits : uint32 +- allocationCreateInfo.flags : uint32 +- allocationCreateInfo.usage : uint32 +- allocationCreateInfo.requiredFlags : uint32 +- allocationCreateInfo.preferredFlags : uint32 +- allocationCreateInfo.memoryTypeBits : uint32 +- allocationCreateInfo.pool : pointer +- allocations (output) : list of pointers +- allocationCreateInfo.pUserData : string (may contain additional commas) + **vmaAllocateMemoryForBuffer, vmaAllocateMemoryForImage** (min format version 1.2) - vkMemoryRequirements.size : uint64 diff --git a/docs/html/globals.html b/docs/html/globals.html index 24b1903..fdc32cf 100644 --- a/docs/html/globals.html +++ b/docs/html/globals.html @@ -195,6 +195,9 @@ $(function() {
  • vmaAllocateMemoryForImage() : vk_mem_alloc.h
  • +
  • vmaAllocateMemoryPages() +: vk_mem_alloc.h +
  • VmaAllocationCreateFlagBits : vk_mem_alloc.h
  • @@ -303,6 +306,9 @@ $(function() {
  • vmaFreeMemory() : vk_mem_alloc.h
  • +
  • vmaFreeMemoryPages() +: vk_mem_alloc.h +
  • vmaFreeStatsString() : vk_mem_alloc.h
  • diff --git a/docs/html/globals_func.html b/docs/html/globals_func.html index 923fba5..a088a81 100644 --- a/docs/html/globals_func.html +++ b/docs/html/globals_func.html @@ -73,6 +73,9 @@ $(function() {
  • vmaAllocateMemoryForImage() : vk_mem_alloc.h
  • +
  • vmaAllocateMemoryPages() +: vk_mem_alloc.h +
  • vmaBindBufferMemory() : vk_mem_alloc.h
  • @@ -142,6 +145,9 @@ $(function() {
  • vmaFreeMemory() : vk_mem_alloc.h
  • +
  • vmaFreeMemoryPages() +: vk_mem_alloc.h +
  • vmaFreeStatsString() : vk_mem_alloc.h
  • diff --git a/docs/html/search/all_10.js b/docs/html/search/all_10.js index e9ab7b9..54a2afc 100644 --- a/docs/html/search/all_10.js +++ b/docs/html/search/all_10.js @@ -58,6 +58,7 @@ var searchData= ['vmaallocatememory',['vmaAllocateMemory',['../vk__mem__alloc_8h.html#abf28077dbf82d0908b8acbe8ee8dd9b8',1,'vk_mem_alloc.h']]], ['vmaallocatememoryforbuffer',['vmaAllocateMemoryForBuffer',['../vk__mem__alloc_8h.html#a7fdf64415b6c3d83c454f28d2c53df7b',1,'vk_mem_alloc.h']]], ['vmaallocatememoryforimage',['vmaAllocateMemoryForImage',['../vk__mem__alloc_8h.html#a0faa3f9e5fb233d29d1e00390650febb',1,'vk_mem_alloc.h']]], + ['vmaallocatememorypages',['vmaAllocateMemoryPages',['../vk__mem__alloc_8h.html#ad37e82e492b3de38fc3f4cffd9ad0ae1',1,'vk_mem_alloc.h']]], ['vmaallocation',['VmaAllocation',['../struct_vma_allocation.html',1,'']]], ['vmaallocationcreateflagbits',['VmaAllocationCreateFlagBits',['../vk__mem__alloc_8h.html#ad9889c10c798b040d59c92f257cae597',1,'VmaAllocationCreateFlagBits(): vk_mem_alloc.h'],['../vk__mem__alloc_8h.html#abf6bf6748c7a9fe7ce5b7835c0f56af4',1,'VmaAllocationCreateFlagBits(): vk_mem_alloc.h']]], ['vmaallocationcreateflags',['VmaAllocationCreateFlags',['../vk__mem__alloc_8h.html#a5225e5e11f8376f6a31a1791f3d6e817',1,'vk_mem_alloc.h']]], @@ -97,6 +98,7 @@ var searchData= ['vmafindmemorytypeindexforimageinfo',['vmaFindMemoryTypeIndexForImageInfo',['../vk__mem__alloc_8h.html#a088da83d8eaf3ce9056d9ea0b981d472',1,'vk_mem_alloc.h']]], ['vmaflushallocation',['vmaFlushAllocation',['../vk__mem__alloc_8h.html#abc34ee6f021f459aff885f3758c435de',1,'vk_mem_alloc.h']]], ['vmafreememory',['vmaFreeMemory',['../vk__mem__alloc_8h.html#a11f0fbc034fa81a4efedd73d61ce7568',1,'vk_mem_alloc.h']]], + ['vmafreememorypages',['vmaFreeMemoryPages',['../vk__mem__alloc_8h.html#ab9e709de044c5d8476bea77a4e755840',1,'vk_mem_alloc.h']]], ['vmafreestatsstring',['vmaFreeStatsString',['../vk__mem__alloc_8h.html#a3104eb30d8122c84dd8541063f145288',1,'vk_mem_alloc.h']]], ['vmagetallocationinfo',['vmaGetAllocationInfo',['../vk__mem__alloc_8h.html#a86dd08aba8633bfa4ad0df2e76481d8b',1,'vk_mem_alloc.h']]], ['vmagetmemoryproperties',['vmaGetMemoryProperties',['../vk__mem__alloc_8h.html#ab88db292a17974f911182543fda52d19',1,'vk_mem_alloc.h']]], diff --git a/docs/html/search/functions_0.js b/docs/html/search/functions_0.js index 4746a90..708afb4 100644 --- a/docs/html/search/functions_0.js +++ b/docs/html/search/functions_0.js @@ -3,6 +3,7 @@ var searchData= ['vmaallocatememory',['vmaAllocateMemory',['../vk__mem__alloc_8h.html#abf28077dbf82d0908b8acbe8ee8dd9b8',1,'vk_mem_alloc.h']]], ['vmaallocatememoryforbuffer',['vmaAllocateMemoryForBuffer',['../vk__mem__alloc_8h.html#a7fdf64415b6c3d83c454f28d2c53df7b',1,'vk_mem_alloc.h']]], ['vmaallocatememoryforimage',['vmaAllocateMemoryForImage',['../vk__mem__alloc_8h.html#a0faa3f9e5fb233d29d1e00390650febb',1,'vk_mem_alloc.h']]], + ['vmaallocatememorypages',['vmaAllocateMemoryPages',['../vk__mem__alloc_8h.html#ad37e82e492b3de38fc3f4cffd9ad0ae1',1,'vk_mem_alloc.h']]], ['vmabindbuffermemory',['vmaBindBufferMemory',['../vk__mem__alloc_8h.html#a6b0929b914b60cf2d45cac4bf3547470',1,'vk_mem_alloc.h']]], ['vmabindimagememory',['vmaBindImageMemory',['../vk__mem__alloc_8h.html#a3d3ca45799923aa5d138e9e5f9eb2da5',1,'vk_mem_alloc.h']]], ['vmabuildstatsstring',['vmaBuildStatsString',['../vk__mem__alloc_8h.html#aa4fee7eb5253377599ef4fd38c93c2a0',1,'vk_mem_alloc.h']]], @@ -26,6 +27,7 @@ var searchData= ['vmafindmemorytypeindexforimageinfo',['vmaFindMemoryTypeIndexForImageInfo',['../vk__mem__alloc_8h.html#a088da83d8eaf3ce9056d9ea0b981d472',1,'vk_mem_alloc.h']]], ['vmaflushallocation',['vmaFlushAllocation',['../vk__mem__alloc_8h.html#abc34ee6f021f459aff885f3758c435de',1,'vk_mem_alloc.h']]], ['vmafreememory',['vmaFreeMemory',['../vk__mem__alloc_8h.html#a11f0fbc034fa81a4efedd73d61ce7568',1,'vk_mem_alloc.h']]], + ['vmafreememorypages',['vmaFreeMemoryPages',['../vk__mem__alloc_8h.html#ab9e709de044c5d8476bea77a4e755840',1,'vk_mem_alloc.h']]], ['vmafreestatsstring',['vmaFreeStatsString',['../vk__mem__alloc_8h.html#a3104eb30d8122c84dd8541063f145288',1,'vk_mem_alloc.h']]], ['vmagetallocationinfo',['vmaGetAllocationInfo',['../vk__mem__alloc_8h.html#a86dd08aba8633bfa4ad0df2e76481d8b',1,'vk_mem_alloc.h']]], ['vmagetmemoryproperties',['vmaGetMemoryProperties',['../vk__mem__alloc_8h.html#ab88db292a17974f911182543fda52d19',1,'vk_mem_alloc.h']]], diff --git a/docs/html/vk__mem__alloc_8h.html b/docs/html/vk__mem__alloc_8h.html index bf21f7b..06a31b0 100644 --- a/docs/html/vk__mem__alloc_8h.html +++ b/docs/html/vk__mem__alloc_8h.html @@ -317,6 +317,9 @@ Functions VkResult vmaAllocateMemory (VmaAllocator allocator, const VkMemoryRequirements *pVkMemoryRequirements, const VmaAllocationCreateInfo *pCreateInfo, VmaAllocation *pAllocation, VmaAllocationInfo *pAllocationInfo)  General purpose memory allocation. More...
      +VkResult vmaAllocateMemoryPages (VmaAllocator allocator, const VkMemoryRequirements *pVkMemoryRequirements, const VmaAllocationCreateInfo *pCreateInfo, size_t allocationCount, VmaAllocation *pAllocations, VmaAllocationInfo *pAllocationInfo) + General purpose memory allocation for multiple allocation objects at once. More...
    +  VkResult vmaAllocateMemoryForBuffer (VmaAllocator allocator, VkBuffer buffer, const VmaAllocationCreateInfo *pCreateInfo, VmaAllocation *pAllocation, VmaAllocationInfo *pAllocationInfo)   VkResult vmaAllocateMemoryForImage (VmaAllocator allocator, VkImage image, const VmaAllocationCreateInfo *pCreateInfo, VmaAllocation *pAllocation, VmaAllocationInfo *pAllocationInfo) @@ -325,9 +328,9 @@ Functions void vmaFreeMemory (VmaAllocator allocator, VmaAllocation allocation)  Frees memory previously allocated using vmaAllocateMemory(), vmaAllocateMemoryForBuffer(), or vmaAllocateMemoryForImage(). More...
      -VkResult vmaResizeAllocation (VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize newSize) - Tries to resize an allocation in place, if there is enough free memory after it. More...
    -  +void vmaFreeMemoryPages (VmaAllocator allocator, size_t allocationCount, VmaAllocation *pAllocations) + Frees memory and destroys multiple allocations. More...
    +  void vmaGetAllocationInfo (VmaAllocator allocator, VmaAllocation allocation, VmaAllocationInfo *pAllocationInfo)  Returns current information about specified allocation and atomically marks it as used in current frame. More...
      @@ -894,8 +897,7 @@ Functions VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT 

    Allocation strategy that chooses biggest possible free range for the allocation.

    -VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT 

    Allocation strategy that chooses first suitable free range for the allocation.

    -

    "First" doesn't necessarily means the one with smallest offset in memory, but rather the one that is easiest and fastest to find.

    +VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT 

    Allocation strategy that chooses first suitable free range for the allocation.

    VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT 

    Allocation strategy that tries to minimize memory usage.

    @@ -1114,7 +1116,7 @@ Functions -

    You should free the memory using vmaFreeMemory().

    +

    You should free the memory using vmaFreeMemory() or vmaFreeMemoryPages().

    It is recommended to use vmaAllocateMemoryForBuffer(), vmaAllocateMemoryForImage(), vmaCreateBuffer(), vmaCreateImage() instead whenever possible.

    @@ -1219,6 +1221,76 @@ Functions

    Function similar to vmaAllocateMemoryForBuffer().

    + + + +

    ◆ vmaAllocateMemoryPages()

    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    VkResult vmaAllocateMemoryPages (VmaAllocator allocator,
    const VkMemoryRequirements * pVkMemoryRequirements,
    const VmaAllocationCreateInfopCreateInfo,
    size_t allocationCount,
    VmaAllocationpAllocations,
    VmaAllocationInfopAllocationInfo 
    )
    +
    + +

    General purpose memory allocation for multiple allocation objects at once.

    +
    Parameters
    + + + + + + + +
    allocatorAllocator object.
    pVkMemoryRequirementsMemory requirements for each allocation.
    pCreateInfoCreation parameters for each alloction.
    allocationCountNumber of allocations to make.
    [out]pAllocationsPointer to array that will be filled with handles to created allocations.
    [out]pAlocationInfoOptional. Pointer to array that will be filled with parameters of created allocations.
    +
    +
    +

    You should free the memory using vmaFreeMemory() or vmaFreeMemoryPages().

    +

    Word "pages" is just a suggestion to use this function to allocate pieces of memory needed for sparse binding. It is just a general purpose allocation function able to make multiple allocations at once. It may be internally optimized to be more efficient than calling vmaAllocateMemory() allocationCount times.

    +

    All allocations are made using same parameters. All of them are created out of the same memory pool and type. If any allocation fails, all allocations already made within this function call are also freed, so that when returned result is not VK_SUCCESS, pAllocation array is always entirely filled with VK_NULL_HANDLE.

    +

    TODO Also write tests for it.

    +

    TODO also write test for allocation that will partially fail.

    +
    @@ -2200,6 +2272,46 @@ Functions

    Frees memory previously allocated using vmaAllocateMemory(), vmaAllocateMemoryForBuffer(), or vmaAllocateMemoryForImage().

    +

    Passing VK_NULL_HANDLE as allocation is valid. Such function call is just skipped.

    + +
    + + +

    ◆ vmaFreeMemoryPages()

    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    void vmaFreeMemoryPages (VmaAllocator allocator,
    size_t allocationCount,
    VmaAllocationpAllocations 
    )
    +
    + +

    Frees memory and destroys multiple allocations.

    +

    Word "pages" is just a suggestion to use this function to free pieces of memory used for sparse binding. It is just a general purpose function to free memory and destroy allocations made using e.g. vmaAllocateMemory(), vmaAllocateMemoryPages() and other functions. It may be internally optimized to be more efficient than calling vmaFreeMemory() allocationCount times.

    +

    Allocations in pAllocations array can come from any memory pools and types. Passing VK_NULL_HANDLE as elements of pAllocations array is valid. Such entries are just skipped.

    +

    TODO Also write tests for it.

    @@ -2548,50 +2660,6 @@ Functions

    This function fails when used on allocation made in memory type that is not HOST_VISIBLE.

    This function always fails when called for allocation that was created with VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag. Such allocations cannot be mapped.

    - - - -

    ◆ vmaResizeAllocation()

    - -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - -
    VkResult vmaResizeAllocation (VmaAllocator allocator,
    VmaAllocation allocation,
    VkDeviceSize newSize 
    )
    -
    - -

    Tries to resize an allocation in place, if there is enough free memory after it.

    -

    Tries to change allocation's size without moving or reallocating it. You can both shrink and grow allocation size. When growing, it succeeds only when the allocation belongs to a memory block with enough free space after it.

    -

    Returns VK_SUCCESS if allocation's size has been successfully changed. Returns VK_ERROR_OUT_OF_POOL_MEMORY if allocation's size could not be changed.

    -

    After successful call to this function, VmaAllocationInfo::size of this allocation changes. All other parameters stay the same: memory pool and type, alignment, offset, mapped pointer.

    - -
    diff --git a/src/Shaders/CompileShaders.bat b/src/Shaders/CompileShaders.bat index b0f4a65..5d9d815 100644 --- a/src/Shaders/CompileShaders.bat +++ b/src/Shaders/CompileShaders.bat @@ -1,3 +1,4 @@ %VULKAN_SDK%/Bin32/glslangValidator.exe -V -o ../../bin/Shader.vert.spv Shader.vert %VULKAN_SDK%/Bin32/glslangValidator.exe -V -o ../../bin/Shader.frag.spv Shader.frag +%VULKAN_SDK%/Bin32/glslangValidator.exe -V -o ../../bin/SparseBindingTest.comp.spv SparseBindingTest.comp pause diff --git a/src/Shaders/SparseBindingTest.comp b/src/Shaders/SparseBindingTest.comp new file mode 100644 index 0000000..21c41ab --- /dev/null +++ b/src/Shaders/SparseBindingTest.comp @@ -0,0 +1,44 @@ +// +// Copyright (c) 2018 Advanced Micro Devices, Inc. All rights reserved. +// +// 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. +// + +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(local_size_x=1, local_size_y=1, local_size_z=1) in; + +layout(binding=0) uniform sampler2D img; +layout(binding=1) buffer buf +{ + uint bufValues[]; +}; + +void main() +{ + ivec2 xy = ivec2(bufValues[gl_GlobalInvocationID.x * 3], + bufValues[gl_GlobalInvocationID.x * 3 + 1]); + vec4 color = texture(img, xy); + bufValues[gl_GlobalInvocationID.x * 3 + 2] = + uint(color.r * 255.0) << 24 | + uint(color.g * 255.0) << 16 | + uint(color.b * 255.0) << 8 | + uint(color.a * 255.0); +} diff --git a/src/SparseBindingTest.cpp b/src/SparseBindingTest.cpp new file mode 100644 index 0000000..3722523 --- /dev/null +++ b/src/SparseBindingTest.cpp @@ -0,0 +1,571 @@ +#include "Common.h" +#include "SparseBindingTest.h" + +#ifdef _WIN32 + +//////////////////////////////////////////////////////////////////////////////// +// External imports + +extern VkDevice g_hDevice; +extern VmaAllocator g_hAllocator; +extern uint32_t g_FrameIndex; +extern bool g_SparseBindingEnabled; +extern VkQueue g_hSparseBindingQueue; +extern VkFence g_ImmediateFence; +extern VkCommandBuffer g_hTemporaryCommandBuffer; + +void BeginSingleTimeCommands(); +void EndSingleTimeCommands(); +void SaveAllocatorStatsToFile(const wchar_t* filePath); +void LoadShader(std::vector& out, const char* fileName); + +//////////////////////////////////////////////////////////////////////////////// +// Class definitions + +static uint32_t CalculateMipMapCount(uint32_t width, uint32_t height, uint32_t depth) +{ + uint32_t mipMapCount = 1; + while(width > 1 || height > 1 || depth > 1) + { + ++mipMapCount; + width /= 2; + height /= 2; + depth /= 2; + } + return mipMapCount; +} + +class BaseImage +{ +public: + virtual void Init(RandomNumberGenerator& rand) = 0; + virtual ~BaseImage(); + + const VkImageCreateInfo& GetCreateInfo() const { return m_CreateInfo; } + + void TestContent(RandomNumberGenerator& rand); + +protected: + VkImageCreateInfo m_CreateInfo = {}; + VkImage m_Image = VK_NULL_HANDLE; + + void FillImageCreateInfo(RandomNumberGenerator& rand); + void UploadContent(); + void ValidateContent(RandomNumberGenerator& rand); +}; + +class TraditionalImage : public BaseImage +{ +public: + virtual void Init(RandomNumberGenerator& rand); + virtual ~TraditionalImage(); + +private: + VmaAllocation m_Allocation = VK_NULL_HANDLE; +}; + +class SparseBindingImage : public BaseImage +{ +public: + virtual void Init(RandomNumberGenerator& rand); + virtual ~SparseBindingImage(); + +private: + std::vector m_Allocations; +}; + +//////////////////////////////////////////////////////////////////////////////// +// class BaseImage + +BaseImage::~BaseImage() +{ + if(m_Image) + { + vkDestroyImage(g_hDevice, m_Image, nullptr); + } +} + +void BaseImage::TestContent(RandomNumberGenerator& rand) +{ + printf("Validating content of %u x %u texture...\n", + m_CreateInfo.extent.width, m_CreateInfo.extent.height); + UploadContent(); + ValidateContent(rand); +} + +void BaseImage::FillImageCreateInfo(RandomNumberGenerator& rand) +{ + constexpr uint32_t imageSizeMin = 8; + constexpr uint32_t imageSizeMax = 2048; + + const bool useMipMaps = rand.Generate() % 2 != 0; + + ZeroMemory(&m_CreateInfo, sizeof(m_CreateInfo)); + m_CreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + m_CreateInfo.imageType = VK_IMAGE_TYPE_2D; + m_CreateInfo.extent.width = rand.Generate() % (imageSizeMax - imageSizeMin) + imageSizeMin; + m_CreateInfo.extent.height = rand.Generate() % (imageSizeMax - imageSizeMin) + imageSizeMin; + m_CreateInfo.extent.depth = 1; + m_CreateInfo.mipLevels = useMipMaps ? + CalculateMipMapCount(m_CreateInfo.extent.width, m_CreateInfo.extent.height, m_CreateInfo.extent.depth) : 1; + m_CreateInfo.arrayLayers = 1; + m_CreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + m_CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + m_CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + m_CreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + m_CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + m_CreateInfo.flags = 0; +} + +void BaseImage::UploadContent() +{ + VkBufferCreateInfo srcBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + srcBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + srcBufCreateInfo.size = 4 * m_CreateInfo.extent.width * m_CreateInfo.extent.height; + + VmaAllocationCreateInfo srcBufAllocCreateInfo = {}; + srcBufAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + srcBufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + + VkBuffer srcBuf = nullptr; + VmaAllocation srcBufAlloc = nullptr; + VmaAllocationInfo srcAllocInfo = {}; + TEST( vmaCreateBuffer(g_hAllocator, &srcBufCreateInfo, &srcBufAllocCreateInfo, &srcBuf, &srcBufAlloc, &srcAllocInfo) == VK_SUCCESS ); + + // Fill texels with: r = x % 255, g = u % 255, b = 13, a = 25 + uint32_t* srcBufPtr = (uint32_t*)srcAllocInfo.pMappedData; + for(uint32_t y = 0, sizeY = m_CreateInfo.extent.height; y < sizeY; ++y) + { + for(uint32_t x = 0, sizeX = m_CreateInfo.extent.width; x < sizeX; ++x, ++srcBufPtr) + { + const uint8_t r = (uint8_t)x; + const uint8_t g = (uint8_t)y; + const uint8_t b = 13; + const uint8_t a = 25; + *srcBufPtr = (uint32_t)r << 24 | (uint32_t)g << 16 | + (uint32_t)b << 8 | (uint32_t)a; + } + } + + BeginSingleTimeCommands(); + + // Barrier undefined to transfer dst. + { + VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = m_Image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, // srcStageMask + VK_PIPELINE_STAGE_TRANSFER_BIT, // dstStageMask + 0, // dependencyFlags + 0, nullptr, // memoryBarriers + 0, nullptr, // bufferMemoryBarriers + 1, &barrier); // imageMemoryBarriers + } + + // CopyBufferToImage + { + VkBufferImageCopy region = {}; + region.bufferOffset = 0; + region.bufferRowLength = 0; // Zeros mean tightly packed. + region.bufferImageHeight = 0; // Zeros mean tightly packed. + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = m_CreateInfo.extent; + vkCmdCopyBufferToImage(g_hTemporaryCommandBuffer, srcBuf, m_Image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + } + + // Barrier transfer dst to fragment shader read only. + { + VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = m_Image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, // srcStageMask + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, // dstStageMask + 0, // dependencyFlags + 0, nullptr, // memoryBarriers + 0, nullptr, // bufferMemoryBarriers + 1, &barrier); // imageMemoryBarriers + } + + EndSingleTimeCommands(); + + vmaDestroyBuffer(g_hAllocator, srcBuf, srcBufAlloc); +} + +void BaseImage::ValidateContent(RandomNumberGenerator& rand) +{ + /* + dstBuf has following layout: + For each of texels to be sampled, [0..valueCount): + struct { + in uint32_t pixelX; + in uint32_t pixelY; + out uint32_t pixelColor; + } + */ + + const uint32_t valueCount = 128; + + VkBufferCreateInfo dstBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + dstBufCreateInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + dstBufCreateInfo.size = valueCount * sizeof(uint32_t) * 3; + + VmaAllocationCreateInfo dstBufAllocCreateInfo = {}; + dstBufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + dstBufAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU; + + VkBuffer dstBuf = nullptr; + VmaAllocation dstBufAlloc = nullptr; + VmaAllocationInfo dstBufAllocInfo = {}; + TEST( vmaCreateBuffer(g_hAllocator, &dstBufCreateInfo, &dstBufAllocCreateInfo, &dstBuf, &dstBufAlloc, &dstBufAllocInfo) == VK_SUCCESS ); + + // Fill dstBuf input data. + { + uint32_t* dstBufContent = (uint32_t*)dstBufAllocInfo.pMappedData; + for(uint32_t i = 0; i < valueCount; ++i) + { + const uint32_t x = rand.Generate() % m_CreateInfo.extent.width; + const uint32_t y = rand.Generate() % m_CreateInfo.extent.height; + dstBufContent[i * 3 ] = x; + dstBufContent[i * 3 + 1] = y; + dstBufContent[i * 3 + 2] = 0; + } + } + + VkSamplerCreateInfo samplerCreateInfo = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; + samplerCreateInfo.magFilter = VK_FILTER_NEAREST; + samplerCreateInfo.minFilter = VK_FILTER_NEAREST; + samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCreateInfo.unnormalizedCoordinates = VK_TRUE; + + VkSampler sampler = nullptr; + TEST( vkCreateSampler( g_hDevice, &samplerCreateInfo, nullptr, &sampler) == VK_SUCCESS ); + + VkDescriptorSetLayoutBinding bindings[2] = {}; + bindings[0].binding = 0; + bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + bindings[0].descriptorCount = 1; + bindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bindings[0].pImmutableSamplers = &sampler; + bindings[1].binding = 1; + bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bindings[1].descriptorCount = 1; + bindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + + VkDescriptorSetLayoutCreateInfo descSetLayoutCreateInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; + descSetLayoutCreateInfo.bindingCount = 2; + descSetLayoutCreateInfo.pBindings = bindings; + + VkDescriptorSetLayout descSetLayout = nullptr; + TEST( vkCreateDescriptorSetLayout(g_hDevice, &descSetLayoutCreateInfo, nullptr, &descSetLayout) == VK_SUCCESS ); + + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; + pipelineLayoutCreateInfo.setLayoutCount = 1; + pipelineLayoutCreateInfo.pSetLayouts = &descSetLayout; + + VkPipelineLayout pipelineLayout = nullptr; + TEST( vkCreatePipelineLayout(g_hDevice, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout) == VK_SUCCESS ); + + std::vector shaderCode; + LoadShader(shaderCode, "SparseBindingTest.comp.spv"); + + VkShaderModuleCreateInfo shaderModuleCreateInfo = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; + shaderModuleCreateInfo.codeSize = shaderCode.size(); + shaderModuleCreateInfo.pCode = (const uint32_t*)shaderCode.data(); + + VkShaderModule shaderModule = nullptr; + TEST( vkCreateShaderModule(g_hDevice, &shaderModuleCreateInfo, nullptr, &shaderModule) == VK_SUCCESS ); + + VkComputePipelineCreateInfo pipelineCreateInfo = { VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO }; + pipelineCreateInfo.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + pipelineCreateInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + pipelineCreateInfo.stage.module = shaderModule; + pipelineCreateInfo.stage.pName = "main"; + pipelineCreateInfo.layout = pipelineLayout; + + VkPipeline pipeline = nullptr; + TEST( vkCreateComputePipelines(g_hDevice, nullptr, 1, &pipelineCreateInfo, nullptr, &pipeline) == VK_SUCCESS ); + + VkDescriptorPoolSize poolSizes[2] = {}; + poolSizes[0].type = bindings[0].descriptorType; + poolSizes[0].descriptorCount = bindings[0].descriptorCount; + poolSizes[1].type = bindings[1].descriptorType; + poolSizes[1].descriptorCount = bindings[1].descriptorCount; + + VkDescriptorPoolCreateInfo descPoolCreateInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; + descPoolCreateInfo.maxSets = 1; + descPoolCreateInfo.poolSizeCount = 2; + descPoolCreateInfo.pPoolSizes = poolSizes; + + VkDescriptorPool descPool = nullptr; + TEST( vkCreateDescriptorPool(g_hDevice, &descPoolCreateInfo, nullptr, &descPool) == VK_SUCCESS ); + + VkDescriptorSetAllocateInfo descSetAllocInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; + descSetAllocInfo.descriptorPool = descPool; + descSetAllocInfo.descriptorSetCount = 1; + descSetAllocInfo.pSetLayouts = &descSetLayout; + + VkDescriptorSet descSet = nullptr; + TEST( vkAllocateDescriptorSets(g_hDevice, &descSetAllocInfo, &descSet) == VK_SUCCESS ); + + VkImageViewCreateInfo imageViewCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + imageViewCreateInfo.image = m_Image; + imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.format = m_CreateInfo.format; + imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageViewCreateInfo.subresourceRange.layerCount = 1; + imageViewCreateInfo.subresourceRange.levelCount = 1; + + VkImageView imageView = nullptr; + TEST( vkCreateImageView(g_hDevice, &imageViewCreateInfo, nullptr, &imageView) == VK_SUCCESS ); + + VkDescriptorImageInfo descImageInfo = {}; + descImageInfo.imageView = imageView; + descImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkDescriptorBufferInfo descBufferInfo = {}; + descBufferInfo.buffer = dstBuf; + descBufferInfo.offset = 0; + descBufferInfo.range = VK_WHOLE_SIZE; + + VkWriteDescriptorSet descWrites[2] = {}; + descWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descWrites[0].dstSet = descSet; + descWrites[0].dstBinding = bindings[0].binding; + descWrites[0].dstArrayElement = 0; + descWrites[0].descriptorCount = 1; + descWrites[0].descriptorType = bindings[0].descriptorType; + descWrites[0].pImageInfo = &descImageInfo; + descWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descWrites[1].dstSet = descSet; + descWrites[1].dstBinding = bindings[1].binding; + descWrites[1].dstArrayElement = 0; + descWrites[1].descriptorCount = 1; + descWrites[1].descriptorType = bindings[1].descriptorType; + descWrites[1].pBufferInfo = &descBufferInfo; + vkUpdateDescriptorSets(g_hDevice, 2, descWrites, 0, nullptr); + + BeginSingleTimeCommands(); + vkCmdBindPipeline(g_hTemporaryCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); + vkCmdBindDescriptorSets(g_hTemporaryCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &descSet, 0, nullptr); + vkCmdDispatch(g_hTemporaryCommandBuffer, valueCount, 1, 1); + EndSingleTimeCommands(); + + // Validate dstBuf output data. + { + const uint32_t* dstBufContent = (const uint32_t*)dstBufAllocInfo.pMappedData; + for(uint32_t i = 0; i < valueCount; ++i) + { + const uint32_t x = dstBufContent[i * 3 ]; + const uint32_t y = dstBufContent[i * 3 + 1]; + const uint32_t color = dstBufContent[i * 3 + 2]; + const uint8_t a = (uint8_t)(color >> 24); + const uint8_t b = (uint8_t)(color >> 16); + const uint8_t g = (uint8_t)(color >> 8); + const uint8_t r = (uint8_t)color; + TEST(r == (uint8_t)x && g == (uint8_t)y && b == 13 && a == 25); + } + } + + vkDestroyImageView(g_hDevice, imageView, nullptr); + vkDestroyDescriptorPool(g_hDevice, descPool, nullptr); + vmaDestroyBuffer(g_hAllocator, dstBuf, dstBufAlloc); + vkDestroyPipeline(g_hDevice, pipeline, nullptr); + vkDestroyShaderModule(g_hDevice, shaderModule, nullptr); + vkDestroyPipelineLayout(g_hDevice, pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(g_hDevice, descSetLayout, nullptr); + vkDestroySampler(g_hDevice, sampler, nullptr); +} + +//////////////////////////////////////////////////////////////////////////////// +// class TraditionalImage + +void TraditionalImage::Init(RandomNumberGenerator& rand) +{ + FillImageCreateInfo(rand); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + // Default BEST_FIT is clearly better. + //allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT; + + ERR_GUARD_VULKAN( vmaCreateImage(g_hAllocator, &m_CreateInfo, &allocCreateInfo, + &m_Image, &m_Allocation, nullptr) ); +} + +TraditionalImage::~TraditionalImage() +{ + if(m_Allocation) + { + vmaFreeMemory(g_hAllocator, m_Allocation); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// class SparseBindingImage + +void SparseBindingImage::Init(RandomNumberGenerator& rand) +{ + assert(g_SparseBindingEnabled && g_hSparseBindingQueue); + + // Create image. + FillImageCreateInfo(rand); + m_CreateInfo.flags |= VK_IMAGE_CREATE_SPARSE_BINDING_BIT; + ERR_GUARD_VULKAN( vkCreateImage(g_hDevice, &m_CreateInfo, nullptr, &m_Image) ); + + // Get memory requirements. + VkMemoryRequirements imageMemReq; + vkGetImageMemoryRequirements(g_hDevice, m_Image, &imageMemReq); + + // This is just to silence validation layer warning. + // But it doesn't help. Looks like a bug in Vulkan validation layers. + // See: https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/364 + uint32_t sparseMemReqCount = 0; + vkGetImageSparseMemoryRequirements(g_hDevice, m_Image, &sparseMemReqCount, nullptr); + TEST(sparseMemReqCount <= 8); + VkSparseImageMemoryRequirements sparseMemReq[8]; + vkGetImageSparseMemoryRequirements(g_hDevice, m_Image, &sparseMemReqCount, sparseMemReq); + + // According to Vulkan specification, for sparse resources memReq.alignment is also page size. + const VkDeviceSize pageSize = imageMemReq.alignment; + const uint32_t pageCount = (uint32_t)ceil_div(imageMemReq.size, pageSize); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + VkMemoryRequirements pageMemReq = imageMemReq; + pageMemReq.size = pageSize; + + // Allocate and bind memory pages. + m_Allocations.resize(pageCount); + std::fill(m_Allocations.begin(), m_Allocations.end(), nullptr); + std::vector binds{pageCount}; + std::vector allocInfo{pageCount}; + ERR_GUARD_VULKAN( vmaAllocateMemoryPages(g_hAllocator, &pageMemReq, &allocCreateInfo, pageCount, m_Allocations.data(), allocInfo.data()) ); + + for(uint32_t i = 0; i < pageCount; ++i) + { + binds[i] = {}; + binds[i].resourceOffset = pageSize * i; + binds[i].size = pageSize; + binds[i].memory = allocInfo[i].deviceMemory; + binds[i].memoryOffset = allocInfo[i].offset; + } + + VkSparseImageOpaqueMemoryBindInfo imageBindInfo; + imageBindInfo.image = m_Image; + imageBindInfo.bindCount = pageCount; + imageBindInfo.pBinds = binds.data(); + + VkBindSparseInfo bindSparseInfo = { VK_STRUCTURE_TYPE_BIND_SPARSE_INFO }; + bindSparseInfo.pImageOpaqueBinds = &imageBindInfo; + bindSparseInfo.imageOpaqueBindCount = 1; + + ERR_GUARD_VULKAN( vkResetFences(g_hDevice, 1, &g_ImmediateFence) ); + ERR_GUARD_VULKAN( vkQueueBindSparse(g_hSparseBindingQueue, 1, &bindSparseInfo, g_ImmediateFence) ); + ERR_GUARD_VULKAN( vkWaitForFences(g_hDevice, 1, &g_ImmediateFence, VK_TRUE, UINT64_MAX) ); +} + +SparseBindingImage::~SparseBindingImage() +{ + vmaFreeMemoryPages(g_hAllocator, m_Allocations.size(), m_Allocations.data()); +} + +//////////////////////////////////////////////////////////////////////////////// +// Private functions + +//////////////////////////////////////////////////////////////////////////////// +// Public functions + +void TestSparseBinding() +{ + struct ImageInfo + { + std::unique_ptr image; + uint32_t endFrame; + }; + std::vector images; + + constexpr uint32_t frameCount = 1000; + constexpr uint32_t imageLifeFramesMin = 1; + constexpr uint32_t imageLifeFramesMax = 400; + + RandomNumberGenerator rand(4652467); + + for(uint32_t i = 0; i < frameCount; ++i) + { + // Bump frame index. + ++g_FrameIndex; + vmaSetCurrentFrameIndex(g_hAllocator, g_FrameIndex); + + // Create one new, random image. + ImageInfo imageInfo; + //imageInfo.image = std::make_unique(); + imageInfo.image = std::make_unique(); + imageInfo.image->Init(rand); + imageInfo.endFrame = g_FrameIndex + rand.Generate() % (imageLifeFramesMax - imageLifeFramesMin) + imageLifeFramesMin; + images.push_back(std::move(imageInfo)); + + // Delete all images that expired. + for(size_t i = images.size(); i--; ) + { + if(g_FrameIndex >= images[i].endFrame) + { + images.erase(images.begin() + i); + } + } + } + + SaveAllocatorStatsToFile(L"SparseBindingTest.json"); + + // Choose biggest image. Test uploading and sampling. + BaseImage* biggestImage = nullptr; + for(size_t i = 0, count = images.size(); i < count; ++i) + { + if(!biggestImage || + images[i].image->GetCreateInfo().extent.width * images[i].image->GetCreateInfo().extent.height > + biggestImage->GetCreateInfo().extent.width * biggestImage->GetCreateInfo().extent.height) + { + biggestImage = images[i].image.get(); + } + } + assert(biggestImage); + + biggestImage->TestContent(rand); + + // Free remaining images. + images.clear(); +} + +#endif // #ifdef _WIN32 diff --git a/src/SparseBindingTest.h b/src/SparseBindingTest.h new file mode 100644 index 0000000..8637c9c --- /dev/null +++ b/src/SparseBindingTest.h @@ -0,0 +1,7 @@ +#pragma once + +#ifdef _WIN32 + +void TestSparseBinding(); + +#endif // #ifdef _WIN32 diff --git a/src/Tests.cpp b/src/Tests.cpp index d0b3ea2..cf4c533 100644 --- a/src/Tests.cpp +++ b/src/Tests.cpp @@ -143,7 +143,7 @@ struct PoolTestResult static const uint32_t IMAGE_BYTES_PER_PIXEL = 1; -static uint32_t g_FrameIndex = 0; +uint32_t g_FrameIndex = 0; struct BufferInfo { @@ -639,7 +639,7 @@ VkResult MainTest(Result& outResult, const Config& config) return res; } -static void SaveAllocatorStatsToFile(const wchar_t* filePath) +void SaveAllocatorStatsToFile(const wchar_t* filePath) { char* stats; vmaBuildStatsString(g_hAllocator, &stats, VK_TRUE); @@ -5010,6 +5010,103 @@ static void BasicTestBuddyAllocator() vmaDestroyPool(g_hAllocator, pool); } +static void BasicTestAllocatePages() +{ + wprintf(L"Basic test allocate pages\n"); + + RandomNumberGenerator rand{765461}; + + VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + sampleBufCreateInfo.size = 1024; // Whatever. + sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo sampleAllocCreateInfo = {}; + sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + + VmaPoolCreateInfo poolCreateInfo = {}; + VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); + TEST(res == VK_SUCCESS); + + // 1 block of 1 MB. + poolCreateInfo.blockSize = 1024 * 1024; + poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1; + + // Create pool. + VmaPool pool = nullptr; + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + TEST(res == VK_SUCCESS); + + // Make 100 allocations of 4 KB - they should fit into the pool. + VkMemoryRequirements memReq; + memReq.memoryTypeBits = UINT32_MAX; + memReq.alignment = 4 * 1024; + memReq.size = 4 * 1024; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocCreateInfo.pool = pool; + + constexpr uint32_t allocCount = 100; + + std::vector alloc{allocCount}; + std::vector allocInfo{allocCount}; + res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), allocInfo.data()); + TEST(res == VK_SUCCESS); + for(uint32_t i = 0; i < allocCount; ++i) + { + TEST(alloc[i] != VK_NULL_HANDLE && + allocInfo[i].pMappedData != nullptr && + allocInfo[i].deviceMemory == allocInfo[0].deviceMemory && + allocInfo[i].memoryType == allocInfo[0].memoryType); + } + + // Free the allocations. + vmaFreeMemoryPages(g_hAllocator, allocCount, alloc.data()); + std::fill(alloc.begin(), alloc.end(), nullptr); + std::fill(allocInfo.begin(), allocInfo.end(), VmaAllocationInfo{}); + + // Try to make 100 allocations of 100 KB. This call should fail due to not enough memory. + // Also test optional allocationInfo = null. + memReq.size = 100 * 1024; + res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), nullptr); + TEST(res != VK_SUCCESS); + TEST(std::find_if(alloc.begin(), alloc.end(), [](VmaAllocation alloc){ return alloc != VK_NULL_HANDLE; }) == alloc.end()); + + // Make 100 allocations of 4 KB, but with required alignment of 128 KB. This should also fail. + memReq.size = 4 * 1024; + memReq.alignment = 128 * 1024; + res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), allocInfo.data()); + TEST(res != VK_SUCCESS); + + // Make 100 dedicated allocations of 4 KB. + memReq.alignment = 4 * 1024; + memReq.size = 4 * 1024; + + VmaAllocationCreateInfo dedicatedAllocCreateInfo = {}; + dedicatedAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + dedicatedAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &dedicatedAllocCreateInfo, allocCount, alloc.data(), allocInfo.data()); + TEST(res == VK_SUCCESS); + for(uint32_t i = 0; i < allocCount; ++i) + { + TEST(alloc[i] != VK_NULL_HANDLE && + allocInfo[i].pMappedData != nullptr && + allocInfo[i].memoryType == allocInfo[0].memoryType && + allocInfo[i].offset == 0); + if(i > 0) + { + TEST(allocInfo[i].deviceMemory != allocInfo[0].deviceMemory); + } + } + + // Free the allocations. + vmaFreeMemoryPages(g_hAllocator, allocCount, alloc.data()); + std::fill(alloc.begin(), alloc.end(), nullptr); + std::fill(allocInfo.begin(), allocInfo.end(), VmaAllocationInfo{}); + + vmaDestroyPool(g_hAllocator, pool); +} + // Test the testing environment. static void TestGpuData() { @@ -5083,6 +5180,7 @@ void Test() TestLinearAllocatorMultiBlock(); BasicTestBuddyAllocator(); + BasicTestAllocatePages(); { FILE* file; diff --git a/src/VmaReplay/VmaReplay.cpp b/src/VmaReplay/VmaReplay.cpp index d555b2d..214d376 100644 --- a/src/VmaReplay/VmaReplay.cpp +++ b/src/VmaReplay/VmaReplay.cpp @@ -73,8 +73,10 @@ enum class VMA_FUNCTION CreateImage, DestroyImage, FreeMemory, + FreeMemoryPages, CreateLostAllocation, AllocateMemory, + AllocateMemoryPages, AllocateMemoryForBuffer, AllocateMemoryForImage, MapMemory, @@ -98,8 +100,10 @@ static const char* VMA_FUNCTION_NAMES[] = { "vmaCreateImage", "vmaDestroyImage", "vmaFreeMemory", + "vmaFreeMemoryPages", "vmaCreateLostAllocation", "vmaAllocateMemory", + "vmaAllocateMemoryPages", "vmaAllocateMemoryForBuffer", "vmaAllocateMemoryForImage", "vmaMapMemory", @@ -1023,8 +1027,10 @@ private: void ExecuteCreateImage(size_t lineNumber, const CsvSplit& csvSplit); void ExecuteDestroyImage(size_t lineNumber, const CsvSplit& csvSplit) { m_Stats.RegisterFunctionCall(VMA_FUNCTION::DestroyImage); DestroyAllocation(lineNumber, csvSplit, "vmaDestroyImage"); } void ExecuteFreeMemory(size_t lineNumber, const CsvSplit& csvSplit) { m_Stats.RegisterFunctionCall(VMA_FUNCTION::FreeMemory); DestroyAllocation(lineNumber, csvSplit, "vmaFreeMemory"); } + void ExecuteFreeMemoryPages(size_t lineNumber, const CsvSplit& csvSplit); void ExecuteCreateLostAllocation(size_t lineNumber, const CsvSplit& csvSplit); void ExecuteAllocateMemory(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteAllocateMemoryPages(size_t lineNumber, const CsvSplit& csvSplit); void ExecuteAllocateMemoryForBufferOrImage(size_t lineNumber, const CsvSplit& csvSplit, OBJECT_TYPE objType); void ExecuteMapMemory(size_t lineNumber, const CsvSplit& csvSplit); void ExecuteUnmapMemory(size_t lineNumber, const CsvSplit& csvSplit); @@ -1158,10 +1164,14 @@ void Player::ExecuteLine(size_t lineNumber, const StrRange& line) ExecuteDestroyImage(lineNumber, csvSplit); else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::FreeMemory])) ExecuteFreeMemory(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::FreeMemoryPages])) + ExecuteFreeMemoryPages(lineNumber, csvSplit); else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::CreateLostAllocation])) ExecuteCreateLostAllocation(lineNumber, csvSplit); else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemory])) ExecuteAllocateMemory(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemoryPages])) + ExecuteAllocateMemoryPages(lineNumber, csvSplit); else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemoryForBuffer])) ExecuteAllocateMemoryForBufferOrImage(lineNumber, csvSplit, OBJECT_TYPE::BUFFER); else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemoryForImage])) @@ -2400,6 +2410,53 @@ void Player::ExecuteCreateImage(size_t lineNumber, const CsvSplit& csvSplit) } } +void Player::ExecuteFreeMemoryPages(size_t lineNumber, const CsvSplit& csvSplit) +{ + m_Stats.RegisterFunctionCall(VMA_FUNCTION::FreeMemoryPages); + + if(ValidateFunctionParameterCount(lineNumber, csvSplit, 1, false)) + { + std::vector origAllocPtrs; + if(StrRangeToPtrList(csvSplit.GetRange(FIRST_PARAM_INDEX), origAllocPtrs)) + { + const size_t allocCount = origAllocPtrs.size(); + size_t notNullCount = 0; + for(size_t i = 0; i < allocCount; ++i) + { + const uint64_t origAllocPtr = origAllocPtrs[i]; + if(origAllocPtr != 0) + { + const auto it = m_Allocations.find(origAllocPtr); + if(it != m_Allocations.end()) + { + Destroy(it->second); + m_Allocations.erase(it); + ++notNullCount; + } + else + { + if(IssueWarning()) + { + printf("Line %zu: Allocation %llX not found.\n", lineNumber, origAllocPtr); + } + } + } + } + if(notNullCount) + { + UpdateMemStats(); + } + } + else + { + if(IssueWarning()) + { + printf("Line %zu: Invalid parameters for vmaFreeMemoryPages.\n", lineNumber); + } + } + } +} + void Player::ExecuteCreateLostAllocation(size_t lineNumber, const CsvSplit& csvSplit) { m_Stats.RegisterFunctionCall(VMA_FUNCTION::CreateLostAllocation); @@ -2479,6 +2536,68 @@ void Player::ExecuteAllocateMemory(size_t lineNumber, const CsvSplit& csvSplit) } } +void Player::ExecuteAllocateMemoryPages(size_t lineNumber, const CsvSplit& csvSplit) +{ + m_Stats.RegisterFunctionCall(VMA_FUNCTION::AllocateMemoryPages); + + if(ValidateFunctionParameterCount(lineNumber, csvSplit, 11, true)) + { + VkMemoryRequirements memReq = {}; + VmaAllocationCreateInfo allocCreateInfo = {}; + uint64_t origPool = 0; + std::vector origPtrs; + + if(StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX), memReq.size) && + StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 1), memReq.alignment) && + StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 2), memReq.memoryTypeBits) && + StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 3), allocCreateInfo.flags) && + StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 4), (uint32_t&)allocCreateInfo.usage) && + StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 5), allocCreateInfo.requiredFlags) && + StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 6), allocCreateInfo.preferredFlags) && + StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 7), allocCreateInfo.memoryTypeBits) && + StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX + 8), origPool) && + StrRangeToPtrList(csvSplit.GetRange(FIRST_PARAM_INDEX + 9), origPtrs)) + { + const size_t allocCount = origPtrs.size(); + if(allocCount > 0) + { + FindPool(lineNumber, origPool, allocCreateInfo.pool); + + if(csvSplit.GetCount() > FIRST_PARAM_INDEX + 10) + { + PrepareUserData( + lineNumber, + allocCreateInfo.flags, + csvSplit.GetRange(FIRST_PARAM_INDEX + 10), + csvSplit.GetLine(), + allocCreateInfo.pUserData); + } + + UpdateMemStats(); + m_Stats.RegisterCreateAllocation(allocCount); + + std::vector allocations(allocCount); + + VkResult res = vmaAllocateMemoryPages(m_Allocator, &memReq, &allocCreateInfo, allocCount, allocations.data(), nullptr); + for(size_t i = 0; i < allocCount; ++i) + { + Allocation allocDesc = {}; + allocDesc.allocationFlags = allocCreateInfo.flags; + allocDesc.allocation = allocations[i]; + AddAllocation(lineNumber, origPtrs[i], res, "vmaAllocateMemoryPages", std::move(allocDesc)); + } + } + } + else + { + if(IssueWarning()) + { + printf("Line %zu: Invalid parameters for vmaAllocateMemoryPages.\n", lineNumber); + } + } + } +} + void Player::ExecuteAllocateMemoryForBufferOrImage(size_t lineNumber, const CsvSplit& csvSplit, OBJECT_TYPE objType) { switch(objType) diff --git a/src/VulkanSample.cpp b/src/VulkanSample.cpp index 8d3b05c..ed79925 100644 --- a/src/VulkanSample.cpp +++ b/src/VulkanSample.cpp @@ -22,6 +22,7 @@ #ifdef _WIN32 +#include "SparseBindingTest.h" #include "Tests.h" #include "VmaUsage.h" #include "Common.h" @@ -46,6 +47,7 @@ bool g_MemoryAliasingWarningEnabled = true; static bool g_EnableValidationLayer = true; static bool VK_KHR_get_memory_requirements2_enabled = false; static bool VK_KHR_dedicated_allocation_enabled = false; +bool g_SparseBindingEnabled = false; static HINSTANCE g_hAppInstance; static HWND g_hWnd; @@ -62,11 +64,13 @@ static std::vector g_Framebuffers; static VkCommandPool g_hCommandPool; static VkCommandBuffer g_MainCommandBuffers[COMMAND_BUFFER_COUNT]; static VkFence g_MainCommandBufferExecutedFances[COMMAND_BUFFER_COUNT]; +VkFence g_ImmediateFence; static uint32_t g_NextCommandBufferIndex; static VkSemaphore g_hImageAvailableSemaphore; static VkSemaphore g_hRenderFinishedSemaphore; static uint32_t g_GraphicsQueueFamilyIndex = UINT_MAX; static uint32_t g_PresentQueueFamilyIndex = UINT_MAX; +static uint32_t g_SparseBindingQueueFamilyIndex = UINT_MAX; static VkDescriptorSetLayout g_hDescriptorSetLayout; static VkDescriptorPool g_hDescriptorPool; static VkDescriptorSet g_hDescriptorSet; // Automatically destroyed with m_DescriptorPool. @@ -86,6 +90,7 @@ static PFN_vkDestroyDebugReportCallbackEXT g_pvkDestroyDebugReportCallbackEXT; static VkDebugReportCallbackEXT g_hCallback; static VkQueue g_hGraphicsQueue; +VkQueue g_hSparseBindingQueue; VkCommandBuffer g_hTemporaryCommandBuffer; static VkPipelineLayout g_hPipelineLayout; @@ -144,7 +149,7 @@ void EndSingleTimeCommands() ERR_GUARD_VULKAN( vkQueueWaitIdle(g_hGraphicsQueue) ); } -static void LoadShader(std::vector& out, const char* fileName) +void LoadShader(std::vector& out, const char* fileName) { std::ifstream file(std::string(SHADER_PATH1) + fileName, std::ios::ate | std::ios::binary); if(file.is_open() == false) @@ -1196,8 +1201,10 @@ static void InitializeApplication() VkPhysicalDeviceProperties physicalDeviceProperties = {}; vkGetPhysicalDeviceProperties(g_hPhysicalDevice, &physicalDeviceProperties); - //VkPhysicalDeviceFeatures physicalDeviceFreatures = {}; - //vkGetPhysicalDeviceFeatures(g_PhysicalDevice, &physicalDeviceFreatures); + VkPhysicalDeviceFeatures physicalDeviceFeatures = {}; + vkGetPhysicalDeviceFeatures(g_hPhysicalDevice, &physicalDeviceFeatures); + + g_SparseBindingEnabled = physicalDeviceFeatures.sparseBinding != 0; // Find queue family index @@ -1208,13 +1215,16 @@ static void InitializeApplication() vkGetPhysicalDeviceQueueFamilyProperties(g_hPhysicalDevice, &queueFamilyCount, queueFamilies.data()); for(uint32_t i = 0; (i < queueFamilyCount) && - (g_GraphicsQueueFamilyIndex == UINT_MAX || g_PresentQueueFamilyIndex == UINT_MAX); + (g_GraphicsQueueFamilyIndex == UINT_MAX || + g_PresentQueueFamilyIndex == UINT_MAX || + (g_SparseBindingEnabled && g_SparseBindingQueueFamilyIndex == UINT_MAX)); ++i) { if(queueFamilies[i].queueCount > 0) { + const uint32_t flagsForGraphicsQueue = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT; if((g_GraphicsQueueFamilyIndex != 0) && - ((queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0)) + ((queueFamilies[i].queueFlags & flagsForGraphicsQueue) == flagsForGraphicsQueue)) { g_GraphicsQueueFamilyIndex = i; } @@ -1225,26 +1235,56 @@ static void InitializeApplication() { g_PresentQueueFamilyIndex = i; } + + if(g_SparseBindingEnabled && + g_SparseBindingQueueFamilyIndex == UINT32_MAX && + (queueFamilies[i].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0) + { + g_SparseBindingQueueFamilyIndex = i; + } } } assert(g_GraphicsQueueFamilyIndex != UINT_MAX); + g_SparseBindingEnabled = g_SparseBindingEnabled && g_SparseBindingQueueFamilyIndex != UINT32_MAX; + // Create logical device const float queuePriority = 1.f; - VkDeviceQueueCreateInfo deviceQueueCreateInfo[2] = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO }; - deviceQueueCreateInfo[0].queueFamilyIndex = g_GraphicsQueueFamilyIndex; - deviceQueueCreateInfo[0].queueCount = 1; - deviceQueueCreateInfo[0].pQueuePriorities = &queuePriority; - deviceQueueCreateInfo[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - deviceQueueCreateInfo[1].queueFamilyIndex = g_PresentQueueFamilyIndex; - deviceQueueCreateInfo[1].queueCount = 1; - deviceQueueCreateInfo[1].pQueuePriorities = &queuePriority; + VkDeviceQueueCreateInfo queueCreateInfo[3] = {}; + uint32_t queueCount = 1; + queueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo[0].queueFamilyIndex = g_GraphicsQueueFamilyIndex; + queueCreateInfo[0].queueCount = 1; + queueCreateInfo[0].pQueuePriorities = &queuePriority; + + if(g_PresentQueueFamilyIndex != g_GraphicsQueueFamilyIndex) + { + + queueCreateInfo[queueCount].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo[queueCount].queueFamilyIndex = g_PresentQueueFamilyIndex; + queueCreateInfo[queueCount].queueCount = 1; + queueCreateInfo[queueCount].pQueuePriorities = &queuePriority; + ++queueCount; + } + + if(g_SparseBindingEnabled && + g_SparseBindingQueueFamilyIndex != g_GraphicsQueueFamilyIndex && + g_SparseBindingQueueFamilyIndex != g_PresentQueueFamilyIndex) + { + + queueCreateInfo[queueCount].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo[queueCount].queueFamilyIndex = g_SparseBindingQueueFamilyIndex; + queueCreateInfo[queueCount].queueCount = 1; + queueCreateInfo[queueCount].pQueuePriorities = &queuePriority; + ++queueCount; + } VkPhysicalDeviceFeatures deviceFeatures = {}; - deviceFeatures.fillModeNonSolid = VK_TRUE; + //deviceFeatures.fillModeNonSolid = VK_TRUE; deviceFeatures.samplerAnisotropy = VK_TRUE; + deviceFeatures.sparseBinding = g_SparseBindingEnabled ? VK_TRUE : VK_FALSE; // Determine list of device extensions to enable. std::vector enabledDeviceExtensions; @@ -1279,8 +1319,8 @@ static void InitializeApplication() deviceCreateInfo.ppEnabledLayerNames = nullptr; deviceCreateInfo.enabledExtensionCount = (uint32_t)enabledDeviceExtensions.size(); deviceCreateInfo.ppEnabledExtensionNames = !enabledDeviceExtensions.empty() ? enabledDeviceExtensions.data() : nullptr; - deviceCreateInfo.queueCreateInfoCount = g_PresentQueueFamilyIndex != g_GraphicsQueueFamilyIndex ? 2 : 1; - deviceCreateInfo.pQueueCreateInfos = deviceQueueCreateInfo; + deviceCreateInfo.queueCreateInfoCount = queueCount; + deviceCreateInfo.pQueueCreateInfos = queueCreateInfo; deviceCreateInfo.pEnabledFeatures = &deviceFeatures; ERR_GUARD_VULKAN( vkCreateDevice(g_hPhysicalDevice, &deviceCreateInfo, nullptr, &g_hDevice) ); @@ -1317,13 +1357,19 @@ static void InitializeApplication() ERR_GUARD_VULKAN( vmaCreateAllocator(&allocatorInfo, &g_hAllocator) ); - // Retrieve queue (doesn't need to be destroyed) + // Retrieve queues (don't need to be destroyed). vkGetDeviceQueue(g_hDevice, g_GraphicsQueueFamilyIndex, 0, &g_hGraphicsQueue); vkGetDeviceQueue(g_hDevice, g_PresentQueueFamilyIndex, 0, &g_hPresentQueue); assert(g_hGraphicsQueue); assert(g_hPresentQueue); + if(g_SparseBindingEnabled) + { + vkGetDeviceQueue(g_hDevice, g_SparseBindingQueueFamilyIndex, 0, &g_hSparseBindingQueue); + assert(g_hSparseBindingQueue); + } + // Create command pool VkCommandPoolCreateInfo commandPoolInfo = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; @@ -1344,6 +1390,8 @@ static void InitializeApplication() ERR_GUARD_VULKAN( vkCreateFence(g_hDevice, &fenceInfo, nullptr, &g_MainCommandBufferExecutedFances[i]) ); } + ERR_GUARD_VULKAN( vkCreateFence(g_hDevice, &fenceInfo, nullptr, &g_ImmediateFence) ); + commandBufferInfo.commandBufferCount = 1; ERR_GUARD_VULKAN( vkAllocateCommandBuffers(g_hDevice, &commandBufferInfo, &g_hTemporaryCommandBuffer) ); @@ -1469,6 +1517,12 @@ static void FinalizeApplication() g_hSampler = VK_NULL_HANDLE; } + if(g_ImmediateFence) + { + vkDestroyFence(g_hDevice, g_ImmediateFence, nullptr); + g_ImmediateFence = VK_NULL_HANDLE; + } + for(size_t i = COMMAND_BUFFER_COUNT; i--; ) { if(g_MainCommandBufferExecutedFances[i] != VK_NULL_HANDLE) @@ -1732,6 +1786,23 @@ static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) printf("ERROR: %s\n", ex.what()); } break; + case 'S': + try + { + if(g_SparseBindingEnabled) + { + TestSparseBinding(); + } + else + { + printf("Sparse binding not supported.\n"); + } + } + catch(const std::exception& ex) + { + printf("ERROR: %s\n", ex.what()); + } + break; } return 0; diff --git a/src/vk_mem_alloc.h b/src/vk_mem_alloc.h index 7cd7ad6..4afcfc1 100644 --- a/src/vk_mem_alloc.h +++ b/src/vk_mem_alloc.h @@ -2484,7 +2484,7 @@ typedef struct VmaAllocationInfo { @param[out] pAllocation Handle to allocated memory. @param[out] pAllocationInfo Optional. Information about allocated memory. It can be later fetched using function vmaGetAllocationInfo(). -You should free the memory using vmaFreeMemory(). +You should free the memory using vmaFreeMemory() or vmaFreeMemoryPages(). It is recommended to use vmaAllocateMemoryForBuffer(), vmaAllocateMemoryForImage(), vmaCreateBuffer(), vmaCreateImage() instead whenever possible. @@ -2496,6 +2496,33 @@ VkResult vmaAllocateMemory( VmaAllocation* pAllocation, VmaAllocationInfo* pAllocationInfo); +/** \brief General purpose memory allocation for multiple allocation objects at once. + +@param allocator Allocator object. +@param pVkMemoryRequirements Memory requirements for each allocation. +@param pCreateInfo Creation parameters for each alloction. +@param allocationCount Number of allocations to make. +@param[out] pAllocations Pointer to array that will be filled with handles to created allocations. +@param[out] pAlocationInfo Optional. Pointer to array that will be filled with parameters of created allocations. + +You should free the memory using vmaFreeMemory() or vmaFreeMemoryPages(). + +Word "pages" is just a suggestion to use this function to allocate pieces of memory needed for sparse binding. +It is just a general purpose allocation function able to make multiple allocations at once. +It may be internally optimized to be more efficient than calling vmaAllocateMemory() `allocationCount` times. + +All allocations are made using same parameters. All of them are created out of the same memory pool and type. +If any allocation fails, all allocations already made within this function call are also freed, so that when +returned result is not `VK_SUCCESS`, `pAllocation` array is always entirely filled with `VK_NULL_HANDLE`. +*/ +VkResult vmaAllocateMemoryPages( + VmaAllocator allocator, + const VkMemoryRequirements* pVkMemoryRequirements, + const VmaAllocationCreateInfo* pCreateInfo, + size_t allocationCount, + VmaAllocation* pAllocations, + VmaAllocationInfo* pAllocationInfo); + /** @param[out] pAllocation Handle to allocated memory. @param[out] pAllocationInfo Optional. Information about allocated memory. It can be later fetched using function vmaGetAllocationInfo(). @@ -2517,11 +2544,29 @@ VkResult vmaAllocateMemoryForImage( VmaAllocation* pAllocation, VmaAllocationInfo* pAllocationInfo); -/// Frees memory previously allocated using vmaAllocateMemory(), vmaAllocateMemoryForBuffer(), or vmaAllocateMemoryForImage(). +/** \brief Frees memory previously allocated using vmaAllocateMemory(), vmaAllocateMemoryForBuffer(), or vmaAllocateMemoryForImage(). + +Passing `VK_NULL_HANDLE` as `allocation` is valid. Such function call is just skipped. +*/ void vmaFreeMemory( VmaAllocator allocator, VmaAllocation allocation); +/** \brief Frees memory and destroys multiple allocations. + +Word "pages" is just a suggestion to use this function to free pieces of memory used for sparse binding. +It is just a general purpose function to free memory and destroy allocations made using e.g. vmaAllocateMemory(), +vmaAllocateMemoryPages() and other functions. +It may be internally optimized to be more efficient than calling vmaFreeMemory() `allocationCount` times. + +Allocations in `pAllocations` array can come from any memory pools and types. +Passing `VK_NULL_HANDLE` as elements of `pAllocations` array is valid. Such entries are just skipped. +*/ +void vmaFreeMemoryPages( + VmaAllocator allocator, + size_t allocationCount, + VmaAllocation* pAllocations); + /** \brief Tries to resize an allocation in place, if there is enough free memory after it. Tries to change allocation's size without moving or reallocating it. @@ -5764,7 +5809,8 @@ public: VkDeviceSize alignment, const VmaAllocationCreateInfo& createInfo, VmaSuballocationType suballocType, - VmaAllocation* pAllocation); + size_t allocationCount, + VmaAllocation* pAllocations); void Free( VmaAllocation hAllocation); @@ -5831,6 +5877,15 @@ private: // after this call. void IncrementallySortBlocks(); + VkResult AllocatePage( + VmaPool hCurrentPool, + uint32_t currentFrameIndex, + VkDeviceSize size, + VkDeviceSize alignment, + const VmaAllocationCreateInfo& createInfo, + VmaSuballocationType suballocType, + VmaAllocation* pAllocation); + // To be used only without CAN_MAKE_OTHER_LOST flag. VkResult AllocateFromBlock( VmaDeviceMemoryBlock* pBlock, @@ -6347,6 +6402,11 @@ public: const VkMemoryRequirements& vkMemReq, const VmaAllocationCreateInfo& createInfo, VmaAllocation allocation); + void RecordAllocateMemoryPages(uint32_t frameIndex, + const VkMemoryRequirements& vkMemReq, + const VmaAllocationCreateInfo& createInfo, + uint64_t allocationCount, + const VmaAllocation* pAllocations); void RecordAllocateMemoryForBuffer(uint32_t frameIndex, const VkMemoryRequirements& vkMemReq, bool requiresDedicatedAllocation, @@ -6361,6 +6421,9 @@ public: VmaAllocation allocation); void RecordFreeMemory(uint32_t frameIndex, VmaAllocation allocation); + void RecordFreeMemoryPages(uint32_t frameIndex, + uint64_t allocationCount, + const VmaAllocation* pAllocations); void RecordResizeAllocation( uint32_t frameIndex, VmaAllocation allocation, @@ -6443,6 +6506,7 @@ private: } } + void PrintPointerList(uint64_t count, const VmaAllocation* pItems); void Flush(); }; @@ -6546,10 +6610,13 @@ public: VkImage dedicatedImage, const VmaAllocationCreateInfo& createInfo, VmaSuballocationType suballocType, - VmaAllocation* pAllocation); + size_t allocationCount, + VmaAllocation* pAllocations); // Main deallocation function. - void FreeMemory(const VmaAllocation allocation); + void FreeMemory( + size_t allocationCount, + const VmaAllocation* pAllocations); VkResult ResizeAllocation( const VmaAllocation alloc, @@ -6632,9 +6699,21 @@ private: const VmaAllocationCreateInfo& createInfo, uint32_t memTypeIndex, VmaSuballocationType suballocType, + size_t allocationCount, + VmaAllocation* pAllocations); + + // Helper function only to be used inside AllocateDedicatedMemory. + VkResult AllocateDedicatedMemoryPage( + VkDeviceSize size, + VmaSuballocationType suballocType, + uint32_t memTypeIndex, + const VkMemoryAllocateInfo& allocInfo, + bool map, + bool isUserDataString, + void* pUserData, VmaAllocation* pAllocation); - // Allocates and registers new VkDeviceMemory specifically for single allocation. + // Allocates and registers new VkDeviceMemory specifically for dedicated allocations. VkResult AllocateDedicatedMemory( VkDeviceSize size, VmaSuballocationType suballocType, @@ -6644,7 +6723,8 @@ private: void* pUserData, VkBuffer dedicatedBuffer, VkImage dedicatedImage, - VmaAllocation* pAllocation); + size_t allocationCount, + VmaAllocation* pAllocations); // Tries to free pMemory as Dedicated Memory. Returns true if found and freed. void FreeDedicatedMemory(VmaAllocation allocation); @@ -11225,6 +11305,51 @@ bool VmaBlockVector::IsCorruptionDetectionEnabled() const static const uint32_t VMA_ALLOCATION_TRY_COUNT = 32; VkResult VmaBlockVector::Allocate( + VmaPool hCurrentPool, + uint32_t currentFrameIndex, + VkDeviceSize size, + VkDeviceSize alignment, + const VmaAllocationCreateInfo& createInfo, + VmaSuballocationType suballocType, + size_t allocationCount, + VmaAllocation* pAllocations) +{ + size_t allocIndex; + VkResult res = VK_SUCCESS; + + { + VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex); + for(allocIndex = 0; allocIndex < allocationCount; ++allocIndex) + { + res = AllocatePage( + hCurrentPool, + currentFrameIndex, + size, + alignment, + createInfo, + suballocType, + pAllocations + allocIndex); + if(res != VK_SUCCESS) + { + break; + } + } + } + + if(res != VK_SUCCESS) + { + // Free all already created allocations. + while(allocIndex--) + { + Free(pAllocations[allocIndex]); + } + memset(pAllocations, 0, sizeof(VmaAllocation) * allocationCount); + } + + return res; +} + +VkResult VmaBlockVector::AllocatePage( VmaPool hCurrentPool, uint32_t currentFrameIndex, VkDeviceSize size, @@ -11276,8 +11401,6 @@ VkResult VmaBlockVector::Allocate( return VK_ERROR_OUT_OF_DEVICE_MEMORY; } - VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex); - /* Under certain condition, this whole section can be skipped for optimization, so we move on directly to trying to allocate with canMakeOtherLost. That's the case @@ -13371,6 +13494,32 @@ void VmaRecorder::RecordAllocateMemory(uint32_t frameIndex, Flush(); } +void VmaRecorder::RecordAllocateMemoryPages(uint32_t frameIndex, + const VkMemoryRequirements& vkMemReq, + const VmaAllocationCreateInfo& createInfo, + uint64_t allocationCount, + const VmaAllocation* pAllocations) +{ + CallParams callParams; + GetBasicParams(callParams); + + VmaMutexLock lock(m_FileMutex, m_UseMutex); + UserDataString userDataStr(createInfo.flags, createInfo.pUserData); + fprintf(m_File, "%u,%.3f,%u,vmaAllocateMemoryPages,%llu,%llu,%u,%u,%u,%u,%u,%u,%p,", callParams.threadId, callParams.time, frameIndex, + vkMemReq.size, + vkMemReq.alignment, + vkMemReq.memoryTypeBits, + createInfo.flags, + createInfo.usage, + createInfo.requiredFlags, + createInfo.preferredFlags, + createInfo.memoryTypeBits, + createInfo.pool); + PrintPointerList(allocationCount, pAllocations); + fprintf(m_File, ",%s\n", userDataStr.GetString()); + Flush(); +} + void VmaRecorder::RecordAllocateMemoryForBuffer(uint32_t frameIndex, const VkMemoryRequirements& vkMemReq, bool requiresDedicatedAllocation, @@ -13441,6 +13590,20 @@ void VmaRecorder::RecordFreeMemory(uint32_t frameIndex, Flush(); } +void VmaRecorder::RecordFreeMemoryPages(uint32_t frameIndex, + uint64_t allocationCount, + const VmaAllocation* pAllocations) +{ + CallParams callParams; + GetBasicParams(callParams); + + VmaMutexLock lock(m_FileMutex, m_UseMutex); + fprintf(m_File, "%u,%.3f,%u,vmaFreeMemoryPages,", callParams.threadId, callParams.time, frameIndex); + PrintPointerList(allocationCount, pAllocations); + fprintf(m_File, "\n"); + Flush(); +} + void VmaRecorder::RecordResizeAllocation( uint32_t frameIndex, VmaAllocation allocation, @@ -13767,6 +13930,18 @@ void VmaRecorder::GetBasicParams(CallParams& outParams) outParams.time = (double)(counter.QuadPart - m_StartCounter) / (double)m_Freq; } +void VmaRecorder::PrintPointerList(uint64_t count, const VmaAllocation* pItems) +{ + if(count) + { + fprintf(m_File, "%p", pItems[0]); + for(uint64_t i = 1; i < count; ++i) + { + fprintf(m_File, " %p", pItems[i]); + } + } +} + void VmaRecorder::Flush() { if((m_Flags & VMA_RECORD_FLUSH_AFTER_CALL_BIT) != 0) @@ -14033,10 +14208,11 @@ VkResult VmaAllocator_T::AllocateMemoryOfType( const VmaAllocationCreateInfo& createInfo, uint32_t memTypeIndex, VmaSuballocationType suballocType, - VmaAllocation* pAllocation) + size_t allocationCount, + VmaAllocation* pAllocations) { - VMA_ASSERT(pAllocation != VMA_NULL); - VMA_DEBUG_LOG(" AllocateMemory: MemoryTypeIndex=%u, Size=%llu", memTypeIndex, vkMemReq.size); + VMA_ASSERT(pAllocations != VMA_NULL); + VMA_DEBUG_LOG(" AllocateMemory: MemoryTypeIndex=%u, AllocationCount=%zu, Size=%llu", memTypeIndex, allocationCount, vkMemReq.size); VmaAllocationCreateInfo finalCreateInfo = createInfo; @@ -14081,7 +14257,8 @@ VkResult VmaAllocator_T::AllocateMemoryOfType( finalCreateInfo.pUserData, dedicatedBuffer, dedicatedImage, - pAllocation); + allocationCount, + pAllocations); } } else @@ -14093,7 +14270,8 @@ VkResult VmaAllocator_T::AllocateMemoryOfType( alignment, finalCreateInfo, suballocType, - pAllocation); + allocationCount, + pAllocations); if(res == VK_SUCCESS) { return res; @@ -14115,7 +14293,8 @@ VkResult VmaAllocator_T::AllocateMemoryOfType( finalCreateInfo.pUserData, dedicatedBuffer, dedicatedImage, - pAllocation); + allocationCount, + pAllocations); if(res == VK_SUCCESS) { // Succeeded: AllocateDedicatedMemory function already filld pMemory, nothing more to do here. @@ -14141,9 +14320,10 @@ VkResult VmaAllocator_T::AllocateDedicatedMemory( void* pUserData, VkBuffer dedicatedBuffer, VkImage dedicatedImage, - VmaAllocation* pAllocation) + size_t allocationCount, + VmaAllocation* pAllocations) { - VMA_ASSERT(pAllocation); + VMA_ASSERT(allocationCount > 0 && pAllocations); VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; allocInfo.memoryTypeIndex = memTypeIndex; @@ -14167,7 +14347,80 @@ VkResult VmaAllocator_T::AllocateDedicatedMemory( } #endif // #if VMA_DEDICATED_ALLOCATION - // Allocate VkDeviceMemory. + size_t allocIndex; + VkResult res; + for(allocIndex = 0; allocIndex < allocationCount; ++allocIndex) + { + res = AllocateDedicatedMemoryPage( + size, + suballocType, + memTypeIndex, + allocInfo, + map, + isUserDataString, + pUserData, + pAllocations + allocIndex); + if(res != VK_SUCCESS) + { + break; + } + } + + if(res == VK_SUCCESS) + { + // Register them in m_pDedicatedAllocations. + { + VmaMutexLockWrite lock(m_DedicatedAllocationsMutex[memTypeIndex], m_UseMutex); + AllocationVectorType* pDedicatedAllocations = m_pDedicatedAllocations[memTypeIndex]; + VMA_ASSERT(pDedicatedAllocations); + for(allocIndex = 0; allocIndex < allocationCount; ++allocIndex) + { + VmaVectorInsertSorted(*pDedicatedAllocations, pAllocations[allocIndex]); + } + } + + VMA_DEBUG_LOG(" Allocated DedicatedMemory Count=%zu, MemoryTypeIndex=#%u", allocationCount, memTypeIndex); + } + else + { + // Free all already created allocations. + while(allocIndex--) + { + VmaAllocation currAlloc = pAllocations[allocIndex]; + VkDeviceMemory hMemory = currAlloc->GetMemory(); + + /* + There is no need to call this, because Vulkan spec allows to skip vkUnmapMemory + before vkFreeMemory. + + if(currAlloc->GetMappedData() != VMA_NULL) + { + (*m_VulkanFunctions.vkUnmapMemory)(m_hDevice, hMemory); + } + */ + + FreeVulkanMemory(memTypeIndex, currAlloc->GetSize(), hMemory); + + currAlloc->SetUserData(this, VMA_NULL); + vma_delete(this, currAlloc); + } + + memset(pAllocations, 0, sizeof(VmaAllocation) * allocationCount); + } + + return res; +} + +VkResult VmaAllocator_T::AllocateDedicatedMemoryPage( + VkDeviceSize size, + VmaSuballocationType suballocType, + uint32_t memTypeIndex, + const VkMemoryAllocateInfo& allocInfo, + bool map, + bool isUserDataString, + void* pUserData, + VmaAllocation* pAllocation) +{ VkDeviceMemory hMemory = VK_NULL_HANDLE; VkResult res = AllocateVulkanMemory(&allocInfo, &hMemory); if(res < 0) @@ -14202,16 +14455,6 @@ VkResult VmaAllocator_T::AllocateDedicatedMemory( FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED); } - // Register it in m_pDedicatedAllocations. - { - VmaMutexLockWrite lock(m_DedicatedAllocationsMutex[memTypeIndex], m_UseMutex); - AllocationVectorType* pDedicatedAllocations = m_pDedicatedAllocations[memTypeIndex]; - VMA_ASSERT(pDedicatedAllocations); - VmaVectorInsertSorted(*pDedicatedAllocations, *pAllocation); - } - - VMA_DEBUG_LOG(" Allocated DedicatedMemory MemoryTypeIndex=#%u", memTypeIndex); - return VK_SUCCESS; } @@ -14287,8 +14530,11 @@ VkResult VmaAllocator_T::AllocateMemory( VkImage dedicatedImage, const VmaAllocationCreateInfo& createInfo, VmaSuballocationType suballocType, - VmaAllocation* pAllocation) + size_t allocationCount, + VmaAllocation* pAllocations) { + memset(pAllocations, 0, sizeof(VmaAllocation) * allocationCount); + VMA_ASSERT(VmaIsPow2(vkMemReq.alignment)); if(vkMemReq.size == 0) @@ -14339,7 +14585,8 @@ VkResult VmaAllocator_T::AllocateMemory( alignmentForPool, createInfo, suballocType, - pAllocation); + allocationCount, + pAllocations); } else { @@ -14362,7 +14609,8 @@ VkResult VmaAllocator_T::AllocateMemory( createInfo, memTypeIndex, suballocType, - pAllocation); + allocationCount, + pAllocations); // Succeeded on first try. if(res == VK_SUCCESS) { @@ -14392,7 +14640,8 @@ VkResult VmaAllocator_T::AllocateMemory( createInfo, memTypeIndex, suballocType, - pAllocation); + allocationCount, + pAllocations); // Allocation from this alternative memory type succeeded. if(res == VK_SUCCESS) { @@ -14415,45 +14664,55 @@ VkResult VmaAllocator_T::AllocateMemory( } } -void VmaAllocator_T::FreeMemory(const VmaAllocation allocation) +void VmaAllocator_T::FreeMemory( + size_t allocationCount, + const VmaAllocation* pAllocations) { - VMA_ASSERT(allocation); + VMA_ASSERT(pAllocations); - if(TouchAllocation(allocation)) + for(size_t allocIndex = allocationCount; allocIndex--; ) { - if(VMA_DEBUG_INITIALIZE_ALLOCATIONS) - { - FillAllocation(allocation, VMA_ALLOCATION_FILL_PATTERN_DESTROYED); - } + VmaAllocation allocation = pAllocations[allocIndex]; - switch(allocation->GetType()) + if(allocation != VK_NULL_HANDLE) { - case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: + if(TouchAllocation(allocation)) { - VmaBlockVector* pBlockVector = VMA_NULL; - VmaPool hPool = allocation->GetPool(); - if(hPool != VK_NULL_HANDLE) + if(VMA_DEBUG_INITIALIZE_ALLOCATIONS) { - pBlockVector = &hPool->m_BlockVector; + FillAllocation(allocation, VMA_ALLOCATION_FILL_PATTERN_DESTROYED); } - else + + switch(allocation->GetType()) { - const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex(); - pBlockVector = m_pBlockVectors[memTypeIndex]; + case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: + { + VmaBlockVector* pBlockVector = VMA_NULL; + VmaPool hPool = allocation->GetPool(); + if(hPool != VK_NULL_HANDLE) + { + pBlockVector = &hPool->m_BlockVector; + } + else + { + const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex(); + pBlockVector = m_pBlockVectors[memTypeIndex]; + } + pBlockVector->Free(allocation); + } + break; + case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: + FreeDedicatedMemory(allocation); + break; + default: + VMA_ASSERT(0); } - pBlockVector->Free(allocation); } - break; - case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: - FreeDedicatedMemory(allocation); - break; - default: - VMA_ASSERT(0); + + allocation->SetUserData(this, VMA_NULL); + vma_delete(this, allocation); } } - - allocation->SetUserData(this, VMA_NULL); - vma_delete(this, allocation); } VkResult VmaAllocator_T::ResizeAllocation( @@ -15660,6 +15919,7 @@ VkResult vmaAllocateMemory( VK_NULL_HANDLE, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_UNKNOWN, + 1, // allocationCount pAllocation); #if VMA_RECORDING_ENABLED @@ -15681,6 +15941,59 @@ VkResult vmaAllocateMemory( return result; } +VkResult vmaAllocateMemoryPages( + VmaAllocator allocator, + const VkMemoryRequirements* pVkMemoryRequirements, + const VmaAllocationCreateInfo* pCreateInfo, + size_t allocationCount, + VmaAllocation* pAllocations, + VmaAllocationInfo* pAllocationInfo) +{ + if(allocationCount == 0) + { + return VK_SUCCESS; + } + + VMA_ASSERT(allocator && pVkMemoryRequirements && pCreateInfo && pAllocations); + + VMA_DEBUG_LOG("vmaAllocateMemoryPages"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + VkResult result = allocator->AllocateMemory( + *pVkMemoryRequirements, + false, // requiresDedicatedAllocation + false, // prefersDedicatedAllocation + VK_NULL_HANDLE, // dedicatedBuffer + VK_NULL_HANDLE, // dedicatedImage + *pCreateInfo, + VMA_SUBALLOCATION_TYPE_UNKNOWN, + allocationCount, + pAllocations); + +#if VMA_RECORDING_ENABLED + if(allocator->GetRecorder() != VMA_NULL) + { + allocator->GetRecorder()->RecordAllocateMemoryPages( + allocator->GetCurrentFrameIndex(), + *pVkMemoryRequirements, + *pCreateInfo, + (uint64_t)allocationCount, + pAllocations); + } +#endif + + if(pAllocationInfo != VMA_NULL && result == VK_SUCCESS) + { + for(size_t i = 0; i < allocationCount; ++i) + { + allocator->GetAllocationInfo(pAllocations[i], pAllocationInfo + i); + } + } + + return result; +} + VkResult vmaAllocateMemoryForBuffer( VmaAllocator allocator, VkBuffer buffer, @@ -15709,6 +16022,7 @@ VkResult vmaAllocateMemoryForBuffer( VK_NULL_HANDLE, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_BUFFER, + 1, // allocationCount pAllocation); #if VMA_RECORDING_ENABLED @@ -15759,6 +16073,7 @@ VkResult vmaAllocateMemoryForImage( image, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN, + 1, // allocationCount pAllocation); #if VMA_RECORDING_ENABLED @@ -15806,7 +16121,38 @@ void vmaFreeMemory( } #endif - allocator->FreeMemory(allocation); + allocator->FreeMemory( + 1, // allocationCount + &allocation); +} + +void vmaFreeMemoryPages( + VmaAllocator allocator, + size_t allocationCount, + VmaAllocation* pAllocations) +{ + if(allocationCount == 0) + { + return; + } + + VMA_ASSERT(allocator); + + VMA_DEBUG_LOG("vmaFreeMemoryPages"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + +#if VMA_RECORDING_ENABLED + if(allocator->GetRecorder() != VMA_NULL) + { + allocator->GetRecorder()->RecordFreeMemoryPages( + allocator->GetCurrentFrameIndex(), + (uint64_t)allocationCount, + pAllocations); + } +#endif + + allocator->FreeMemory(allocationCount, pAllocations); } VkResult vmaResizeAllocation( @@ -16201,6 +16547,7 @@ VkResult vmaCreateBuffer( VK_NULL_HANDLE, // dedicatedImage *pAllocationCreateInfo, VMA_SUBALLOCATION_TYPE_BUFFER, + 1, // allocationCount pAllocation); #if VMA_RECORDING_ENABLED @@ -16231,7 +16578,9 @@ VkResult vmaCreateBuffer( return VK_SUCCESS; } - allocator->FreeMemory(*pAllocation); + allocator->FreeMemory( + 1, // allocationCount + pAllocation); *pAllocation = VK_NULL_HANDLE; (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, *pBuffer, allocator->GetAllocationCallbacks()); *pBuffer = VK_NULL_HANDLE; @@ -16276,7 +16625,9 @@ void vmaDestroyBuffer( if(allocation != VK_NULL_HANDLE) { - allocator->FreeMemory(allocation); + allocator->FreeMemory( + 1, // allocationCount + &allocation); } } @@ -16333,6 +16684,7 @@ VkResult vmaCreateImage( *pImage, // dedicatedImage *pAllocationCreateInfo, suballocType, + 1, // allocationCount pAllocation); #if VMA_RECORDING_ENABLED @@ -16363,7 +16715,9 @@ VkResult vmaCreateImage( return VK_SUCCESS; } - allocator->FreeMemory(*pAllocation); + allocator->FreeMemory( + 1, // allocationCount + pAllocation); *pAllocation = VK_NULL_HANDLE; (*allocator->GetVulkanFunctions().vkDestroyImage)(allocator->m_hDevice, *pImage, allocator->GetAllocationCallbacks()); *pImage = VK_NULL_HANDLE; @@ -16407,7 +16761,9 @@ void vmaDestroyImage( } if(allocation != VK_NULL_HANDLE) { - allocator->FreeMemory(allocation); + allocator->FreeMemory( + 1, // allocationCount + &allocation); } }