How to copy fragment shader output image into a RGBA 8 bits buffer

Hi,

My fragment shader is:

layout(location = 0) out vec4 outColor; // Output color
void main() {
    outColor = vec4(1.0f, 0.5f, 0.1f, 1.0f);   
}

The output image was created with format VK_FORMAT_R8G8B8A8_UNORM.
After render pass execution I need to copy the output image outColor into a RGBA 8 bits buffer.

Any idea appreciated!

Thanks,
Andrei

You can’t do that from the fragment shader directly. Instead you need to copy from the output image using e.g. vkCmdCopyImageToBuffer from the image to a host accessible buffer that you can then access in your application. Make sure to properly sync access to ensure image writes are visible/done at the time you do the copy.

I implemented copy image to buffer at the end of render pass but my buffer is filled with clearValues[0].color value:

   transitionImageLayout(commandBuffer,inputImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL );
    copyBufferToImage(commandBuffer, inputBuffers, inputImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, cfg.width, cfg.height);
    transitionImageLayout(commandBuffer,inputImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);

    VkRenderPassBeginInfo renderPassInfo = {};
    renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
    renderPassInfo.renderPass = renderPass; // Your render pass handle
    renderPassInfo.framebuffer = frameBuffer; // Your framebuffer handle
    renderPassInfo.renderArea.offset = {0, 0};
    renderPassInfo.renderArea.extent = extent; // Your swap chain extent

    std::array<VkClearValue, 3> clearValues = {};
    clearValues[0].color = { 0.2f, 0.3f, 0.4f, 1.0f };   //background color of the image
    clearValues[1].color = { 0.6f, 0.65f, 0.4f, 1.0f };   //background color of the image
    clearValues[2].depthStencil.depth = 1.0f;

    renderPassInfo.pClearValues = clearValues.data();
    renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
    vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

    //Bind the first pipeline for the first shader
    vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);

    vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,pipelineLayout,
                                    0, 1, &descriptorSets, 0, nullptr);

    vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,pipelineLayout,
                                    1, 1, &ubodescriptorSet, 0, nullptr);

    //Bind vertex buffer
    VkBuffer vertexBuffers[] = { vertexBuffer};
    VkDeviceSize offsets[] = { 0 };
    vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);

    vkCmdDraw(commandBuffer, static_cast<uint32_t>(meshVertices.size()), 1, 0, 0);
    vkCmdEndRenderPass(commandBuffer);

    transitionImageLayout(commandBuffer,outputImage, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);

    copyImageToBuffer(commandBuffer, outputBuffers, outputImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, cfg.width, cfg.height);

I debugged the output color from the fragment shader by writing the color into a starage buffer and the color is correct:

    outColor =texture(inputImage, coords);   
    //debug 
    outRGBA[idx]=colorToRGBA(outColor * 255.0f);	//ok

So the fragment shader produces correct outColor but the output image is all clearValues[0].color = { 0.2f, 0.3f, 0.4f}

Where should I look for the bug?

Thanks,
Andrei

I used this function for synchronization:

