Image
Images represent multidimensional arrays of data. In Vulkan, they are represented by VkImage
handles.
Like buffers, an image is usually accompanied by memory(VkDeviceMemory
) and an image view(VkImageView
). However, in the last post, we didn’t talk about buffer view(VkBufferView
) just because we do not need them right now. A VkImage
is more like an image id than the image itself as we access the data by VkImageView
and the data is stored in VkDeviceMemory
. Also, any operations on an image need a VkDevice
.
It takes a similar procedure to build an image as a buffer does: create a texture(in a derived class), allocate memory for it, bind the memory to the image, and additionally, create the image view. GenerateMipmap
is kind of complicated so we’ll leave it to an independent post.
We define stb definitions in here as I said in part 1. AllocateTexture
takes an already-created image as an argument and queries its memory requirements with vkGetImageMemoryRequirements
. Like we did in buffer creation we also query memory type index with MapMemoryTypeToIndex
so that we can allocate memory for the image with vkAllocateMemory
.
CreateImageView
takes a TextureAttribs
struct declared in texture.h
. We specify the image on which the image view based, the image format, which aspect(s) of the image are included in the view, first mipmap level, the number of mipmap levels (starting from 0 in this case), the first array layer, and the number of array layers. With all of those information, we create an image view with vkCreateImageView
.
CreateTexture2D
takes a TextureAttribs
struct as an argument whose fields except sRGB
are filled depending on the texture we load from disk or memory. The candidate formats are based on channelsPerPixel
and sRGB
. However, my testing device does not support formats for three channels.
FindDeviceSupportedFormat
iterates VkFormat
s to check linearTilingFeatures
(if tiling
is VK_IMAGE_TILING_LINEAR
) or optimalTilingFeatures
(if tiling
is VK_IMAGE_TILING_OPTIMAL
) masks of VkFormatProperties
retrieved by vkGetPhysicalDeviceFormatProperties
contain the VkFormatFeatureFlags
we request.
Usually, we use VK_IMAGE_TILING_OTIMAL
for better performance and support. Only when we want to manipulate pixels directly on the image we use VK_IMAGE_TILING_LINEAR
.
The texture will be used for texture mapping and as the destination of pixels transfer, so we want its format to be VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT
and its usage flag to be the bitwise-or of VK_IMAGE_USAGE_SAMPLED_BIT
and VK_IMAGE_USAGE_TRANSFER_DST_BIT
.
Finally, we create VkImageCreateInfo
with ImageCreateInfo
and feed it to vkCreateImage
to create the image.
BuildTexture2D
copy the pixel data to the buffer and copy the buffer data to the image. It is faster than it seems to be than copy pixels one by one to the image which requires that image tiling to be VK_IMAGE_TILING_LINEAR
.
To go into detail, first, we create a buffer whose job is storing the pixel data which will be transferred to the image. Therefore, we should indicate our purpose by specifying VK_BUFFER_USAGE_SRC_BIT
.
Next, we need to specify VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
in order for CPU to access the buffer memory and specify VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
in order for the memory content of CPU and GPU staying synchronized.
Buffer::Map
retrieves a host virtual address pointer to a region of a mappable memory object and store it into Buffer::mapped
pointer so we can copy the pixels. We should unmap it immediately once we are done with it due to the limited resource of the memory which is visible to both the CPU and GPU. Here don’t forget to free the pixel data since we’ve uploaded the pixels to the buffer.
The next four lines are self-explanatory, they create the texture, allocate memory for it, bind the memory with the texture, and create derived image view.
Before copying, we specify a VkImageMemoryBarrier
that no memory access takes effect on stage VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT
when the image is in the layout VK_IMAGE_LAYOUT_UNDEFINED
and any write access to an image in a copy operation takes effect on stage VK_PIPELINE_STAGE_TRANSFER_BIT
when the layout of the image is in VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
, and record it with PipelineBarrier
. And, we can record a command that copies the buffer data to the image with vkCmdCopyBufferToImage
, and submit the whole command buffer to the device queue.
Here is a missing part, the texture will be used for texture mapping, that is to say, it will be sampled by a shader. So we need to transition the image layout from VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
after the copy. In fact, the final step is within mipmaps generation which we ignore for this moment. So if we are not intended to generate mipmaps we must complement this missing part.