Why would vkCreateSwapchainKHR result in an access violation at 0?
Asked Answered
M

3

11

I'm trying to learn Vulkan by following the great tutorials from vulkan-tutorial.com but I'm having some trouble at the point where I must create the swap chain. As stated in the title, the vkCreateSwapchainKHR creates the following error: Access violation executing location 0x0000000000000000.

The tutorial suggest this might be a conflict with the steam overlay. This is not the case for me as copying the whole code from the tutorial works.

I'm trying to figure out what went wrong with my code and to learn how to debug such issues as I will not have a reference code in the future. The incriminated line looks this:

if (vkCreateSwapchainKHR(device, &swapChainCreateInfo, nullptr, &swapChain) != VK_SUCCESS) {
    throw std::runtime_error("Could not create swap chain");
}

I setup a breakpoint at this line to compare the values of the arguments in my code with the values from the reference code. As far as I can tell, there is no difference. (The adresses of course are different)

Where should I look for a problem in my code? The variable swapChain is a NULL as expected. A wrongly formed swapChainCreateInfo should not make vkCreateSwapchainKHR crash. It would merely make it return something that is not VK_SUCCESS. And device was created without problem:

if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
    throw std::runtime_error("Failed to create logical device");
}

EDIT - I am using the validation layer VK_LAYER_LUNARG_standard_validation and my createInfo setup is the following.

// Useful functions and structures
VkPhysicalDevice physicalDevice;
VkSurfaceKHR surface;
VkSwapchainKHR swapChain;

struct QueueFamilyIndices {
    std::optional<uint32_t> graphicsFamily;
    std::optional<uint32_t> presentationFamily;
    bool isComplete() {
        return graphicsFamily.has_value() && presentationFamily.has_value();
    }
};

struct SwapChainSupportDetails {
    VkSurfaceCapabilitiesKHR surfaceCapabilities;
    std::vector<VkSurfaceFormatKHR> formats;
    std::vector<VkPresentModeKHR> presentModes;
};

SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice physicalDevice) {
    SwapChainSupportDetails swapChainSupportDetails;

    vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &swapChainSupportDetails.surfaceCapabilities);

    uint32_t formatCount = 0;
    vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, nullptr);
    if (formatCount != 0) {
        swapChainSupportDetails.formats.resize(formatCount);
        vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, swapChainSupportDetails.formats.data());
    }

    uint32_t presentModeCount = 0;
    vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, nullptr);
    if (presentModeCount != 0) {
        swapChainSupportDetails.presentModes.resize(presentModeCount);
        vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, swapChainSupportDetails.presentModes.data());
    }

    return swapChainSupportDetails;
}

VkSurfaceFormatKHR chooseSwapChainSurfaceFormat(const std::vector<VkSurfaceFormatKHR> & availableFormats) {
    if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) {
        return { VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
    }

    for (const auto & availableFormat : availableFormats) {
        if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
            return availableFormat;
        }
    }

    return availableFormats[0];
}

VkPresentModeKHR chooseSwapChainPresentMode(const std::vector<VkPresentModeKHR> & availablePresentModes) {

    VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR;
    for (const auto & availablePresentMode : availablePresentModes) {
        if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
            return availablePresentMode;
        }
        else if (availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
            bestMode = availablePresentMode;
        }
    }

    return bestMode;
}

VkExtent2D chooseSwapChainExtent2D(const VkSurfaceCapabilitiesKHR & surfaceCapabilities) {
    if (surfaceCapabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
        return surfaceCapabilities.currentExtent;
    }
    else {
        VkExtent2D actualExtent = { WIDTH, HEIGHT };
        actualExtent.width = std::max(std::min(surfaceCapabilities.maxImageExtent.width, actualExtent.width), surfaceCapabilities.minImageExtent.width);
        actualExtent.height = std::max(std::min(surfaceCapabilities.maxImageExtent.height, actualExtent.height), surfaceCapabilities.minImageExtent.height);
        return actualExtent;
    }
}

// Swap Chain creation code

SwapChainSupportDetails swapChainSupportDetails = querySwapChainSupport(physicalDevice);

VkSurfaceFormatKHR surfaceFormat = chooseSwapChainSurfaceFormat(swapChainSupportDetails.formats);
VkPresentModeKHR presentMode = chooseSwapChainPresentMode(swapChainSupportDetails.presentModes);
VkExtent2D extent = chooseSwapChainExtent2D(swapChainSupportDetails.surfaceCapabilities);
uint32_t imageCount = swapChainSupportDetails.surfaceCapabilities.minImageCount + 1;
if (swapChainSupportDetails.surfaceCapabilities.maxImageCount > 0 && imageCount > swapChainSupportDetails.surfaceCapabilities.maxImageCount) {
    imageCount = swapChainSupportDetails.surfaceCapabilities.minImageCount;
}