void transitionImageLayout(VkCommandBuffer commandBuffer, VkImage image,
                           VkImageLayout oldLayout, VkImageLayout newLayout)
{
    VkImageMemoryBarrier imageMemoryBarrier = {};
    imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    imageMemoryBarrier.oldLayout = oldLayout;
    imageMemoryBarrier.newLayout = newLayout;
    imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    imageMemoryBarrier.image = image;
    imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;  //this states that image is RGBA 8bits
    imageMemoryBarrier.subresourceRange.baseMipLevel = 0;
    imageMemoryBarrier.subresourceRange.levelCount = 1;
    imageMemoryBarrier.subresourceRange.baseArrayLayer = 0;
    imageMemoryBarrier.subresourceRange.layerCount = 1;

    VkPipelineStageFlags srcStage= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
    VkPipelineStageFlags dstStage= VK_PIPELINE_STAGE_TRANSFER_BIT;


    if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
    {
        //Setup ideal layout for copy buffer in transfer stage
        imageMemoryBarrier.srcAccessMask = 0;                                 //after this stage
        imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;	     //before this stage

        srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
        dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
    }
    else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
    {
        //Setup ideal layout for shader in fragment stage
        imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;         //after this stage
        imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;	     //before this stage

        srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
        dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
    }
    else if (oldLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_GENERAL)
    {
        //Setup ideal layout for shader in fragment stage
        imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;         //after this stage
        imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;	     //before this stage

        srcStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
        dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
    }
    else if (oldLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
    {
        //Setup ideal layout for shader in fragment stage
        imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;         //after this stage
        imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;	     //before this stage

        srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
        dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
    }
    else
    {
        throw std::invalid_argument("Error: transitionImageLayout: unsupported layout transition!");
    }

    vkCmdPipelineBarrier(commandBuffer, srcStage, dstStage,
                         0,
                         0, nullptr,
                         0, nullptr,
                         1, &imageMemoryBarrier);

}

Hard to tell without seeing the code as a whole, but this might be caused by how you’ve set up your attachment descriptions. Make sure results are stored at the end of a render pass.

This is the code for render pass and frame buffer. I though that if I declare the attachment the output will be store there. Do I need to do something specific to store the result?

void GpuWarp2s::createRenderPass(VkDevice device, VkFormat imageFormat, VkRenderPass* renderPass) {
    VkAttachmentDescription colorAttachment{};
    colorAttachment.format = imageFormat;
    colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
    colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
    colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

    VkAttachmentReference colorAttachmentRef{};
    colorAttachmentRef.attachment = 0;
    colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

    VkSubpassDescription subpass{};
    subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
    subpass.colorAttachmentCount = 1;
    subpass.pColorAttachments = &colorAttachmentRef;

    //Implicit layout transitions
    //need to determine when layout transitions occur between subpasses
    std::array<VkSubpassDependency, 2> subpassDependencies;

    //convert from VK_IMAGE_LAYOUT_UNDEFINED to VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIML
    subpassDependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
    subpassDependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    subpassDependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;

    subpassDependencies[0].dstSubpass = 0;
    subpassDependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    subpassDependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    subpassDependencies[0].dependencyFlags = 0;

    //convert from VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIML to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
    subpassDependencies[1].srcSubpass = 0;
    subpassDependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    subpassDependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;

    subpassDependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
    subpassDependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    subpassDependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
    subpassDependencies[1].dependencyFlags = 0;

    std::array<VkAttachmentDescription, 1> renderPassAttachments = { colorAttachment };

    VkRenderPassCreateInfo renderPassInfo{};
    renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
    renderPassInfo.attachmentCount = static_cast<uint32_t> (renderPassAttachments.size());
    renderPassInfo.pAttachments = renderPassAttachments.data();
    renderPassInfo.subpassCount = 1;
    renderPassInfo.pSubpasses = &subpass;
    renderPassInfo.dependencyCount = static_cast<uint32_t>(subpassDependencies.size());
    renderPassInfo.pDependencies = subpassDependencies.data();

    if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass) != VK_SUCCESS) {
        throw std::runtime_error("failed to create render pass!");
    }
}

void GpuWarp2s::createFrameBuffer(VkDevice device, VkRenderPass renderPass, VkFramebuffer*  pframeBuffer)
{
    std::array<VkImageView, 1> attachments = {
            outputImageView, //this image is the output image where the fragment shader will write.
                            //the input image is connected to the layout
    };

    VkFramebufferCreateInfo framebufferCreateInfo = {};
    framebufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
    framebufferCreateInfo.renderPass = renderPass;
    framebufferCreateInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
    framebufferCreateInfo.pAttachments = attachments.data();   //1 to 1 to render attachments
    framebufferCreateInfo.width = cfg.width;
    framebufferCreateInfo.height = cfg.height;
    framebufferCreateInfo.layers = 1;

    VkResult result = vkCreateFramebuffer(device, &framebufferCreateInfo, nullptr, &frameBuffer);
    if (result != VK_SUCCESS)
    {
        throw std::runtime_error("Failed to create a Frame Buffer!");
    }
}

I disabled blending in VkPipelineColorBlendAttachmentState and it works now:
colourState.blendEnable = VK_FALSE;