// // Copyright (c) 2017-2020 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. // #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