Wrote basic test for sparse binding.
diff --git a/src/SparseBindingTest.cpp b/src/SparseBindingTest.cpp
new file mode 100644
index 0000000..26a1bfb
--- /dev/null
+++ b/src/SparseBindingTest.cpp
@@ -0,0 +1,248 @@
+#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;

+

+void SaveAllocatorStatsToFile(const wchar_t* filePath);

+

+////////////////////////////////////////////////////////////////////////////////

+// Class definitions

+

+class BaseImage

+{

+public:

+    virtual VkResult Init(RandomNumberGenerator& rand) = 0;

+    virtual ~BaseImage();

+

+protected:

+    VkImage m_Image = VK_NULL_HANDLE;

+

+    void FillImageCreateInfo(VkImageCreateInfo& outInfo, RandomNumberGenerator& rand);

+};

+

+class TraditionalImage : public BaseImage

+{

+public:

+    virtual VkResult Init(RandomNumberGenerator& rand);

+    virtual ~TraditionalImage();

+

+private:

+    VmaAllocation m_Allocation = VK_NULL_HANDLE;

+};

+

+class SparseBindingImage : public BaseImage

+{

+public:

+    virtual VkResult Init(RandomNumberGenerator& rand);

+    virtual ~SparseBindingImage();

+

+private:

+    std::vector<VmaAllocation> m_Allocations;

+};

+

+////////////////////////////////////////////////////////////////////////////////

+// class BaseImage

+

+BaseImage::~BaseImage()

+{

+    if(m_Image)

+    {

+        vkDestroyImage(g_hDevice, m_Image, nullptr);

+    }

+}

+

+void BaseImage::FillImageCreateInfo(VkImageCreateInfo& outInfo, RandomNumberGenerator& rand)

+{

+    constexpr uint32_t imageSizeMin = 8;

+    constexpr uint32_t imageSizeMax = 2048;

+

+    ZeroMemory(&outInfo, sizeof(outInfo));

+    outInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;

+    outInfo.imageType = VK_IMAGE_TYPE_2D;

+    outInfo.extent.width = rand.Generate() % (imageSizeMax - imageSizeMin) + imageSizeMin;

+    outInfo.extent.height = rand.Generate() % (imageSizeMax - imageSizeMin) + imageSizeMin;

+    outInfo.extent.depth = 1;

+    outInfo.mipLevels = 1; // TODO ?

+    outInfo.arrayLayers = 1; // TODO ?

+    outInfo.format = VK_FORMAT_R8G8B8A8_UNORM;

+    outInfo.tiling = VK_IMAGE_TILING_OPTIMAL;

+    outInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;

+    outInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;

+    outInfo.samples = VK_SAMPLE_COUNT_1_BIT;

+    outInfo.flags = 0;

+}

+

+////////////////////////////////////////////////////////////////////////////////

+// class TraditionalImage

+

+VkResult TraditionalImage::Init(RandomNumberGenerator& rand)

+{

+    VkImageCreateInfo imageCreateInfo;

+    FillImageCreateInfo(imageCreateInfo, 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;

+    

+    const VkResult res = vmaCreateImage(g_hAllocator, &imageCreateInfo, &allocCreateInfo,

+        &m_Image, &m_Allocation, nullptr);

+    

+    return res;

+}

+

+TraditionalImage::~TraditionalImage()

+{

+    if(m_Allocation)

+    {

+        vmaFreeMemory(g_hAllocator, m_Allocation);

+    }

+}

+

+////////////////////////////////////////////////////////////////////////////////

+// class SparseBindingImage

+

+VkResult SparseBindingImage::Init(RandomNumberGenerator& rand)

+{

+    assert(g_SparseBindingEnabled && g_hSparseBindingQueue);

+

+    // Create image.

+    VkImageCreateInfo imageCreateInfo;

+    FillImageCreateInfo(imageCreateInfo, rand);

+    imageCreateInfo.flags |= VK_IMAGE_CREATE_SPARSE_BINDING_BIT;

+    VkResult res = vkCreateImage(g_hDevice, &imageCreateInfo, nullptr, &m_Image);

+    if(res != VK_SUCCESS)

+    {

+        return res;

+    }

+

+    // 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.

+    uint32_t sparseMemReqCount = 0;

+    vkGetImageSparseMemoryRequirements(g_hDevice, m_Image, &sparseMemReqCount, nullptr);

+    assert(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<VkDeviceSize>(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<VkSparseMemoryBind> binds{pageCount};

+    VmaAllocationInfo allocInfo;

+    for(uint32_t i = 0; i < pageCount; ++i)

+    {

+        res = vmaAllocateMemory(g_hAllocator, &pageMemReq, &allocCreateInfo, &m_Allocations[i], &allocInfo);

+        if(res != VK_SUCCESS)

+        {

+            return res;

+        }

+

+        binds[i] = {};

+        binds[i].resourceOffset = pageSize * i;

+        binds[i].size = pageSize;

+        binds[i].memory = allocInfo.deviceMemory;

+        binds[i].memoryOffset = allocInfo.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) );

+

+    return VK_SUCCESS;

+}

+

+SparseBindingImage::~SparseBindingImage()

+{

+    for(size_t i = m_Allocations.size(); i--; )

+    {

+        vmaFreeMemory(g_hAllocator, m_Allocations[i]);

+    }

+}

+

+////////////////////////////////////////////////////////////////////////////////

+// Private functions

+

+////////////////////////////////////////////////////////////////////////////////

+// Public functions

+

+void TestSparseBinding()

+{

+    struct ImageInfo

+    {

+        std::unique_ptr<BaseImage> image;

+        uint32_t endFrame;

+    };

+    std::vector<ImageInfo> images;

+

+    constexpr uint32_t frameCount = 2000;

+    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<TraditionalImage>();

+        imageInfo.image = std::make_unique<SparseBindingImage>();

+        if(imageInfo.image->Init(rand) == VK_SUCCESS)

+        {

+            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");

+

+    // Free remaining images.

+    images.clear();

+}

+

+#endif // #ifdef _WIN32