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