graphicssynchronizationvulkan

How to Pass Previous Frame as Sampler2D to Fragment Shader in Vulkan?


I am currently developing a Vulkan project and I am facing an issue that I cannot resolve.

I need to pass the image of the previous frame as a sampler2D to the fragment shader. I have followed the Vulkan tutorial for setting up the project, so I have the swap chain correctly implemented.

My idea was to use vkCmdCopyImage to copy the current frame displayed on the swap chain image to a destination VkImage before starting to render the next frame. This way, I can pass this VkImage as a sampler2D in the fragment shader to render the next frame.

Firstly, I would like to ask if my approach is correct or if there is a better way to handle this situation.

If this solution is feasible, I would appreciate some guidance on what might be wrong with my code. Below, I provide the drawFrame() method, which is called in the main loop of my application, and the texture() method, which handles the image copying.

The problem is that if I do not call the texture() method, everything works fine, but I do not have the previous frame in the fragment shader. However, if I call the texture() method, everything works without errors, but it seems that the VkImage is never updated.

void texture(int curImageIndex) {
    VkImage textureImage = getImage(); // destination image
    VkCommandBuffer commandBuffer = beginSingleTimeCommands();

    VkImageMemoryBarrier barrierSwapchainToTransfer = {};
    barrierSwapchainToTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    barrierSwapchainToTransfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    barrierSwapchainToTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
    barrierSwapchainToTransfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrierSwapchainToTransfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrierSwapchainToTransfer.image = swapChainImages[curImageIndex];
    barrierSwapchainToTransfer.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    barrierSwapchainToTransfer.subresourceRange.baseMipLevel = 0;
    barrierSwapchainToTransfer.subresourceRange.levelCount = 1;
    barrierSwapchainToTransfer.subresourceRange.baseArrayLayer = 0;
    barrierSwapchainToTransfer.subresourceRange.layerCount = 1;
    barrierSwapchainToTransfer.srcAccessMask = VK_ACCESS_NONE_KHR;
    barrierSwapchainToTransfer.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;

    vkCmdPipelineBarrier(
        commandBuffer,
        VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
        0,
        0, nullptr,
        0, nullptr,
        1, &barrierSwapchainToTransfer
    );

    VkImageMemoryBarrier barrierTextureToTransfer = {};
    barrierTextureToTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    barrierTextureToTransfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    barrierTextureToTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
    barrierTextureToTransfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrierTextureToTransfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrierTextureToTransfer.image = textureImage;
    barrierTextureToTransfer.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    barrierTextureToTransfer.subresourceRange.baseMipLevel = 0;
    barrierTextureToTransfer.subresourceRange.levelCount = 1;
    barrierTextureToTransfer.subresourceRange.baseArrayLayer = 0;
    barrierTextureToTransfer.subresourceRange.layerCount = 1;
    barrierTextureToTransfer.srcAccessMask = VK_ACCESS_NONE_KHR;
    barrierTextureToTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;

    vkCmdPipelineBarrier(
        commandBuffer,
        VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
        0,
        0, nullptr,
        0, nullptr,
        1, &barrierTextureToTransfer
    );

    VkImageCopy copyRegion = {};
    copyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    copyRegion.srcSubresource.mipLevel = 0;
    copyRegion.srcSubresource.baseArrayLayer = 0;
    copyRegion.srcSubresource.layerCount = 1;
    copyRegion.srcOffset = { 0, 0, 0 };
    copyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    copyRegion.dstSubresource.mipLevel = 0;
    copyRegion.dstSubresource.baseArrayLayer = 0;
    copyRegion.dstSubresource.layerCount = 1;
    copyRegion.dstOffset = { 0, 0, 0 };
    copyRegion.extent = { swapChainExtent.width, swapChainExtent.height, 1 };

    vkCmdCopyImage(
        commandBuffer,
        swapChainImages[curImageIndex], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
        textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
        1, &copyRegion
    );

    VkImageMemoryBarrier barrierTextureToShaderRead = {};
    barrierTextureToShaderRead.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    barrierTextureToShaderRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
    barrierTextureToShaderRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    barrierTextureToShaderRead.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrierTextureToShaderRead.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrierTextureToShaderRead.image = textureImage;
    barrierTextureToShaderRead.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    barrierTextureToShaderRead.subresourceRange.baseMipLevel = 0;
    barrierTextureToShaderRead.subresourceRange.levelCount = 1;
    barrierTextureToShaderRead.subresourceRange.baseArrayLayer = 0;
    barrierTextureToShaderRead.subresourceRange.layerCount = 1;
    barrierTextureToShaderRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
    barrierTextureToShaderRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;

    vkCmdPipelineBarrier(
        commandBuffer,
        VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
        0,
        0, nullptr,
        0, nullptr,
        1, &barrierTextureToShaderRead
    );

    VkImageMemoryBarrier barrierSwapchainToPresent = {};
    barrierSwapchainToPresent.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    barrierSwapchainToPresent.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
    barrierSwapchainToPresent.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
    barrierSwapchainToPresent.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrierSwapchainToPresent.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrierSwapchainToPresent.image = swapChainImages[curImageIndex];
    barrierSwapchainToPresent.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    barrierSwapchainToPresent.subresourceRange.baseMipLevel = 0;
    barrierSwapchainToPresent.subresourceRange.levelCount = 1;
    barrierSwapchainToPresent.subresourceRange.baseArrayLayer = 0;
    barrierSwapchainToPresent.subresourceRange.layerCount = 1;
    barrierSwapchainToPresent.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
    barrierSwapchainToPresent.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;

    vkCmdPipelineBarrier(
        commandBuffer,
        VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
        0,
        0, nullptr,
        0, nullptr,
        1, &barrierSwapchainToPresent
    );

    endSingleTimeCommands(commandBuffer);;
}
void drawFrame() {
    vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
    
    texture(currentFrame);

    uint32_t imageIndex;

    VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);

    if (result == VK_ERROR_OUT_OF_DATE_KHR) {
        recreateSwapChain();
        return;
    }
    else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
        throw std::runtime_error("failed to acquire swap chain image!");
    }


    if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) {
        vkWaitForFences(device, 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX);
    }
    imagesInFlight[imageIndex] = inFlightFences[currentFrame];

    updateUniformBuffer(imageIndex);

    VkSubmitInfo submitInfo{};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    VkSemaphore waitSemaphores[] = { imageAvailableSemaphores[currentFrame] };
    VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; 
    submitInfo.waitSemaphoreCount = 1;
    submitInfo.pWaitSemaphores = waitSemaphores;
    submitInfo.pWaitDstStageMask = waitStages;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
    VkSemaphore signalSemaphores[] = { renderFinishedSemaphores[currentFrame] };
    submitInfo.signalSemaphoreCount = 1;
    submitInfo.pSignalSemaphores = signalSemaphores;

    vkResetFences(device, 1, &inFlightFences[currentFrame]);

    if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { 
        throw std::runtime_error("failed to submit draw command buffer!");
    }

    VkPresentInfoKHR presentInfo{}; 
    presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
    presentInfo.waitSemaphoreCount = 1;
    presentInfo.pWaitSemaphores = signalSemaphores;

    VkSwapchainKHR swapChains[] = { swapChain };
    presentInfo.swapchainCount = 1;
    presentInfo.pSwapchains = swapChains;
    presentInfo.pImageIndices = &imageIndex;
    presentInfo.pResults = nullptr; 

    result = vkQueuePresentKHR(presentQueue, &presentInfo);

    if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR ||
        framebufferResized) {
        framebufferResized = false;
        recreateSwapChain();
    }
    else if (result != VK_SUCCESS) {
        throw std::runtime_error("failed to present swap chain image!");
    }

    currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}

