Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vulkan failing to clear depth

Tags:

c++

3d

vulkan

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:

Bad Raptor

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:

Bad Depth

Bad Tri Depth

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:

image infoimage view info

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.

like image 726
Honeybunch Avatar asked Mar 14 '16 18:03

Honeybunch


2 Answers

This is exactly what happened to me when I started to get my Vulkan examples work on AMD hardware:

enter image description here

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

like image 167
Sascha Willems Avatar answered Nov 01 '22 21:11

Sascha Willems


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"

like image 45
Honeybunch Avatar answered Nov 01 '22 21:11

Honeybunch