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!");
}
}
};