I have 2 queues for which I submit commands. The first one is the graphics queue and the other one is the compute queue. They are also getting executed in this order. (graphics -> compute -> present)
In my rendering stage I'm using an image which has to be transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL in order to use and in the compute stage it has to be transitioned again to VK_IMAGE_LAYOUT_GENERAL.
After around 5 to 10 frames it's complaining abt that the image cannot be transitioned anymore, my first guess is, that I have messed up the synchronization somewhere but I cannot find it! I have tried for 3 days now and still the error persists.
Can somebody spot an issue?
And before anybody asks, the initial layout has been properly set to VK_IMAGE_LAYOUT_GENERAL, so that, when the rendering happens first, it can be properly transitioned for the first frame.
Here is the error that occurs after a few frames:
Validation Error: [ VUID-vkCmdDraw-None-09600 ] Object 0: handle = 0x22225086530, type = VK_OBJECT_TYPE_COMMAND_BUFFER; Object 1: handle = 0x1983b0000000008a, type = VK_OBJECT_TYPE_IMAGE; | MessageID = 0x46582f7b | vkQueueSubmit(): pSubmits[0].pCommandBuffers[0] command buffer VkCommandBuffer 0x22225086530[] expects VkImage 0x1983b0000000008a[] (subresource: aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, mipLevel = 0, arrayLayer = 0) to be in layout VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL--instead, current layout is VK_IMAGE_LAYOUT_GENERAL.
Here is my complete render loop:
void vulkan_renderer_draw(transform_t* transform, camera_t* camera)
{
VkFence fences[] = { s_vulkan_renderer_graphics_fences[s_vulkan_renderer_frame_index], s_vulkan_renderer_compute_fences[s_vulkan_renderer_frame_index] };
vkWaitForFences(g_vulkan_context_device, 2, fences, 1, UINT64_MAX);
vkResetFences(g_vulkan_context_device, 2, fences);
vkResetCommandBuffer(s_vulkan_renderer_graphics_command_buffers[s_vulkan_renderer_frame_index], VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
vkResetCommandBuffer(s_vulkan_renderer_compute_command_buffers[s_vulkan_renderer_frame_index], VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
vkAcquireNextImageKHR(
g_vulkan_context_device,
g_vulkan_context_swap_chain,
UINT64_MAX,
s_vulkan_renderer_image_available_semaphores[s_vulkan_renderer_frame_index],
0,
&s_vulkan_renderer_image_index
);
vulkan_renderer_update_uniform_buffers(transform, camera);
vulkan_renderer_record_graphics_commands();
{
VkPipelineStageFlags graphics_wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
VkSubmitInfo graphics_submit_info = { 0 };
graphics_submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
graphics_submit_info.pWaitSemaphores = &s_vulkan_renderer_image_available_semaphores[s_vulkan_renderer_frame_index];
graphics_submit_info.waitSemaphoreCount = 1;
graphics_submit_info.pSignalSemaphores = &s_vulkan_renderer_graphics_complete_semaphores[s_vulkan_renderer_frame_index];
graphics_submit_info.signalSemaphoreCount = 1;
graphics_submit_info.pCommandBuffers = &s_vulkan_renderer_graphics_command_buffers[s_vulkan_renderer_frame_index];
graphics_submit_info.commandBufferCount = 1;
graphics_submit_info.pWaitDstStageMask = graphics_wait_stages;
vkQueueSubmit(g_vulkan_context_graphics_queue, 1, &graphics_submit_info, s_vulkan_renderer_graphics_fences[s_vulkan_renderer_frame_index]);
}
vulkan_renderer_record_compute_commands();
{
VkPipelineStageFlags compute_wait_stages[] = { VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT };
VkSubmitInfo compute_submit_info = { 0 };
compute_submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
compute_submit_info.pWaitSemaphores = &s_vulkan_renderer_graphics_complete_semaphores[s_vulkan_renderer_frame_index];
compute_submit_info.waitSemaphoreCount = 1;
compute_submit_info.pSignalSemaphores = &s_vulkan_renderer_compute_complete_semaphores[s_vulkan_renderer_frame_index];
compute_submit_info.signalSemaphoreCount = 1;
compute_submit_info.pCommandBuffers = &s_vulkan_renderer_compute_command_buffers[s_vulkan_renderer_frame_index];
compute_submit_info.commandBufferCount = 1;
compute_submit_info.pWaitDstStageMask = compute_wait_stages;
vkQueueSubmit(g_vulkan_context_compute_queue, 1, &compute_submit_info, s_vulkan_renderer_compute_fences[s_vulkan_renderer_frame_index]);
}
{
VkPresentInfoKHR present_info = { 0 };
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present_info.pWaitSemaphores = &s_vulkan_renderer_compute_complete_semaphores[s_vulkan_renderer_frame_index];
present_info.waitSemaphoreCount = 1;
present_info.pSwapchains = &g_vulkan_context_swap_chain;
present_info.swapchainCount = 1;
present_info.pImageIndices = &s_vulkan_renderer_image_index;
vkQueuePresentKHR(g_vulkan_context_present_queue, &present_info);
}
s_vulkan_renderer_frame_index = (s_vulkan_renderer_frame_index + 1) % VULKAN_RENDERER_FRAMES_IN_FLIGHT;
}
In the "vulkan_renderer_record_graphics_commands" function, I simply record the command buffer, but the first thing I do before rendering is to transition my image layout like this:
VkImageMemoryBarrier chunk_image_memory_barrier = { 0 };
chunk_image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
chunk_image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
chunk_image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
chunk_image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; // TODO
chunk_image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; // TODO
chunk_image_memory_barrier.image = s_vulkan_renderer_chunk_image[s_vulkan_renderer_frame_index];
chunk_image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
chunk_image_memory_barrier.subresourceRange.baseMipLevel = 0;
chunk_image_memory_barrier.subresourceRange.levelCount = 1;
chunk_image_memory_barrier.subresourceRange.baseArrayLayer = 0;
chunk_image_memory_barrier.subresourceRange.layerCount = 1;
chunk_image_memory_barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
chunk_image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_MEMORY_READ_BIT;
vkCmdPipelineBarrier(
s_vulkan_renderer_graphics_command_buffers[s_vulkan_renderer_frame_index],
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0, 0, 0, 0, 0, 1, &chunk_image_memory_barrier
);
In the "vulkan_renderer_record_compute_commands" function, I do the exact opposite, transitioning the layout back for the compute shader to use it:
VkImageMemoryBarrier chunk_image_memory_barrier = { 0 };
chunk_image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
chunk_image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
chunk_image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
chunk_image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; // TODO
chunk_image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; // TODO
chunk_image_memory_barrier.image = s_vulkan_renderer_chunk_image[s_vulkan_renderer_frame_index];
chunk_image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
chunk_image_memory_barrier.subresourceRange.baseMipLevel = 0;
chunk_image_memory_barrier.subresourceRange.levelCount = 1;
chunk_image_memory_barrier.subresourceRange.baseArrayLayer = 0;
chunk_image_memory_barrier.subresourceRange.layerCount = 1;
chunk_image_memory_barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_MEMORY_READ_BIT;
chunk_image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
vkCmdPipelineBarrier(
s_vulkan_renderer_compute_command_buffers[s_vulkan_renderer_frame_index],
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0, 0, 0, 0, 0, 1, &chunk_image_memory_barrier
);