|
| 1 | +#include "Common.h" |
| 2 | +#include "SparseBindingTest.h" |
| 3 | + |
| 4 | +#ifdef _WIN32 |
| 5 | + |
| 6 | +//////////////////////////////////////////////////////////////////////////////// |
| 7 | +// External imports |
| 8 | + |
| 9 | +extern VkDevice g_hDevice; |
| 10 | +extern VmaAllocator g_hAllocator; |
| 11 | +extern uint32_t g_FrameIndex; |
| 12 | +extern bool g_SparseBindingEnabled; |
| 13 | +extern VkQueue g_hSparseBindingQueue; |
| 14 | +extern VkFence g_ImmediateFence; |
| 15 | + |
| 16 | +void SaveAllocatorStatsToFile(const wchar_t* filePath); |
| 17 | + |
| 18 | +//////////////////////////////////////////////////////////////////////////////// |
| 19 | +// Class definitions |
| 20 | + |
| 21 | +class BaseImage |
| 22 | +{ |
| 23 | +public: |
| 24 | + virtual VkResult Init(RandomNumberGenerator& rand) = 0; |
| 25 | + virtual ~BaseImage(); |
| 26 | + |
| 27 | +protected: |
| 28 | + VkImage m_Image = VK_NULL_HANDLE; |
| 29 | + |
| 30 | + void FillImageCreateInfo(VkImageCreateInfo& outInfo, RandomNumberGenerator& rand); |
| 31 | +}; |
| 32 | + |
| 33 | +class TraditionalImage : public BaseImage |
| 34 | +{ |
| 35 | +public: |
| 36 | + virtual VkResult Init(RandomNumberGenerator& rand); |
| 37 | + virtual ~TraditionalImage(); |
| 38 | + |
| 39 | +private: |
| 40 | + VmaAllocation m_Allocation = VK_NULL_HANDLE; |
| 41 | +}; |
| 42 | + |
| 43 | +class SparseBindingImage : public BaseImage |
| 44 | +{ |
| 45 | +public: |
| 46 | + virtual VkResult Init(RandomNumberGenerator& rand); |
| 47 | + virtual ~SparseBindingImage(); |
| 48 | + |
| 49 | +private: |
| 50 | + std::vector<VmaAllocation> m_Allocations; |
| 51 | +}; |
| 52 | + |
| 53 | +//////////////////////////////////////////////////////////////////////////////// |
| 54 | +// class BaseImage |
| 55 | + |
| 56 | +BaseImage::~BaseImage() |
| 57 | +{ |
| 58 | + if(m_Image) |
| 59 | + { |
| 60 | + vkDestroyImage(g_hDevice, m_Image, nullptr); |
| 61 | + } |
| 62 | +} |
| 63 | + |
| 64 | +void BaseImage::FillImageCreateInfo(VkImageCreateInfo& outInfo, RandomNumberGenerator& rand) |
| 65 | +{ |
| 66 | + constexpr uint32_t imageSizeMin = 8; |
| 67 | + constexpr uint32_t imageSizeMax = 2048; |
| 68 | + |
| 69 | + ZeroMemory(&outInfo, sizeof(outInfo)); |
| 70 | + outInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; |
| 71 | + outInfo.imageType = VK_IMAGE_TYPE_2D; |
| 72 | + outInfo.extent.width = rand.Generate() % (imageSizeMax - imageSizeMin) + imageSizeMin; |
| 73 | + outInfo.extent.height = rand.Generate() % (imageSizeMax - imageSizeMin) + imageSizeMin; |
| 74 | + outInfo.extent.depth = 1; |
| 75 | + outInfo.mipLevels = 1; // TODO ? |
| 76 | + outInfo.arrayLayers = 1; // TODO ? |
| 77 | + outInfo.format = VK_FORMAT_R8G8B8A8_UNORM; |
| 78 | + outInfo.tiling = VK_IMAGE_TILING_OPTIMAL; |
| 79 | + outInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
| 80 | + outInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; |
| 81 | + outInfo.samples = VK_SAMPLE_COUNT_1_BIT; |
| 82 | + outInfo.flags = 0; |
| 83 | +} |
| 84 | + |
| 85 | +//////////////////////////////////////////////////////////////////////////////// |
| 86 | +// class TraditionalImage |
| 87 | + |
| 88 | +VkResult TraditionalImage::Init(RandomNumberGenerator& rand) |
| 89 | +{ |
| 90 | + VkImageCreateInfo imageCreateInfo; |
| 91 | + FillImageCreateInfo(imageCreateInfo, rand); |
| 92 | + |
| 93 | + VmaAllocationCreateInfo allocCreateInfo = {}; |
| 94 | + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; |
| 95 | + // Default BEST_FIT is clearly better. |
| 96 | + //allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT; |
| 97 | + |
| 98 | + const VkResult res = vmaCreateImage(g_hAllocator, &imageCreateInfo, &allocCreateInfo, |
| 99 | + &m_Image, &m_Allocation, nullptr); |
| 100 | + |
| 101 | + return res; |
| 102 | +} |
| 103 | + |
| 104 | +TraditionalImage::~TraditionalImage() |
| 105 | +{ |
| 106 | + if(m_Allocation) |
| 107 | + { |
| 108 | + vmaFreeMemory(g_hAllocator, m_Allocation); |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +//////////////////////////////////////////////////////////////////////////////// |
| 113 | +// class SparseBindingImage |
| 114 | + |
| 115 | +VkResult SparseBindingImage::Init(RandomNumberGenerator& rand) |
| 116 | +{ |
| 117 | + assert(g_SparseBindingEnabled && g_hSparseBindingQueue); |
| 118 | + |
| 119 | + // Create image. |
| 120 | + VkImageCreateInfo imageCreateInfo; |
| 121 | + FillImageCreateInfo(imageCreateInfo, rand); |
| 122 | + imageCreateInfo.flags |= VK_IMAGE_CREATE_SPARSE_BINDING_BIT; |
| 123 | + VkResult res = vkCreateImage(g_hDevice, &imageCreateInfo, nullptr, &m_Image); |
| 124 | + if(res != VK_SUCCESS) |
| 125 | + { |
| 126 | + return res; |
| 127 | + } |
| 128 | + |
| 129 | + // Get memory requirements. |
| 130 | + VkMemoryRequirements imageMemReq; |
| 131 | + vkGetImageMemoryRequirements(g_hDevice, m_Image, &imageMemReq); |
| 132 | + |
| 133 | + // This is just to silence validation layer warning. |
| 134 | + // But it doesn't help. Looks like a bug in Vulkan validation layers. |
| 135 | + uint32_t sparseMemReqCount = 0; |
| 136 | + vkGetImageSparseMemoryRequirements(g_hDevice, m_Image, &sparseMemReqCount, nullptr); |
| 137 | + assert(sparseMemReqCount <= 8); |
| 138 | + VkSparseImageMemoryRequirements sparseMemReq[8]; |
| 139 | + vkGetImageSparseMemoryRequirements(g_hDevice, m_Image, &sparseMemReqCount, sparseMemReq); |
| 140 | + |
| 141 | + // According to Vulkan specification, for sparse resources memReq.alignment is also page size. |
| 142 | + const VkDeviceSize pageSize = imageMemReq.alignment; |
| 143 | + const uint32_t pageCount = (uint32_t)ceil_div<VkDeviceSize>(imageMemReq.size, pageSize); |
| 144 | + |
| 145 | + VmaAllocationCreateInfo allocCreateInfo = {}; |
| 146 | + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; |
| 147 | + |
| 148 | + VkMemoryRequirements pageMemReq = imageMemReq; |
| 149 | + pageMemReq.size = pageSize; |
| 150 | + |
| 151 | + // Allocate and bind memory pages. |
| 152 | + m_Allocations.resize(pageCount); |
| 153 | + std::fill(m_Allocations.begin(), m_Allocations.end(), nullptr); |
| 154 | + std::vector<VkSparseMemoryBind> binds{pageCount}; |
| 155 | + VmaAllocationInfo allocInfo; |
| 156 | + for(uint32_t i = 0; i < pageCount; ++i) |
| 157 | + { |
| 158 | + res = vmaAllocateMemory(g_hAllocator, &pageMemReq, &allocCreateInfo, &m_Allocations[i], &allocInfo); |
| 159 | + if(res != VK_SUCCESS) |
| 160 | + { |
| 161 | + return res; |
| 162 | + } |
| 163 | + |
| 164 | + binds[i] = {}; |
| 165 | + binds[i].resourceOffset = pageSize * i; |
| 166 | + binds[i].size = pageSize; |
| 167 | + binds[i].memory = allocInfo.deviceMemory; |
| 168 | + binds[i].memoryOffset = allocInfo.offset; |
| 169 | + } |
| 170 | + |
| 171 | + VkSparseImageOpaqueMemoryBindInfo imageBindInfo; |
| 172 | + imageBindInfo.image = m_Image; |
| 173 | + imageBindInfo.bindCount = pageCount; |
| 174 | + imageBindInfo.pBinds = binds.data(); |
| 175 | + |
| 176 | + VkBindSparseInfo bindSparseInfo = { VK_STRUCTURE_TYPE_BIND_SPARSE_INFO }; |
| 177 | + bindSparseInfo.pImageOpaqueBinds = &imageBindInfo; |
| 178 | + bindSparseInfo.imageOpaqueBindCount = 1; |
| 179 | + |
| 180 | + ERR_GUARD_VULKAN( vkResetFences(g_hDevice, 1, &g_ImmediateFence) ); |
| 181 | + ERR_GUARD_VULKAN( vkQueueBindSparse(g_hSparseBindingQueue, 1, &bindSparseInfo, g_ImmediateFence) ); |
| 182 | + ERR_GUARD_VULKAN( vkWaitForFences(g_hDevice, 1, &g_ImmediateFence, VK_TRUE, UINT64_MAX) ); |
| 183 | + |
| 184 | + return VK_SUCCESS; |
| 185 | +} |
| 186 | + |
| 187 | +SparseBindingImage::~SparseBindingImage() |
| 188 | +{ |
| 189 | + for(size_t i = m_Allocations.size(); i--; ) |
| 190 | + { |
| 191 | + vmaFreeMemory(g_hAllocator, m_Allocations[i]); |
| 192 | + } |
| 193 | +} |
| 194 | + |
| 195 | +//////////////////////////////////////////////////////////////////////////////// |
| 196 | +// Private functions |
| 197 | + |
| 198 | +//////////////////////////////////////////////////////////////////////////////// |
| 199 | +// Public functions |
| 200 | + |
| 201 | +void TestSparseBinding() |
| 202 | +{ |
| 203 | + struct ImageInfo |
| 204 | + { |
| 205 | + std::unique_ptr<BaseImage> image; |
| 206 | + uint32_t endFrame; |
| 207 | + }; |
| 208 | + std::vector<ImageInfo> images; |
| 209 | + |
| 210 | + constexpr uint32_t frameCount = 2000; |
| 211 | + constexpr uint32_t imageLifeFramesMin = 1; |
| 212 | + constexpr uint32_t imageLifeFramesMax = 400; |
| 213 | + |
| 214 | + RandomNumberGenerator rand(4652467); |
| 215 | + |
| 216 | + for(uint32_t i = 0; i < frameCount; ++i) |
| 217 | + { |
| 218 | + // Bump frame index. |
| 219 | + ++g_FrameIndex; |
| 220 | + vmaSetCurrentFrameIndex(g_hAllocator, g_FrameIndex); |
| 221 | + |
| 222 | + // Create one new, random image. |
| 223 | + ImageInfo imageInfo; |
| 224 | + //imageInfo.image = std::make_unique<TraditionalImage>(); |
| 225 | + imageInfo.image = std::make_unique<SparseBindingImage>(); |
| 226 | + if(imageInfo.image->Init(rand) == VK_SUCCESS) |
| 227 | + { |
| 228 | + imageInfo.endFrame = g_FrameIndex + rand.Generate() % (imageLifeFramesMax - imageLifeFramesMin) + imageLifeFramesMin; |
| 229 | + images.push_back(std::move(imageInfo)); |
| 230 | + } |
| 231 | + |
| 232 | + // Delete all images that expired. |
| 233 | + for(size_t i = images.size(); i--; ) |
| 234 | + { |
| 235 | + if(g_FrameIndex >= images[i].endFrame) |
| 236 | + { |
| 237 | + images.erase(images.begin() + i); |
| 238 | + } |
| 239 | + } |
| 240 | + } |
| 241 | + |
| 242 | + SaveAllocatorStatsToFile(L"SparseBindingTest.json"); |
| 243 | + |
| 244 | + // Free remaining images. |
| 245 | + images.clear(); |
| 246 | +} |
| 247 | + |
| 248 | +#endif // #ifdef _WIN32 |
0 commit comments