Related
I'm having some problems getting a Irradiance Map working in Vulkan for multiple days.
It seems like the texture aren't stretching to fit the plain it's projected on. I've tried multiple way to figure out how to get the get the full text displayed and nothings working. If the cube map is 512 x 512 the CubeMap will only show the 512x512 pixels and cut off the rest. And if it's 2048x2048 it shows all of the texture but the rest of it just blank space.
The irradiance part isn't applied here. It just taking the 6 views of the cube and projecting them.
2048 x 2048 view
512x512 View
This is irradiance code I've been working with.
#pragma once
#include "BaseRenderPass.h"
#include "RenderedDepthTexture.h"
#include "BlinnPhongPipeline.h"
#include "MeshManager.h"
#include "RenderedColorTexture.h"
#include "SkyBoxRenderPipeline.h"
#include "RenderedCubeMapTexture.h"
#include "IrradiancePipeline.h"
class IrradianceRenderPass : public BaseRenderPass
{
private:
void CreateRenderPass();
void CreateRendererFramebuffers();
void SetUpCommandBuffers();
uint32_t CubeMapSize;
VkFramebuffer IrradianceMapFrameBuffer;
public:
IrradianceRenderPass();
IrradianceRenderPass(std::shared_ptr<VulkanEngine> engine);
~IrradianceRenderPass();
std::shared_ptr<RenderedCubeMapTexture> RenderedCubeMap;
std::shared_ptr<IrradiancePipeline> irradiancePipeline;
void RebuildSwapChain();
void Draw();
void Destroy();
};
#include "IrradianceRenderPass.h"
#include "GraphicsPipeline.h"
#include "Skybox.h"
IrradianceRenderPass::IrradianceRenderPass() : BaseRenderPass()
{
}
IrradianceRenderPass::IrradianceRenderPass(std::shared_ptr<VulkanEngine> engine) : BaseRenderPass()
{
CubeMapSize = 512.0f;
RenderedCubeMap = std::make_shared<RenderedCubeMapTexture>(RenderedCubeMapTexture(glm::ivec2(CubeMapSize)));
CreateRenderPass();
CreateRendererFramebuffers();
irradiancePipeline = std::make_shared<IrradiancePipeline>(IrradiancePipeline(RenderPass));
SetUpCommandBuffers();
Draw();
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &CommandBuffer[EnginePtr::GetEnginePtr()->CMDIndex];
VkFenceCreateInfo fenceCreateInfo{};
fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceCreateInfo.flags = 0;
VkFence fence;
vkCreateFence(VulkanPtr::GetDevice(), &fenceCreateInfo, nullptr, &fence);
vkQueueSubmit(VulkanPtr::GetGraphicsQueue(), 1, &submitInfo, fence);
vkWaitForFences(VulkanPtr::GetDevice(), 1, &fence, VK_TRUE, UINT64_MAX);
vkDestroyFence(VulkanPtr::GetDevice(), fence, nullptr);
}
IrradianceRenderPass::~IrradianceRenderPass()
{
}
void IrradianceRenderPass::CreateRenderPass()
{
std::vector<VkAttachmentDescription> AttachmentDescriptionList;
VkAttachmentDescription CubeMapAttachment = {};
CubeMapAttachment.format = VK_FORMAT_R8G8B8A8_UNORM;
CubeMapAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
CubeMapAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
CubeMapAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
CubeMapAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
CubeMapAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
CubeMapAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
CubeMapAttachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
AttachmentDescriptionList.emplace_back(CubeMapAttachment);
std::vector<VkAttachmentReference> ColorRefsList;
ColorRefsList.emplace_back(VkAttachmentReference{ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
VkSubpassDescription subpassDescription = {};
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpassDescription.colorAttachmentCount = static_cast<uint32_t>(ColorRefsList.size());
subpassDescription.pColorAttachments = ColorRefsList.data();
std::vector<VkSubpassDependency> DependencyList;
VkSubpassDependency FirstDependency = {};
FirstDependency.srcSubpass = VK_SUBPASS_EXTERNAL;
FirstDependency.dstSubpass = 0;
FirstDependency.srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
FirstDependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
FirstDependency.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
FirstDependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
FirstDependency.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
DependencyList.emplace_back(FirstDependency);
VkSubpassDependency SecondDependency = {};
SecondDependency.srcSubpass = 0;
SecondDependency.dstSubpass = VK_SUBPASS_EXTERNAL;
SecondDependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
SecondDependency.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
SecondDependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
SecondDependency.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
SecondDependency.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
DependencyList.emplace_back(SecondDependency);
const uint32_t viewMask = 0b00111111;
const uint32_t correlationMask = 0b00111111;
VkRenderPassMultiviewCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO;
createInfo.subpassCount = 1;
createInfo.pViewMasks = &viewMask;
createInfo.correlationMaskCount = 1;
createInfo.pCorrelationMasks = &correlationMask;
VkRenderPassCreateInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = static_cast<uint32_t>(AttachmentDescriptionList.size());
renderPassInfo.pAttachments = AttachmentDescriptionList.data();
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpassDescription;
renderPassInfo.dependencyCount = static_cast<uint32_t>(DependencyList.size());
renderPassInfo.pDependencies = DependencyList.data();
renderPassInfo.pNext = &createInfo;
if (vkCreateRenderPass(EnginePtr::GetEnginePtr()->Device, &renderPassInfo, nullptr, &RenderPass))
{
throw std::runtime_error("failed to create GBuffer RenderPass!");
}
}
void IrradianceRenderPass::CreateRendererFramebuffers()
{
SwapChainFramebuffers.resize(EnginePtr::GetEnginePtr()->SwapChain.GetSwapChainImageCount());
for (size_t i = 0; i < EnginePtr::GetEnginePtr()->SwapChain.GetSwapChainImageCount(); i++)
{
std::vector<VkImageView> AttachmentList;
AttachmentList.emplace_back(RenderedCubeMap->View);
VkFramebufferCreateInfo frameBufferCreateInfo = {};
frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
frameBufferCreateInfo.renderPass = RenderPass;
frameBufferCreateInfo.attachmentCount = static_cast<uint32_t>(AttachmentList.size());
frameBufferCreateInfo.pAttachments = AttachmentList.data();
frameBufferCreateInfo.width = CubeMapSize;
frameBufferCreateInfo.height = CubeMapSize;
frameBufferCreateInfo.layers = 1;
if (vkCreateFramebuffer(EnginePtr::GetEnginePtr()->Device, &frameBufferCreateInfo, nullptr, &SwapChainFramebuffers[i]))
{
throw std::runtime_error("Failed to create Gbuffer FrameBuffer.");
}
}
}
void IrradianceRenderPass::SetUpCommandBuffers()
{
CommandBuffer.resize(EnginePtr::GetEnginePtr()->SwapChain.GetSwapChainImageCount());
for (size_t i = 0; i < EnginePtr::GetEnginePtr()->SwapChain.GetSwapChainImageCount(); i++)
{
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = EnginePtr::GetEnginePtr()->CommandPool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = 1;
if (vkAllocateCommandBuffers(EnginePtr::GetEnginePtr()->Device, &allocInfo, &CommandBuffer[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to allocate command buffers!");
}
}
}
void IrradianceRenderPass::RebuildSwapChain()
{
irradiancePipeline->Destroy();
vkDestroyRenderPass(EnginePtr::GetEnginePtr()->Device, RenderPass, nullptr);
RenderPass = VK_NULL_HANDLE;
for (auto& framebuffer : SwapChainFramebuffers)
{
vkDestroyFramebuffer(EnginePtr::GetEnginePtr()->Device, framebuffer, nullptr);
framebuffer = VK_NULL_HANDLE;
}
CreateRenderPass();
CreateRendererFramebuffers();
irradiancePipeline->UpdateGraphicsPipeLine(RenderPass);
SetUpCommandBuffers();
}
void IrradianceRenderPass::Draw()
{
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
if (vkBeginCommandBuffer(CommandBuffer[EnginePtr::GetEnginePtr()->CMDIndex], &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("failed to begin recording command buffer!");
}
std::array<VkClearValue, 2> clearValues{};
clearValues[0].color = { {0.0f, 1.0f, 0.0f, 1.0f} };
clearValues[1].color = { {0.0f, 1.0f, 0.0f, 1.0f} };
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = RenderPass;
renderPassInfo.framebuffer = SwapChainFramebuffers[EnginePtr::GetEnginePtr()->ImageIndex];
renderPassInfo.renderArea.offset = { 0, 0 };
renderPassInfo.renderArea.extent.width = CubeMapSize;
renderPassInfo.renderArea.extent.height = CubeMapSize;
renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
renderPassInfo.pClearValues = clearValues.data();
vkCmdBindPipeline(CommandBuffer[EnginePtr::GetEnginePtr()->CMDIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, irradiancePipeline->ShaderPipeline);
vkCmdBindDescriptorSets(CommandBuffer[EnginePtr::GetEnginePtr()->CMDIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, irradiancePipeline->ShaderPipelineLayout, 0, 1, &irradiancePipeline->DescriptorSet, 0, nullptr);
vkCmdBeginRenderPass(CommandBuffer[EnginePtr::GetEnginePtr()->CMDIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
static_cast<Skybox*>(MeshManagerPtr::GetMeshManagerPtr()->GetMeshByType(MeshTypeFlag::Mesh_Type_SkyBox)[0].get())->Draw(CommandBuffer[EnginePtr::GetEnginePtr()->CMDIndex]);
vkCmdEndRenderPass(CommandBuffer[EnginePtr::GetEnginePtr()->CMDIndex]);
if (vkEndCommandBuffer(CommandBuffer[EnginePtr::GetEnginePtr()->CMDIndex]) != VK_SUCCESS) {
throw std::runtime_error("failed to record command buffer!");
}
}
void IrradianceRenderPass::Destroy()
{
irradiancePipeline->Destroy();
BaseRenderPass::Destroy();
}
I am attempting to make a game engine. Currently I am trying to implement a HDR skybox which also has mip maps, however have been stuck with an error for a while. The Example runs completely fine loading non-HDR 8 bit colour textures. But the moment I attempt to use VK_FORMAT_R32G32B32A32_SFLOAT (which I'm fairly sure is the correct format in order to correspond to 4 bit floats loaded from stbi) the command buffer fails to ever complete and seems to always be in a pending state, I have tried giving the texture manager its own command buffer with no luck and using both vkQueueWaitIdle as well as fences but they both return VK_SUCCEED. The validation layers then throw an error once vkResetCommandBuffer is invoked because the command buffer is in pending state. Seemingly it sometimes rarely works if I click to focus on the console, more errors appear afterwards but was not always the case and HDR seemed to be working once it loaded but was still a 1/3 occasion mostly throwing the same error.
This is the code that is used to load the cubemaps in:
Cubemap::Cubemap(CubemapInfo cubemapInfo)
{
RenderSystem& renderSystem = RenderSystem::instance();
TextureManager& textureManager = TextureManager::instance();
VkImageFormatProperties formatProperties;
assert(("[ERROR] Unsupported texture format", !vkGetPhysicalDeviceImageFormatProperties(renderSystem.mPhysicalDevice, cubemapInfo.format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 0, &formatProperties)));
FormatInfo formatInfo = getFormatInfo(cubemapInfo.format);
#pragma region Create cubemap resources
stbi_set_flip_vertically_on_load(true);
void* textureData[6];
// Load images
int width, height, channels;
bool hdr = cubemapInfo.format == VK_FORMAT_R16_SFLOAT || cubemapInfo.format == VK_FORMAT_R16G16_SFLOAT || cubemapInfo.format == VK_FORMAT_R16G16B16_SFLOAT || cubemapInfo.format == VK_FORMAT_R16G16B16A16_SFLOAT || cubemapInfo.format == VK_FORMAT_R32_SFLOAT || cubemapInfo.format == VK_FORMAT_R32G32_SFLOAT || cubemapInfo.format == VK_FORMAT_R32G32B32_SFLOAT || cubemapInfo.format == VK_FORMAT_R32G32B32A32_SFLOAT;
if (hdr)
{
if (formatInfo.bytesPerChannel == 4)
{
for (unsigned int i = 0; i < 6; i++)
{
textureData[i] = stbi_loadf(cubemapInfo.directories[i].c_str(), &width, &height, &channels, formatInfo.nChannels);
}
}
else if (formatInfo.bytesPerChannel == 2)
{
for (unsigned int i = 0; i < 6; i++)
{
float* data = stbi_loadf(cubemapInfo.directories[i].c_str(), &width, &height, &channels, formatInfo.nChannels);
unsigned long long dataSize = width * height * formatInfo.nChannels;
textureData[i] = new float16[dataSize];
for (unsigned long long j = 0; j < dataSize; j++)
{
((float16*)textureData[i])[j] = floatToFloat16(data[j]);
}
stbi_image_free((void*)data);
}
}
}
else
{
for (unsigned int i = 0; i < 6; i++)
{
textureData[i] = stbi_load(cubemapInfo.directories[i].c_str(), &width, &height, &channels, formatInfo.nChannels);
}
}
const VkDeviceSize imageSize = 6 * VkDeviceSize(width) * height * formatInfo.nChannels * formatInfo.bytesPerChannel;
unsigned int nMips = unsigned int(std::floor(std::log2(width > height ? width : height))) + 1;
assert(("[ERROR] Unsupported texture format", formatProperties.maxExtent.width >= width && formatProperties.maxExtent.height >= height && formatProperties.maxExtent.depth >= 1 && formatProperties.maxMipLevels >= 1 && formatProperties.maxArrayLayers >= 1 && formatProperties.sampleCounts & VK_SAMPLE_COUNT_1_BIT && formatProperties.maxResourceSize >= imageSize));
// Create image
VkImageCreateInfo imageCreateInfo = {};
imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageCreateInfo.pNext = nullptr;
imageCreateInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
imageCreateInfo.format = cubemapInfo.format;
imageCreateInfo.extent = { unsigned int(width), unsigned int(height), 1 };
imageCreateInfo.mipLevels = nMips;
imageCreateInfo.arrayLayers = 6;
imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageCreateInfo.queueFamilyIndexCount = 0;
imageCreateInfo.pQueueFamilyIndices = nullptr;
imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkResult result = vkCreateImage(renderSystem.mDevice, &imageCreateInfo, nullptr, &mImage);
validateResult(result);
VkMemoryRequirements memoryRequirements;
vkGetImageMemoryRequirements(renderSystem.mDevice, mImage, &memoryRequirements);
VkMemoryAllocateInfo memoryAllocateInfo = {};
memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memoryAllocateInfo.allocationSize = memoryRequirements.size;
memoryAllocateInfo.memoryTypeIndex = memoryTypeFromProperties(renderSystem.mPhysicalDeviceMemoryProperties, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
result = vkAllocateMemory(renderSystem.mDevice, &memoryAllocateInfo, nullptr, &mImageMemory);
validateResult(result);
result = vkBindImageMemory(renderSystem.mDevice, mImage, mImageMemory, 0);
validateResult(result);
// Create staging buffer
VkBuffer stagingBuffer;
VkDeviceMemory stagingMemory;
VkBufferCreateInfo bufferCreateInfo = {};
bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferCreateInfo.pNext = nullptr;
bufferCreateInfo.flags = 0;
bufferCreateInfo.size = imageSize;
bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
bufferCreateInfo.queueFamilyIndexCount = 0;
bufferCreateInfo.pQueueFamilyIndices = nullptr;
result = vkCreateBuffer(renderSystem.mDevice, &bufferCreateInfo, nullptr, &stagingBuffer);
validateResult(result);
vkGetBufferMemoryRequirements(renderSystem.mDevice, stagingBuffer, &memoryRequirements);
memoryAllocateInfo.allocationSize = memoryRequirements.size;
memoryAllocateInfo.memoryTypeIndex = memoryTypeFromProperties(renderSystem.mPhysicalDeviceMemoryProperties, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
result = vkAllocateMemory(renderSystem.mDevice, &memoryAllocateInfo, nullptr, &stagingMemory);
validateResult(result);
result = vkBindBufferMemory(renderSystem.mDevice, stagingBuffer, stagingMemory, 0);
validateResult(result);
unsigned char* data;
result = vkMapMemory(renderSystem.mDevice, stagingMemory, 0, imageSize, 0, (void**)&data);
validateResult(result);
unsigned long long dataLayer = unsigned long long(width) * height * formatInfo.nChannels * formatInfo.bytesPerChannel;
for (unsigned int i = 0; i < 6; i++)
{
memcpy((void*)(data + i * dataLayer), textureData[i], dataLayer);
stbi_image_free(textureData[i]);
}
vkUnmapMemory(renderSystem.mDevice, stagingMemory);
result = vkBeginCommandBuffer(textureManager.mCommandBuffer, &renderSystem.mCommandBufferBeginInfo);
validateResult(result);
VkImageMemoryBarrier barrier = {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.pNext = nullptr;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT /* Additional >> */ | VK_ACCESS_TRANSFER_READ_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = mImage;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = nMips;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 6;
vkCmdPipelineBarrier(textureManager.mCommandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
VkBufferImageCopy copyRegion = {};
copyRegion.bufferOffset = 0;
copyRegion.bufferRowLength = 0;
copyRegion.bufferImageHeight = 0;
copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copyRegion.imageSubresource.mipLevel = 0;
copyRegion.imageSubresource.baseArrayLayer = 0;
copyRegion.imageSubresource.layerCount = 6;
copyRegion.imageOffset = { 0, 0, 0 };
copyRegion.imageExtent = { unsigned int(width), unsigned int(height), 1 };
vkCmdCopyBufferToImage(textureManager.mCommandBuffer, stagingBuffer, mImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region);
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barrier.subresourceRange.levelCount = 1;
VkImageBlit imageBlit = {};
imageBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageBlit.srcSubresource.baseArrayLayer = 0;
imageBlit.srcSubresource.layerCount = 6;
imageBlit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageBlit.dstSubresource.baseArrayLayer = 0;
imageBlit.dstSubresource.layerCount = 6;
unsigned int mipWidth = width, mipHeight = height;
for (unsigned int i = 1; i < nMips; i++)
{
barrier.subresourceRange.baseMipLevel = i - 1;
vkCmdPipelineBarrier(textureManager.mCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
imageBlit.srcSubresource.mipLevel = i - 1;
imageBlit.srcOffsets[0] = { 0, 0, 0 };
imageBlit.srcOffsets[1] = { int(mipWidth), int(mipHeight), 1 };
imageBlit.dstSubresource.mipLevel = i;
if (mipWidth > 1)
mipWidth /= 2;
if (mipHeight > 1)
mipHeight /= 2;
imageBlit.dstOffsets[0] = { 0, 0, 0 };
imageBlit.dstOffsets[1] = { int(mipWidth), int(mipHeight), 1 };
vkCmdBlitImage(textureManager.mCommandBuffer, mImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, mImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageBlit, VK_FILTER_LINEAR);
}
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
for (unsigned int i = 0; i < nMips; i++)
{
barrier.oldLayout = i == nMips - 1 ? VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barrier.subresourceRange.baseMipLevel = i;
vkCmdPipelineBarrier(textureManager.mCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}
result = vkEndCommandBuffer(textureManager.mCommandBuffer);
validateResult(result);
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.pNext = nullptr;
submitInfo.waitSemaphoreCount = 0;
submitInfo.pWaitSemaphores = nullptr;
submitInfo.pWaitDstStageMask = nullptr;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &textureManager.mCommandBuffer;
submitInfo.signalSemaphoreCount = 0;
submitInfo.pSignalSemaphores = nullptr;
result = vkQueueSubmit(renderSystem.mGraphicsQueue, 1, &submitInfo, NULL);
validateResult(result);
// Create image view
VkImageViewCreateInfo imageViewCreateInfo = {};
imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
imageViewCreateInfo.pNext = nullptr;
imageViewCreateInfo.flags = 0;
imageViewCreateInfo.image = mImage;
imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
imageViewCreateInfo.format = cubemapInfo.format;
imageViewCreateInfo.components = formatInfo.componentMapping;
imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageViewCreateInfo.subresourceRange.baseMipLevel = 0;
imageViewCreateInfo.subresourceRange.levelCount = nMips;
imageViewCreateInfo.subresourceRange.baseArrayLayer = 0;
imageViewCreateInfo.subresourceRange.layerCount = 6;
result = vkCreateImageView(renderSystem.mDevice, &imageViewCreateInfo, nullptr, &mImageView);
validateResult(result);
// Create sampler
VkSamplerCreateInfo samplerCreateInfo = {};
samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerCreateInfo.pNext = nullptr;
samplerCreateInfo.flags = 0;
samplerCreateInfo.magFilter = VK_FILTER_LINEAR;
samplerCreateInfo.minFilter = VK_FILTER_LINEAR;
samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerCreateInfo.mipLodBias = 0.0f;
samplerCreateInfo.minLod = 0.0f;
samplerCreateInfo.maxLod = float(nMips);
samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;;
samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerCreateInfo.anisotropyEnable = VK_TRUE;
samplerCreateInfo.maxAnisotropy = renderSystem.mPhysicalDeviceProperties.limits.maxSamplerAnisotropy;
samplerCreateInfo.compareEnable = VK_FALSE;
samplerCreateInfo.compareOp = VK_COMPARE_OP_ALWAYS;
samplerCreateInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
samplerCreateInfo.unnormalizedCoordinates = VK_FALSE;
result = vkCreateSampler(renderSystem.mDevice, &samplerCreateInfo, nullptr, &mSampler);
validateResult(result);
result = vkQueueWaitIdle(renderSystem.mGraphicsQueue);
validateResult(result);
result = vkResetCommandBuffer(textureManager.mCommandBuffer, 0);
validateResult(result);
vkDestroyBuffer(renderSystem.mDevice, stagingBuffer, nullptr);
vkFreeMemory(renderSystem.mDevice, stagingMemory, nullptr);
#pragma endregion
}
Exact errors that occurr:
VUID-vkResetCommandBuffer-commandBuffer-00045(ERROR / SPEC): msgNum: 511214570 - Validation Error: [ VUID-vkResetCommandBuffer-commandBuffer-00045 ] Object 0: handle = 0x19323492138, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x1e7883ea | Attempt to reset VkCommandBuffer 0x19323492138[] which is in use. The Vulkan spec states: commandBuffer must not be in the pending state (https://vulkan.lunarg.com/doc/view/1.2.162.1/windows/1.2-extensions/vkspec.html#VUID-vkResetCommandBuffer-commandBuffer-00045)
Objects: 1
[0] 0x19323492138, type: 6, name: NULL
UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout(ERROR / SPEC): msgNum: 1303270965 - Validation Error: [ UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout ] Object 0: handle = 0x19323492138, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x4dae5635 | Submitted command buffer expects VkImage 0x5fb0e800000000cd[] (subresource: aspectMask 0x1 array layer 0, mip level 0) to be in layout VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL--instead, current layout is VK_IMAGE_LAYOUT_UNDEFINED.
Objects: 1
[0] 0x19323492138, type: 6, name: NULL
(^ This error is thrown continuously for mip levels 0-12 ^)
View full source:
https://github.com/finnbuhse/Vulkan-Engine-V1.0
Although assets and shader binaries are not on the github so compile the shader sources into files with names identical to those found in mesh.cpp line 1083 and adjust main.cpp to include custom models if you wish to try compile and run the source.
Any clue as to why this might be happening would be greatly appreciated
So, after almost an entire year of agonising over this one error... I found what seemed to have happened was the GPU memory ran out, and the skybox I picked initially was simply too large; each face was 4K and I found the whole cubemap had to be allocated over a gigabyte of video memory, and having a mere NVIDIA GTX 1050 Ti, is a quarter of it. However I did think this was possible early on, which is why I validated every VkResult I could thinking if this happened, VK_ERROR_OUT_OF_DEVICE_MEMORY would be returned. However nothing but success from what the 'results' could tell. Perhaps it wasn't so much video memory but the GPU had a hard time mip-mapping such a large image. Either way, with a different HDR skybox (1k) it works perfectly fine with both 16 bit floating point images aswell as 32 bits.
I'm currently writing a rendering engine with the Vulkan API and my setup uses a different queue for transfer operations than for graphics operations if possible. When rendering images, they should be in the VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL layout, however since the image is currently owned by a queue that is only flagged with the transfer bit, i also need to transfer the ownership of the image to the graphics queue simultaneously.
This however seems to fail for some reason, as even though the command buffer with the pipeline barrier is executed, the image remains in the VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL layout, resulting in a validation error.
This is the method which transitions image layouts:
int PGraphicsContext::transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
{
VkCommandBuffer cmdBuffer = this->beginTransferCmdBuffer();
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = oldLayout;
barrier.newLayout = newLayout;
barrier.image = image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
VkPipelineStageFlags srcStage;
VkPipelineStageFlags dstStage;
if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
{
PApplication::getInstance()->getLogger()->debug("Transitioning image layout from VK_IMAGE_LAYOUT_UNDEFINED to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL");
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
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)
{
PApplication::getInstance()->getLogger()->debug("Transitioning image layout from VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL");
if (this->queueFamilies[PQueueIndex::TRANSFER_QUEUE].index != this->queueFamilies[PQueueIndex::GRAPHICS_QUEUE].index)
{
barrier.srcQueueFamilyIndex = this->queueFamilies[PQueueIndex::TRANSFER_QUEUE].index;
barrier.dstQueueFamilyIndex = this->queueFamilies[PQueueIndex::GRAPHICS_QUEUE].index;
}
else
{
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
}
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
}
else
{
// TODO: implement this
this->endTransferCmdBuffer(cmdBuffer);
PApplication::getInstance()->getLogger()->error("Failed to transition image layout from 0x%X to 0x%X", oldLayout, newLayout);
return PINE_ERROR_VULKAN_UNSUPPORTED_LAYER_TRANSITION;
}
vkCmdPipelineBarrier(cmdBuffer, srcStage, dstStage, {}, 0, nullptr, 0, nullptr, 1, &barrier);
this->endTransferCmdBuffer(cmdBuffer);
return PINE_SUCCESS;
}
This seems to work fine if the index of the transfer queue and the graphics queue are the same, meaning the source and destination queue family indices are both set to VK_QUEUE_FAMILY_IGNORED.
Here is an example of some log messages:
[15 JUN 23:17:27][Taiga::DEBUG]: Transitioning image layout from VK_IMAGE_LAYOUT_UNDEFINED to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
[15 JUN 23:17:27][Taiga::DEBUG]: Transitioning image layout from VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
[15 JUN 23:17:27][Taiga::ERROR]: Validation Error: [ UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout ] Object 0: handle = 0x7f34b4842668, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x4dae5635 | Submitted command buffer expects VkImage 0xec4bec000000000b[] (subresource: aspectMask 0x1 array layer 0, mip level 0) to be in layout VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL--instead, current layout is VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL.
The validation error only occurs if this->queueFamilies[PQueueIndex::TRANSFER_QUEUE].index is a different queue family index than this->queueFamilies[PQueueIndex::GRAPHICS_QUEUE].index.
Is it even possible to transfer ownership and transition layout at the same time or would the correct way be to first record a command buffer that only transfers the ownership of the image from the transfer to the graphics queue and then a second one which actually transtitions the layout or should I scrap the whole idea of using a separate transfer queues (for images)?
Ok, I found the solution to my problem.
The documentation states that I have to issue the same pipeline barrier command in the other queue as well after the first barrier was executed, which I completely missed.
So here is a solution that works, although it is far from optimal because it runs synchronously on one thread on the CPU and recreates command buffers everytime I want to transition a layout.
int PGraphicsContext::beginCmdBuffer(VkCommandBuffer *cmdBuffer, const PQueueIndex queueIndex)
{
if (queueIndex != PQueueIndex::GRAPHICS_QUEUE && queueIndex != PQueueIndex::TRANSFER_QUEUE)
return PINE_ERROR_INVALID_ARGUMENT;
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = queueIndex == PQueueIndex::TRANSFER_QUEUE ? this->transferCommandPool : this->graphicsCommandPool;
allocInfo.commandBufferCount = 1;
vkAllocateCommandBuffers(this->device, &allocInfo, cmdBuffer);
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(*cmdBuffer, &beginInfo);
return PINE_SUCCESS;
}
int PGraphicsContext::endCmdBuffer(VkCommandBuffer cmdBuffer, const PQueueIndex queueIndex, VkFence fence, const VkSemaphore *waitSemaphore, VkPipelineStageFlags *waitStageFlags, const VkSemaphore *signalSemaphore)
{
vkEndCommandBuffer(cmdBuffer);
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &cmdBuffer;
if (waitSemaphore != nullptr)
{
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphore;
submitInfo.pWaitDstStageMask = waitStageFlags;
}
if (signalSemaphore != nullptr)
{
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphore;
}
vkQueueSubmit(this->queueFamilies[queueIndex].queue, 1, &submitInfo, fence);
return PINE_SUCCESS;
}
int PGraphicsContext::transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
{
VkCommandBuffer cmdBuffer;
this->beginCmdBuffer(&cmdBuffer);
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = oldLayout;
barrier.newLayout = newLayout;
barrier.image = image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
{
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(cmdBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, {}, 0, nullptr, 0, nullptr, 1, &barrier);
this->endCmdBuffer(cmdBuffer, PQueueIndex::TRANSFER_QUEUE, this->syncFence);
vkWaitForFences(this->device, 1, &this->syncFence, VK_TRUE, UINT64_MAX);
vkResetFences(this->device, 1, &this->syncFence);
vkFreeCommandBuffers(this->device, this->transferCommandPool, 1, &cmdBuffer);
}
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
{
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
if (this->queueFamilies[PQueueIndex::TRANSFER_QUEUE].index != this->queueFamilies[PQueueIndex::GRAPHICS_QUEUE].index)
{
barrier.dstAccessMask = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.srcQueueFamilyIndex = this->queueFamilies[PQueueIndex::TRANSFER_QUEUE].index;
barrier.dstQueueFamilyIndex = this->queueFamilies[PQueueIndex::GRAPHICS_QUEUE].index;
VkSemaphoreCreateInfo semaphoreInfo{};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VkSemaphore transferSemaphore;
if (vkCreateSemaphore(this->device, &semaphoreInfo, this->allocator, &transferSemaphore) != VK_SUCCESS)
{
this->endCmdBuffer(cmdBuffer, PQueueIndex::TRANSFER_QUEUE);
return PINE_ERROR_VULKAN_UNSUPPORTED_LAYER_TRANSITION;
}
vkCmdPipelineBarrier(cmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, {}, 0, nullptr, 0, nullptr, 1, &barrier);
this->endCmdBuffer(cmdBuffer, PQueueIndex::TRANSFER_QUEUE, VK_NULL_HANDLE, nullptr, nullptr, &transferSemaphore);
barrier.srcAccessMask = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
VkCommandBuffer queueBuffer;
this->beginCmdBuffer(&queueBuffer, PQueueIndex::GRAPHICS_QUEUE);
vkCmdPipelineBarrier(queueBuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, {}, 0, nullptr, 0, nullptr, 1, &barrier);
VkPipelineStageFlags flags = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
this->endCmdBuffer(queueBuffer, PQueueIndex::GRAPHICS_QUEUE, this->syncFence, &transferSemaphore, &flags, nullptr);
vkWaitForFences(this->device, 1, &this->syncFence, VK_TRUE, UINT64_MAX);
vkResetFences(this->device, 1, &this->syncFence);
vkFreeCommandBuffers(this->device, this->transferCommandPool, 1, &cmdBuffer);
vkFreeCommandBuffers(this->device, this->graphicsCommandPool, 1, &queueBuffer);
vkDestroySemaphore(this->device, transferSemaphore, this->allocator);
}
else
{
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
vkCmdPipelineBarrier(cmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, {}, 0, nullptr, 0, nullptr, 1, &barrier);
this->endCmdBuffer(cmdBuffer, PQueueIndex::TRANSFER_QUEUE, this->syncFence);
vkWaitForFences(this->device, 1, &this->syncFence, VK_TRUE, UINT64_MAX);
vkResetFences(this->device, 1, &this->syncFence);
vkFreeCommandBuffers(this->device, this->transferCommandPool, 1, &cmdBuffer);
}
}
else
{
// TODO: implement this
this->endCmdBuffer(cmdBuffer, PQueueIndex::TRANSFER_QUEUE, this->syncFence);
vkWaitForFences(this->device, 1, &this->syncFence, VK_TRUE, UINT64_MAX);
vkResetFences(this->device, 1, &this->syncFence);
vkFreeCommandBuffers(this->device, this->transferCommandPool, 1, &cmdBuffer);
PApplication::getInstance()->getLogger()->error("Failed to transition image layout from 0x%X to 0x%X", oldLayout, newLayout);
return PINE_ERROR_VULKAN_UNSUPPORTED_LAYER_TRANSITION;
}
return PINE_SUCCESS;
}
I have now added the ability to provide a wait and signal semaphore to the queue submit operation when ending a command buffer. In the transitionImageLayout() method I now use this to create a semaphore when I also need to change ownership which is signaled when the transfer queue is done with the pipeline barrier. I then also creates a second command buffer on the graphics queue which waits for the semaphore before it runs the same pipeline barrier command.
I'm trying to reuse the depth attachment from the first renderpass into the second renderpass. but, it's not loading the depth values in the second renderpass.
//code that creates attachments
void VulkanRenderTarget::addAttachment(AttachmentCreateInfo createinfo)
{
auto device = mRenderer->getDevice();
Attachment attachment;
attachment.format = createinfo.format;
VkImageAspectFlags aspectMask = 0;
// Select aspect mask and layout depending on usage
// Color attachment
if (createinfo.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
{
aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
}
// Depth (and/or stencil) attachment
if (createinfo.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
{
if (attachment.hasDepth())
{
aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
}
if (attachment.hasStencil())
{
aspectMask = aspectMask | VK_IMAGE_ASPECT_STENCIL_BIT;
}
}
assert(aspectMask > 0);
VkImageCreateInfo image = VulkanInitializers::imageCreateInfo();
image.imageType = VK_IMAGE_TYPE_2D;
image.format = createinfo.format;
image.extent.width = createinfo.width;
image.extent.height = createinfo.height;
image.extent.depth = 1;
image.mipLevels = 1;
image.arrayLayers = createinfo.layerCount;
image.samples = createinfo.imageSampleCount;
image.tiling = createinfo.tiling;
image.usage = createinfo.usage;
VkMemoryAllocateInfo memAlloc = VulkanInitializers::memoryAllocateInfo();
VkMemoryRequirements memReqs;
// Create image for this attachment
VERIFY(vkCreateImage(device, &image, nullptr, &attachment.image));
vkGetImageMemoryRequirements(device, attachment.image, &memReqs);
memAlloc.allocationSize = memReqs.size;
memAlloc.memoryTypeIndex = mRenderer->getMemoryType(memReqs.memoryTypeBits, createinfo.memoryFlag);
VERIFY(vkAllocateMemory(device, &memAlloc, nullptr, &attachment.memory));
VERIFY(vkBindImageMemory(device, attachment.image, attachment.memory, 0));
attachment.subresourceRange = {};
attachment.subresourceRange.aspectMask = aspectMask;
attachment.subresourceRange.levelCount = 1;
attachment.subresourceRange.layerCount = createinfo.layerCount;
VkImageViewCreateInfo imageView = VulkanInitializers::imageViewCreateInfo();
imageView.viewType = (createinfo.layerCount == 1) ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY;
imageView.format = createinfo.format;
imageView.subresourceRange = attachment.subresourceRange;
//todo: workaround for depth+stencil attachments
imageView.subresourceRange.aspectMask = (attachment.hasDepth()) ? VK_IMAGE_ASPECT_DEPTH_BIT : aspectMask;
imageView.image = attachment.image;
VERIFY(vkCreateImageView(device, &imageView, nullptr, &attachment.view));
// Fill attachment description
attachment.description = {};
attachment.description.samples = createinfo.imageSampleCount;
attachment.description.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachment.description.storeOp = (createinfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachment.description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment.description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachment.description.format = createinfo.format;
attachment.description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
// Final layout
// If not, final layout depends on attachment type
if (attachment.hasDepth() || attachment.hasStencil())
{
attachment.description.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
//attachment.description.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
}
else
{
attachment.description.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
mAttachments.push_back(attachment);
}
///first renderpass creation
// Four attachments (3 color, 1 depth)
AttachmentCreateInfo attachmentInfo = {};
attachmentInfo.width = mWidth;
attachmentInfo.height = mHeight;
attachmentInfo.layerCount = 1;
attachmentInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
attachmentInfo.imageSampleCount = mRenderer->getMSAAsamples();
// Color attachments
// Attachment 0: (World space) Positions
//attachmentInfo.format = VK_FORMAT_R16G16B16A16_SFLOAT;
attachmentInfo.format = VK_FORMAT_R32G32B32A32_SFLOAT;
addAttachment(attachmentInfo);
// Attachment 1: (World space) Normals
attachmentInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
addAttachment(attachmentInfo);
// Attachment 2: Albedo (color)
attachmentInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
addAttachment(attachmentInfo);
//depth
attachmentInfo.format = mRenderer->getDepthFormat();
attachmentInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
addAttachment(attachmentInfo);
{
//create sampler
VkSamplerCreateInfo samplerInfo = VulkanInitializers::samplerCreateInfo();
samplerInfo.magFilter = VK_FILTER_NEAREST;
samplerInfo.minFilter = VK_FILTER_NEAREST;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.mipLodBias = 0.0f;
samplerInfo.maxAnisotropy = 1.0f;
samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = 1.0f;
samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
VERIFY(vkCreateSampler(device, &samplerInfo, nullptr, &sampler));
}
//create renderpass and frame buffer
{
std::vector<VkAttachmentDescription> attachmentDescriptions;
for (auto& attachment : mAttachments)
{
attachmentDescriptions.push_back(attachment.description);
};
// Collect attachment references
std::vector<VkAttachmentReference> colorReferences;
VkAttachmentReference depthReference = {};
bool hasDepth = false;
bool hasColor = false;
uint32_t attachmentIndex = 0;
for (auto& attachment : mAttachments)
{
if (attachment.isDepthStencil())
{
// Only one depth attachment allowed
assert(!hasDepth);
depthReference.attachment = attachmentIndex;
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
hasDepth = true;
}
else
{
colorReferences.push_back({ attachmentIndex, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
hasColor = true;
}
attachmentIndex++;
};
// Default render pass setup uses only one subpass
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
if (hasColor)
{
subpass.pColorAttachments = colorReferences.data();
subpass.colorAttachmentCount = static_cast<uint32_t>(colorReferences.size());
}
if (hasDepth)
{
subpass.pDepthStencilAttachment = &depthReference;
}
// Use subpass dependencies for attachment layout transitions
std::array<VkSubpassDependency, 2> dependencies;
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
dependencies[0].dstSubpass = 0;
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
dependencies[1].srcSubpass = 0;
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
// Create render pass
VkRenderPassCreateInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.pAttachments = attachmentDescriptions.data();
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachmentDescriptions.size());
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
renderPassInfo.pDependencies = dependencies.data();
VERIFY(vkCreateRenderPass(device, &renderPassInfo, nullptr, &mRenderPass));
std::vector<VkImageView> attachmentViews;
for (auto attachment : mAttachments)
{
attachmentViews.push_back(attachment.view);
}
// Find. max number of layers across attachments
uint32_t maxLayers = 0;
for (auto attachment : mAttachments)
{
if (attachment.subresourceRange.layerCount > maxLayers)
{
maxLayers = attachment.subresourceRange.layerCount;
}
}
VkFramebufferCreateInfo framebufferInfo = {};
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferInfo.renderPass = mRenderPass;
framebufferInfo.pAttachments = attachmentViews.data();
framebufferInfo.attachmentCount = static_cast<uint32_t>(attachmentViews.size());
framebufferInfo.width = mWidth;
framebufferInfo.height = mHeight;
framebufferInfo.layers = maxLayers;
mFrameBuffers.clear();
mFrameBuffers.resize(1);
VERIFY(vkCreateFramebuffer(device, &framebufferInfo, nullptr, &mFrameBuffers[0]));
}
///second renderpass
finalImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
std::array<VkAttachmentDescription, 3> attachments = {};
// Multisampled attachment that we render to
attachments[0].format = mRenderer->getSCImageFormat();// swapChain.colorFormat;
attachments[0].samples = mRenderer->getMSAAsamples();// settings.sampleCount;
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
// This is the frame buffer attachment to where the multisampled image
// will be resolved to and which will be presented to the swapchain
attachments[1].format = mRenderer->getSCImageFormat();;
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachments[1].finalLayout = finalImageLayout;
// Multisampled depth attachment we render to
attachments[2].format = mRenderer->getDepthFormat();// depthFormat;
attachments[2].samples = mRenderer->getMSAAsamples();// settings.sampleCount;
attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;// VK_ATTACHMENT_LOAD_OP_LOAD; //VK_ATTACHMENT_LOAD_OP_CLEAR; use depth from deferred renderer
attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_STORE;// VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachments[2].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentReference colorReference = {};
colorReference.attachment = 0;
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference depthReference = {};
depthReference.attachment = 2;
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
// Resolve attachment reference for the color attachment
VkAttachmentReference resolveReference = {};
resolveReference.attachment = 1;
resolveReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorReference;
// Pass our resolve attachments to the sub pass
subpass.pResolveAttachments = &resolveReference;
subpass.pDepthStencilAttachment = &depthReference;
std::vector<VkSubpassDependency> dependencies;
dependencies.resize(2);
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
dependencies[0].dstSubpass = 0;
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; // Both stages might have access the depth-buffer, so need both in src/dstStageMask;;
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
dependencies[1].srcSubpass = 0;
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
VkRenderPassCreateInfo renderPassCI = {};
renderPassCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassCI.attachmentCount = static_cast<uint32_t>(attachments.size());
renderPassCI.pAttachments = attachments.data();
renderPassCI.subpassCount = 1;
renderPassCI.pSubpasses = &subpass;
renderPassCI.dependencyCount = static_cast<uint32_t>(dependencies.size());
renderPassCI.pDependencies = dependencies.data();
VERIFY(vkCreateRenderPass(device, &renderPassCI, nullptr, &mRenderPass));
VkImageCreateInfo imageCI{};
imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageCI.imageType = VK_IMAGE_TYPE_2D;
imageCI.format = mRenderer->getSCImageFormat();// swapChain.colorFormat;
imageCI.extent.width = mWidth;// width;
imageCI.extent.height = mHeight;
imageCI.extent.depth = 1;
imageCI.mipLevels = 1;
imageCI.arrayLayers = 1;
imageCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
imageCI.samples = mRenderer->getMSAAsamples();// settings.sampleCount;
imageCI.usage = VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
imageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VERIFY(vkCreateImage(device, &imageCI, nullptr, &multisampleTarget.color.image));
VkMemoryRequirements memReqs;
vkGetImageMemoryRequirements(device, multisampleTarget.color.image, &memReqs);
VkMemoryAllocateInfo memAllocInfo{};
memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memAllocInfo.allocationSize = memReqs.size;
VkBool32 lazyMemTypePresent;
memAllocInfo.memoryTypeIndex = mRenderer->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT, &lazyMemTypePresent);
if (!lazyMemTypePresent) {
memAllocInfo.memoryTypeIndex = mRenderer->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
}
VERIFY(vkAllocateMemory(device, &memAllocInfo, nullptr, &multisampleTarget.color.memory));
vkBindImageMemory(device, multisampleTarget.color.image, multisampleTarget.color.memory, 0);
// Create image view for the MSAA target
VkImageViewCreateInfo imageViewCI{};
imageViewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
imageViewCI.image = multisampleTarget.color.image;
imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
imageViewCI.format = mRenderer->getSCImageFormat();// swapChain.colorFormat;
imageViewCI.components.r = VK_COMPONENT_SWIZZLE_R;
imageViewCI.components.g = VK_COMPONENT_SWIZZLE_G;
imageViewCI.components.b = VK_COMPONENT_SWIZZLE_B;
imageViewCI.components.a = VK_COMPONENT_SWIZZLE_A;
imageViewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageViewCI.subresourceRange.levelCount = 1;
imageViewCI.subresourceRange.layerCount = 1;
VERIFY(vkCreateImageView(device, &imageViewCI, nullptr, &multisampleTarget.color.view));
AttachmentCreateInfo attachmentInfo = {};
attachmentInfo.width = mWidth;
attachmentInfo.height = mHeight;
attachmentInfo.layerCount = 1;
attachmentInfo.imageSampleCount = mRenderer->getMSAAsamples();
attachmentInfo.format = mRenderer->getDepthFormat();
attachmentInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
addAttachment(attachmentInfo);
auto& depthAttachment = mAttachments.back();
multisampleTarget.depth.image = depthAttachment.image;
multisampleTarget.depth.view = depthAttachment.view;
multisampleTarget.depth.memory = depthAttachment.memory;
std::vector<VkImageView> attachments;
attachments.emplace_back(multisampleTarget.color.view);
attachments.emplace_back(color.view);
attachments.emplace_back(multisampleTarget.depth.view);
VkFramebufferCreateInfo frameBufferCI{};
frameBufferCI.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
frameBufferCI.pNext = NULL;
frameBufferCI.renderPass = mRenderPass;
frameBufferCI.attachmentCount = static_cast<uint32_t>(attachments.size());
frameBufferCI.pAttachments = attachments.data();
frameBufferCI.width = mWidth;
frameBufferCI.height = mHeight;
frameBufferCI.layers = 1;
mFrameBuffers.resize(1);
VERIFY(vkCreateFramebuffer(device, &frameBufferCI, nullptr, &mFrameBuffers[0]));
In renderDoc I could see the second renderpass depth attachment as 'undefined img'
I'm guessing that I'm using Load and StoreOps the right way and also created the subpass dependencies which takes care of transfering the depth values from first render pass to second. But!! it's not working. :( Apologies for my novice coding style. Most were from SaschaWillems examples.
In second pass, depth attachment description, it "loads" image in undefined state:
attachments[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
Based on your attachment creating code, I assume it should be VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
When resizing the images of the swapchain (because the window size changed) I am getting some white blinking. I don't really understand why is the source of this issue.
I have this issue only while using VK_PRESENT_MODE_FIFO_KHR present mode with my Intel(R) UHD Graphics 630 integrated GPU, I don't have this issue with the GeForce GTX 1050. I am find that having different behaviors depending on the GPU really curious with Vulkan.
Maybe the ideal solution for what I try to achieve is to have a swapchain that always do the size of the screen and blit only the visible part if it is possible to do it?
Here is my swapchain resize code (far from optimal as I redo some operations that can be avoided).
bool resize_swapchain(VK_Renderer* renderer, Window* window) {
assert(renderer);
VkResult res;
clear_swapchain(renderer);
// Build the swapchain
// Get the list of VkFormats that are supported:
get_enumeration(vkGetPhysicalDeviceSurfaceFormatsKHR,
VkSurfaceFormatKHR,
surface_formats,
"Failed to get physical device surface formats.\n",
"Found %d surface formats.\n",
renderer->physical_device,
renderer->surface);
// If the format list includes just one entry of VK_FORMAT_UNDEFINED,
// the surface has no preferred format. Otherwise, at least one
// supported format will be returned.
if (surface_formats.size() == 1 && surface_formats[0].format == VK_FORMAT_UNDEFINED) {
renderer->surface_format = VK_FORMAT_B8G8R8A8_UNORM;
} else {
renderer->surface_format = surface_formats[0].format;
}
VkSurfaceCapabilitiesKHR surface_capabilities;
res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(renderer->physical_device, renderer->surface, &surface_capabilities);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to get physical device surface capabilities.\n");
clear_swapchain(renderer);
return false;
}
get_enumeration(vkGetPhysicalDeviceSurfacePresentModesKHR,
VkPresentModeKHR,
present_modes,
"Failed to get physical device surface present modes.\n",
"Found %d present modes.\n",
renderer->physical_device,
renderer->surface);
// width and height are either both 0xFFFFFFFF, or both not 0xFFFFFFFF.
if (surface_capabilities.currentExtent.width == 0xFFFFFFFF) {
// If the surface size is undefined, the size is set to
// the size of the images requested.
renderer->swapchain_extent.width = window->size.x;
renderer->swapchain_extent.height = window->size.y;
if (renderer->swapchain_extent.width < surface_capabilities.minImageExtent.width) {
renderer->swapchain_extent.width = surface_capabilities.minImageExtent.width;
} else if (renderer->swapchain_extent.width > surface_capabilities.maxImageExtent.width) {
renderer->swapchain_extent.width = surface_capabilities.maxImageExtent.width;
}
if (renderer->swapchain_extent.height < surface_capabilities.minImageExtent.height) {
renderer->swapchain_extent.height = surface_capabilities.minImageExtent.height;
} else if (renderer->swapchain_extent.height > surface_capabilities.maxImageExtent.height) {
renderer->swapchain_extent.height = surface_capabilities.maxImageExtent.height;
}
} else {
// If the surface size is defined, the swap chain size must match
renderer->swapchain_extent = surface_capabilities.currentExtent;
}
// The FIFO present mode is guaranteed by the spec to be supported
#if defined(FL_PROFILING_MODE)
VkPresentModeKHR swapchain_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
#else
VkPresentModeKHR swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR;
#endif
// Determine the number of VkImage's to use in the swap chain.
// We need to acquire only 1 presentable image at at time.
// Asking for minImageCount images ensures that we can acquire
// 1 presentable image as long as we present it before attempting
// to acquire another.
uint32_t desired_number_of_swapchain_images = surface_capabilities.minImageCount;
VkSurfaceTransformFlagBitsKHR surface_transform;
if (surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
surface_transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
} else {
surface_transform = surface_capabilities.currentTransform;
}
// Find a supported composite alpha mode - one of these is guaranteed to be set
VkCompositeAlphaFlagBitsKHR composite_alpha_flag = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
// TODO change the order if we want to be able to blend the window of our application with the Windows Desktop
VkCompositeAlphaFlagBitsKHR composite_alpha_flags[4] = {
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
};
for (uint32_t i = 0; i < sizeof(composite_alpha_flags); i++) {
if (surface_capabilities.supportedCompositeAlpha & composite_alpha_flags[i]) {
composite_alpha_flag = composite_alpha_flags[i];
break;
}
}
VkSwapchainCreateInfoKHR swapchain_info = {};
swapchain_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchain_info.pNext = nullptr;
swapchain_info.surface = renderer->surface;
swapchain_info.minImageCount = desired_number_of_swapchain_images;
swapchain_info.imageFormat = renderer->surface_format;
swapchain_info.imageExtent.width = renderer->swapchain_extent.width;
swapchain_info.imageExtent.height = renderer->swapchain_extent.height;
swapchain_info.preTransform = surface_transform;
swapchain_info.compositeAlpha = composite_alpha_flag;
swapchain_info.imageArrayLayers = 1;
swapchain_info.presentMode = swapchain_present_mode;
swapchain_info.oldSwapchain = nullptr;
swapchain_info.clipped = true;
swapchain_info.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
swapchain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
swapchain_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapchain_info.queueFamilyIndexCount = 0;
swapchain_info.pQueueFamilyIndices = nullptr;
uint32_t queue_family_indices[2] = {(uint32_t)renderer->graphics_queue_family_index, (uint32_t)renderer->present_queue_family_index};
if (renderer->graphics_queue_family_index != renderer->present_queue_family_index) {
// If the graphics and present queues are from different queue families,
// we either have to explicitly transfer ownership of images between
// the queues, or we have to create the swapchain with imageSharingMode
// as VK_SHARING_MODE_CONCURRENT
swapchain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swapchain_info.queueFamilyIndexCount = 2;
swapchain_info.pQueueFamilyIndices = queue_family_indices;
// TODO #Speedup We may want optimize this by using VK_SHARING_MODE_EXCLUSIVE and be explicit about transfert ownership
}
res = vkCreateSwapchainKHR(renderer->device, &swapchain_info, nullptr, &renderer->swapchain);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create the swapchain.\n");
clear_swapchain(renderer);
return false;
}
log(globals.logger, Log_Level::verbose, "Swapchain created with size (%d, %d).\n",
swapchain_info.imageExtent.width,
swapchain_info.imageExtent.height);
get_enumeration(vkGetSwapchainImagesKHR,
VkImage,
swapchain_images,
"Failed to get swapchain images.\n",
"Found %d swapchain images.\n",
renderer->device,
renderer->swapchain);
renderer->swapchain_buffers.resize(swapchain_images.size());
for (uint32_t i = 0; i < swapchain_images.size(); i++) {
renderer->swapchain_buffers[i].image = swapchain_images[i];
}
for (uint32_t i = 0; i < swapchain_images.size(); i++) {
VkImageViewCreateInfo color_image_view = {};
color_image_view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
color_image_view.pNext = nullptr;
color_image_view.flags = 0;
color_image_view.image = renderer->swapchain_buffers[i].image;
color_image_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
color_image_view.format = renderer->surface_format;
color_image_view.components.r = VK_COMPONENT_SWIZZLE_R;
color_image_view.components.g = VK_COMPONENT_SWIZZLE_G;
color_image_view.components.b = VK_COMPONENT_SWIZZLE_B;
color_image_view.components.a = VK_COMPONENT_SWIZZLE_A;
color_image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
color_image_view.subresourceRange.baseMipLevel = 0;
color_image_view.subresourceRange.levelCount = 1;
color_image_view.subresourceRange.baseArrayLayer = 0;
color_image_view.subresourceRange.layerCount = 1;
res = vkCreateImageView(renderer->device, &color_image_view, nullptr, &renderer->swapchain_buffers[i].view);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create image view.\n");
clear_swapchain(renderer);
return false;
}
log(globals.logger, Log_Level::verbose, "Image view %d created.\n", i);
}
// Build the depth buffer
VkImageCreateInfo image_info = {};
const VkFormat depth_format = VK_FORMAT_D32_SFLOAT;
VkFormatProperties format_properties;
bool found_memory_type_index;
vkGetPhysicalDeviceFormatProperties(renderer->physical_device, depth_format, &format_properties);
if (format_properties.linearTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
image_info.tiling = VK_IMAGE_TILING_LINEAR;
} else if (format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
} else {
// #TODO choose an other format?
log(globals.logger, Log_Level::error, "VK_FORMAT_D32_SFLOAT Unsupported.\n");
clear_swapchain(renderer);
return false;
}
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.pNext = nullptr;
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.format = depth_format;
image_info.extent.width = renderer->swapchain_extent.width;
image_info.extent.height = renderer->swapchain_extent.height;
image_info.extent.depth = 1;
image_info.mipLevels = 1;
image_info.arrayLayers = 1;
image_info.samples = renderer->sample_count_flag;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
image_info.queueFamilyIndexCount = 0;
image_info.pQueueFamilyIndices = nullptr;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_info.flags = 0;
VkMemoryAllocateInfo memory_allocation_info = {};
memory_allocation_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_allocation_info.pNext = nullptr;
memory_allocation_info.allocationSize = 0;
memory_allocation_info.memoryTypeIndex = 0;
VkImageViewCreateInfo view_info = {};
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_info.pNext = nullptr;
view_info.image = nullptr;
view_info.format = depth_format;
view_info.components.r = VK_COMPONENT_SWIZZLE_R;
view_info.components.g = VK_COMPONENT_SWIZZLE_G;
view_info.components.b = VK_COMPONENT_SWIZZLE_B;
view_info.components.a = VK_COMPONENT_SWIZZLE_A;
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
view_info.subresourceRange.baseMipLevel = 0;
view_info.subresourceRange.levelCount = 1;
view_info.subresourceRange.baseArrayLayer = 0;
view_info.subresourceRange.layerCount = 1;
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.flags = 0;
VkMemoryRequirements memory_requirements;
renderer->depth_buffer.format = depth_format;
/* Create image */
res = vkCreateImage(renderer->device, &image_info, nullptr, &renderer->depth_buffer.image);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create the depth image.\n");
clear_swapchain(renderer);
return false;
}
vkGetImageMemoryRequirements(renderer->device, renderer->depth_buffer.image, &memory_requirements);
memory_allocation_info.allocationSize = memory_requirements.size;
/* Use the memory properties to determine the type of memory required */
found_memory_type_index = memory_type_from_properties(renderer, memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memory_allocation_info.memoryTypeIndex);
if (!found_memory_type_index) {
log(globals.logger, Log_Level::error, "Failed to find memory type to allocate the depth image.\n");
clear_swapchain(renderer);
return false;
}
/* Allocate memory */
res = vkAllocateMemory(renderer->device, &memory_allocation_info, nullptr, &renderer->depth_buffer.memory);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create memory for depth image.\n");
clear_swapchain(renderer);
return false;
}
/* Bind memory */
res = vkBindImageMemory(renderer->device, renderer->depth_buffer.image, renderer->depth_buffer.memory, 0);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to bind the depth image memory.\n");
clear_swapchain(renderer);
return false;
}
/* Create image view */
view_info.image = renderer->depth_buffer.image;
res = vkCreateImageView(renderer->device, &view_info, nullptr, &renderer->depth_buffer.view);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create the depth image view.\n");
clear_swapchain(renderer);
return false;
}
log(globals.logger, Log_Level::verbose, "Depth buffer created.\n");
for (size_t i = 0; i < renderer->scenes.size(); i++) {
swapchain_resized(renderer->scenes[i], renderer->swapchain_extent.width, renderer->swapchain_extent.height);
}
return true;
}
Edit: Maybe my issue is more related on how I submit rendered images to the swapchain or the image acquisition.
for (size_t i = 0; i < scene->meshes.size(); i++) {
draw_mesh(scene->meshes[i]);
}
// End the Render pass
vkCmdEndRenderPass(scene->renderer->graphical_command_buffer);
// End command buffer
{
res = vkEndCommandBuffer(scene->renderer->graphical_command_buffer);
}
// Execute queue command buffer
{
/* Queue the command buffer for execution */
const VkCommandBuffer command_buffers[] = {scene->renderer->graphical_command_buffer};
VkFenceCreateInfo fence_create_info;
VkFence draw_fence;
fence_create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_create_info.pNext = nullptr;
fence_create_info.flags = 0;
vkCreateFence(scene->renderer->device, &fence_create_info, nullptr, &draw_fence);
VkPipelineStageFlags pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submit_info[1] = {};
submit_info[0].pNext = nullptr;
submit_info[0].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info[0].waitSemaphoreCount = 1;
submit_info[0].pWaitSemaphores = &scene->image_acquired_semaphore;
submit_info[0].pWaitDstStageMask = &pipe_stage_flags;
submit_info[0].commandBufferCount = 1;
submit_info[0].pCommandBuffers = command_buffers;
submit_info[0].signalSemaphoreCount = 0;
submit_info[0].pSignalSemaphores = nullptr;
/* Queue the command buffer for execution */
res = vkQueueSubmit(scene->renderer->graphics_queue, 1, submit_info, draw_fence);
assert(res == VK_SUCCESS);
/* Now present the image in the window */
VkPresentInfoKHR present;
present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present.pNext = nullptr;
present.swapchainCount = 1;
present.pSwapchains = &scene->renderer->swapchain;
present.pImageIndices = &scene->current_buffer;
present.pWaitSemaphores = nullptr;
present.waitSemaphoreCount = 0;
present.pResults = nullptr;
/* Make sure command buffer is finished before presenting */
do {
res = vkWaitForFences(scene->renderer->device, 1, &draw_fence, VK_TRUE, scene->renderer->draw_fence_timeout_us);
} while (res == VK_TIMEOUT);
assert(res == VK_SUCCESS);
res = vkQueuePresentKHR(scene->renderer->present_queue, &present);
assert(res == VK_SUCCESS);
vkDestroyFence(scene->renderer->device, draw_fence, nullptr);
}
On vulkan-tutorial.com it is write that we should also recreate the command buffers with the swapchain (https://vulkan-tutorial.com/Drawing_a_triangle/Swap_chain_recreation), is it really mandatory?
I'm the guy from that reddit thread. I'm not sure 100% if we have the exact same issue, but I can explain what I was dealing with and how I worked around it.
So the issue here has several layers. The first one is that window resizing on Windows blocks the message queue because it needs to capture all the input events for itself. So to work around this you need to make your window update asynchronous, for example through threads.
Now your rendering and window resized work asynchronously, which is great, until someone resizes the windows while you are halfway through rendering the new frame. This IMMEDIATELY causes the swapchain to be VK_ERROR_OUT_OF_DATE_KHR, making you unable to present your render result to the screen. This can cause a variety of different artifacts on the surface, depening on GPU vendor, driver version, and even between different GPUs from the same vendor. This is quite literally undefined behavior. But flickering is definitely one of the common results, where it simply doesn't show anything on the surface until a new successful suspend. So far I have not found a single vendor that supports VK_SUBOPTIMAL_KHR to allow you to keep rendering.
A naive solution would be to give the window full control over the framerate instead, but would give very poor and inconsistent frame timings, especially when going over 60hz. You want the rendering to run as fast as it can, with as little latency as possible.
So before I go into the solution, lets summarize the requirements:
The application (including rendering) doesn't freeze when resizing.
The window does not resize between Acquire and Present
The frame timings are not controlled by the window message queue*
* When not resizing
You might have noticed the asterisk on the last requirement. This is because we will have to make a small compromise. The idea is that we only let the window take control over the frame timings when it is resizing. Outside of that we can draw as fast as possible, since nothing else can invalidate the swapchain in between.
To do this I used Fibers. You can think of fibers as a stack without a thread. You can then jump from the fiber to a different fiber and back. Remember that the message queue (specifically GetMessage/PeekMessage calls) doesn't return when resizing? Well, you can jump out of that loop and back in using fibers! Combined with a timer that causes the switch, we can synchronously update the window as well as render frames. Here is a sample from my code:
LRESULT Window::Impl::WndProc(HWND a_HWND, UINT a_Message, WPARAM a_WParam, LPARAM a_LParam)
{
switch (a_Message)
{
case WM_ENTERSIZEMOVE:
SetTimer(a_HWND, 0, 1, NULL);
break;
case WM_EXITSIZEMOVE:
KillTimer(a_HWND, 0);
break;
case WM_TIMER:
m_MainFiber.Switch();
break;
case WM_MOVE:
if (m_MoveCallback)
{
m_MoveCallback(m_This, Vector2i(static_cast<int16_t>(LOWORD(a_LParam)), static_cast<int16_t>(HIWORD(a_LParam))));
}
break;
case WM_SIZE:
switch (a_WParam)
{
case SIZE_MINIMIZED:
if (m_MinimizeCallback)
{
m_MinimizeCallback(m_This);
}
break;
case SIZE_MAXIMIZED:
if (m_MaximizeCallback)
{
m_MaximizeCallback(m_This);
}
break;
}
if (m_ResizeCallback)
{
m_ResizeCallback(m_This, Vector2i(static_cast<int16_t>(LOWORD(a_LParam)), static_cast<int16_t>(HIWORD(a_LParam))));
}
break;
case WM_CLOSE:
if (m_CloseCallback)
{
m_CloseCallback(m_This);
}
break;
}
if (a_Message == WM_CLOSE)
{
return 0;
}
return DefWindowProcW(a_HWND, a_Message, a_WParam, a_LParam);
}
As you can see, it is actually pretty simple. Start a timer when resize starts, stop it when resize ends, and switch back to the original fiber when it triggers.
Here is the fiber callback itself:
void Window::Impl::FiberCallback()
{
MSG msg;
for (;;)
{
if (PeekMessageW(&msg, m_Window, 0, 0, PM_REMOVE) != 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
else
{
m_MainFiber.Switch();
}
}
}
And then the actual polling is as simple as this:
void Window::PollEvents()
{
m_Impl->m_MessageFiber.Switch();
}
This should make PollEvents always return immediately when not resizing, and after the timer expires when you are resizing. It also completely avoids threading since it all runs on the same thread, it just switches between stacks.
Leave a comment if something is unclear, and I hope it solves your problem.