#ifndef _GRAPHICS_PIPELINE_VULKAN_H #define _GRAPHICS_PIPELINE_VULKAN_H #include "graphics-pipeline.hpp" #include #include #include #include #include "vulkan-utils.hpp" // TODO: Maybe change the name of this struct so I can call the list something other than descriptorInfoList struct DescriptorInfo { VkDescriptorType type; VkShaderStageFlags stageFlags; // Only one of the below properties should be set vector* bufferDataList; VkDescriptorImageInfo* imageData; }; // TODO: Change the index type to uint32_t and check the Vulkan Tutorial loading model section as a reference // TODO: Create a typedef for index type so I can easily change uin16_t to something else later template struct SceneObject { vector vertices; vector indices; }; template class GraphicsPipeline_Vulkan : public GraphicsPipeline { public: GraphicsPipeline_Vulkan(); GraphicsPipeline_Vulkan(VkPhysicalDevice physicalDevice, VkDevice device, VkRenderPass renderPass, Viewport viewport, size_t vertexCapacity, size_t indexCapacity); ~GraphicsPipeline_Vulkan(); void updateRenderPass(VkRenderPass renderPass); // Maybe I should rename these to addVertexAttribute (addVaryingAttribute) and addUniformAttribute void addAttribute(VkFormat format, size_t offset); void addDescriptorInfo(VkDescriptorType type, VkShaderStageFlags stageFlags, vector* bufferData); void addDescriptorInfo(VkDescriptorType type, VkShaderStageFlags stageFlags, VkDescriptorImageInfo* imageData); void createPipeline(string vertShaderFile, string fragShaderFile); void createDescriptorSetLayout(); void createDescriptorPool(vector& swapChainImages); void createDescriptorSets(vector& swapChainImages); void createRenderCommands(VkCommandBuffer& commandBuffer, uint32_t currentImage); const vector>& getObjects(); void addObject(const vector& vertices, vector indices, VkCommandPool commandPool, VkQueue graphicsQueue); void cleanup(); void cleanupBuffers(); private: VkPhysicalDevice physicalDevice; VkDevice device; VkRenderPass renderPass; VkPipeline pipeline; VkPipelineLayout pipelineLayout; VkVertexInputBindingDescription bindingDescription; vector attributeDescriptions; vector descriptorInfoList; VkDescriptorSetLayout descriptorSetLayout; VkDescriptorPool descriptorPool; vector descriptorSets; size_t numVertices; size_t vertexCapacity; VkBuffer vertexBuffer; VkDeviceMemory vertexBufferMemory; size_t numIndices; size_t indexCapacity; VkBuffer indexBuffer; VkDeviceMemory indexBufferMemory; // TODO: THe objects vector isn't used at all in this class, except in the method that returns // the number of objects. Move this vector and the SceneObject declaration into VulkanGame, esp. // since I'll be adding other // object-specific fields sich as transforms to SceneObject later vector> objects; VkShaderModule createShaderModule(const vector& code); vector readFile(const string& filename); void resizeVertexBuffer(VkCommandPool commandPool, VkQueue graphicsQueue); void resizeIndexBuffer(VkCommandPool commandPool, VkQueue graphicsQueue); }; /*** PUBLIC METHODS ***/ template GraphicsPipeline_Vulkan::GraphicsPipeline_Vulkan() { } // TODO: Verify that vertex capacity and index capacity are both > 0 template GraphicsPipeline_Vulkan::GraphicsPipeline_Vulkan(VkPhysicalDevice physicalDevice, VkDevice device, VkRenderPass renderPass, Viewport viewport, size_t vertexCapacity, size_t indexCapacity) { this->physicalDevice = physicalDevice; this->device = device; this->renderPass = renderPass; this->viewport = viewport; // Since there is only one array of vertex data, we use binding = 0 // I'll probably do that for the foreseeable future // I can calculate the stride myself given info about all the varying attributes this->bindingDescription.binding = 0; this->bindingDescription.stride = sizeof(VertexType); this->bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; this->numVertices = 0; this->vertexCapacity = vertexCapacity; VulkanUtils::createBuffer(device, physicalDevice, vertexCapacity * sizeof(VertexType), VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); this->numIndices = 0; this->indexCapacity = indexCapacity; VulkanUtils::createBuffer(device, physicalDevice, indexCapacity * sizeof(uint16_t), VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); } template GraphicsPipeline_Vulkan::~GraphicsPipeline_Vulkan() { } template void GraphicsPipeline_Vulkan::updateRenderPass(VkRenderPass renderPass) { this->renderPass = renderPass; } template void GraphicsPipeline_Vulkan::addAttribute(VkFormat format, size_t offset) { VkVertexInputAttributeDescription attributeDesc = {}; attributeDesc.binding = 0; attributeDesc.location = this->attributeDescriptions.size(); attributeDesc.format = format; attributeDesc.offset = offset; this->attributeDescriptions.push_back(attributeDesc); } template void GraphicsPipeline_Vulkan::addDescriptorInfo(VkDescriptorType type, VkShaderStageFlags stageFlags, vector* bufferData) { this->descriptorInfoList.push_back({ type, stageFlags, bufferData, nullptr }); } template void GraphicsPipeline_Vulkan::addDescriptorInfo(VkDescriptorType type, VkShaderStageFlags stageFlags, VkDescriptorImageInfo* imageData) { this->descriptorInfoList.push_back({ type, stageFlags, nullptr, imageData }); } template void GraphicsPipeline_Vulkan::createPipeline(string vertShaderFile, string fragShaderFile) { vector vertShaderCode = readFile(vertShaderFile); vector fragShaderCode = readFile(fragShaderFile); VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; vertShaderStageInfo.module = vertShaderModule; vertShaderStageInfo.pName = "main"; VkPipelineShaderStageCreateInfo fragShaderStageInfo = {}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; fragShaderStageInfo.pName = "main"; VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo }; VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 1; vertexInputInfo.vertexAttributeDescriptionCount = static_cast(this->attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &this->bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = this->attributeDescriptions.data(); VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; inputAssembly.primitiveRestartEnable = VK_FALSE; VkViewport viewport = {}; viewport.x = (float)this->viewport.x; viewport.y = (float)this->viewport.y; viewport.width = (float)this->viewport.width; viewport.height = (float)this->viewport.height; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; VkRect2D scissor = {}; scissor.offset = { 0, 0 }; scissor.extent = { (uint32_t)this->viewport.width, (uint32_t)this->viewport.height }; VkPipelineViewportStateCreateInfo viewportState = {}; viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportState.viewportCount = 1; viewportState.pViewports = &viewport; viewportState.scissorCount = 1; viewportState.pScissors = &scissor; VkPipelineRasterizationStateCreateInfo rasterizer = {}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; rasterizer.polygonMode = VK_POLYGON_MODE_FILL; rasterizer.lineWidth = 1.0f; rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; VkPipelineMultisampleStateCreateInfo multisampling = {}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; 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_TRUE; colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; VkPipelineColorBlendStateCreateInfo colorBlending = {}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; colorBlending.attachmentCount = 1; colorBlending.pAttachments = &colorBlendAttachment; colorBlending.blendConstants[0] = 0.0f; colorBlending.blendConstants[1] = 0.0f; colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; VkPipelineDepthStencilStateCreateInfo depthStencil = {}; depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depthStencil.depthTestEnable = VK_TRUE; depthStencil.depthWriteEnable = VK_TRUE; depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; depthStencil.depthBoundsTestEnable = VK_FALSE; depthStencil.minDepthBounds = 0.0f; depthStencil.maxDepthBounds = 1.0f; depthStencil.stencilTestEnable = VK_FALSE; depthStencil.front = {}; depthStencil.back = {}; VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; pipelineLayoutInfo.pSetLayouts = &this->descriptorSetLayout; pipelineLayoutInfo.pushConstantRangeCount = 0; if (vkCreatePipelineLayout(this->device, &pipelineLayoutInfo, nullptr, &this->pipelineLayout) != VK_SUCCESS) { throw runtime_error("failed to create pipeline layout!"); } VkGraphicsPipelineCreateInfo pipelineInfo = {}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; pipelineInfo.pVertexInputState = &vertexInputInfo; pipelineInfo.pInputAssemblyState = &inputAssembly; pipelineInfo.pViewportState = &viewportState; pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pDepthStencilState = &depthStencil; pipelineInfo.pColorBlendState = &colorBlending; pipelineInfo.pDynamicState = nullptr; pipelineInfo.layout = this->pipelineLayout; pipelineInfo.renderPass = this->renderPass; pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; pipelineInfo.basePipelineIndex = -1; if (vkCreateGraphicsPipelines(this->device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &this->pipeline) != VK_SUCCESS) { throw runtime_error("failed to create graphics pipeline!"); } vkDestroyShaderModule(this->device, vertShaderModule, nullptr); vkDestroyShaderModule(this->device, fragShaderModule, nullptr); } template void GraphicsPipeline_Vulkan::createDescriptorSetLayout() { vector bindings(this->descriptorInfoList.size()); for (size_t i = 0; i < bindings.size(); i++) { bindings[i].binding = i; bindings[i].descriptorCount = 1; bindings[i].descriptorType = this->descriptorInfoList[i].type; bindings[i].stageFlags = this->descriptorInfoList[i].stageFlags; bindings[i].pImmutableSamplers = nullptr; } VkDescriptorSetLayoutCreateInfo layoutInfo = {}; layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; layoutInfo.bindingCount = static_cast(bindings.size()); layoutInfo.pBindings = bindings.data(); if (vkCreateDescriptorSetLayout(this->device, &layoutInfo, nullptr, &this->descriptorSetLayout) != VK_SUCCESS) { throw runtime_error("failed to create descriptor set layout!"); } } template void GraphicsPipeline_Vulkan::createDescriptorPool(vector& swapChainImages) { vector poolSizes(this->descriptorInfoList.size()); for (size_t i = 0; i < poolSizes.size(); i++) { poolSizes[i].type = this->descriptorInfoList[i].type; poolSizes[i].descriptorCount = static_cast(swapChainImages.size()); } VkDescriptorPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; poolInfo.poolSizeCount = static_cast(poolSizes.size()); poolInfo.pPoolSizes = poolSizes.data(); poolInfo.maxSets = static_cast(swapChainImages.size()); if (vkCreateDescriptorPool(this->device, &poolInfo, nullptr, &this->descriptorPool) != VK_SUCCESS) { throw runtime_error("failed to create descriptor pool!"); } } template void GraphicsPipeline_Vulkan::createDescriptorSets(vector& swapChainImages) { vector layouts(swapChainImages.size(), this->descriptorSetLayout); VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = this->descriptorPool; allocInfo.descriptorSetCount = static_cast(swapChainImages.size()); allocInfo.pSetLayouts = layouts.data(); this->descriptorSets.resize(swapChainImages.size()); if (vkAllocateDescriptorSets(device, &allocInfo, this->descriptorSets.data()) != VK_SUCCESS) { throw runtime_error("failed to allocate descriptor sets!"); } for (size_t i = 0; i < swapChainImages.size(); i++) { vector descriptorWrites(this->descriptorInfoList.size()); for (size_t j = 0; j < descriptorWrites.size(); j++) { descriptorWrites[j].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; descriptorWrites[j].dstSet = this->descriptorSets[i]; descriptorWrites[j].dstBinding = j; descriptorWrites[j].dstArrayElement = 0; descriptorWrites[j].descriptorType = this->descriptorInfoList[j].type; descriptorWrites[j].descriptorCount = 1; descriptorWrites[j].pBufferInfo = nullptr; descriptorWrites[j].pImageInfo = nullptr; descriptorWrites[j].pTexelBufferView = nullptr; switch (descriptorWrites[j].descriptorType) { case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: descriptorWrites[j].pBufferInfo = &(*this->descriptorInfoList[j].bufferDataList)[i]; break; case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: descriptorWrites[j].pImageInfo = this->descriptorInfoList[j].imageData; break; default: throw runtime_error("Unknown descriptor type: " + to_string(descriptorWrites[j].descriptorType)); } } vkUpdateDescriptorSets(this->device, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } } template void GraphicsPipeline_Vulkan::createRenderCommands(VkCommandBuffer& commandBuffer, uint32_t currentImage) { vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentImage], 0, nullptr); VkBuffer vertexBuffers[] = { vertexBuffer }; VkDeviceSize offsets[] = { 0 }; vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); vkCmdDrawIndexed(commandBuffer, static_cast(numIndices), 1, 0, 0, 0); } template const vector>& GraphicsPipeline_Vulkan::getObjects() { return objects; } template void GraphicsPipeline_Vulkan::addObject(const vector& vertices, vector indices, VkCommandPool commandPool, VkQueue graphicsQueue) { if (numVertices + vertices.size() > vertexCapacity) { resizeVertexBuffer(commandPool, graphicsQueue); } if (numIndices + indices.size() > indexCapacity) { resizeIndexBuffer(commandPool, graphicsQueue); } for (uint16_t& idx : indices) { idx += numVertices; } objects.push_back({ vertices, indices }); VulkanUtils::copyDataToBuffer(device, physicalDevice, commandPool, vertices, vertexBuffer, numVertices, graphicsQueue); numVertices += vertices.size(); VulkanUtils::copyDataToBuffer(device, physicalDevice, commandPool, indices, indexBuffer, numIndices, graphicsQueue); numIndices += indices.size(); } template void GraphicsPipeline_Vulkan::cleanup() { vkDestroyPipeline(device, pipeline, nullptr); vkDestroyDescriptorPool(device, descriptorPool, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); } template void GraphicsPipeline_Vulkan::cleanupBuffers() { vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); vkDestroyBuffer(device, vertexBuffer, nullptr); vkFreeMemory(device, vertexBufferMemory, nullptr); vkDestroyBuffer(device, indexBuffer, nullptr); vkFreeMemory(device, indexBufferMemory, nullptr); } /*** PRIVATE METHODS ***/ template VkShaderModule GraphicsPipeline_Vulkan::createShaderModule(const vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); createInfo.pCode = reinterpret_cast(code.data()); VkShaderModule shaderModule; if (vkCreateShaderModule(this->device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw runtime_error("failed to create shader module!"); } return shaderModule; } template vector GraphicsPipeline_Vulkan::readFile(const string& filename) { ifstream file(filename, ios::ate | ios::binary); if (!file.is_open()) { throw runtime_error("failed to open file!"); } size_t fileSize = (size_t)file.tellg(); vector buffer(fileSize); file.seekg(0); file.read(buffer.data(), fileSize); file.close(); return buffer; } template void GraphicsPipeline_Vulkan::resizeVertexBuffer(VkCommandPool commandPool, VkQueue graphicsQueue) { VkBuffer newVertexBuffer; VkDeviceMemory newVertexBufferMemory; vertexCapacity *= 2; VulkanUtils::createBuffer(device, physicalDevice, vertexCapacity * sizeof(VertexType), VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, newVertexBuffer, newVertexBufferMemory); VulkanUtils::copyBuffer(device, commandPool, vertexBuffer, newVertexBuffer, 0, 0, numVertices * sizeof(VertexType), graphicsQueue); vkDestroyBuffer(device, vertexBuffer, nullptr); vkFreeMemory(device, vertexBufferMemory, nullptr); vertexBuffer = newVertexBuffer; vertexBufferMemory = newVertexBufferMemory; } template void GraphicsPipeline_Vulkan::resizeIndexBuffer(VkCommandPool commandPool, VkQueue graphicsQueue) { VkBuffer newIndexBuffer; VkDeviceMemory newIndexBufferMemory; indexCapacity *= 2; VulkanUtils::createBuffer(device, physicalDevice, indexCapacity * sizeof(uint16_t), VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, newIndexBuffer, newIndexBufferMemory); VulkanUtils::copyBuffer(device, commandPool, indexBuffer, newIndexBuffer, 0, 0, numIndices * sizeof(uint16_t), graphicsQueue); vkDestroyBuffer(device, indexBuffer, nullptr); vkFreeMemory(device, indexBufferMemory, nullptr); indexBuffer = newIndexBuffer; indexBufferMemory = newIndexBufferMemory; } #endif // _GRAPHICS_PIPELINE_VULKAN_H