0

I recently learned how to create fancy template functions to make pipeline loading much more intuitive from one of my other questions (located in this stack thread). Applying the help I received to my code has been hit-and-miss.

The generalized function is as follows:

template<typename T, auto mVar>
std::vector<T> transformVector(auto& srcVec) {
    std::vector<T> dstVec(srcVec.size());
    std::transform(srcVec.begin(), srcVec.end(), dstVec.begin(), [](auto const& mSrc) { return mSrc.*mVar; });
    return dstVec;
}

Creating the resources:

// Data container initialization
UBO uniforms;
VkTexture planks("textures/planks.png");

// Data loading/Buffer creation
VkDataBuffer<UBO> ubo(uniforms, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT);
VkTextureSet textureSet(planks);
// Both inherit from a common struct 'VkDescriptor'

// Shader initialization
VkShader vertShader("shaders/vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
VkShader fragShader("shaders/frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);

The function works perfectly for loading descriptors:

std::vector<VkDescriptor> descriptors = { ubo, textureSet, ssbo };

std::vector<VkDescriptorSetLayout> descTest = transformVector<VkDescriptorSetLayout, &VkDescriptor::SetLayout>(descriptors);

std::vector<VkPipelineShaderStageCreateInfo> shaderStages = { vertShader.stageInfo, fragShader.stageInfo };

VkGraphicsPipeline pipeline(descTest, shaderStages);

However, creating an 'std::vector' of 'VkShader' objects to use the 'transformVector' function for shader loading fails:

std::vector<VkDescriptor> descriptors = { ubo, textureSet };
std::vector<VkShader> shaders = { vertShader, fragShader };

std::vector<VkDescriptorSetLayout> descTest = transformVector<VkDescriptorSetLayout, &VkDescriptor::SetLayout>(descriptors);

//std::vector<VkPipelineShaderStageCreateInfo> shaderStages = { vertShader.stageInfo, fragShader.stageInfo };

std::vector<VkPipelineShaderStageCreateInfo> stageTest = transformVector<VkPipelineShaderStageCreateInfo, &VkShader::stageInfo>(shaders);

VkGraphicsPipeline pipeline(descTest, stageTest);

and generates these validation layer callbacks:

Validation Layer:
Validation Error: [ VUID-VkPipelineShaderStageCreateInfo-module-parameter ] Object 0: handle = 0x1b0fafb15e0, type = VK_OBJECT_TYPE_INSTANCE; | MessageID = 0xfd71dc70 | Invalid VkShaderModule Object 0x4b7df1000000002f. The Vulkan spec states: If module is not VK_NULL_HANDLE, module must be a valid VkShaderModule handle (https://vulkan.lunarg.com/doc/view/1.3.243.0/windows/1.3-extensions/vkspec.html#VUID-VkPipelineShaderStageCreateInfo-module-parameter)

Validation Layer:
Validation Error: [ VUID-VkPipelineShaderStageCreateInfo-module-parameter ] Object 0: handle = 0x1b0fafb15e0, type = VK_OBJECT_TYPE_INSTANCE; | MessageID = 0xfd71dc70 | Invalid VkShaderModule Object 0xa21a4e0000000030. The Vulkan spec states: If module is not VK_NULL_HANDLE, module must be a valid VkShaderModule handle (https://vulkan.lunarg.com/doc/view/1.3.243.0/windows/1.3-extensions/vkspec.html#VUID-VkPipelineShaderStageCreateInfo-module-parameter)

Validation Layer:
Validation Error: [ VUID-VkPipelineShaderStageCreateInfo-module-parameter ] Object 0: handle = 0x1b0fe2b0070, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0xfd71dc70 | vkCreateGraphicsPipelines(): pCreateInfos[0] VkShaderModule 0x0[] does not contain valid spirv for stage VK_SHADER_STAGE_VERTEX_BIT. The Vulkan spec states: If module is not VK_NULL_HANDLE, module must be a valid VkShaderModule handle (https://vulkan.lunarg.com/doc/view/1.3.243.0/windows/1.3-extensions/vkspec.html#VUID-VkPipelineShaderStageCreateInfo-module-parameter)

Validation Layer:
Validation Error: [ VUID-VkPipelineShaderStageCreateInfo-module-parameter ] Object 0: handle = 0x1b0fe2b0070, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0xfd71dc70 | vkCreateGraphicsPipelines(): pCreateInfos[0] VkShaderModule 0x0[] does not contain valid spirv for stage VK_SHADER_STAGE_FRAGMENT_BIT. The Vulkan spec states: If module is not VK_NULL_HANDLE, module must be a valid VkShaderModule handle (https://vulkan.lunarg.com/doc/view/1.3.243.0/windows/1.3-extensions/vkspec.html#VUID-VkPipelineShaderStageCreateInfo-module-parameter)

I do not believe that the 'transformVector' function is the issue here because removing the function altogether, but leaving the 'std::vector' of 'VkShader' objects will give the same error:

std::vector<VkDescriptor> descriptors = { ubo, textureSet };
std::vector<VkShader> shaders = { vertShader, fragShader };

std::vector<VkDescriptorSetLayout> descTest = transformVector<VkDescriptorSetLayout, &VkDescriptor::SetLayout>(descriptors);

std::vector<VkPipelineShaderStageCreateInfo> shaderStages = { vertShader.stageInfo, fragShader.stageInfo };

//std::vector<VkPipelineShaderStageCreateInfo> stageTest = transformVector<VkPipelineShaderStageCreateInfo, &VkShader::stageInfo>(Shaders);

VkGraphicsPipeline pipeline(descTest, stageTest);

and moving the 'std::vector' of 'VkShader' objects after the pipeline creation eliminates the validation layer error upon start-up but gives a different validation layer error when the application is closed.

The code:

std::vector<VkDescriptor> descriptors = { ubo, textureSet };

std::vector<VkDescriptorSetLayout> descTest = transformVector<VkDescriptorSetLayout, &VkDescriptor::SetLayout>(descriptors);

std::vector<VkPipelineShaderStageCreateInfo> shaderStages = { vertShader.stageInfo, fragShader.stageInfo };

//std::vector<VkPipelineShaderStageCreateInfo> stageTest = transformVector<VkPipelineShaderStageCreateInfo, &VkShader::stageInfo>(Shaders);

VkGraphicsPipeline pipeline(descTest, stageTest);

std::vector<VkShader> shaders = { vertShader, fragShader };

The error:

Validation Layer:
Validation Error: [ VUID-vkDestroyShaderModule-shaderModule-parameter ] Object 0: handle = 0x1e3744cc190, type = VK_OBJECT_TYPE_INSTANCE; | MessageID = 0x1292ada1 | Invalid VkShaderModule Object 0x4b7df1000000002f. The Vulkan spec states: If shaderModule is not VK_NULL_HANDLE, shaderModule must be a valid VkShaderModule handle (https://vulkan.lunarg.com/doc/view/1.3.243.0/windows/1.3-extensions/vkspec.html#VUID-vkDestroyShaderModule-shaderModule-parameter)

Validation Layer:
Validation Error: [ UNASSIGNED-Threading-Info ] Object 0: handle = 0x4b7df1000000002f, type = VK_OBJECT_TYPE_SHADER_MODULE; | MessageID = 0x5d6b67e2 | Couldn't find VkShaderModule Object 0x4b7df1000000002f. This should not happen and may indicate a bug in the application.

Validation Layer:
Validation Error: [ UNASSIGNED-Threading-Info ] Object 0: handle = 0x4b7df1000000002f, type = VK_OBJECT_TYPE_SHADER_MODULE; | MessageID = 0x5d6b67e2 | Couldn't find VkShaderModule Object 0x4b7df1000000002f. This should not happen and may indicate a bug in the application.

Validation Layer:
Validation Error: [ VUID-vkDestroyShaderModule-shaderModule-parameter ] Object 0: handle = 0x1e3744cc190, type = VK_OBJECT_TYPE_INSTANCE; | MessageID = 0x1292ada1 | Invalid VkShaderModule Object 0xa21a4e0000000030. The Vulkan spec states: If shaderModule is not VK_NULL_HANDLE, shaderModule must be a valid VkShaderModule handle (https://vulkan.lunarg.com/doc/view/1.3.243.0/windows/1.3-extensions/vkspec.html#VUID-vkDestroyShaderModule-shaderModule-parameter)

Validation Layer:
Validation Error: [ UNASSIGNED-Threading-Info ] Object 0: handle = 0xa21a4e0000000030, type = VK_OBJECT_TYPE_SHADER_MODULE; | MessageID = 0x5d6b67e2 | Couldn't find VkShaderModule Object 0xa21a4e0000000030. This should not happen and may indicate a bug in the application.

Validation Layer:
Validation Error: [ UNASSIGNED-Threading-Info ] Object 0: handle = 0xa21a4e0000000030, type = VK_OBJECT_TYPE_SHADER_MODULE; | MessageID = 0x5d6b67e2 | Couldn't find VkShaderModule Object 0xa21a4e0000000030. This should not happen and may indicate a bug in the application.

Validation Layer:
Validation Error: [ VUID-vkDestroyShaderModule-shaderModule-parameter ] Object 0: handle = 0x1e3744cc190, type = VK_OBJECT_TYPE_INSTANCE; | MessageID = 0x1292ada1 | Invalid VkShaderModule Object 0xa21a4e0000000030. The Vulkan spec states: If shaderModule is not VK_NULL_HANDLE, shaderModule must be a valid VkShaderModule handle (https://vulkan.lunarg.com/doc/view/1.3.243.0/windows/1.3-extensions/vkspec.html#VUID-vkDestroyShaderModule-shaderModule-parameter)

Validation Layer:
Validation Error: [ UNASSIGNED-Threading-Info ] Object 0: handle = 0xa21a4e0000000030, type = VK_OBJECT_TYPE_SHADER_MODULE; | MessageID = 0x5d6b67e2 | Couldn't find VkShaderModule Object 0xa21a4e0000000030. This should not happen and may indicate a bug in the application.

Validation Layer:
Validation Error: [ UNASSIGNED-Threading-Info ] Object 0: handle = 0xa21a4e0000000030, type = VK_OBJECT_TYPE_SHADER_MODULE; | MessageID = 0x5d6b67e2 | Couldn't find VkShaderModule Object 0xa21a4e0000000030. This should not happen and may indicate a bug in the application.

Validation Layer:
Validation Error: [ VUID-vkDestroyShaderModule-shaderModule-parameter ] Object 0: handle = 0x1e3744cc190, type = VK_OBJECT_TYPE_INSTANCE; | MessageID = 0x1292ada1 | Invalid VkShaderModule Object 0x4b7df1000000002f. The Vulkan spec states: If shaderModule is not VK_NULL_HANDLE, shaderModule must be a valid VkShaderModule handle (https://vulkan.lunarg.com/doc/view/1.3.243.0/windows/1.3-extensions/vkspec.html#VUID-vkDestroyShaderModule-shaderModule-parameter)

Validation Layer:
Validation Error: [ UNASSIGNED-Threading-Info ] Object 0: handle = 0x4b7df1000000002f, type = VK_OBJECT_TYPE_SHADER_MODULE; | MessageID = 0x5d6b67e2 | Couldn't find VkShaderModule Object 0x4b7df1000000002f. This should not happen and may indicate a bug in the application.

Validation Layer:
Validation Error: [ UNASSIGNED-Threading-Info ] Object 0: handle = 0x4b7df1000000002f, type = VK_OBJECT_TYPE_SHADER_MODULE; | MessageID = 0x5d6b67e2 | Couldn't find VkShaderModule Object 0x4b7df1000000002f. This should not happen and may indicate a bug in the application.

It appears to me that loading the 'std::vector' with the 'VkShader' objects is creating duplicates, but I do understand why it is causing a validation layer callback while creating the pipeline if I am only sending one vertex and one fragment shader when initializing the pipeline. I understand partially why it is causing a validation layer callback in the second case (after creating the pipeline), as the original shaders would have been destroyed, so duplicates would be missing or unable to be destroyed by the VkDevice handle. I would appreciate any insight into why Vulkan is not handling loading the 'VkShader' objects into std::vectors well, as well as any potential solutions.

Edit:

Code behind the VkShader object and VkGraphicsPipeline objects:

struct VkShader {
    VkShaderModule shaderModule;
    VkPipelineShaderStageCreateInfo stageInfo
    { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO };
    VkShader(const std::string& filename, VkShaderStageFlagBits shaderStage) {
        auto shaderCode = readFile(filename);
        createShaderModule(shaderCode);

        stageInfo.stage = shaderStage;
        stageInfo.module = shaderModule;
        stageInfo.pName = "main";
    }
    ~VkShader() {
        vkDestroyShaderModule(VkGPU::device, shaderModule, nullptr);
    }
private:
    void createShaderModule(const std::vector<char>& code) {
        VkShaderModuleCreateInfo createInfo
        { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
        createInfo.codeSize = code.size();
        createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());

        if (vkCreateShaderModule(VkGPU::device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
            throw std::runtime_error("failed to create shader module!");
        }
    }
    // File Reader
    static std::vector<char> readFile(const std::string& filename) {
        std::ifstream file(filename, std::ios::ate | std::ios::binary);

        if (!file.is_open()) {
            throw std::runtime_error("failed to open file!");
        }

        size_t fileSize = (size_t)file.tellg();
        std::vector<char> buffer(fileSize);

        file.seekg(0);
        file.read(buffer.data(), fileSize);

        file.close();

        return buffer;
    }
};

struct VkGraphicsPipeline {
    VkPipeline mPipeline;
    VkPipelineLayout mLayout;

    VkBufferObject<Vertex> VBO;
    VkBufferObject<uint16_t> EBO;

    std::vector<VkDescriptorSet> Sets;

    VkGraphicsPipeline(std::vector<VkDescriptorSetLayout>& layouts, std::vector<VkPipelineShaderStageCreateInfo>& shaderStages) :
        VBO(vertices), EBO(indices) 
    {
        vkLoadSetLayout(layouts);
        createShaderPipeline<Vertex>(shaderStages);
    }
    ~VkGraphicsPipeline() {
        vkDestroyPipeline(VkGPU::device, mPipeline, nullptr);
        vkDestroyPipelineLayout(VkGPU::device, mLayout, nullptr);
    }
    void bind(VkCommandBuffer commandBuffer, uint32_t setCount, VkDescriptorSet* sets) {
        // Pipeline binding
        vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, mLayout, 0, setCount, sets, 0, nullptr);
        vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, mPipeline);

        // Vertex binding (objects to draw)
        VkDeviceSize offsets[] = { 0 };
        vkCmdBindVertexBuffers(commandBuffer, 0, 1, &VBO.mBuffer, offsets);
        vkCmdBindIndexBuffer(commandBuffer, EBO.mBuffer, 0, VK_INDEX_TYPE_UINT16);
    }
private:
    template<typename T>
    void createShaderPipeline(std::vector<VkPipelineShaderStageCreateInfo>& shaderStages) {
        //auto bindingDescription = T::vkCreateBindings();
        //auto attributeDescriptions = T::vkCreateAttributes();
        VkPipelineVertexInputStateCreateInfo vertexInputInfo = T::vkCreateVertexInput();

        VkPipelineInputAssemblyStateCreateInfo inputAssembly
        { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO };
        inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
        inputAssembly.primitiveRestartEnable = VK_FALSE;

        VkPipelineViewportStateCreateInfo viewportState
        { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO };
        viewportState.viewportCount = 1;
        viewportState.scissorCount = 1;

        VkPipelineRasterizationStateCreateInfo rasterizer = VkUtils::vkCreateRaster(VK_POLYGON_MODE_FILL);

        VkPipelineMultisampleStateCreateInfo multisampling
        { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO };
        multisampling.sampleShadingEnable = VK_TRUE; // enable sample shading in the pipeline
        multisampling.minSampleShading = .2f; // min fraction for sample shading; closer to one is smoother
        multisampling.rasterizationSamples = VkGPU::msaaSamples;

        VkPipelineDepthStencilStateCreateInfo depthStencil = VkUtils::vkCreateDepthStencil();

        VkPipelineColorBlendAttachmentState colorBlendAttachment{};
        colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
        colorBlendAttachment.blendEnable = VK_FALSE;

        VkPipelineColorBlendStateCreateInfo colorBlending = VkUtils::vkCreateColorBlend(colorBlendAttachment, VK_FALSE);

        std::vector<VkDynamicState> dynamicStates = {
            VK_DYNAMIC_STATE_VIEWPORT,
            VK_DYNAMIC_STATE_SCISSOR
        };
        VkPipelineDynamicStateCreateInfo dynamicState
        { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO };
        dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
        dynamicState.pDynamicStates = dynamicStates.data();

        VkGraphicsPipelineCreateInfo pipelineInfo
        { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
        pipelineInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
        pipelineInfo.pStages = shaderStages.data();
        //pipelineInfo.pStages = pipeStages;
        pipelineInfo.pVertexInputState = &vertexInputInfo;
        pipelineInfo.pInputAssemblyState = &inputAssembly;
        pipelineInfo.pViewportState = &viewportState;
        pipelineInfo.pRasterizationState = &rasterizer;
        pipelineInfo.pMultisampleState = &multisampling;
        pipelineInfo.pColorBlendState = &colorBlending;
        pipelineInfo.pDynamicState = &dynamicState;
        pipelineInfo.layout = mLayout;
        pipelineInfo.renderPass = VkSwapChain::renderPass;
        pipelineInfo.subpass = 0;
        pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
        pipelineInfo.pDepthStencilState = &depthStencil;

        if (vkCreateGraphicsPipelines(VkGPU::device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &mPipeline) != VK_SUCCESS) {
            throw std::runtime_error("failed to create graphics pipeline!");
        }
    }
    void vkLoadSetLayout(std::vector<VkDescriptorSetLayout>& SetLayout) {
        VkPipelineLayoutCreateInfo pipelineLayoutInfo
        { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
        pipelineLayoutInfo.setLayoutCount = static_cast<uint32_t>(SetLayout.size());
        pipelineLayoutInfo.pSetLayouts = SetLayout.data();

        if (vkCreatePipelineLayout(VkGPU::device, &pipelineLayoutInfo, nullptr, &mLayout) != VK_SUCCESS) {
            throw std::runtime_error("failed to create pipeline layout!");
        }
    }
};
  • All of the actual Vulkan code is hidden behind an abstraction layer. That makes it difficult to know what exactly is going on. – Nicol Bolas Jul 02 '23 at 22:46
  • I edited the post with the code behind the VkShader and VkGraphicsPipeline objects. I hope that it helps! – ModernEraCaveman Jul 02 '23 at 23:07
  • 2
    You're destroying your Vulkan shader module in your `VkShader` destructor, then relying on `vector<>` copying? What's your thinking here? And why didn't you just do a trivial debugging session to see your shader objects being deleted? This has nothing to do with Vulkan, this is C++ 101. – Blindy Jul 02 '23 at 23:16
  • 1
    @ModernEraCaveman: Technically, that's an OpenGL question, but you made the exact same mistake, just in Vulkan. Like Blindy said, it's a C++ problem, not a Vulkan problem. – Nicol Bolas Jul 03 '23 at 03:44

0 Answers0