VkSwapchainCreateInfoKHR swapChainCreateInfo = {};
swapChainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapChainCreateInfo.surface = surface;
swapChainCreateInfo.minImageCount = imageCount;
swapChainCreateInfo.imageFormat = surfaceFormat.format;
swapChainCreateInfo.imageColorSpace = surfaceFormat.colorSpace;
swapChainCreateInfo.imageExtent = extent;
swapChainCreateInfo.imageArrayLayers = 1;
swapChainCreateInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

QueueFamilyIndices familyIndices = findQueueFamilies(physicalDevice);
uint32_t queueFamilyIndices[] = { familyIndices.graphicsFamily.value(), familyIndices.presentationFamily.value() };
if (familyIndices.graphicsFamily != familyIndices.presentationFamily) {
    swapChainCreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
    swapChainCreateInfo.queueFamilyIndexCount = 2;
    swapChainCreateInfo.pQueueFamilyIndices = queueFamilyIndices;
}
else {
    swapChainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
    swapChainCreateInfo.queueFamilyIndexCount = 0;
    swapChainCreateInfo.pQueueFamilyIndices = nullptr;
}

swapChainCreateInfo.preTransform = swapChainSupportDetails.surfaceCapabilities.currentTransform;
swapChainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
swapChainCreateInfo.presentMode = presentMode;
swapChainCreateInfo.clipped = VK_TRUE;
swapChainCreateInfo.oldSwapchain = VK_NULL_HANDLE;

if (vkCreateSwapchainKHR(device, &swapChainCreateInfo, nullptr, &swapChain) != VK_SUCCESS) {
    throw std::runtime_error("Could not create swap chain");
}

I get the resulting structure: enter image description here

Mooneyham answered 12/3, 2019 at 22:10 Comment(4)
Have you verified the vkCreateSwapchainKHR function-pointer was successfully loaded by your loader and not nullptr?Bracing
No I don't check that in the code. I just did using my breakpoint though and the address is not nullptr. Thanks for the answer!Ciccia
"A wrongly formed swapChainCreateInfo should not make vkCreateSwapchainKHR crash. It would merely make it return something that is not VK_SUCCESS." This is not true. Most of the time in Vulkan, if you give it invalid input (the spec defines what is valid), behavior is undefined, and undefined behavior can include crashing. You should be using validation layers so you get a message explaining what was wrong before you crash. If you post the VkSwapchainCreateInfo contents here we may be able to spot the problem for you.Walworth
Thanks for the correction about the swapChainCreateInfo! I am using the VK_LAYER_LUNARG_standard_validation validation layer but I'm not sure I understand how it operates yet. Also, I updated the post with my VkSwapchainCreateInfo setup. Let me know if you need more and thank you :)Ciccia
M
12

Well, when creating the logical device one needs to set enabledExtensionCount to the actual number of required extensions and not 0 if one expects extensions to work. In my case, it was a simple edit failure. Here is the gem in my code:

createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();

createInfo.enabledExtensionCount = 0;

I figured it out by replacing every function from my code by the ones from the reference code until it worked. I'm a bit disappointed that the validation layers didn't catch this. Did I set them wrong? Is this something they should be catching?

EDIT: As pointed out by LIANG LIU, here is the initialization for deviceExtensions:

