I've been working with Vulkan for the past couple weeks and I've run into a problem that has only been happening on AMD cards. Specifically the AMD 7970M. I've ran my project on GTX 700 and 900 series cards with no problem. I've even ran on Windows an Linux (Steam OS) with Nvidia cards without a hitch. The problem only shows up on AMD cards and only with my project; all the samples and projects from Sascha Willems run no problem.
Right now I am drawing a textured Raptor model and spinning it in place. I render that off to a texture and then apply that texture to a fullscreen triangle; basic offscreen rendering. However the depth doesn't seem to clear correctly on my 7970M. Instead I get this weird artifacting like the depth isn't being cleared properly:
Of course I tried digging into this with RenderDoc and the depth is totally wrong. Both the Raptor and the Fullscreen Triangle its drawn onto are just a mess:
I've tried comparing my code to the Offscreen example from Sascha Willems and I appear do be doing almost everything the same way. I thought maybe something would be wrong with the way I created my depth but it seems fine in comparison to all the examples I've seen.
Here are some debug views of where I am creating the depth image and view:
Here's the whole method:
bool VKRenderTarget::setupFramebuffer(VKRenderer* renderer)
{
VkDevice device = renderer->GetVKDevice();
VkCommandBuffer setupCommand;
m_colorFormat = renderer->GetPreferredImageFormat();
m_depthFormat = renderer->GetPreferredDepthFormat();
renderer->CreateSetupCommandBuffer();
setupCommand = renderer->GetSetupCommandBuffer();
VkResult err;
//Color attachment
VkImageCreateInfo imageInfo = {};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.pNext = nullptr;
imageInfo.format = m_colorFormat;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.width = m_width;
imageInfo.extent.height = m_height;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
imageInfo.flags = 0;
VkMemoryAllocateInfo memAllocInfo = {};
memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
VkMemoryRequirements memReqs;
err = vkCreateImage(device, &imageInfo, nullptr, &m_color.image);
assert(!err);
if (err != VK_SUCCESS)
{
#ifdef _DEBUG
Core::DebugPrintF("VKRenderTarget::VPrepare(): Error creating color image!\n");
#endif
return false;
}
vkGetImageMemoryRequirements(device, m_color.image, &memReqs);
memAllocInfo.allocationSize = memReqs.size;
renderer->MemoryTypeFromProperties(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memAllocInfo.memoryTypeIndex);
err = vkAllocateMemory(device, &memAllocInfo, nullptr, &m_color.memory);
assert(!err);
if (err != VK_SUCCESS)
{
#ifdef _DEBUG
Core::DebugPrintF("VKRenderTarget::VPrepare(): Error allocating color image memory!\n");
#endif
return false;
}
err = vkBindImageMemory(device, m_color.image, m_color.memory, 0);
if (err != VK_SUCCESS)
{
#ifdef _DEBUG
Core::DebugPrintF("VKRenderTarget::VPrepare(): Error binding color image memory!\n");
#endif
return false;
}
renderer->SetImageLayout(setupCommand, m_color.image, VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
VkImageViewCreateInfo viewInfo = {};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.pNext = nullptr;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = m_colorFormat;
viewInfo.flags = 0;
viewInfo.subresourceRange = {};
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
viewInfo.image = m_color.image;
err = vkCreateImageView(device, &viewInfo, nullptr, &m_color.view);
if (err != VK_SUCCESS)
{
#ifdef _DEBUG
Core::DebugPrintF("VKRenderTarget::VPrepare(): Error creating color image view!\n");
#endif
return false;
}
//We can reuse the same info structs to build the depth image
imageInfo.format = m_depthFormat;
imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
err = vkCreateImage(device, &imageInfo, nullptr, &(m_depth.image));
assert(!err);
if (err != VK_SUCCESS)
{
#ifdef _DEBUG
Core::DebugPrintF("VKRenderTarget::VPrepare(): Error creating depth image!\n");
#endif
return false;
}
viewInfo.format = m_depthFormat;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
vkGetImageMemoryRequirements(device, m_depth.image, &memReqs);
memAllocInfo.allocationSize = memReqs.size;
renderer->MemoryTypeFromProperties(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memAllocInfo.memoryTypeIndex);
err = vkAllocateMemory(device, &memAllocInfo, nullptr, &m_depth.memory);
assert(!err);
if (err != VK_SUCCESS)
{
#ifdef _DEBUG
Core::DebugPrintF("VKRenderTarget::VPrepare(): Error allocating depth image memory!\n");
#endif
return false;
}
err = vkBindImageMemory(device, m_depth.image, m_depth.memory, 0);
if (err != VK_SUCCESS)
{
#ifdef _DEBUG
Core::DebugPrintF("VKRenderTarget::VPrepare(): Error binding depth image memory!\n");
#endif
return false;
}
renderer->SetImageLayout(setupCommand, m_depth.image,
VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
viewInfo.image = m_depth.image;
err = vkCreateImageView(device, &viewInfo, nullptr, &m_depth.view);
if (err != VK_SUCCESS)
{
#ifdef _DEBUG
Core::DebugPrintF("VKRenderTarget::VPrepare(): Error creating depth image view!\n");
#endif
return false;
}
renderer->FlushSetupCommandBuffer();
//Finally create internal framebuffer
VkImageView attachments[2];
attachments[0] = m_color.view;
attachments[1] = m_depth.view;
VkFramebufferCreateInfo framebufferInfo = {};
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferInfo.pNext = nullptr;
framebufferInfo.flags = 0;
framebufferInfo.renderPass = *((VKRenderPass*)m_renderPass)->GetVkRenderPass();
framebufferInfo.attachmentCount = 2;
framebufferInfo.pAttachments = attachments;
framebufferInfo.width = m_width;
framebufferInfo.height = m_height;
framebufferInfo.layers = 1;
err = vkCreateFramebuffer(device, &framebufferInfo, nullptr, &m_framebuffer);
if (err != VK_SUCCESS)
{
#ifdef _DEBUG
Core::DebugPrintF("VKRenderTarget::VPrepare(): Error creating framebuffer!\n");
#endif
return false;
}
return true;
}
If anyone wants more info on the code feel free to ask and I will provide it. There's a LOT of lines of code for this project so I don't want everyone to have to wade through it all. If you'd like to though all the code can be found at http://github.com/thirddegree/HatchitGraphics/tree/dev
Edit: After a bit more poking around I've found that even the color doesn't really clear properly. RenderDoc shows that each frame only renders the cutout of the raptor and doesn't clear the rest of the frame. Is this a driver problem?
Edit: Some more info. I've found that if I draw NOTHING, just begin and end a render pass not even drawing my fullscreen triangle, the screen will clear. However if I draw just the triangle, the depth is wrong (even if I don't blit anything from offscreen or apply any sort of texture).
Edit: More specifically the color will clear but the depth does not. If I don't draw anything the depth will stay black; all 0s. Why the fullscreen triangle causes the weird static of depth I am not sure.
This is exactly what happened to me when I started to get my Vulkan examples work on AMD hardware:
Their GPUs rely heavily on correct image transitions (which are mostly ignored by e.g. NVIDIA) and I think the corruption you see in your screenshots is the result of a missing pre-present barrier.
The pre-present barrier (see here) transforms the image layout of your color attachment into a presentation format for passing presenting it to the swap chain.
This has to be done after you have finished rendering to your color attachment to make sure that the attachment is completed before presenting it.
You can see an example of this in the draw routine of my examples.
On rendering the next frame you need to transform the color attachment's image format back in order to be able to render to it again.
To sum it up:
Before rendering to your color attachment transition your image from VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
to VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
(aka "post present")
Do your rendering
Transition your color attachment image from VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
and present that to the swap chain
Thanks to Sascha and some extra errors that popped up with the new 1.0.5 LunarG SDK I've managed to fix the problem. The commit with the fixing changes (and a couple other little things) can be found here: https://github.com/thirddegree/HatchitGraphics/commit/515d0303f45a8e9c00f67a74c824530ea37b687a
It was a combination of a few things:
I needed to set the depth image on the framebuffer attachment of the swapchain to VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT
rather than just VK_IMAGE_ASPECT_DEPTH_BIT
For pretty much every image memory barrier I forgot to specifiy the baseArrayLayer
of the subresourceRange
. This did not produce an error until version 1.0.5.
Another error that didn't pop up until 1.0.5 that might help you track a similar bug down and affected my texture generation was that before I mapped device memory for a texture to host memory I needed to transition it from VK_IMAGE_LAYOUT_UNDEFINED
to VK_IMAGE_LAYOUT_GENERAL
, submit that command, map the memory and then transition it from GENERAL to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
(don't forget to submit that command too). Again this is only for textures that you want to sample but I guess the moral here is "actually submit your image transitions"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With