c++vulkan

In my Vulkan Program, glfw opens window, but crashes on vkCreateSwapchainKHR what is wrong?


On PopOS 22, I can successfully build Vulkan demos and compile. So the libraries work. I wrote a compact demo, which is crashing on vkCreateSwapchainKHR. In the call, the device appears to be correct (not null anyway) and yet it crashes in the call.

I can't debug it beyond looking at the device structure. It's not null, when I look at *device it is opaque.

in gdb code is crashing on line 142 of vulkan_core.cpp

/*
    Vulkan core utilities
    

    Author: 
    Date: 2025-03-11
*/

#include <vulkan/vulkan.h>
#include <vector>
#include "vulkan_core.hh"
#include <fstream>
using namespace std;

language current_lang = language::en;

class vulkan_core {
private:
    VkInstance instance;
    VkPhysicalDevice physical_device;
    VkDevice device;
    VkSurfaceKHR surface;
    int32_t graphics_queue_family;
    VkQueue graphics_queue;
    VkSwapchainKHR swapchain;
    VkFormat swapchain_format;
    VkExtent2D swapchain_extent;
    GLFWwindow* window;
    
    void create_instance() {
        VkApplicationInfo app_info = {};
        app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
        app_info.pApplicationName = settings.title.c_str();
        app_info.engineVersion = 1;
        app_info.apiVersion = VK_API_VERSION_1_0;

        VkInstanceCreateInfo inst_info = {};
        inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        inst_info.pApplicationInfo = &app_info;

        VkResult res = vkCreateInstance(&inst_info, nullptr, &instance);
        vkcheck_msg(res, error_type::initialization, "vkCreateInstance");
    }

    void physical_device_init() {
        uint32_t gpu_count = 0;
        vkcheck_msg(vkEnumeratePhysicalDevices(instance, &gpu_count, nullptr), 
                error_type::initialization, "vkEnumeratePhysicalDevices");
        
        std::vector<VkPhysicalDevice> physical_devices(gpu_count);
        vkcheck_msg(vkEnumeratePhysicalDevices(instance, &gpu_count, physical_devices.data()),
                error_type::initialization, "vkEnumeratePhysicalDevices");
        
        if (gpu_count > 0) {
            physical_device = physical_devices[0];
            
            uint32_t queue_family_count = 0;
            vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr);
            std::vector<VkQueueFamilyProperties> queue_families(queue_family_count);
            vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_families.data());

            graphics_queue_family = -1;
            for (uint32_t i = 0; i < queue_family_count; i++) {
                if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
                    graphics_queue_family = i;
                    break;
                }
            }
            if (graphics_queue_family == -1) {
                throw ex(__FILE__, __LINE__, error_type::initialization, "No graphics queue found");
            }
        } else {
            throw ex(__FILE__, __LINE__, error_type::initialization, "No physical devices found");
        }
    }

    void create_logical_device() {
        float queue_priority = 1.0f;
        VkDeviceQueueCreateInfo queue_create_info = {};
        queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
        queue_create_info.queueFamilyIndex = graphics_queue_family;
        queue_create_info.queueCount = 1;
        queue_create_info.pQueuePriorities = &queue_priority;

        // Add required device extensions before device_create_info
        const std::vector<const char*> device_extensions = {
            VK_KHR_SWAPCHAIN_EXTENSION_NAME
        };

        VkDeviceCreateInfo device_create_info = {};
        device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
        device_create_info.queueCreateInfoCount = 1;
        device_create_info.pQueueCreateInfos = &queue_create_info;
        device_create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
        device_create_info.ppEnabledExtensionNames = device_extensions.data();

        vkcheck_msg(vkCreateDevice(physical_device, &device_create_info, nullptr, &device),
                error_type::initialization, "vkCreateDevice");

        vkGetDeviceQueue(device, graphics_queue_family, 0, &graphics_queue);
    }

    void create_swapchain() {
        VkSurfaceCapabilitiesKHR capabilities;
        vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &capabilities);

        uint32_t format_count;
        vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, nullptr);
        std::vector<VkSurfaceFormatKHR> formats(format_count);
        vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, formats.data());
        
        VkSurfaceFormatKHR surface_format = formats[0];
        for (const auto& format : formats) {
            if (format.format == VK_FORMAT_B8G8R8A8_SRGB && 
                format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
                surface_format = format;
                break;
            }
        }

        swapchain_format = surface_format.format;
        swapchain_extent = capabilities.currentExtent;

        VkSwapchainCreateInfoKHR create_info = {};
        create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
        create_info.surface = surface;
        create_info.minImageCount = capabilities.minImageCount + 1;
        create_info.imageFormat = surface_format.format;
        create_info.imageColorSpace = surface_format.colorSpace;
        create_info.imageExtent = capabilities.currentExtent;
        create_info.imageArrayLayers = 1;
        create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
        create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
        create_info.preTransform = capabilities.currentTransform;
        create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
        create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR;
        create_info.clipped = VK_TRUE;

        if (surface == VK_NULL_HANDLE) {
            throw ex(__FILE__, __LINE__, error_type::initialization, "Device handle is null before swapchain creation");
        }
        vkcheck_msg(vkCreateSwapchainKHR(device, &create_info, nullptr, &swapchain),
                error_type::initialization, "vkCreateSwapchainKHR");
    }

    void create_render_pass() {
    }

    void create_framebuffers() {
    }

    void create_command_pool() {
    }

    void create_vertex_buffer() {
    }

    void create_command_buffers() {
    }

    void create_sync_objects() {
    }