const std::vector<const char*> deviceExtensions = {
    VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
Mooneyham answered 13/3, 2019 at 22:54 Comment(6)
vkCreateSwapchainKHR() function is introduced by the extension. If the extension is not enabled during logical device creation, You cannot use this function. So this is something that definitely should be caught by validation layers. And I'm pretty sure they do catch problems like this. The easiest way to check if validation layers work correctly is to enable "api dump" layer and see if Vulkan function calls are properly listed.Huguenot
I didn't know about that one, thank you! As Cerulean Quasar pointed out in his answer bellow, the validation layer requires an extension itself. So by setting the count to 0 I actually disabled the validation layer.Ciccia
That's why I'm not enabling validation layers through code but through environment variable which doesn't require code changes. Probably not all validation layers can be enabled this way and this solution is not as flexible, but it is enough in most cases and errors like these are easily caught.Huguenot
Plot twist, VK_EXT_DEBUG_UTILS_EXTENSION_NAME is an instance level extension, not a logical device extension. That means it was enabled for the instance and the validation layer should have caught my error :/. I tried enabling the VK_LAYER_LUNARG_api_dump validation layer and I do get a lot of info printed to the console. That must mean my validation layers work. They just don't seem to catch this specific error!Ciccia
At least 8 people seem to have had this exact problem (as of now), so it seems like something they should look into catching.Plowshare
Thanks for posting this issue, and the solution. I just had exactly this same issue, with precisely the same root cause. Validation layers did not yield anything useful.Popeyed
S
5

Enable VK_KHR_SWAPCHAIN_EXTENSION_NAME when creating VkDevice

void VKRenderer::createVkLogicalDevice()
{
    // device extensions
    vector<const char*>::type deviceExtensionNames = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };

    // priorities
    float queuePrioritys[2] = { 1.f, 1.f};

    // graphics queue
    VkDeviceQueueCreateInfo queueCreateInfos;
    queueCreateInfos.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    queueCreateInfos.pNext = nullptr;
    queueCreateInfos.queueFamilyIndex = getGraphicsQueueFamilyIndex();
    queueCreateInfos.queueCount = 1;
    queueCreateInfos.pQueuePriorities = &queuePrioritys[0];

    // device features
    VkPhysicalDeviceFeatures deviceFeatures = {};

    VkDeviceCreateInfo createInfo = {};
    createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
    createInfo.pNext = nullptr;
    createInfo.pQueueCreateInfos = &queueCreateInfos;
    createInfo.queueCreateInfoCount = 1;
    createInfo.pEnabledFeatures = &deviceFeatures;
    createInfo.enabledExtensionCount = deviceExtensionNames.size();
    createInfo.ppEnabledExtensionNames = deviceExtensionNames.data();

    // create logical device and retrieve graphics queue
    if (VK_SUCCESS == vkCreateDevice(m_vkPhysicalDevice, &createInfo, nullptr, &m_vkDevice))
    {
        vkGetDeviceQueue(m_vkDevice, getGraphicsQueueFamilyIndex(), 0, &m_vkGraphicsQueue);
        vkGetDeviceQueue(m_vkDevice, getPresentQueueFamilyIndex(), 0, &m_vkPresentQueue);
    }
    else
    {
        EchoLogError("Failed to create vulkan logical device!");
    }
}
Slue answered 6/8, 2019 at 14:32 Comment(3)
Yep, as you can see in my answer above I had done that. Unfortunately, I had also set the extension count to 0 which disabled this extension. Someone later told me that this should be caught by validation layers but no one added that specific check.Ciccia
I know, I answer this question, just because I can't find the value of the "ppEnabledExtensionNames" from your answer.Slue
Oh right, good call! I also edited my answer and gave you credit.Ciccia
P
0

It looks like you are calling vkCreateDevice at the end of your code segment for creating the swapchain and passing in the VkSwapchainCreateInfo into it. Perhaps you want to call vkCreateSwapchainKHR instead, like:

if (vkCreateSwapchainKHR(device, &swapChainCreateInfo, nullptr, &swapChain) !=
    VK_SUCCESS) {
    throw std::runtime_error("failed to create swap chain");
}

If you are actually calling vkCreateSwapchainKHR, could you edit your question to indicate this?

Paid answered 13/3, 2019 at 5:50 Comment(9)
Yes I'm actually calling vkCreateSwapchainKHR, sorry about that. I edited the code in the question :)Ciccia
As @JesseHall suggested, it is best to look at the output from the validation layers to do debugging. Could you post the message(s) you get from the validation layer?Paid
I don't get any messages, just the crash. I know the validation layers work though. Before trying to setup the swap chain I had a functioning program. In this program I tried 'forgetting' to delete the device and then I got a message from the validation layers stating there was a memory leak. Does that make sense or am I missing something?Ciccia
is it possible that the error message gets caught in an output buffer and never gets printed because the program crashes? Perhaps you should put a break in your validation layers debug callback to make sure this is not the case?Paid
As you can see I solved my problem but I will definitely try this!Ciccia
Nope, the validation layer is not triggered :/. The only messages I see are "Added messenger". I tried re-introducing my bug in the reference code and I'm getting the same result. A crash without any error message, that's not cool :/.Ciccia
The validation layers require VK_EXT_DEBUG_UTILS_EXTENSION_NAME extension in order to be able to report back the message via the callback you register. So, since you requested no extensions, perhaps the messages were not going to your callback?Paid
Yes, definitely! I tried adding only the extension VK_EXT_DEBUG_UTILS_EXTENSION_NAME and now my surface creation fails. I messed up the one thing that really mattered haha! Thank you for your help :)Ciccia
Plot twist, VK_EXT_DEBUG_UTILS_EXTENSION_NAME is an instance level extension, not a logical device extension. That means it was enabled for the instance and the validation layer should have caught my error :/.Ciccia

© 2022 - 2024 — McMap. All rights reserved.