В настоящее время я разрабатываю проект Vulkan и столкнулся с проблемой, которую не могу решить.
Мне нужно передать изображение предыдущего кадра в качестве sampler2D во фрагментный шейдер. Я следовал руководству Vulkan по настройке проекта, поэтому у меня правильно реализована цепочка обмена.
Моя идея заключалась в том, чтобы использовать vkCmdCopyImage для копирования текущего кадра, отображаемого в изображении цепочки обмена, в целевой VkImage перед началом рендеринга следующего кадра. Таким образом, я могу передать этот VkImage как sampler2D во фрагментный шейдер для рендеринга следующего кадра.
Во-первых, я хотел бы спросить, правильный ли мой подход или есть ли лучший способ справиться с этой ситуацией.
Если это решение осуществимо, я был бы признателен за некоторые рекомендации о том, что может быть не так с моим кодом. Ниже я предоставляю метод drawFrame(), который вызывается в основном цикле моего приложения, и методtexture(), который обрабатывает копирование изображения.
Проблема в том, что если я не вызываю метод текстуры(), все работает нормально, но у меня нет предыдущего кадра во фрагментном шейдере. Однако если я вызываю метод текстуры(), все работает без ошибок, но кажется, что VkImage никогда не обновляется.
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, ©Region
);
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;
}
Здесь несколько ошибок:
Вы звоните texture()
по номеру кадра, а не по индексу изображения. У вас может быть 3 изображения в вашей цепочке обмена, а максимальное количество кадров в полете равно 2. Это означает, что вы, вероятно, будете читать неправильное изображение цепочки обмена и никогда не будете читать изображение цепочки обмена 2 в этом случае. Кажется, вместо этого вы хотите пройти imageIndex
.
Вы звоните texture()
перед рендерингом. Это означает, что вы, вероятно, будете читать изображение с неопределенным содержимым. Возможно, вы захотите вызвать его после отправки в очередь и до настоящего момента.
Барьеры в texture()
не совсем правильные. Вот версия, которая мне подошла. Я не реализовал шейдерное чтение изображения и просто использовал RenderDoc, чтобы убедиться, что копия работает (возможно, вы захотите сделать то же самое). Итак, параметры barrierTextureToShaderRead
настроены для просмотра текстуры в RenderDoc. Возможно, вам придется оставить их такими, какими они были у вас, для использования шейдером.
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, ©Region
);
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);;
}
Наконец, посмотрите этот скриншот кода в качестве примера чтения образа цепочки обмена.
Позже я понял, что вам, вероятно, нужно использовать функцию текстуры так, как она была у меня, а затем перевести текстуру в режим чтения шейдера, когда она будет использоваться в следующем кадре. Но я думаю, вы это поняли. Что касается размытости, я могу только догадываться, что вы накладываете текстуру на гораздо меньшую область экрана, и вам следует посмотреть настройки сэмплера. Возможно, для него установлено значение «ближайший», и вам нужно что-то вроде линейного для лучшей выборки. Ожидается некоторая потеря деталей.
Спасибо за ваш ответ. Сейчас работает, но не в полной мере. Проблема в том, что VkImage корректно обновляется, но кажется размытым. Я попробовал использовать vkCmdBlitImage, так как мое устройство поддерживает копирование, и я получил лучший результат, но изображение все равно размыто. Знаете ли вы, какие могут быть возможные причины?