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 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(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}; + 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 image; + uint32_t endFrame; + }; + std::vector 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(); + imageInfo.image = std::make_unique(); + 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 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 30b8bf9..0940573 100644 --- a/src/Tests.cpp +++ b/src/Tests.cpp @@ -139,7 +139,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 { @@ -635,7 +635,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); diff --git a/src/VulkanSample.cpp b/src/VulkanSample.cpp index d33861e..b606433 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; static VkCommandBuffer g_hTemporaryCommandBuffer; static VkPipelineLayout g_hPipelineLayout; @@ -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,7 +1215,9 @@ 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) @@ -1225,26 +1234,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 +1318,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) ); @@ -1308,13 +1347,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 }; @@ -1335,6 +1380,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) ); @@ -1460,6 +1507,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) @@ -1716,6 +1769,9 @@ static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) case 'T': Test(); break; + case 'S': + TestSparseBinding(); + break; } return 0;