Solution

  • A few things wrong here:

    1. You're calling texture() with a frame number and not an image index. You might have 3 images in your swapchain and your max frames in flight is 2. This means that you'll probably be reading the wrong swapchain image and never reading swapchain image 2 in this case. It seems like you want to pass imageIndex instead.

    2. You're calling texture() before rendering. This means that you'll probably be reading an image with undefined contents. Perhaps you want to call it after the queue submit and before the present.

    3. The barriers aren't quite right in texture(). Here's a version that worked for me, sort of. I didn't implement the shader read of the image and just used RenderDoc to verify the copy worked (you might want to do the same). So the parameters for the barrierTextureToShaderRead are set up to view the texture in RenderDoc. You may need to leave them the way you had them for use by the shader.

       void texture(int curImageIndex) {
       VkImage textureImage = getImage(); // destination image
       VkCommandBuffer commandBuffer = beginSingleTimeCommands();
      
       VkImageMemoryBarrier barrierSwapchainToTransfer = {};
       barrierSwapchainToTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
       barrierSwapchainToTransfer.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;  // was: UNDEFINED
       barrierSwapchainToTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
       barrierSwapchainToTransfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
       barrierSwapchainToTransfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
       barrierSwapchainToTransfer.image = swapChainImages[curImageIndex];
       barrierSwapchainToTransfer.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
       barrierSwapchainToTransfer.subresourceRange.baseMipLevel = 0;
       barrierSwapchainToTransfer.subresourceRange.levelCount = 1;
       barrierSwapchainToTransfer.subresourceRange.baseArrayLayer = 0;
       barrierSwapchainToTransfer.subresourceRange.layerCount = 1;
       barrierSwapchainToTransfer.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;  // was: NONE
       barrierSwapchainToTransfer.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; 
      
       vkCmdPipelineBarrier(
           commandBuffer,
           VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,  // was: TOP, TRANSFER
           0,
           0, nullptr,
           0, nullptr,
           1, &barrierSwapchainToTransfer
       );
      
       VkImageMemoryBarrier barrierTextureToTransfer = {};
       barrierTextureToTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
       barrierTextureToTransfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
       barrierTextureToTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
       barrierTextureToTransfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
       barrierTextureToTransfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
       barrierTextureToTransfer.image = textureImage;
       barrierTextureToTransfer.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
       barrierTextureToTransfer.subresourceRange.baseMipLevel = 0;
       barrierTextureToTransfer.subresourceRange.levelCount = 1;
       barrierTextureToTransfer.subresourceRange.baseArrayLayer = 0;
       barrierTextureToTransfer.subresourceRange.layerCount = 1;
       barrierTextureToTransfer.srcAccessMask = VK_ACCESS_NONE_KHR;
       barrierTextureToTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
      
       vkCmdPipelineBarrier(
           commandBuffer,
           VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,  // was: TOP, TRANSFER
           0,
           0, nullptr,
           0, nullptr,
           1, &barrierTextureToTransfer
       );
      
       VkImageCopy copyRegion = {};
       copyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
       copyRegion.srcSubresource.mipLevel = 0;
       copyRegion.srcSubresource.baseArrayLayer = 0;
       copyRegion.srcSubresource.layerCount = 1;
       copyRegion.srcOffset = { 0, 0, 0 };
       copyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
       copyRegion.dstSubresource.mipLevel = 0;
       copyRegion.dstSubresource.baseArrayLayer = 0;
       copyRegion.dstSubresource.layerCount = 1;
       copyRegion.dstOffset = { 0, 0, 0 };
       copyRegion.extent = { swapChainExtent.width, swapChainExtent.height, 1 };
      
       vkCmdCopyImage(
           commandBuffer,
           swapChainImages[curImageIndex], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
           textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
           1, &copyRegion
       );
      
       VkImageMemoryBarrier barrierTextureToShaderRead = {};
       barrierTextureToShaderRead.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
       barrierTextureToShaderRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
       barrierTextureToShaderRead.newLayout = VK_IMAGE_LAYOUT_GENERAL;// VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
       barrierTextureToShaderRead.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
       barrierTextureToShaderRead.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
       barrierTextureToShaderRead.image = textureImage;
       barrierTextureToShaderRead.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
       barrierTextureToShaderRead.subresourceRange.baseMipLevel = 0;
       barrierTextureToShaderRead.subresourceRange.levelCount = 1;
       barrierTextureToShaderRead.subresourceRange.baseArrayLayer = 0;
       barrierTextureToShaderRead.subresourceRange.layerCount = 1;
       barrierTextureToShaderRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
       barrierTextureToShaderRead.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;//VK_ACCESS_SHADER_READ_BIT;
      
       vkCmdPipelineBarrier(
           commandBuffer,
           VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,  // was: TRANSFER, FRAGMENT_SHADER
           0,
           0, nullptr,
           0, nullptr,
           1, &barrierTextureToShaderRead
       );
      
       VkImageMemoryBarrier barrierSwapchainToPresent = {};
       barrierSwapchainToPresent.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
       barrierSwapchainToPresent.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
       barrierSwapchainToPresent.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
       barrierSwapchainToPresent.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
       barrierSwapchainToPresent.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
       barrierSwapchainToPresent.image = swapChainImages[curImageIndex];
       barrierSwapchainToPresent.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
       barrierSwapchainToPresent.subresourceRange.baseMipLevel = 0;
       barrierSwapchainToPresent.subresourceRange.levelCount = 1;
       barrierSwapchainToPresent.subresourceRange.baseArrayLayer = 0;
       barrierSwapchainToPresent.subresourceRange.layerCount = 1;
       barrierSwapchainToPresent.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
       barrierSwapchainToPresent.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
      
       vkCmdPipelineBarrier(
           commandBuffer,
           VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,  // was: TRANSFER, BOTTOM
           0,
           0, nullptr,
           0, nullptr,
           1, &barrierSwapchainToPresent
       );
      
       endSingleTimeCommands(commandBuffer);;
      

      }

    Finally check out this screenshot code as an example of reading a swapchain image.