Create depth buffer

The depth buffer is the first image we will initialize ourselves in the Vulkan renderer. This is the image that will require the least amount of setup, as unlike textures, no operations are required to fill the buffer with data.

When allocating any image in Vulkan, memory will have to be allocated in order to be able to use the image. Explicit control over memory will allow us to allocate a large block of memory for multiple resources to use.

For now, we will not really bother with allocating the optimal memory type, but this will be covered later on.

result = vkCreateImage (
    renderer->device,
    &(VkImageCreateInfo){
        .sType         = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
        .pNext         = NULL,
        .flags         = 0,
        .imageType     = VK_IMAGE_TYPE_2D,
        .format        = VK_FORMAT_D32_SFLOAT,
        .extent        = { .width = newWidth, .height = newHeight, .depth = 1 },
        .mipLevels     = 1,
        .arrayLayers   = 1,
        .samples       = VK_SAMPLE_COUNT_1_BIT,
        .tiling        = VK_IMAGE_TILING_OPTIMAL,
        .usage         = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
        .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
    },
    NULL,
    &renderer->depthImage
);
if ( result != VK_SUCCESS )
    RETURN_ERROR(-1, "vkCreateImage failed (0x%08X)", (uint32_t)result);

VkMemoryRequirements memoryRequirements;
vkGetImageMemoryRequirements ( renderer->device, renderer->depthImage, &memoryRequirements );

uint32_t typeIndex = 0xFFFFFFFF;
for ( uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++ )
{
    if ( memoryRequirements.memoryTypeBits & (1 << i) )
    {
        typeIndex = i;
        break;
    }
}
if ( typeIndex == 0xFFFFFFFF )
    RETURN_ERROR(-1, "No compatible memory type found for depth buffer");

VkDeviceMemory deviceMemory;
result = vkAllocateMemory (
    renderer->device,
    &(VkMemoryAllocateInfo){
        .sType           = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
        .pNext           = NULL,
        .allocationSize  = memoryRequirements.size,
        .memoryTypeIndex = typeIndex,
    },
    NULL,
    &deviceMemory
);
if ( result != VK_SUCCESS )
    RETURN_ERROR(-1, "vkAllocateMemory failed (0x%08X)", (uint32_t)result);

result = vkBindImageMemory (
    renderer->device,
    renderer->depthImage,
    deviceMemory,
    0
);
if ( result != VK_SUCCESS )
    RETURN_ERROR(-1, "vkBindImageMemory failed (0x%08X)", (uint32_t)result);

result = vkCreateImageView (
    renderer->device,
    &(VkImageViewCreateInfo){
        .sType            = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
        .pNext            = NULL,
        .flags            = 0,
        .image            = renderer->depthImage,
        .viewType         = VK_IMAGE_VIEW_TYPE_2D,
        .format           = VK_FORMAT_D32_SFLOAT,
        //.components       = , // NOTE(Rick): Components all maps to VK_COMPONENT_SWIZZLE_IDENTITY
        .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, .levelCount = 1, .layerCount = 1 }
    },
    NULL,
    &renderer->depthImageView
);
if ( result != VK_SUCCESS )
    RETURN_ERROR(-1, "vkCreateImageView failed (0x%08X)", (uint32_t)result);

Memory allocated using vkAllocateMemory is not automatically in use; Only after the call to vkBindImageMemory will the image have the memory required for operation. This memory can be offset in the memory in order to allow multiple resources to be after one another in the buffer.

The image view will serve as our depth render target descriptor. This image view will be used in constructing a framebuffer for use in rendering.

Similarly to the swap buffer backbuffer images, we will need to transition the resource to a layout we can use in the future.

vkCmdPipelineBarrier (
    preloaderCommandBuffer,
    VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
    VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
    0,
    0, NULL,
    0, NULL,
    1, (VkImageMemoryBarrier[1]){
        [0] = {
            .sType               = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
            .pNext               = NULL,
            .srcAccessMask       = 0,
            .dstAccessMask       = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
            .oldLayout           = VK_IMAGE_LAYOUT_UNDEFINED,
            .newLayout           = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
            .srcQueueFamilyIndex = 0,
            .dstQueueFamilyIndex = 0,
            .image               = renderer->depthImage,
            .subresourceRange    = { .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, .levelCount = 1, .layerCount = 1 },
        },
    }
);

The image we created started out in the VK_IMAGE_LAYOUT_UNDEFINED image layout, which is not usable for any purpose. As such, we transition it to the VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL layout in order to have the optimal layout for depth render target usage, and allow binding as a writable depth-stencil attachment in the dstAccessMask field.