public:
    vulkan_core(GLFWwindow* win) {
        create_instance();
        physical_device_init();
        create_logical_device();
        create_swapchain();
        create_render_pass();
        create_framebuffers();
        create_command_pool();
        create_vertex_buffer();
        create_command_buffers();
        create_sync_objects();
    }

    ~vulkan_core() {
        if (instance != VK_NULL_HANDLE) {
            vkDestroyInstance(instance, nullptr);
        }
        // Will add other cleanup as we implement each component
    }
};


void test_all_exceptions(ofstream& log) {
#if MULTI_LANGUAGE
    // Cast to underlying type to allow iteration
    for (int lang = static_cast<int>(language::en); 
         lang <= static_cast<int>(language::tr); 
         lang++) {
        set_language(static_cast<language>(lang));
        for (int err = static_cast<int>(error_type::out_of_memory); 
             err <= static_cast<int>(error_type::file_not_a_regular_file); 
             err++) {
            try {
                throw ex(__FILE__, __LINE__, static_cast<error_type>(err), "test");
            } catch (const ex& e) {
                log << e << endl;
            }
        }
    }
#endif
}

// Verify all enum values are present exactly once
constexpr bool verify_mappings(ofstream& log) {
    bool seen[static_cast<size_t>(error_type::cleanup) + 1] = {};
    
    // Check for duplicates
    for (const auto& m : error_strings) {
        size_t index = static_cast<size_t>(m.err);
        if (index >= sizeof(seen)/sizeof(seen[0])) {
            log << "Error index " << index << " out of bounds\n";
            return false;
        }
        if (seen[index]) {
            log << "Duplicate error mapping for index " << index << "\n";
            return false;
        }
        seen[index] = true;
    }

    // Check for missing mappings
    for (size_t i = 0; i < sizeof(seen)/sizeof(seen[0]); ++i) {
        if (!seen[i]) {
            cerr << "Missing error mapping for index " << i << "\n";
            return false;
        }
    }
    return true;
}

PFN_vkCreateDebugReportCallbackEXT my_vkCreateDebugReportCallbackEXT = NULL;


VKAPI_ATTR VkBool32 VKAPI_CALL MyDebugReportCallback(
    VkDebugReportFlagsEXT       flags,
    VkDebugReportObjectTypeEXT  objectType,
    uint64_t                    object,
    size_t                      location,
    int32_t                     messageCode,
    const char*                 pLayerPrefix,
    const char*                 pMessage,
    void*                       pUserData) {
    cerr << pMessage << endl;
    return VK_FALSE;
}


bool input_verbose = false;
//TODO: This should move into utilities with programmable key bindings
void key_callback(GLFWwindow* win, int key, int scancode, int action, int mods) {
    if ((key == GLFW_KEY_ESCAPE) && (action == GLFW_PRESS)) {
        glfwSetWindowShouldClose(win, GLFW_TRUE);
    }
    if (input_verbose)
    cout << "key " << key << " " << scancode << " " << action << " " << mods << endl;
}

void cursor_position_callback(GLFWwindow* win, double xpos, double ypos) {
    if (input_verbose)
        cout << "mouse position " << xpos << " " << ypos << endl;
}

void mouse_callback(GLFWwindow* win, int button, int action, int mods) {
    double xpos, ypos;
    glfwGetCursorPos(win, &xpos, &ypos);
    if (input_verbose)
        cout << "mouse " << button << " " << action << " " << mods 
             << " at " << xpos << "," << ypos << endl;
}

vulkan_core* vk;

