# Only each third image in swapchain is being used, causing flickering

I've managed to write a "small" Vulkan program that outputs a solid color using compute shader directly to swapchain.

However, it doesn't work properly. ImageMemoryBarriers I've setup don't transition the layouts the way they should and only every third swapchain image (it has three in my implementation) is actually rendered to.

Two most interesting functions are the one where I record the command buffers and the one where is submit them to the compute/present queue:

Recording:

void createCommandBuffers() {
commandBuffers.resize(swapchainImages.size());

vk::CommandBufferAllocateInfo allocInfo = {};
allocInfo.commandPool = commandPool.get();
allocInfo.level = vk::CommandBufferLevel::ePrimary;
allocInfo.commandBufferCount = (uint32_t)commandBuffers.size();

commandBuffers = device->allocateCommandBuffersUnique(allocInfo);

for (size_t i = 0; i < commandBuffers.size(); ++i) {
vk::CommandBufferBeginInfo beginInfo = {};
beginInfo.flags = vk::CommandBufferUsageFlagBits::eSimultaneousUse;

commandBuffers[i]->begin(beginInfo);

commandBuffers[i]->bindPipeline(vk::PipelineBindPoint::eCompute, *pipeline);
commandBuffers[i]->bindDescriptorSets(vk::PipelineBindPoint::eCompute, *pipelineLayout, 0, descriptorSets[i], nullptr);

vk::ImageMemoryBarrier imageMemoryBarrier = {};
imageMemoryBarrier.oldLayout = vk::ImageLayout::eUndefined;
imageMemoryBarrier.newLayout = vk::ImageLayout::eGeneral;
imageMemoryBarrier.image = swapchainImages[i];
imageMemoryBarrier.subresourceRange.baseMipLevel = 0;
imageMemoryBarrier.subresourceRange.levelCount = 1;
imageMemoryBarrier.subresourceRange.baseArrayLayer = 0;
imageMemoryBarrier.subresourceRange.layerCount = 1;

commandBuffers[i]->pipelineBarrier(
vk::PipelineStageFlagBits::eTopOfPipe,
vk::DependencyFlagBits::eByRegion,
nullptr,
nullptr,
imageMemoryBarrier);

commandBuffers[i]->dispatch(WIDTH, HEIGHT, 1);

imageMemoryBarrier = {};
imageMemoryBarrier.oldLayout = vk::ImageLayout::eGeneral;
imageMemoryBarrier.newLayout = vk::ImageLayout::ePresentSrcKHR;
imageMemoryBarrier.image = swapchainImages[i];
imageMemoryBarrier.subresourceRange.baseMipLevel = 0;
imageMemoryBarrier.subresourceRange.levelCount = 1;
imageMemoryBarrier.subresourceRange.baseArrayLayer = 0;
imageMemoryBarrier.subresourceRange.layerCount = 1;

commandBuffers[i]->pipelineBarrier(
vk::PipelineStageFlagBits::eBottomOfPipe,
vk::DependencyFlagBits::eByRegion,
nullptr,
nullptr,
imageMemoryBarrier);

commandBuffers[i]->end();
}
}


Submitting:

void drawFrame() {
device->waitForFences(*inFlightFences[currentFrame], VK_TRUE, std::numeric_limits<uint64_t>::max());
device->resetFences(*inFlightFences[currentFrame]);

uint32_t imageIndex;

try {
device->acquireNextImageKHR(*swapchain, std::numeric_limits<uint64_t>::max(), *imageAvailableSemaphores[currentFrame], {}, &imageIndex);
}
catch (vk::OutOfDateKHRError) {
recreateSwapchain();
return;
}

vk::SubmitInfo submitInfo = {};

vk::Semaphore waitSemaphores[] = { *imageAvailableSemaphores[currentFrame] };
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &waitSemaphores[0];

submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &*commandBuffers[imageIndex];

vk::Semaphore signalSemaphores[] = { *renderFinishedSemaphores[currentFrame] };
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &signalSemaphores[0];

vk::PipelineStageFlags waitStages[] = { vk::PipelineStageFlagBits::eTopOfPipe };

computeQueue.submit(submitInfo, *inFlightFences[currentFrame]);

vk::PresentInfoKHR presentInfo = {};
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &signalSemaphores[0];

vk::SwapchainKHR swapchains[] = { *swapchain };
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapchains;
presentInfo.pImageIndices = &imageIndex;

try {
vk::Result result = computeQueue.presentKHR(presentInfo);
if (result == vk::Result::eSuboptimalKHR || framebufferResized) {
framebufferResized = false;
recreateSwapchain();
}
}
catch (vk::OutOfDateKHRError) {
framebufferResized = false;
recreateSwapchain();
}

currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}


