为什么会出现 "Invalid VkShaderModule Object" 错误?

Why do I get "Invalid VkShaderModule Object" error?

我正在学习 Vulkan vulkan-tutorial.com

我卡住了,因为我无法弄清楚为什么在创建图形管道时会出现此错误。

VUID-VkPipelineShaderStageCreateInfo-module-parameter(ERROR / SPEC): msgNum: 0 - Invalid VkShaderModule Object 0x70000000007. The Vulkan spec states: module must be a valid VkShaderModule handle (https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#VUID-VkPipelineShaderStageCreateInfo-module-parameter)
    Objects: 1
        [0] 0x70000000007, type: 15, name: NULL
VUID-VkPipelineShaderStageCreateInfo-module-parameter(ERROR / SPEC): msgNum: 0 - Invalid VkShaderModule Object 0x80000000008. The Vulkan spec states: module must be a valid VkShaderModule handle (https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#VUID-VkPipelineShaderStageCreateInfo-module-parameter)
    Objects: 1
        [0] 0x80000000008, type: 15, name: NULL

这就是我创建着色器模块的方式:

auto createShaderModule = [](const char* fileName)
{
    int len;
    char* data = readBinFile(len, fileName);
    VkShaderModuleCreateInfo info = {};
    info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
    info.codeSize = len;
    info.pCode = (u32*)data;
    VkShaderModule module;
    const VkResult res = vkCreateShaderModule(vk.device, &info, nullptr, &module);
    if(res != VK_SUCCESS) {
        printf("Error: could not create vertex shader module\n");
        exit(-1);
    }
    delete[] data;
    return module;
};

VkShaderModule vertShadModule = createShaderModule(SHADERS_PATH"/simple.vert.glsl.spv");
VkShaderModule fragShadModule = createShaderModule(SHADERS_PATH"/simple.frag.glsl.spv");

auto createStage = [](VkShaderStageFlagBits stage, VkShaderModule module)
{
    VkPipelineShaderStageCreateInfo info = {};
    info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    info.stage = stage;
    info.module = module;
    info.pName = "main";
    info.pSpecializationInfo = nullptr; // allows to specify values for shader constants
    return info;
};

const VkPipelineShaderStageCreateInfo stagesInfos[] = {
    createStage(VK_SHADER_STAGE_VERTEX_BIT, vertShadModule),
    createStage(VK_SHADER_STAGE_FRAGMENT_BIT, fragShadModule)
};

如您所见,我正在检查创建的着色器模块是否正常 (VK_SUCCESS)。但是验证层仍然说模块无效。

这是完整的代码:

#include <stdio.h>
#include <vector>
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <vulkan/vulkan.h>

typedef const char* const ConstStr;
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;

GLFWwindow* window;
const int WINDOW_WIDTH = 800;
const int WINDOW_HEIGHT = 600;

struct VulkanData
{
    VkInstance inst;
    VkSurfaceKHR surface;
    VkPhysicalDevice physicalDevice;
    VkDevice device;
    u32 graphicsQueueFamily, presentationQueueFamily;
    VkQueue graphicsQueue;
    VkQueue presentationQueue;
    VkSwapchainKHR swapchain;
    VkImageView swapchainImageViews[2];
    VkRenderPass renderPass;
    VkPipeline pipeline;
} vk;

static VKAPI_ATTR VkBool32 VKAPI_CALL vulkanDebugCallback(
    VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
    VkDebugUtilsMessageTypeFlagsEXT messageType,
    const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
    void* pUserData) {

    printf("%s\n", pCallbackData->pMessage);

    return VK_FALSE;
}

void initVulkan()
{
    { // create vulkan instance
        VkApplicationInfo appInfo = {};
        appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
        appInfo.pNext = nullptr;
        appInfo.pApplicationName = "hello";
        appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.pEngineName = "hello_engine";
        appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 0);

        VkInstanceCreateInfo instInfo = {};
        instInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        instInfo.pNext = nullptr;
        instInfo.flags = 0;
        instInfo.pApplicationInfo = &appInfo;
        static ConstStr layers[] = {
        #if !defined(NDEBUG)
            "VK_LAYER_KHRONOS_validation"
        #endif
        }; 
        instInfo.enabledLayerCount = std::size(layers);
        instInfo.ppEnabledLayerNames =  layers;
        u32 numGlfwExtensions;
        ConstStr* glfwExtensions = glfwGetRequiredInstanceExtensions(&instInfo.enabledExtensionCount);
        std::vector<const char*> extensions;
        extensions.reserve(numGlfwExtensions + 1);
        for(u32 i = 0; i < numGlfwExtensions; i++)
            extensions.push_back(glfwExtensions[i]);
        #ifndef NDEBUG
            extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
        #endif
        instInfo.ppEnabledExtensionNames = glfwGetRequiredInstanceExtensions(&instInfo.enabledExtensionCount);

        if (vkCreateInstance(&instInfo, nullptr, &vk.inst) != VK_SUCCESS) {
            printf("Error creating vulkan instance\n");
            exit(-1);
        }
    }

    // create window surface
    if(glfwCreateWindowSurface(vk.inst, window, nullptr, &vk.surface) != VK_SUCCESS) {
        printf("Error: can't create window surface\n");
        exit(-1);
    }

    { // pick physical device

        u32 numPhysicalDevices;
        vkEnumeratePhysicalDevices(vk.inst, &numPhysicalDevices, nullptr);
        std::vector<VkPhysicalDevice> physicalDevices(numPhysicalDevices);
        if(numPhysicalDevices == 0) {
            printf("Error: there are no devices supporting Vulkan\n");
            exit(-1);
        }
        vkEnumeratePhysicalDevices(vk.inst, &numPhysicalDevices, &physicalDevices[0]);

        auto compareProps = [](
            const VkPhysicalDeviceProperties& propsA,
            const VkPhysicalDeviceMemoryProperties& memPropsA,
            const VkPhysicalDeviceProperties& propsB,
            const VkPhysicalDeviceMemoryProperties& memPropsB) -> bool
        {
            auto calcDeviceTypeScore = [](VkPhysicalDeviceType a) -> u8 {
                switch(a) {
                    case VK_PHYSICAL_DEVICE_TYPE_CPU: return 1;
                    case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: return 2;
                    case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: return 3;
                    case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: return 4;
                    default: return 0;
                }
            };
            const u8 scoreA = calcDeviceTypeScore(propsA.deviceType);
            const u8 scoreB = calcDeviceTypeScore(propsB.deviceType);
            if(scoreA != scoreB)
                return scoreA < scoreB;

            auto calcMem = [](const VkPhysicalDeviceMemoryProperties& a) -> u64
            {
                u64 mem = 0;
                for(u32 i = 0; i < a.memoryHeapCount; i++) {
                    if(a.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT)
                        mem = std::max(mem, a.memoryHeaps[i].size);
                }
                return mem;
            };
            u32 memA = calcMem(memPropsA);
            u32 memB = calcMem(memPropsB);

            return memA < memB;
        };

        VkPhysicalDeviceProperties bestProps;
        vkGetPhysicalDeviceProperties(physicalDevices[0], &bestProps);
        VkPhysicalDeviceMemoryProperties bestMemProps;
        vkGetPhysicalDeviceMemoryProperties(physicalDevices[0], &bestMemProps);
        u32 bestI = 0;
        for(u32 i = 1; i < numPhysicalDevices; i++) {
            VkPhysicalDeviceProperties props;
            VkPhysicalDeviceMemoryProperties memProps;
            vkGetPhysicalDeviceProperties(physicalDevices[i], &props);
            vkGetPhysicalDeviceMemoryProperties(physicalDevices[i], &memProps);
            if(compareProps(bestProps, bestMemProps, props, memProps)) {
                bestProps = props;
                bestMemProps = memProps;
                bestI = i;
            }
        }
        vk.physicalDevice = physicalDevices[bestI];
    }

    { // create logical device
        u32 numQueueFamilies;
        vkGetPhysicalDeviceQueueFamilyProperties(vk.physicalDevice, &numQueueFamilies, nullptr);
        std::vector<VkQueueFamilyProperties> queueFamilyProps(numQueueFamilies);
        vkGetPhysicalDeviceQueueFamilyProperties(vk.physicalDevice, &numQueueFamilies, &queueFamilyProps[0]);

        vk.graphicsQueueFamily = numQueueFamilies;
        vk.presentationQueueFamily = numQueueFamilies;
        for(u32 i = 0; i < numQueueFamilies; i++) {
            if(queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
                vk.graphicsQueueFamily = i;
            VkBool32 supportPresentation;
            vkGetPhysicalDeviceSurfaceSupportKHR(vk.physicalDevice, i, vk.surface, &supportPresentation);
            if(supportPresentation)
                vk.presentationQueueFamily = i;
        }

        if(vk.graphicsQueueFamily == numQueueFamilies) {
            printf("Error: there is no queue that supports graphics\n");
            exit(-1);
        }
        if(vk.presentationQueueFamily == numQueueFamilies) {
            printf("Error: there is no queue that supports presentation\n");
            exit(-1);
        }

        u32 queueFamilyInds[2] = {vk.graphicsQueueFamily};
        u32 numQueues;
        if(vk.graphicsQueueFamily == vk.presentationQueueFamily) {
            numQueues = 1;
        }
        else {
            numQueues = 2;
            queueFamilyInds[1] = vk.graphicsQueueFamily;
        }

        const float queuePriorities[] = {1.f};
        VkDeviceQueueCreateInfo queueCreateInfos[2] = {};
        for(u32 i = 0; i < numQueues; i++)
        {
            queueCreateInfos[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
            queueCreateInfos[i].queueFamilyIndex = queueFamilyInds[i];
            queueCreateInfos[i].queueCount = 1;
            queueCreateInfos[i].pQueuePriorities = queuePriorities;
        }

        VkDeviceCreateInfo info = {};
        info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
        info.queueCreateInfoCount = numQueues;
        info.pQueueCreateInfos = queueCreateInfos;
        info.enabledLayerCount = 0;
        info.ppEnabledLayerNames = nullptr;
        ConstStr deviceExtensions[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
        info.enabledExtensionCount = std::size(deviceExtensions);
        info.ppEnabledExtensionNames = deviceExtensions;
        const VkResult deviceCreatedOk = vkCreateDevice(vk.physicalDevice, &info, nullptr, &vk.device);
        if(deviceCreatedOk != VK_SUCCESS) {
            printf("Error: couldn't create device\n");
            exit(-1);
        }

        vkGetDeviceQueue(vk.device, vk.graphicsQueueFamily, 0, &vk.graphicsQueue);
        vkGetDeviceQueue(vk.device, vk.presentationQueueFamily, 0, &vk.presentationQueue);

        // queues
        // https://community.khronos.org/t/guidelines-for-selecting-queues-and-families/7222
        // https://www.reddit.com/r/vulkan/comments/aara8f/best_way_for_selecting_queuefamilies/
        // 
    }

    { // crete swapchain
        VkSurfaceCapabilitiesKHR surfaceCpabilities;
        vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk.physicalDevice, vk.surface, &surfaceCpabilities);

        VkSwapchainCreateInfoKHR createInfo = {};
        createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
        createInfo.surface = vk.surface;
        createInfo.minImageCount = 2;
        createInfo.imageFormat = VK_FORMAT_B8G8R8A8_SRGB; // TODO: I think this format has mandatory support but I'm not sure
        createInfo.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
        createInfo.imageExtent = surfaceCpabilities.currentExtent;
        createInfo.imageArrayLayers = 1;
        createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
        createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
        createInfo.queueFamilyIndexCount = 1;
        createInfo.pQueueFamilyIndices = &vk.presentationQueueFamily;
        createInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
        createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
        createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR;
        createInfo.clipped = VK_FALSE;
        //createInfo.oldSwapchain = ; // this can be used to recycle the old swapchain when resizing the window

        vkCreateSwapchainKHR(vk.device, &createInfo, nullptr, &vk.swapchain);
    }

    { // create image views of the swapchain
        u32 imageCount;
        VkImage images[2];
        vkGetSwapchainImagesKHR(vk.device, vk.swapchain, &imageCount, nullptr);
        assert(imageCount == 2);
        vkGetSwapchainImagesKHR(vk.device, vk.swapchain, &imageCount, images);

        for(u32 i = 0; i < 2; i++)
        {
            VkImageViewCreateInfo info = {};
            info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
            info.image = images[0];
            info.viewType = VK_IMAGE_VIEW_TYPE_2D;
            info.format = VK_FORMAT_B8G8R8A8_SRGB;
            // info.components = ; // channel swizzling VK_COMPONENT_SWIZZLE_IDENTITY is 0
            info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
            info.subresourceRange.baseMipLevel = 0;
            info.subresourceRange.levelCount = 1;
            info.subresourceRange.baseArrayLayer = 0;
            info.subresourceRange.layerCount = 1;

            vkCreateImageView(vk.device, &info, nullptr, &vk.swapchainImageViews[i]);
        }
    }
}

#define SHADERS_PATH "shaders"

char* readBinFile(int& len, const char* fileName)
{
    FILE* file = fopen(fileName, "rb");
    fseek(file, 0, SEEK_END);
    len = ftell(file);
    rewind(file);
    char* txt = new char[len];
    fread(txt, len, 1, file);
    fclose(file);
    return txt;
}

void createTheGraphicsPipeline()
{
    auto createShaderModule = [](const char* fileName)
    {
        int len;
        char* data = readBinFile(len, fileName);
        VkShaderModuleCreateInfo info = {};
        info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
        info.codeSize = len;
        info.pCode = (u32*)data;
        VkShaderModule module;
        const VkResult res = vkCreateShaderModule(vk.device, &info, nullptr, &module);
        if(res != VK_SUCCESS) {
            printf("Error: could not create vertex shader module\n");
            exit(-1);
        }
        delete[] data;
        return module;
    };

    VkShaderModule vertShadModule = createShaderModule(SHADERS_PATH"/simple.vert.glsl.spv");
    VkShaderModule fragShadModule = createShaderModule(SHADERS_PATH"/simple.frag.glsl.spv");

    auto createStage = [](VkShaderStageFlagBits stage, VkShaderModule module)
    {
        VkPipelineShaderStageCreateInfo info = {};
        info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
        info.stage = stage;
        info.module = module;
        info.pName = "main";
        info.pSpecializationInfo = nullptr; // allows to specify values for shader constants
        return info;
    };

    const VkPipelineShaderStageCreateInfo stagesInfos[] = {
        createStage(VK_SHADER_STAGE_VERTEX_BIT, vertShadModule),
        createStage(VK_SHADER_STAGE_FRAGMENT_BIT, fragShadModule)
    };

    VkPipelineVertexInputStateCreateInfo vertInputInfo = {};
    vertInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
    vertInputInfo.vertexBindingDescriptionCount = 0;
    vertInputInfo.pVertexBindingDescriptions = nullptr;
    vertInputInfo.vertexAttributeDescriptionCount = 0;
    vertInputInfo.pVertexAttributeDescriptions = nullptr;

    VkPipelineInputAssemblyStateCreateInfo assemblyInfo = {};
    assemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
    assemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
    assemblyInfo.primitiveRestartEnable = VK_FALSE;

    VkViewport viewport;
    viewport.x = 0;
    viewport.y = 0;
    viewport.width = WINDOW_WIDTH;
    viewport.height = WINDOW_HEIGHT;
    viewport.minDepth = 0;
    viewport.maxDepth = 1;
    VkRect2D scissor;
    scissor.offset = {0, 0};
    scissor.extent = {WINDOW_WIDTH, WINDOW_HEIGHT};
    VkPipelineViewportStateCreateInfo viewportInfo = {};
    viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
    viewportInfo.viewportCount = 1;
    viewportInfo.pViewports = &viewport;
    viewportInfo.scissorCount = 1;
    viewportInfo.pScissors = &scissor;
    
    VkPipelineRasterizationStateCreateInfo rasterizerInfo = {};
    rasterizerInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
    rasterizerInfo.depthClampEnable = VK_FALSE; // enabling will clamp depth instead of discarding which can be useful when rendering shadowmaps
    rasterizerInfo.rasterizerDiscardEnable = VK_FALSE; // if enable discards all geometry (could be useful for transformfeedback)
    rasterizerInfo.polygonMode = VK_POLYGON_MODE_FILL;
    rasterizerInfo.lineWidth = 1.f;
    rasterizerInfo.cullMode = VK_CULL_MODE_BACK_BIT;
    rasterizerInfo.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
    rasterizerInfo.depthBiasEnable = VK_FALSE; // useful for shadow mapping

    VkPipelineMultisampleStateCreateInfo multisampleInfo = {}; // multisampling: it works by combining the fragment shader results of multiple polygons that rasterize to the same pixel
    multisampleInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
    multisampleInfo.sampleShadingEnable = VK_FALSE;
    multisampleInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;

    //VkPipelineDepthStencilStateCreateInfo depthStencilInfo = {};

    VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
    colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_G_BIT;
    colorBlendAttachment.blendEnable = VK_FALSE;
    VkPipelineColorBlendStateCreateInfo colorBlendInfo = {};
    colorBlendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
    colorBlendInfo.attachmentCount = 1;
    colorBlendInfo.pAttachments = &colorBlendAttachment;

    const VkDynamicState dynamicStates[] = {
        VK_DYNAMIC_STATE_VIEWPORT,
        VK_DYNAMIC_STATE_SCISSOR
    };
    VkPipelineDynamicStateCreateInfo dynamicStateInfo = {};
    dynamicStateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
    dynamicStateInfo.dynamicStateCount = 2;
    dynamicStateInfo.pDynamicStates = dynamicStates;

    VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
    pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
    VkPipelineLayout pipelineLayout;
    vkCreatePipelineLayout(vk.device, &pipelineLayoutInfo, nullptr, &pipelineLayout);

    vkDestroyShaderModule(vk.device, fragShadModule, nullptr);
    vkDestroyShaderModule(vk.device, vertShadModule, nullptr);

    // -- create the renderPass ---
    VkAttachmentDescription attachmentDesc = {};
    attachmentDesc.format = VK_FORMAT_B8G8R8A8_SRGB;
    attachmentDesc.samples = VK_SAMPLE_COUNT_1_BIT;
    attachmentDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // clear before rendering
    attachmentDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; // store the result after rendering
    attachmentDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    attachmentDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    attachmentDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // we don't care. This doesn't guarantee that the contents of th eimage will be preserved, but that's not a problem since we are going to clear it anyways
    attachmentDesc.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

    VkAttachmentReference colorAttachmentRef = {};
    colorAttachmentRef.attachment = 0; // the index in the attachemntDescs array (we only have one)
    colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // this is an output attachment so we use this enum for best performance

    VkSubpassDescription subpassDesc = {};
    subpassDesc.colorAttachmentCount = 1;
    subpassDesc.pColorAttachments = &colorAttachmentRef; // the index of the attachement in this array is referenced in the shader with "layout(location = 0) out vec4 o_color"

    VkRenderPassCreateInfo renderPassInfo = {};
    renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
    renderPassInfo.attachmentCount = 1;
    renderPassInfo.pAttachments = &attachmentDesc;
    renderPassInfo.subpassCount = 1;
    renderPassInfo.pSubpasses = &subpassDesc;

    if(vkCreateRenderPass(vk.device, &renderPassInfo, nullptr, &vk.renderPass) != VK_SUCCESS) {
        printf("Error creating the renderPass\n");
        exit(-1);
    }

    // -- finally create the pipeline
    VkGraphicsPipelineCreateInfo pipelineInfo = {};
    pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
    pipelineInfo.stageCount = 2;
    pipelineInfo.pStages = stagesInfos;
    pipelineInfo.pVertexInputState = &vertInputInfo;
    pipelineInfo.pInputAssemblyState = &assemblyInfo;
    pipelineInfo.pViewportState = &viewportInfo;
    pipelineInfo.pRasterizationState = &rasterizerInfo;
    pipelineInfo.pMultisampleState = &multisampleInfo;
    pipelineInfo.pDepthStencilState = nullptr;
    pipelineInfo.pColorBlendState = &colorBlendInfo;
    pipelineInfo.pDynamicState = &dynamicStateInfo;
    pipelineInfo.layout = pipelineLayout;
    pipelineInfo.renderPass = vk.renderPass; // render pass describing the enviroment in which the pipeline will be used
        // the pipeline must only be used with a render pass compatilble with this one
    pipelineInfo.subpass = 0; // index of the subpass in the render pass where this pipeline will be used
    //pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // you can derive from another pipeline
    //pipelineInfo.basePipelineIndex = -1;

    if(vkCreateGraphicsPipelines(vk.device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &vk.pipeline) != VK_SUCCESS) {
        printf("Error creating the graphics piepeline\n");
        exit(-1);
    }

}

int main()
{
    printf("hello vulkan\n");

    glfwInit();
    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); // explicitly tell GLFW not to create an OpenGL context
    window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "hello vulkan", nullptr, nullptr);

    initVulkan();
    createTheGraphicsPipeline();

    while(!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
    }

    glfwDestroyWindow(window);
    glfwTerminate();
}

simple.vert.glsl:

#version 450

vec2 positions[3] = vec2[](
    vec2(0.0, -0.5),
    vec2(0.5, 0.5),
    vec2(-0.5, 0.5)
);

const vec3 colors[3] = vec3[](
    vec3(1, 0, 0),
    vec3(0, 1, 0),
    vec3(0, 0, 1)
);

layout (location = 0)out vec3 v_color;

void main()
{
    gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
    v_color = colors[gl_VertexIndex];
}

simple.frag.glsl

#version 450

layout(location = 0) in vec3 v_color;

layout(location = 0) out vec4 o_color;

void main() {
    o_color = vec4(v_color, 1.0);
}

我终于找到了问题所在:我过早地破坏了着色器模块。看起来您必须在创建管线后让着色器模块保持活动状态。

这是固定码

#include <stdio.h>
#include <string.h>
#include <vector>
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <vulkan/vulkan.h>

typedef const char* const ConstStr;
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;

GLFWwindow* window;
const int WINDOW_WIDTH = 800;
const int WINDOW_HEIGHT = 600;

struct VulkanData
{
    VkInstance inst;
    VkSurfaceKHR surface;
    VkPhysicalDevice physicalDevice;
    VkDevice device;
    u32 graphicsQueueFamily, presentationQueueFamily;
    VkQueue graphicsQueue;
    VkQueue presentationQueue;
    VkSwapchainKHR swapchain;
    VkImageView swapchainImageViews[2];
    VkRenderPass renderPass;
    VkPipeline pipeline;
} vk;

static VKAPI_ATTR VkBool32 VKAPI_CALL vulkanDebugCallback(
    VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
    VkDebugUtilsMessageTypeFlagsEXT messageType,
    const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
    void* pUserData) {

    printf("%s\n", pCallbackData->pMessage);

    return VK_FALSE;
}

void initVulkan()
{
    { // create vulkan instance
        VkApplicationInfo appInfo = {};
        appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
        appInfo.pNext = nullptr;
        appInfo.pApplicationName = "hello";
        appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.pEngineName = "hello_engine";
        appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.apiVersion = VK_API_VERSION_1_0;

        static ConstStr layers[] = {
        #if !defined(NDEBUG)
            "VK_LAYER_KHRONOS_validation",
            //"VK_LAYER_LUNARG_api_dump",
        #endif
        };
        #if !defined(NDEBUG)
            u32 numSupportedLayers;
            vkEnumerateInstanceLayerProperties(&numSupportedLayers, nullptr);
            std::vector<VkLayerProperties> supportedLayers(numSupportedLayers);
            vkEnumerateInstanceLayerProperties(&numSupportedLayers, &supportedLayers[0]);
            for(ConstStr layer : layers) {
                bool supported = false;
                for(const auto& supportedLayer : supportedLayers) {
                    if(strcmp(supportedLayer.layerName, layer) == 0) {
                        supported = true;
                        break;
                    }
                }
                if(!supported) {
                    printf("Layer %s is not supported\n", layer);
                    //assert(false);
                }
            }
        #endif

        VkInstanceCreateInfo instInfo = {};
        instInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        instInfo.pApplicationInfo = &appInfo;
        instInfo.enabledLayerCount = std::size(layers);
        instInfo.ppEnabledLayerNames =  layers;
        u32 numGlfwExtensions;
        ConstStr* glfwExtensions = glfwGetRequiredInstanceExtensions(&numGlfwExtensions);
        std::vector<const char*> extensions;
        extensions.reserve(numGlfwExtensions + 1);
        for(u32 i = 0; i < numGlfwExtensions; i++)
            extensions.push_back(glfwExtensions[i]);
        #ifndef NDEBUG
            extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
        #endif
        instInfo.enabledExtensionCount = extensions.size();
        instInfo.ppEnabledExtensionNames = extensions.data();
        
        if (vkCreateInstance(&instInfo, nullptr, &vk.inst) != VK_SUCCESS) {
            printf("Error creating vulkan instance\n");
            exit(-1);
        }
    }

    // create window surface
    if(glfwCreateWindowSurface(vk.inst, window, nullptr, &vk.surface) != VK_SUCCESS) {
        printf("Error: can't create window surface\n");
        exit(-1);
    }

    { // pick physical device

        u32 numPhysicalDevices;
        vkEnumeratePhysicalDevices(vk.inst, &numPhysicalDevices, nullptr);
        std::vector<VkPhysicalDevice> physicalDevices(numPhysicalDevices);
        if(numPhysicalDevices == 0) {
            printf("Error: there are no devices supporting Vulkan\n");
            exit(-1);
        }
        vkEnumeratePhysicalDevices(vk.inst, &numPhysicalDevices, &physicalDevices[0]);

        auto compareProps = [](
            const VkPhysicalDeviceProperties& propsA,
            const VkPhysicalDeviceMemoryProperties& memPropsA,
            const VkPhysicalDeviceProperties& propsB,
            const VkPhysicalDeviceMemoryProperties& memPropsB) -> bool
        {
            auto calcDeviceTypeScore = [](VkPhysicalDeviceType a) -> u8 {
                switch(a) {
                    case VK_PHYSICAL_DEVICE_TYPE_CPU: return 1;
                    case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: return 2;
                    case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: return 3;
                    case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: return 4;
                    default: return 0;
                }
            };
            const u8 scoreA = calcDeviceTypeScore(propsA.deviceType);
            const u8 scoreB = calcDeviceTypeScore(propsB.deviceType);
            if(scoreA != scoreB)
                return scoreA < scoreB;

            auto calcMem = [](const VkPhysicalDeviceMemoryProperties& a) -> u64
            {
                u64 mem = 0;
                for(u32 i = 0; i < a.memoryHeapCount; i++) {
                    if(a.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT)
                        mem = std::max(mem, a.memoryHeaps[i].size);
                }
                return mem;
            };
            u32 memA = calcMem(memPropsA);
            u32 memB = calcMem(memPropsB);

            return memA < memB;
        };

        VkPhysicalDeviceProperties bestProps;
        vkGetPhysicalDeviceProperties(physicalDevices[0], &bestProps);
        VkPhysicalDeviceMemoryProperties bestMemProps;
        vkGetPhysicalDeviceMemoryProperties(physicalDevices[0], &bestMemProps);
        u32 bestI = 0;
        for(u32 i = 1; i < numPhysicalDevices; i++) {
            VkPhysicalDeviceProperties props;
            VkPhysicalDeviceMemoryProperties memProps;
            vkGetPhysicalDeviceProperties(physicalDevices[i], &props);
            vkGetPhysicalDeviceMemoryProperties(physicalDevices[i], &memProps);
            if(compareProps(bestProps, bestMemProps, props, memProps)) {
                bestProps = props;
                bestMemProps = memProps;
                bestI = i;
            }
        }
        vk.physicalDevice = physicalDevices[bestI];
    }

    { // create logical device
        u32 numQueueFamilies;
        vkGetPhysicalDeviceQueueFamilyProperties(vk.physicalDevice, &numQueueFamilies, nullptr);
        std::vector<VkQueueFamilyProperties> queueFamilyProps(numQueueFamilies);
        vkGetPhysicalDeviceQueueFamilyProperties(vk.physicalDevice, &numQueueFamilies, &queueFamilyProps[0]);

        vk.graphicsQueueFamily = numQueueFamilies;
        vk.presentationQueueFamily = numQueueFamilies;
        for(u32 i = 0; i < numQueueFamilies; i++) {
            if(queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
                vk.graphicsQueueFamily = i;
            VkBool32 supportPresentation;
            vkGetPhysicalDeviceSurfaceSupportKHR(vk.physicalDevice, i, vk.surface, &supportPresentation);
            if(supportPresentation)
                vk.presentationQueueFamily = i;
        }

        if(vk.graphicsQueueFamily == numQueueFamilies) {
            printf("Error: there is no queue that supports graphics\n");
            exit(-1);
        }
        if(vk.presentationQueueFamily == numQueueFamilies) {
            printf("Error: there is no queue that supports presentation\n");
            exit(-1);
        }

        u32 queueFamilyInds[2] = {vk.graphicsQueueFamily};
        u32 numQueues;
        if(vk.graphicsQueueFamily == vk.presentationQueueFamily) {
            numQueues = 1;
        }
        else {
            numQueues = 2;
            queueFamilyInds[1] = vk.graphicsQueueFamily;
        }

        const float queuePriorities[] = {1.f};
        VkDeviceQueueCreateInfo queueCreateInfos[2] = {};
        for(u32 i = 0; i < numQueues; i++)
        {
            queueCreateInfos[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
            queueCreateInfos[i].queueFamilyIndex = queueFamilyInds[i];
            queueCreateInfos[i].queueCount = 1;
            queueCreateInfos[i].pQueuePriorities = queuePriorities;
        }

        VkDeviceCreateInfo info = {};
        info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
        info.queueCreateInfoCount = numQueues;
        info.pQueueCreateInfos = queueCreateInfos;
        info.enabledLayerCount = 0;
        info.ppEnabledLayerNames = nullptr;
        ConstStr deviceExtensions[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
        info.enabledExtensionCount = std::size(deviceExtensions);
        info.ppEnabledExtensionNames = deviceExtensions;
        const VkResult deviceCreatedOk = vkCreateDevice(vk.physicalDevice, &info, nullptr, &vk.device);
        if(deviceCreatedOk != VK_SUCCESS) {
            printf("Error: couldn't create device\n");
            exit(-1);
        }

        vkGetDeviceQueue(vk.device, vk.graphicsQueueFamily, 0, &vk.graphicsQueue);
        vkGetDeviceQueue(vk.device, vk.presentationQueueFamily, 0, &vk.presentationQueue);

        // queues
        // https://community.khronos.org/t/guidelines-for-selecting-queues-and-families/7222
        // https://www.reddit.com/r/vulkan/comments/aara8f/best_way_for_selecting_queuefamilies/
        // 
    }

    { // crete swapchain
        VkSurfaceCapabilitiesKHR surfaceCpabilities;
        vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk.physicalDevice, vk.surface, &surfaceCpabilities);

        VkSwapchainCreateInfoKHR createInfo = {};
        createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
        createInfo.surface = vk.surface;
        createInfo.minImageCount = 2;
        createInfo.imageFormat = VK_FORMAT_B8G8R8A8_SRGB; // TODO: I think this format has mandatory support but I'm not sure
        createInfo.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
        createInfo.imageExtent = surfaceCpabilities.currentExtent;
        createInfo.imageArrayLayers = 1;
        createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
        createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
        createInfo.queueFamilyIndexCount = 1;
        createInfo.pQueueFamilyIndices = &vk.presentationQueueFamily;
        createInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
        createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
        createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR;
        createInfo.clipped = VK_FALSE;
        //createInfo.oldSwapchain = ; // this can be used to recycle the old swapchain when resizing the window

        vkCreateSwapchainKHR(vk.device, &createInfo, nullptr, &vk.swapchain);
    }

    { // create image views of the swapchain
        u32 imageCount;
        VkImage images[2];
        vkGetSwapchainImagesKHR(vk.device, vk.swapchain, &imageCount, nullptr);
        assert(imageCount == 2);
        vkGetSwapchainImagesKHR(vk.device, vk.swapchain, &imageCount, images);

        for(u32 i = 0; i < 2; i++)
        {
            VkImageViewCreateInfo info = {};
            info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
            info.image = images[0];
            info.viewType = VK_IMAGE_VIEW_TYPE_2D;
            info.format = VK_FORMAT_B8G8R8A8_SRGB;
            // info.components = ; // channel swizzling VK_COMPONENT_SWIZZLE_IDENTITY is 0
            info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
            info.subresourceRange.baseMipLevel = 0;
            info.subresourceRange.levelCount = 1;
            info.subresourceRange.baseArrayLayer = 0;
            info.subresourceRange.layerCount = 1;

            vkCreateImageView(vk.device, &info, nullptr, &vk.swapchainImageViews[i]);
        }
    }
}

#define SHADERS_PATH "shaders"

char* readBinFile(int& len, const char* fileName)
{
    FILE* file = fopen(fileName, "rb");
    if(!file)
        return nullptr;
    fseek(file, 0, SEEK_END);
    len = ftell(file);
    rewind(file);
    char* txt = new char[len];
    fread(txt, len, 1, file);
    fclose(file);
    return txt;
}

VkShaderModule createShaderModule(const char* fileName)
{
    int len;
    char* data = readBinFile(len, fileName);
    if(!data) {
        printf("Error loading shader spir-v\n");
        exit(-1);
    }
    VkShaderModuleCreateInfo info = {};
    info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
    info.codeSize = len;
    info.pCode = (u32*)data;
    VkShaderModule module;
    const VkResult res = vkCreateShaderModule(vk.device, &info, nullptr, &module);
    if(res != VK_SUCCESS) {
        printf("Error: could not create vertex shader module\n");
        exit(-1);
    }
    delete[] data;
    return module;
}

VkPipelineShaderStageCreateInfo makeStageCreateInfo(VkShaderStageFlagBits stage, VkShaderModule module)
{
    VkPipelineShaderStageCreateInfo info = {};
    info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    info.stage = stage;
    info.module = module;
    info.pName = "main";
    info.pSpecializationInfo = nullptr; // allows to specify values for shader constants
    return info;
}

void createTheGraphicsPipeline()
{
    VkShaderModule vertShadModule = createShaderModule(SHADERS_PATH"/simple.vert.glsl.spv");
    VkShaderModule fragShadModule = createShaderModule(SHADERS_PATH"/simple.frag.glsl.spv");

    const VkPipelineShaderStageCreateInfo stagesInfos[] = {
        makeStageCreateInfo(VK_SHADER_STAGE_VERTEX_BIT, vertShadModule),
        makeStageCreateInfo(VK_SHADER_STAGE_FRAGMENT_BIT, fragShadModule)
    };

    VkPipelineVertexInputStateCreateInfo vertInputInfo = {};
    vertInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
    vertInputInfo.vertexBindingDescriptionCount = 0;
    vertInputInfo.pVertexBindingDescriptions = nullptr;
    vertInputInfo.vertexAttributeDescriptionCount = 0;
    vertInputInfo.pVertexAttributeDescriptions = nullptr;

    VkPipelineInputAssemblyStateCreateInfo assemblyInfo = {};
    assemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
    assemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
    assemblyInfo.primitiveRestartEnable = VK_FALSE;

    VkViewport viewport;
    viewport.x = 0;
    viewport.y = 0;
    viewport.width = WINDOW_WIDTH;
    viewport.height = WINDOW_HEIGHT;
    viewport.minDepth = 0;
    viewport.maxDepth = 1;
    VkRect2D scissor;
    scissor.offset = {0, 0};
    scissor.extent = {WINDOW_WIDTH, WINDOW_HEIGHT};
    VkPipelineViewportStateCreateInfo viewportInfo = {};
    viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
    viewportInfo.viewportCount = 1;
    viewportInfo.pViewports = &viewport;
    viewportInfo.scissorCount = 1;
    viewportInfo.pScissors = &scissor;
    
    VkPipelineRasterizationStateCreateInfo rasterizerInfo = {};
    rasterizerInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
    rasterizerInfo.depthClampEnable = VK_FALSE; // enabling will clamp depth instead of discarding which can be useful when rendering shadowmaps
    rasterizerInfo.rasterizerDiscardEnable = VK_FALSE; // if enable discards all geometry (could be useful for transformfeedback)
    rasterizerInfo.polygonMode = VK_POLYGON_MODE_FILL;
    rasterizerInfo.lineWidth = 1.f;
    rasterizerInfo.cullMode = VK_CULL_MODE_BACK_BIT;
    rasterizerInfo.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
    rasterizerInfo.depthBiasEnable = VK_FALSE; // useful for shadow mapping

    VkPipelineMultisampleStateCreateInfo multisampleInfo = {}; // multisampling: it works by combining the fragment shader results of multiple polygons that rasterize to the same pixel
    multisampleInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
    multisampleInfo.sampleShadingEnable = VK_FALSE;
    multisampleInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;

    //VkPipelineDepthStencilStateCreateInfo depthStencilInfo = {};

    VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
    colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_G_BIT;
    colorBlendAttachment.blendEnable = VK_FALSE;
    VkPipelineColorBlendStateCreateInfo colorBlendInfo = {};
    colorBlendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
    //colorBlendInfo.logicOpEnable = VK_FALSE;
    //colorBlendInfo.logicOp = VK_LOGIC_OP_COPY;
    colorBlendInfo.attachmentCount = 1;
    colorBlendInfo.pAttachments = &colorBlendAttachment;

    //const VkDynamicState dynamicStates[] = {
    //    VK_DYNAMIC_STATE_VIEWPORT,
    //    VK_DYNAMIC_STATE_SCISSOR
    //};
    //VkPipelineDynamicStateCreateInfo dynamicStateInfo = {};
    //dynamicStateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
    //dynamicStateInfo.dynamicStateCount = 2;
    //dynamicStateInfo.pDynamicStates = dynamicStates;

    VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
    pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
    VkPipelineLayout pipelineLayout;
    vkCreatePipelineLayout(vk.device, &pipelineLayoutInfo, nullptr, &pipelineLayout);

    // -- create the renderPass ---
    VkAttachmentDescription attachmentDesc = {};
    attachmentDesc.format = VK_FORMAT_B8G8R8A8_SRGB;
    attachmentDesc.samples = VK_SAMPLE_COUNT_1_BIT;
    attachmentDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // clear before rendering
    attachmentDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; // store the result after rendering
    attachmentDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    attachmentDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    attachmentDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // we don't care. This doesn't guarantee that the contents of th eimage will be preserved, but that's not a problem since we are going to clear it anyways
    attachmentDesc.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

    VkAttachmentReference colorAttachmentRef = {};
    colorAttachmentRef.attachment = 0; // the index in the attachemntDescs array (we only have one)
    colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // this is an output attachment so we use this enum for best performance

    VkSubpassDescription subpassDesc = {};
    subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
    subpassDesc.colorAttachmentCount = 1;
    subpassDesc.pColorAttachments = &colorAttachmentRef; // the index of the attachement in this array is referenced in the shader with "layout(location = 0) out vec4 o_color"

    VkRenderPassCreateInfo renderPassInfo = {};
    renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
    renderPassInfo.attachmentCount = 1;
    renderPassInfo.pAttachments = &attachmentDesc;
    renderPassInfo.subpassCount = 1;
    renderPassInfo.pSubpasses = &subpassDesc;

    if(vkCreateRenderPass(vk.device, &renderPassInfo, nullptr, &vk.renderPass) != VK_SUCCESS) {
        printf("Error creating the renderPass\n");
        exit(-1);
    }

    // -- finally create the pipeline
    VkGraphicsPipelineCreateInfo pipelineInfo = {};
    pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
    pipelineInfo.stageCount = 2;
    pipelineInfo.pStages = stagesInfos;
    pipelineInfo.pVertexInputState = &vertInputInfo;
    pipelineInfo.pInputAssemblyState = &assemblyInfo;
    pipelineInfo.pViewportState = &viewportInfo;
    pipelineInfo.pRasterizationState = &rasterizerInfo;
    pipelineInfo.pMultisampleState = &multisampleInfo;
    pipelineInfo.pDepthStencilState = nullptr;
    pipelineInfo.pColorBlendState = &colorBlendInfo;
    //pipelineInfo.pDynamicState = &dynamicStateInfo;
    pipelineInfo.layout = pipelineLayout;
    pipelineInfo.renderPass = vk.renderPass; // render pass describing the enviroment in which the pipeline will be used
        // the pipeline must only be used with a render pass compatilble with this one
    pipelineInfo.subpass = 0; // index of the subpass in the render pass where this pipeline will be used
    //pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // you can derive from another pipeline
    //pipelineInfo.basePipelineIndex = -1;

    getchar();
    if(vkCreateGraphicsPipelines(vk.device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &vk.pipeline) != VK_SUCCESS) {
        printf("Error creating the graphics piepeline\n");
        exit(-1);
    }

    vkDestroyShaderModule(vk.device, fragShadModule, nullptr);
    vkDestroyShaderModule(vk.device, vertShadModule, nullptr);
}

int main()
{
    printf("hello vulkan\n");

    glfwInit();
    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); // explicitly tell GLFW not to create an OpenGL context
    window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "hello vulkan", nullptr, nullptr);

    initVulkan();
    createTheGraphicsPipeline();

    while(!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
    }

    glfwDestroyWindow(window);
    glfwTerminate();
}