void window_init() {
    if (!glfwInit()) {
        throw "Failed to initialize GLFW";
    }

    if (!glfwVulkanSupported()) {
        throw "GLFW does not support Vulkan";
    }

    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* win = glfwCreateWindow(settings.width, settings.height, settings.title.c_str(), nullptr, nullptr);

    vk = new vulkan_core(win);
    app = init_app();
    //m_vkCore.Init(title, nullptr, false);  // The NULL is for forward compatibility with the following tutorials
    if (win == nullptr) throw "Failed openging window";
    glfwSetKeyCallback(win, key_callback);
    glfwSetMouseButtonCallback(win, mouse_callback);
    glfwSetCursorPosCallback(win, cursor_position_callback);
    try {
            app->init();
    } catch (const ex& e) {
        cerr << e << endl;
    }
    while (!glfwWindowShouldClose(win)) {
        try {
                app->render();
        } catch (const ex& e) {
            cerr << e << endl;
        }
        // TODO: add fatalex to break out.
        glfwPollEvents();
    }
    glfwTerminate();
}

void unit_tests() {
    ofstream log("unit_tests.log");
    if (!verify_mappings(log))
        log << "Error mapping is incomplete or has duplicates";
    test_all_exceptions(log);
}

int main(int argc, char* argv[]) {
    unit_tests();
    set_language(language::en);
    try {
        window_init();
    } catch (const char* msg) {
        cerr << msg << endl;
    }
    return 0;
}

void vulkan_app_base::init() {
}

void vulkan_app_base::render() {
}

// Define the global variables
vulkan_app_base* app = nullptr;

This code also uses ex_en.hh

#pragma once
#include <iostream>

#if !MULTI_LANGUAGE

enum class language {
    en, fr, ja, cn, tr
};

enum class error_type {
    out_of_memory,
    invalid_argument,
    invalid_value,
    invalid_operation,
    internal_error,
    library_initialization,
    file_not_found,
    file_access_denied,
    file_invalid_data,
    file_not_enough_space,
    file_already_exists,
    file_not_a_directory,
    file_not_a_regular_file,
    initialization,
    mainloop,
    cleanup
};

struct error_mapping {
    error_type err;
    const char* msg;
    
    constexpr error_mapping(error_type e, const char* msg_str)
        : err(e), msg(msg_str) {}
};

constexpr error_mapping error_strings[] = {
    {error_type::out_of_memory, "Out of memory"},
    {error_type::invalid_argument, "Invalid argument"},
    {error_type::invalid_value, "Invalid value"},
    {error_type::invalid_operation, "Invalid operation"},
    {error_type::internal_error, "Internal error"},
    {error_type::library_initialization, "Library initialization failed"},
    {error_type::file_not_found, "File not found"},
    {error_type::file_access_denied, "File access denied"},
    {error_type::file_invalid_data, "File contains invalid data"},
    {error_type::file_not_enough_space, "Not enough space"},
    {error_type::file_already_exists, "File already exists"},
    {error_type::file_not_a_directory, "Not a directory"},
    {error_type::file_not_a_regular_file, "Not a regular file"},
    {error_type::initialization, "Initialization failed"},
    {error_type::mainloop, "Main loop failed"},
    {error_type::cleanup, "Cleanup failed"}
};

class ex {
public:
    const char* srcfile;
    uint32_t line;
    error_type err;
    const char* strparam;

    ex(const char* srcfile, uint32_t line, error_type err, const char* strparam = nullptr) : 
        srcfile(srcfile), line(line), err(err), strparam(strparam) {}
        
    friend std::ostream& operator<<(std::ostream& os, const ex& e) {
        os << e.srcfile << ":" << e.line << " " << error_strings[static_cast<size_t>(e.err)].msg;
        if (e.strparam) os << " " << e.strparam;
        return os;
    }
};

inline void set_language(language lang)  {
}

#define vkcheck(result, err) \
if (result != VK_SUCCESS) \
    throw ex(__FILE__, __LINE__, err);

#define vkcheck_msg(result, err, msg) \
if (result != VK_SUCCESS) \
    throw ex(__FILE__, __LINE__, err, msg);

#endif

Finally, tut01.cpp:

/*
    Streamlined Vulkan tutorial
    Author:
    Created: 2025-03-11
*/

#include "vulkan_core.hh"

using namespace std;

class vulkan_app : public vulkan_app_base {
public:
    vulkan_app(){}
    ~vulkan_app() {}
    void init() {}
    void render(){}
private:
};

vulkan_app_base* init_app() {
    return new vulkan_app();
}

app_settings settings = {"Tutorial 01", 1200, 1000, color(0,0,0.4)};

compiled using the command:

g++ -DVULKAN -ggdb3 tut01.cpp vulkan_core.cpp -o tut01 -lglfw -lvulkan