The last function is basically the main loop at this point.

I've been staring at it for a while now and I don't get it. I don't know what is wrong. This is also the first Vulkan program I've written semi independently, so there's that as well.

Here's the rest of the code, if something is unclear: pastebin

Also, for completeness sake, here's the shader, as simple as can be:

#version 450

layout (binding = 0, rgba8) uniform image2D resultImage;

void main() {
vec4 green = vec4(0.0, 1.0, 0.0, 1.0);

imageStore(resultImage, ivec2(gl_GlobalInvocationID.xy), green);
}

• Did you verify that your implementation allows you to use the swapchain image as a storage image? – Nicol Bolas Aug 13 at 22:15
• Yeah, it allows. I've added push constants to the mix since I asked the question and am colouring each image of the swapchain the other colour to better see what's going on. Only image that is properly shown is the last swapchain image created, and the validation layers complain only about that image having the incorrect layout (the first barrier not working). Interestingly enough, if I change the first barrier to expect ePreinitialized layout, the program still works the same, but starts to complain about all of the swapchain images. – Karlovsky120 Aug 13 at 22:21

I have managed to find the culprit:

    std::vector<vk::WriteDescriptorSet> writeDescriptorSets;

for (int i = 0; i < swapchainImages.size(); ++i) {
vk::DescriptorImageInfo descriptorImageInfo = {};
descriptorImageInfo.imageView = *swapchainImageViews[i];
descriptorImageInfo.imageLayout = vk::ImageLayout::eGeneral;

vk::WriteDescriptorSet writeDescriptorSet = {};
writeDescriptorSet.dstSet = descriptorSets[i];
writeDescriptorSet.dstBinding = 0;
writeDescriptorSet.descriptorCount = 1;
writeDescriptorSet.descriptorType = vk::DescriptorType::eStorageImage;
writeDescriptorSet.pImageInfo = &descriptorImageInfo;
writeDescriptorSets.push_back(writeDescriptorSet);
}

device->updateDescriptorSets((uint32_t)writeDescriptorSets.size(), writeDescriptorSets.data(), 0, nullptr);


Do you see it yet?

What if I told you this fixed it:

    std::vector<vk::WriteDescriptorSet> writeDescriptorSets;
std::vector<vk::DescriptorImageInfo> descriptorImageInfos;

for (int i = 0; i < swapchainImages.size(); ++i) {
vk::DescriptorImageInfo descriptorImageInfo = {};
descriptorImageInfo.imageView = *swapchainImageViews[i];
descriptorImageInfo.imageLayout = vk::ImageLayout::eGeneral;
descriptorImageInfos.push_back(descriptorImageInfo);

vk::WriteDescriptorSet writeDescriptorSet = {};
writeDescriptorSet.dstSet = descriptorSets[i];
writeDescriptorSet.dstBinding = 0;
writeDescriptorSet.descriptorCount = 1;
writeDescriptorSet.descriptorType = vk::DescriptorType::eStorageImage;
writeDescriptorSet.pImageInfo = &descriptorImageInfos[i];
writeDescriptorSets.push_back(writeDescriptorSet);
}

device->updateDescriptorSets((uint32_t)writeDescriptorSets.size(), writeDescriptorSets.data(), 0, nullptr);


vk::DescriptorImageInfo for the first two images was being overwritten before updateDescriptorSets was called.

Remember kids, when you could swear you're treating several things in the absolute same way, but they somehow behave differently (in most cases, only the last one is working), you're probably overwriting something somewhere, or something is going out of scope before you thought it would.