Implementing the image resource with optimal tiling

Optimal tiling is implemented through the staging buffer. First, a buffer resource object is created and stored with the raw image data contents. Next, the buffer resource data contents are copied to a newly created image object using the buffer-to-image copy command. The buffer-to-image copy command (vkCmdCopyBufferToImage) copies the buffer memory contents to the image memory.

In this section, we will implement the image resources using optimal tiling. In order to create an image resource with optimal tiling our user defined function VulkanRenderer::createTextureOptimal() can be used. This function takes parameters in the same way as the createTextureLinear() function:

void VulkanRenderer::createTextureOptimal(const char* filename, TextureData *texture, VkImageUsageFlags imageUsageFlags, VkFormat format); 

Let's understand and implement these functions step by step.

Loading the image file

Load the image file and retrieve its dimensions and the mipmap-level information:

// Load the image 
gli::texture2D image2D(gli::load(filename)); assert(!image2D.empty()); 
 
// Get the image dimensions 
texture->textureWidth  = uint32_t(image2D[0].dimensions().x); 
texture->textureHeight = uint32_t(image2D[0].dimensions().y); 
 
// Get number of mip-map levels 
texture->mipMapLevels  = uint32_t(image2D.levels()); 

Buffer object memory allocation and binding

The created image object does not have any device memory backing. In this step, we will allocate the physical device memory and bind it with the created texture->image. For more information on memory allocation and the binding process, refer to the Memory allocation and binding image resources section in Chapter 6, Allocating Image Resources and Building a Swapchain with WSI:

// Create a staging buffer resource states using.

// Indicate it be the source of the transfer command.

// .usage    = VK_BUFFER_USAGE_TRANSFER_SRC_BIT 
VkBufferCreateInfo bufferCreateInfo = {}; 
bufferCreateInfo.sType              = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; 
bufferCreateInfo.size               = image2D.size(); 
bufferCreateInfo.usage              = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; 
bufferCreateInfo.sharingMode        = VK_SHARING_MODE_EXCLUSIVE; 
 
// Get the buffer memory requirements for the staging buffer 
VkMemoryRequirements memRqrmnt; 
VkDeviceMemory devMemory; 
vkGetBufferMemoryRequirements(deviceObj->device, buffer, 
                                  &memRqrmnt); 
 
VkMemoryAllocateInfo memAllocInfo = {}; 
memAllocInfo.sType                = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; 
memAllocInfo.pNext                = NULL; 
memAllocInfo.allocationSize       = 0; 
memAllocInfo.memoryTypeIndex      = 0; 
memAllocInfo.allocationSize       = memRqrmnt.size; 
 
// Determine the type of memory required for 

// the host-visible buffer 
deviceObj->memoryTypeFromProperties(memRqrmnt.memoryTypeBits, 
               VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |  
               VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,  
               &memAllocInfo.memoryTypeIndex); 
          
// Allocate the memory for host-visible buffer objects - 
error = vkAllocateMemory(deviceObj->device, &memAllocInfo,  
                           nullptr, &devMemory); 
assert(!error); 
 
// Bind the host-visible buffer with allocated device memory - 
error=vkBindBufferMemory(deviceObj->device,buffer,devMemory,0); 
assert(!error); 

Populating the allocated device memory

Use vkMapMemory() and populate the raw contents of the loaded image into the buffer object's device memory. Once mapped, use vkUnmapMemory() to complete the process of uploading data from the host to the device memory:

// Populate the raw image data into the device memory 
uint8_t *data; 
error = vkMapMemory(deviceObj->device, devMemory, 0, 
                     memRqrmnt.size, 0, (void **)&data); 
assert(!error); 
 
memcpy(data, image2D.data(), image2D.size()); 
vkUnmapMemory(deviceObj->device, devMemory); 

Creating the image object

The image's create info object (VkImageCreateInfo) must be created using tiling (.tiling) options as optimal tiling (VK_IMAGE_TILING_OPTIMAL). In addition, the image's usage flag must be set with VK_IMAGE_USAGE_TRANSFER_DST_BIT, making it a destination for the copy commands to transfer data contents to texture->image from the buffer object:

// Create image info with optimal tiling

// support (.tiling = VK_IMAGE_TILING_OPTIMAL) 
VkImageCreateInfo imageCreateInfo = {}; 
imageCreateInfo.sType             = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; 
imageCreateInfo.pNext             = NULL; 
imageCreateInfo.imageType         = VK_IMAGE_TYPE_2D; 
imageCreateInfo.format            = format; 
imageCreateInfo.mipLevels         = texture->mipMapLevels; 
imageCreateInfo.arrayLayers       = 1; 
imageCreateInfo.samples           = VK_SAMPLE_COUNT_1_BIT; 
imageCreateInfo.tiling            = VK_IMAGE_TILING_OPTIMAL; 
imageCreateInfo.sharingMode       = VK_SHARING_MODE_EXCLUSIVE; 
imageCreateInfo.initialLayout     = VK_IMAGE_LAYOUT_UNDEFINED; 
imageCreateInfo.extent            = { texture->textureWidth,  
                                   texture->textureHeight, 1 }; 