Solution

  • There are multiple problems in this code. I highly reccommend to review the vulkan-tutorial to better understand the reasons why your code doesn't work.

    Surface Creation

    The first thing you are missing is initialization of the VkSurfaceKHR, which is easily, and portably done using the handy

    glfwCreateWindowSurface(instance, win, NULL, &surface);
    

    Which returns VK_SUCCESS on successful creation. You can do this after succesful calls to glfwCreateWindow and vkCreateInstance , in that order. Without a valid surface, it is not possible to create a swapchain.

    Swapchain Creation

    After retrieving formats and capabilities for your physical device, you set this member:

    swapchain_extent = capabilities.currentExtent;
    

    But never check if the width and height are valid values. Indeed on my machine, they are set to UINT32_MAX, hence a simple solution to this would be:

    if (capabilities.currentExtent.width != UINT32_MAX) {
        swapchain_extent = capabilities.currentExtent;
    } else {
        uint32_t w, h;
        glfwGetFramebufferSize(window, (int*)&w, (int*)&h);
        swapchain_extent.width = w < capabilities.minImageExtent.width ? capabilities.minImageExtent.width : w;
        swapchain_extent.width = w > capabilities.maxImageExtent.width ? capabilities.maxImageExtent.width : w;
    
        swapchain_extent.height = h < capabilities.minImageExtent.height ? capabilities.minImageExtent.height : h;
        swapchain_extent.height = h > capabilities.maxImageExtent.height ? capabilities.maxImageExtent.height : h;
    }
    

    What happens here is that if the value of the width/height of your extent is UINT32_MAX, you retrieve the current size of the glfw framebuffer and use those, which happen to be the right values for the surface previously created.

    Off course, after doing this you need to set the imageExtent to your variable and not to the extent retrieved inside capabilities:

    create_info.imageExtent = swapchain_extent;
    

    So, condensing this corrections I pointed to inside vulkan_core::create_swapchain, gives you the following:

    void create_swapchain() {
        // NOTE: this line was added
        vk_check(glfwCreateWindowSurface(instance, window, NULL, &surface),
                         error_type::initialization, "vkCreateSwapchainKHR");
        
        VkSurfaceCapabilitiesKHR capabilities;
        vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &capabilities);
    
        uint32_t format_count;
        vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, nullptr);
        std::vector<VkSurfaceFormatKHR> formats(format_count);
        vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, formats.data());
            
        VkSurfaceFormatKHR surface_format = formats[0];
        for (const auto& format : formats) {
            if (format.format == VK_FORMAT_B8G8R8A8_SRGB && 
                format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
                surface_format = format;
                break;
            }
        }
    
        swapchain_format = surface_format.format;
        // NOTE: this was modified
        if (capabilities.currentExtent.width != UINT32_MAX) {
            swapchain_extent = capabilities.currentExtent;
        } else {
            uint32_t w, h;
            glfwGetFramebufferSize(window, (int*)&w, (int*)&h);
            swapchain_extent.width = w < capabilities.minImageExtent.width ? capabilities.minImageExtent.width : w;
            swapchain_extent.width = w > capabilities.maxImageExtent.width ? capabilities.maxImageExtent.width : w;
    
            swapchain_extent.height = h < capabilities.minImageExtent.height ? capabilities.minImageExtent.height : h;
            swapchain_extent.height = h > capabilities.maxImageExtent.height ? capabilities.maxImageExtent.height : h;
        }
        // up to here.
    
        VkSwapchainCreateInfoKHR create_info = {};
        create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
        create_info.surface = surface;
        create_info.minImageCount = capabilities.minImageCount + 1;
        create_info.imageFormat = surface_format.format;
        create_info.imageColorSpace = surface_format.colorSpace;
        // NOTE: this line changed
        create_info.imageExtent = swapchain_extent;
        create_info.imageArrayLayers = 1;
        create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
        create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
        create_info.preTransform = capabilities.currentTransform;
        create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
        create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR;
        create_info.clipped = VK_TRUE;
        create_info.oldSwapchain = VK_NULL_HANDLE;
        vkcheck_msg(vkCreateSwapchainKHR(device, &create_info, nullptr, &swapchain),
                    error_type::initialization, "vkCreateSwapchainKHR");
    }
    

    This code compiles, and doesn't give segmentation fault in the VkCreateSwapchainKHR call.

    Other issues

    I will briefly point out another problem which I see in your code; you can better understand using the referenced vulkan-tutorial. The problem is that you don't check nor set the glfw required extensions in the VkInstance creation. You can obtain those calling glfwGetRequiredInstanceExtensions.