The following examples illustrate basic creation of sparse images and binding them to physical memory.
This basic example creates a normal VkImage
object but uses
fine-grained memory allocation to back the resource with multiple memory
ranges.
VkDevice device; VkQueue queue; VkImage sparseImage; VkMemoryRequirements memoryRequirements = {}; VkDeviceSize offset = 0; VkSparseMemoryBind binds[MAX_CHUNKS] = {}; // MAX_CHUNKS is NOT part of Vulkan uint32_t bindCount = 0; // ... // Allocate image object const VkImageCreateInfo sparseImageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // sType NULL, // pNext VK_IMAGE_CREATE_SPARSE_BINDING_BIT | ..., // flags ... }; vkCreateImage(device, &sparseImageInfo, &sparseImage); // Get memory requirements vkGetImageMemoryRequirements( device, sparseImage, &memoryRequirements); // Bind memory in fine-grained fashion, find available memory ranges // from potentially multiple VkDeviceMemory pools. // (Illustration purposes only, can be optimized for perf) while (memoryRequirements.size && bindCount < MAX_CHUNKS) { VkSparseMemoryBind* pBind = &binds[bindCount]; pBind->resourceOffset = offset; AllocateOrGetMemoryRange( device, &memoryRequirements, &pBind->memory, &pBind->memoryOffset, &pBind->size); // memory ranges must be sized as multiples of the alignment assert(IsMultiple(pBind->size, memoryRequirements.alignment)); assert(IsMultiple(pBind->memoryOffset, memoryRequirements.alignment)); memoryRequirements.size -= pBind->size; offset += pBind->size; bindCount++; } // Ensure all image has backing if (memoryRequirements.size) { // Error condition - too many chunks } const VkSparseImageOpaqueMemoryBindInfo opaqueBindInfo = { sparseImage, // image bindCount, // bindCount binds // pBinds }; const VkBindSparseInfo bindSparseInfo = { VK_STRUCTURE_TYPE_BIND_SPARSE_INFO, // sType NULL, // pNext ... 1, // imageOpaqueBindCount &opaqueBindInfo, // pImageOpaqueBinds ... }; // vkQueueBindSparse is application synchronized per queue object. AcquireQueueOwnership(queue); // Actually bind memory vkQueueBindSparse(queue, 1, &bindSparseInfo, VK_NULL_HANDLE); ReleaseQueueOwnership(queue);
This more advanced example creates an arrayed color attachment / texture image and binds only LOD zero and the required metadata to physical memory.
VkDevice device; VkQueue queue; VkImage sparseImage; VkMemoryRequirements memoryRequirements = {}; uint32_t sparseRequirementsCount = 0; VkSparseImageMemoryRequirements* pSparseReqs = NULL; VkSparseMemoryBind binds[MY_IMAGE_ARRAY_SIZE] = {}; VkSparseImageMemoryBind imageBinds[MY_IMAGE_ARRAY_SIZE] = {}; uint32_t bindCount = 0; // Allocate image object (both renderable and sampleable) const VkImageCreateInfo sparseImageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // sType NULL, // pNext VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT | ..., // flags ... VK_FORMAT_R8G8B8A8_UNORM, // format ... MY_IMAGE_ARRAY_SIZE, // arrayLayers ... VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, // usage ... }; vkCreateImage(device, &sparseImageInfo, &sparseImage); // Get memory requirements vkGetImageMemoryRequirements( device, sparseImage, &memoryRequirements); // Get sparse image aspect properties vkGetImageSparseMemoryRequirements( device, sparseImage, &sparseRequirementsCount, NULL); pSparseReqs = (VkSparseImageMemoryRequirements*) malloc(sparseRequirementsCount * sizeof(VkSparseImageMemoryRequirements)); vkGetImageSparseMemoryRequirements( device, sparseImage, &sparseRequirementsCount, pSparseReqs); // Bind LOD level 0 and any required metadata to memory for (uint32_t i = 0; i < sparseRequirementsCount; ++i) { if (pSparseReqs[i].formatProperties.aspectMask & VK_IMAGE_ASPECT_METADATA_BIT) { // Metadata must not be combined with other aspects assert(pSparseReqs[i].formatProperties.aspectMask == VK_IMAGE_ASPECT_METADATA_BIT); if (pSparseReqs[i].formatProperties.flags & VK_SPARSE_IMAGE_FORMAT_SINGLE_MIPTAIL_BIT) { VkSparseMemoryBind* pBind = &binds[bindCount]; pBind->memorySize = pSparseReqs[i].imageMipTailSize; bindCount++; // ... Allocate memory range pBind->resourceOffset = pSparseReqs[i].imageMipTailOffset; pBind->memoryOffset = /* allocated memoryOffset */; pBind->memory = /* allocated memory */; pBind->flags = VK_SPARSE_MEMORY_BIND_METADATA_BIT; } else { // Need a mip tail region per array layer. for (uint32_t a = 0; a < sparseImageInfo.arrayLayers; ++a) { VkSparseMemoryBind* pBind = &binds[bindCount]; pBind->memorySize = pSparseReqs[i].imageMipTailSize; bindCount++; // ... Allocate memory range pBind->resourceOffset = pSparseReqs[i].imageMipTailOffset + (a * pSparseReqs[i].imageMipTailStride); pBind->memoryOffset = /* allocated memoryOffset */; pBind->memory = /* allocated memory */ pBind->flags = VK_SPARSE_MEMORY_BIND_METADATA_BIT; } } } else { // resource data VkExtent3D lod0BlockSize = { AlignedDivide( sparseImageInfo.extent.width, pSparseReqs[i].formatProperties.imageGranularity.width); AlignedDivide( sparseImageInfo.extent.height, pSparseReqs[i].formatProperties.imageGranularity.height); AlignedDivide( sparseImageInfo.extent.depth, pSparseReqs[i].formatProperties.imageGranularity.depth); } size_t totalBlocks = lod0BlockSize.width * lod0BlockSize.height * lod0BlockSize.depth; VkDeviceSize lod0MemSize = totalBlocks * memoryRequirements.alignment; // Allocate memory for each array layer for (uint32_t a = 0; a < sparseImageInfo.arrayLayers; ++a) { // ... Allocate memory range VkSparseImageMemoryBind* pBind = &imageBinds[a]; pBind->subresource.aspectMask = pSparseReqs[i].formatProperties.aspectMask; pBind->subresource.mipLevel = 0; pBind->subresource.arrayLayer = a; pBind->offset = (VkOffset3D){0, 0, 0}; pBind->extent = sparseImageInfo.extent; pBind->memoryOffset = /* allocated memoryOffset */; pBind->memory = /* allocated memory */; pBind->flags = 0; } } free(pSparseReqs); } const VkSparseImageOpaqueMemoryBindInfo opaqueBindInfo = { sparseImage, // image bindCount, // bindCount binds // pBinds }; const VkSparseImageMemoryBindInfo imageBindInfo = { sparseImage, // image sparseImageInfo.arrayLayers, // bindCount imageBinds // pBinds }; const VkBindSparseInfo bindSparseInfo = { VK_STRUCTURE_TYPE_BIND_SPARSE_INFO, // sType NULL, // pNext ... 1, // imageOpaqueBindCount &opaqueBindInfo, // pImageOpaqueBinds 1, // imageBindCount &imageBindInfo, // pImageBinds ... }; // vkQueueBindSparse is application synchronized per queue object. AcquireQueueOwnership(queue); // Actually bind memory vkQueueBindSparse(queue, 1, &bindSparseInfo, VK_NULL_HANDLE); ReleaseQueueOwnership(queue);