imageCreateInfo.usage             = imageUsageFlags; 
 
// Set image object with VK_IMAGE_USAGE_TRANSFER_DST_BIT if

// not set already. This allows to copy the source VkBuffer 

// object (with VK_IMAGE_USAGE_TRANSFER_DST_BIT) contents

// into this image object memory(destination). 
if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT)){ 
    imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; 
} 
 
error = vkCreateImage(deviceObj->device, &imageCreateInfo, 
                           nullptr, &texture->image); 
assert(!error); 

Image object memory allocation and binding

Allocate the physical memory backing and bind it with the created texture->image. For more information on memory allocation and the binding process, refer to the Memory allocation and binding image resources section in Chapter 6, Allocating Image Resources and Building a Swapchain with WSI:

// Get the image memory requirements 
vkGetImageMemoryRequirements(deviceObj->device, texture->image, 
                                        &memRqrmnt); 
 
// Set the allocation size equal to the buffer allocation 
memAllocInfo.allocationSize = memRqrmnt.size; 
 
// Determine the type of memory required with the help of memory properties 
deviceObj->memoryTypeFromProperties(memRqrmnt.memoryTypeBits, 
    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 
    &memAllocInfo.memoryTypeIndex); 
 
// Allocate the physical memory on the GPU 
error = vkAllocateMemory(deviceObj->device, &memAllocInfo,  
                                 nullptr, &texture->mem); 
assert(!error); 
 
// Bound the physical memory with the created image object  
error = vkBindImageMemory(deviceObj->device, texture->image, 
                                        texture->mem, 0); 
assert(!error); 

Creating a command buffer object

The image resource objects are created using the command buffer object, cmdTexture, defined in the VulkanRenderer class. Allocate the command buffer to set the image layout and start recording the command buffer:

// Command buffer allocation and recording begins 
CommandBufferMgr::allocCommandBuffer(&deviceObj->device, 
                                 cmdPool, &cmdTexture); 
CommandBufferMgr::beginCommandBuffer(cmdTexture); 

Setting the image layout

Set the image layout (VkImageLayout) to be VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL since the data contents will be copied from the staging buffer object (source) to the image object (destination). For more information on the setImageLayout() function, refer to the Set the image layout with memory barriers section in Chapter 6, Allocating Image Resources and Building a Swapchain with WSI:

VkImageSubresourceRange subresourceRange = {}; 
subresourceRange.aspectMask      = VK_IMAGE_ASPECT_COLOR_BIT; 
subresourceRange.baseMipLevel    = 0; 
subresourceRange.levelCount      = texture->mipMapLevels; 
subresourceRange.layerCount      = 1; 
 
// Set the image layout to be 

// VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL

// since it is destination for copying buffer 

// into image using vkCmdCopyBufferToImage - 
setImageLayout(texture->image, VK_IMAGE_ASPECT_COLOR_BIT, 
 VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 
 (VkAccessFlagBits)0, subresourceRange, cmdTexture); 

Buffer to image copy

Create buffer image copy regions for the image object and its subresource mipmaps. Use the copy command to transfer the buffer object's (buffer) device memory contents to the image object's (texture->image) memory contents:

// List contain buffer image copy for each mipLevel 
std::vector<VkBufferImageCopy> bufferImgCopyList; 
 
uint32_t bufferOffset = 0; 
// Iterater through each mip level and set buffer image copy 
for (uint32_t i = 0; i < texture->mipMapLevels; i++) 
{ 
    VkBufferImageCopy bufImgCopyItem = {}; 
    bufImgCopyItem.imageSubresource.aspectMask = 
                VK_IMAGE_ASPECT_COLOR_BIT; 
    bufImgCopyItem.imageSubresource.mipLevel = i; 
    bufImgCopyItem.imageSubresource.layerCount = 1; 
    bufImgCopyItem.imageSubresource.baseArrayLayer = 0; 
    bufImgCopyItem.imageExtent.width  = 
               uint32_t(image2D[i].dimensions().x); 
    bufImgCopyItem.imageExtent.height = 
               uint32_t(image2D[i].dimensions().y); 
    bufImgCopyItem.imageExtent.depth   = 1; 
    bufImgCopyItem.bufferOffset        = bufferOffset; 
 
    bufferImgCopyList.push_back(bufImgCopyItem); 
 
    // adjust buffer offset 
    bufferOffset += uint32_t(image2D[i].size()); 
} 
 
 
// Copy the staging buffer memory data containing the

// staged raw data(with mip levels) into the image object 
vkCmdCopyBufferToImage(cmdTexture, buffer, texture->image,      
         VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 
         uint32_t(bufferImgCopyList.size()), 
         bufferImgCopyList.data()); 

For more information on the copy commands, please refer to our next section, Understanding the copy commands.

Setting the optimal image layout

Set the image layout, indicating the new layout to be optimal tiling compatible. The underlying implementation uses this flag and chooses a suitable technique to lay out the image contents in an optimal manner:

// Advised to change the image layout to shader read

