/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "PipelineState.h" #include "lib/hash.h" #include "ps/CLogger.h" #include "ps/containers/StaticVector.h" #include "renderer/backend/vulkan/Device.h" #include "renderer/backend/vulkan/Framebuffer.h" #include "renderer/backend/vulkan/Mapping.h" #include "renderer/backend/vulkan/ShaderProgram.h" #include "renderer/backend/vulkan/Utilities.h" #include namespace Renderer { namespace Backend { namespace Vulkan { namespace { VkStencilOpState MakeStencilOpState(const SStencilOpState& opState) { VkStencilOpState result{}; result.failOp = Mapping::FromStencilOp(opState.failOp); result.passOp = Mapping::FromStencilOp(opState.passOp); result.depthFailOp = Mapping::FromStencilOp(opState.depthFailOp); result.compareOp = Mapping::FromCompareOp(opState.compareOp); return result; } } // anonymous namespace size_t CGraphicsPipelineState::CacheKeyHash::operator()(const CacheKey& cacheKey) const { size_t seed = 0; hash_combine(seed, cacheKey.vertexInputLayoutUID); hash_combine(seed, cacheKey.framebufferUID); return seed; } bool CGraphicsPipelineState::CacheKeyEqual::operator()(const CacheKey& lhs, const CacheKey& rhs) const { return lhs.vertexInputLayoutUID == rhs.vertexInputLayoutUID && lhs.framebufferUID == rhs.framebufferUID; } // static std::unique_ptr CGraphicsPipelineState::Create( CDevice* device, const SGraphicsPipelineStateDesc& desc) { ENSURE(desc.shaderProgram); std::unique_ptr pipelineState{new CGraphicsPipelineState()}; pipelineState->m_Device = device; pipelineState->m_UID = device->GenerateNextDeviceObjectUID(); pipelineState->m_Desc = desc; return pipelineState; } CGraphicsPipelineState::~CGraphicsPipelineState() { for (const auto& it : m_PipelineMap) { if (it.second != VK_NULL_HANDLE) m_Device->ScheduleObjectToDestroy( VK_OBJECT_TYPE_PIPELINE, it.second, VK_NULL_HANDLE); } } VkPipeline CGraphicsPipelineState::GetOrCreatePipeline( const CVertexInputLayout* vertexInputLayout, CFramebuffer* framebuffer) { CShaderProgram* shaderProgram = m_Desc.shaderProgram->As(); const CacheKey cacheKey = { vertexInputLayout->GetUID(), framebuffer->GetUID() }; auto it = m_PipelineMap.find(cacheKey); if (it != m_PipelineMap.end()) return it->second; PS::StaticVector attributeBindings; PS::StaticVector attributes; const VkPhysicalDeviceLimits& limits = m_Device->GetChoosenPhysicalDevice().properties.limits; const uint32_t maxVertexInputAttributes = limits.maxVertexInputAttributes; const uint32_t maxVertexInputAttributeOffset = limits.maxVertexInputAttributeOffset; for (const SVertexAttributeFormat& vertexAttributeFormat : vertexInputLayout->GetAttributes()) { ENSURE(vertexAttributeFormat.bindingSlot < maxVertexInputAttributes); ENSURE(vertexAttributeFormat.offset < maxVertexInputAttributeOffset); const uint32_t streamLocation = shaderProgram->GetStreamLocation(vertexAttributeFormat.stream); if (streamLocation == std::numeric_limits::max()) continue; auto it = std::find_if(attributeBindings.begin(), attributeBindings.end(), [slot = vertexAttributeFormat.bindingSlot](const VkVertexInputBindingDescription& desc) -> bool { return desc.binding == slot; }); const VkVertexInputBindingDescription desc{ vertexAttributeFormat.bindingSlot, vertexAttributeFormat.stride, vertexAttributeFormat.rate == VertexAttributeRate::PER_INSTANCE ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX }; if (it == attributeBindings.end()) attributeBindings.emplace_back(desc); else { // All attribute sharing the same binding slot should have the same description. ENSURE(desc.inputRate == it->inputRate && desc.stride == it->stride); } attributes.push_back({ streamLocation, vertexAttributeFormat.bindingSlot, Mapping::FromFormat(vertexAttributeFormat.format), vertexAttributeFormat.offset }); } VkPipelineVertexInputStateCreateInfo vertexInputCreateInfo{}; vertexInputCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputCreateInfo.vertexBindingDescriptionCount = std::size(attributeBindings); vertexInputCreateInfo.pVertexBindingDescriptions = attributeBindings.data(); vertexInputCreateInfo.vertexAttributeDescriptionCount = std::size(attributes); vertexInputCreateInfo.pVertexAttributeDescriptions = attributes.data(); VkPipelineInputAssemblyStateCreateInfo inputAssemblyCreateInfo{}; inputAssemblyCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAssemblyCreateInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; inputAssemblyCreateInfo.primitiveRestartEnable = VK_FALSE; // We don't need to specify sizes for viewports and scissors as they're in // dynamic state. VkViewport viewport{}; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = 0.0f; viewport.height = 0.0f; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; VkRect2D scissor{}; VkPipelineViewportStateCreateInfo viewportStateCreateInfo{}; viewportStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportStateCreateInfo.viewportCount = 1; viewportStateCreateInfo.pViewports = &viewport; viewportStateCreateInfo.scissorCount = 1; viewportStateCreateInfo.pScissors = &scissor; VkPipelineDepthStencilStateCreateInfo depthStencilStateCreateInfo{}; depthStencilStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depthStencilStateCreateInfo.depthTestEnable = m_Desc.depthStencilState.depthTestEnabled ? VK_TRUE : VK_FALSE; depthStencilStateCreateInfo.depthWriteEnable = m_Desc.depthStencilState.depthWriteEnabled ? VK_TRUE : VK_FALSE; depthStencilStateCreateInfo.depthCompareOp = Mapping::FromCompareOp(m_Desc.depthStencilState.depthCompareOp); depthStencilStateCreateInfo.stencilTestEnable = m_Desc.depthStencilState.stencilTestEnabled ? VK_TRUE : VK_FALSE; depthStencilStateCreateInfo.front = MakeStencilOpState(m_Desc.depthStencilState.stencilFrontFace); depthStencilStateCreateInfo.front.reference = m_Desc.depthStencilState.stencilReference; depthStencilStateCreateInfo.front.compareMask = m_Desc.depthStencilState.stencilReadMask; depthStencilStateCreateInfo.front.writeMask = m_Desc.depthStencilState.stencilWriteMask; depthStencilStateCreateInfo.back = MakeStencilOpState(m_Desc.depthStencilState.stencilBackFace); depthStencilStateCreateInfo.back.reference = m_Desc.depthStencilState.stencilReference; depthStencilStateCreateInfo.back.compareMask = m_Desc.depthStencilState.stencilReadMask; depthStencilStateCreateInfo.back.writeMask = m_Desc.depthStencilState.stencilWriteMask; VkPipelineRasterizationStateCreateInfo rasterizationStateCreateInfo{}; rasterizationStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizationStateCreateInfo.depthClampEnable = VK_FALSE; rasterizationStateCreateInfo.rasterizerDiscardEnable = VK_FALSE; const PolygonMode polygonMode = m_Device->GetChoosenPhysicalDevice().features.fillModeNonSolid ? m_Desc.rasterizationState.polygonMode : PolygonMode::FILL; rasterizationStateCreateInfo.polygonMode = Mapping::FromPolygonMode(polygonMode); rasterizationStateCreateInfo.cullMode = Mapping::FromCullMode(m_Desc.rasterizationState.cullMode); rasterizationStateCreateInfo.frontFace = m_Desc.rasterizationState.frontFace == FrontFace::CLOCKWISE ? VK_FRONT_FACE_CLOCKWISE : VK_FRONT_FACE_COUNTER_CLOCKWISE; rasterizationStateCreateInfo.depthBiasEnable = m_Desc.rasterizationState.depthBiasEnabled ? VK_TRUE : VK_FALSE; rasterizationStateCreateInfo.depthBiasConstantFactor = m_Desc.rasterizationState.depthBiasConstantFactor; rasterizationStateCreateInfo.depthBiasSlopeFactor = m_Desc.rasterizationState.depthBiasSlopeFactor; rasterizationStateCreateInfo.lineWidth = 1.0f; VkPipelineMultisampleStateCreateInfo multisampleStateCreateInfo{}; multisampleStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampleStateCreateInfo.rasterizationSamples = Mapping::FromSampleCount(framebuffer->GetSampleCount()); multisampleStateCreateInfo.minSampleShading = 1.0f; VkPipelineColorBlendAttachmentState colorBlendAttachmentState{}; colorBlendAttachmentState.blendEnable = m_Desc.blendState.enabled ? VK_TRUE : VK_FALSE; colorBlendAttachmentState.colorBlendOp = Mapping::FromBlendOp(m_Desc.blendState.colorBlendOp); colorBlendAttachmentState.srcColorBlendFactor = Mapping::FromBlendFactor(m_Desc.blendState.srcColorBlendFactor); colorBlendAttachmentState.dstColorBlendFactor = Mapping::FromBlendFactor(m_Desc.blendState.dstColorBlendFactor); colorBlendAttachmentState.alphaBlendOp = Mapping::FromBlendOp(m_Desc.blendState.alphaBlendOp); colorBlendAttachmentState.srcAlphaBlendFactor = Mapping::FromBlendFactor(m_Desc.blendState.srcAlphaBlendFactor); colorBlendAttachmentState.dstAlphaBlendFactor = Mapping::FromBlendFactor(m_Desc.blendState.dstAlphaBlendFactor); colorBlendAttachmentState.colorWriteMask = Mapping::FromColorWriteMask(m_Desc.blendState.colorWriteMask); VkPipelineColorBlendStateCreateInfo colorBlendStateCreateInfo{}; colorBlendStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlendStateCreateInfo.logicOpEnable = VK_FALSE; colorBlendStateCreateInfo.logicOp = VK_LOGIC_OP_CLEAR; colorBlendStateCreateInfo.attachmentCount = 1; colorBlendStateCreateInfo.pAttachments = &colorBlendAttachmentState; colorBlendStateCreateInfo.blendConstants[0] = m_Desc.blendState.constant.r; colorBlendStateCreateInfo.blendConstants[1] = m_Desc.blendState.constant.g; colorBlendStateCreateInfo.blendConstants[2] = m_Desc.blendState.constant.b; colorBlendStateCreateInfo.blendConstants[3] = m_Desc.blendState.constant.a; const VkDynamicState dynamicStates[] = { VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_VIEWPORT }; VkPipelineDynamicStateCreateInfo dynamicStateCreateInfo{}; dynamicStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicStateCreateInfo.dynamicStateCount = static_cast(std::size(dynamicStates)); dynamicStateCreateInfo.pDynamicStates = dynamicStates; VkGraphicsPipelineCreateInfo pipelineCreateInfo{}; pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineCreateInfo.stageCount = shaderProgram->GetStages().size(); pipelineCreateInfo.pStages = shaderProgram->GetStages().data(); pipelineCreateInfo.pVertexInputState = &vertexInputCreateInfo; pipelineCreateInfo.pInputAssemblyState = &inputAssemblyCreateInfo; pipelineCreateInfo.pViewportState = &viewportStateCreateInfo; pipelineCreateInfo.pRasterizationState = &rasterizationStateCreateInfo; pipelineCreateInfo.pMultisampleState = &multisampleStateCreateInfo; // If renderPass is not VK_NULL_HANDLE, the pipeline is being created with // fragment shader state, and subpass uses a depth/stencil attachment, // pDepthStencilState must be a not null pointer. if (framebuffer->GetDepthStencilAttachment()) pipelineCreateInfo.pDepthStencilState = &depthStencilStateCreateInfo; if (!framebuffer->GetColorAttachments().empty()) pipelineCreateInfo.pColorBlendState = &colorBlendStateCreateInfo; pipelineCreateInfo.pDynamicState = &dynamicStateCreateInfo; pipelineCreateInfo.layout = shaderProgram->GetPipelineLayout(); pipelineCreateInfo.renderPass = framebuffer->GetRenderPass(); pipelineCreateInfo.subpass = 0; pipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE; pipelineCreateInfo.basePipelineIndex = -1; VkPipeline pipeline = VK_NULL_HANDLE; ENSURE_VK_SUCCESS(vkCreateGraphicsPipelines( m_Device->GetVkDevice(), VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &pipeline)); m_PipelineMap[cacheKey] = pipeline; return pipeline; } IDevice* CGraphicsPipelineState::GetDevice() { return m_Device; } // static std::unique_ptr CComputePipelineState::Create( CDevice* device, const SComputePipelineStateDesc& desc) { ENSURE(desc.shaderProgram); CShaderProgram* shaderProgram = desc.shaderProgram->As(); if (shaderProgram->GetStages().empty()) return nullptr; std::unique_ptr pipelineState{new CComputePipelineState()}; pipelineState->m_Device = device; pipelineState->m_UID = device->GenerateNextDeviceObjectUID(); pipelineState->m_Desc = desc; VkComputePipelineCreateInfo pipelineCreateInfo{}; pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; pipelineCreateInfo.layout = shaderProgram->GetPipelineLayout(); pipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE; pipelineCreateInfo.basePipelineIndex = -1; pipelineCreateInfo.stage = shaderProgram->GetStages()[0]; ENSURE_VK_SUCCESS(vkCreateComputePipelines( device->GetVkDevice(), VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &pipelineState->m_Pipeline)); return pipelineState; } CComputePipelineState::~CComputePipelineState() { if (m_Pipeline != VK_NULL_HANDLE) m_Device->ScheduleObjectToDestroy( VK_OBJECT_TYPE_PIPELINE, m_Pipeline, VK_NULL_HANDLE); } IDevice* CComputePipelineState::GetDevice() { return m_Device; } } // namespace Vulkan } // namespace Backend } // namespace Renderer