// after staged buffer copied into image memory 
texture->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; 
setImageLayout(texture->image, VK_IMAGE_ASPECT_COLOR_BIT, 
         VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 
         texture->imageLayout, 
         subresourceRange, cmdTexture); 

Note

The layout of an image can be controlled at:

a) Creation of the image resource by specifying the initial layout

b) Specifying explicitly using memory barriers

c) Or while using it in the Render Pass

Submitting the command buffer

Finalize the command buffer recording and submit it to the graphics queue:

// Submit command buffer containing copy 

// and image layout commands 
CommandBufferMgr::endCommandBuffer(cmdTexture); 
 
// Create a fence object to ensure that the command

// buffer is executed, coping our staged raw data

//  from the buffers to image memory with

// respective image layout and attributes into consideration 
VkFence fence; 
VkFenceCreateInfo fenceCI  = {}; 
fenceCI.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; 
fenceCI.flags = 0; 
 
error = vkCreateFence(deviceObj->device, &fenceCI, nullptr, 
                                              &fence); 
assert(!error); 
 
VkSubmitInfo submitInfo = {}; 
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 
submitInfo.pNext = NULL; 
submitInfo.commandBufferCount    = 1; 
submitInfo.pCommandBuffers = &cmdTexture; 
 
CommandBufferMgr::submitCommandBuffer(deviceObj->queue, 
                      &cmdTexture, &submitInfo, fence); 
 
error = vkWaitForFences(deviceObj->device, 1, &fence, VK_TRUE, 
                                        10000000000); 
assert(!error); 
 
vkDestroyFence(deviceObj->device, fence, nullptr); 
 
// destroy the allocated resoureces 
vkFreeMemory(deviceObj->device, devMemory, nullptr); 
vkDestroyBuffer(deviceObj->device, buffer, nullptr); 

Add a fence as a synchronization primitive to ensure the image layout is prepared successfully before it could utilize the image. Release the fence object once the fence is signaled. In case the fence fails to signal, then the wait command vkWaitForFences() waits for a maximum of 10 seconds to ensure the system never halts or goes into an infinite wait condition.

Note

For more information on fences, refer to the Understanding synchronization primitives in Vulkan section in Chapter 9, Drawing Objects.

Creating an image sampler

Create an image sampler with linear filtering for minification (minFilter) and magnification (magFilter) and also enable anisotropy filtering:

// Create sampler 
VkSamplerCreateInfo samplerCI = {}; 
samplerCI.sType        = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; 
samplerCI.pNext        = NULL; 
samplerCI.magFilter    = VK_FILTER_LINEAR; 
samplerCI.minFilter    = VK_FILTER_LINEAR; 
samplerCI.mipmapMode   = VK_SAMPLER_MIPMAP_MODE_LINEAR; 
samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 
samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 
samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 
samplerCI.mipLodBias   = 0.0f; 
 
if (deviceObj->deviceFeatures.samplerAnisotropy == VK_TRUE) 
{ 
    samplerCI.anisotropyEnable   = VK_TRUE; 
    samplerCI.maxAnisotropy      = 8; 
} 
else 
{ 
    samplerCI.anisotropyEnable   = VK_FALSE; 
    samplerCI.maxAnisotropy      = 1; 
} 
 
samplerCI.compareOp              = VK_COMPARE_OP_NEVER; 
samplerCI.minLod                 = 0.0f; 
samplerCI.maxLod                 = (float)texture->mipMapLevels; 
samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; 
samplerCI.unnormalizedCoordinates = VK_FALSE; 
 
error = vkCreateSampler(deviceObj->device, &samplerCI, nullptr, 
                            &texture->sampler); 
assert(!error); 
 
// Specify the sampler in the texture's descsImgInfo 
texture->descsImgInfo.sampler = texture->sampler; 

Creating the image view

Create the image view and store it in the local TextureData object's texture:

// Create image view to allow shader to
// access the texture information 
VkImageViewCreateInfo viewCI = {}; 
viewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; 
viewCI.pNext                           = NULL; 
viewCI.image                           = VK_NULL_HANDLE; 
viewCI.viewType                        = VK_IMAGE_VIEW_TYPE_2D; 
viewCI.format                          = format; 
viewCI.components.r                    = VK_COMPONENT_SWIZZLE_R; 
viewCI.components.g                    = VK_COMPONENT_SWIZZLE_G; 
viewCI.components.b                    = VK_COMPONENT_SWIZZLE_B; 
viewCI.components.a                    = VK_COMPONENT_SWIZZLE_A; 
viewCI.subresourceRange                = subresourceRange; 
viewCI.subresourceRange.levelCount     = texture->mipMapLevels;  
// Optimal tiling supports mip map levels very well set it. 
viewCI.image                           = texture->image; 
 
error = vkCreateImageView(deviceObj->device, &viewCI, NULL, 
                                 &texture->view); 
assert(!error); 
 
// Fill descriptor image info that can be 
// used for setting up descriptor sets 
texture->descsImgInfo.imageView = texture->view; 
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.187.210