/* ** Vulkan backend ** Copyright (c) 2016-2020 Magnus Norddahl ** ** This software is provided 'as-is', without any express or implied ** warranty. In no event will the authors be held liable for any damages ** arising from the use of this software. ** ** Permission is granted to anyone to use this software for any purpose, ** including commercial applications, and to alter it and redistribute it ** freely, subject to the following restrictions: ** ** 1. The origin of this software must not be misrepresented; you must not ** claim that you wrote the original software. If you use this software ** in a product, an acknowledgment in the product documentation would be ** appreciated but is not required. ** 2. Altered source versions must be plainly marked as such, and must not be ** misrepresented as being the original software. ** 3. This notice may not be removed or altered from any source distribution. ** */ #include "vk_renderpass.h" #include "vk_renderbuffers.h" #include "vk_renderstate.h" #include "vulkan/textures/vk_samplers.h" #include "vulkan/shaders/vk_shader.h" #include "vulkan/system/vk_builders.h" #include "vulkan/system/vk_framebuffer.h" #include "vulkan/system/vk_buffers.h" #include "flatvertices.h" #include "hw_viewpointuniforms.h" #include "v_2ddrawer.h" VkRenderPassManager::VkRenderPassManager() { } VkRenderPassManager::~VkRenderPassManager() { DynamicSet.reset(); // Needed since it must come before destruction of DynamicDescriptorPool } void VkRenderPassManager::Init() { CreateDynamicSetLayout(); CreateDescriptorPool(); CreateDynamicSet(); CreateNullTexture(); } void VkRenderPassManager::RenderBuffersReset() { RenderPassSetup.clear(); } void VkRenderPassManager::TextureSetPoolReset() { if (auto fb = GetVulkanFrameBuffer()) { auto &deleteList = fb->FrameDeleteList; for (auto &desc : TextureDescriptorPools) { deleteList.DescriptorPools.push_back(std::move(desc)); } } NullTextureDescriptorSet.reset(); TextureDescriptorPools.clear(); TextureDescriptorSetsLeft = 0; TextureDescriptorsLeft = 0; } VkRenderPassSetup *VkRenderPassManager::GetRenderPass(const VkRenderPassKey &key) { auto &item = RenderPassSetup[key]; if (!item) item.reset(new VkRenderPassSetup(key)); return item.get(); } int VkRenderPassManager::GetVertexFormat(int numBindingPoints, int numAttributes, size_t stride, const FVertexBufferAttribute *attrs) { for (size_t i = 0; i < VertexFormats.size(); i++) { const auto &f = VertexFormats[i]; if (f.Attrs.size() == (size_t)numAttributes && f.NumBindingPoints == numBindingPoints && f.Stride == stride) { bool matches = true; for (int j = 0; j < numAttributes; j++) { if (memcmp(&f.Attrs[j], &attrs[j], sizeof(FVertexBufferAttribute)) != 0) { matches = false; break; } } if (matches) return (int)i; } } VkVertexFormat fmt; fmt.NumBindingPoints = numBindingPoints; fmt.Stride = stride; fmt.UseVertexData = 0; for (int j = 0; j < numAttributes; j++) { if (attrs[j].location == VATTR_COLOR) fmt.UseVertexData |= 1; else if (attrs[j].location == VATTR_NORMAL) fmt.UseVertexData |= 2; fmt.Attrs.push_back(attrs[j]); } VertexFormats.push_back(fmt); return (int)VertexFormats.size() - 1; } VkVertexFormat *VkRenderPassManager::GetVertexFormat(int index) { return &VertexFormats[index]; } void VkRenderPassManager::CreateDynamicSetLayout() { DescriptorSetLayoutBuilder builder; builder.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); builder.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT); builder.addBinding(2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); builder.addBinding(3, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); builder.addBinding(4, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT); DynamicSetLayout = builder.create(GetVulkanFrameBuffer()->device); DynamicSetLayout->SetDebugName("VkRenderPassManager.DynamicSetLayout"); } VulkanDescriptorSetLayout *VkRenderPassManager::GetTextureSetLayout(int numLayers) { if (TextureSetLayouts.size() < (size_t)numLayers) TextureSetLayouts.resize(numLayers); auto &layout = TextureSetLayouts[numLayers - 1]; if (layout) return layout.get(); DescriptorSetLayoutBuilder builder; for (int i = 0; i < numLayers; i++) { builder.addBinding(i, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT); } layout = builder.create(GetVulkanFrameBuffer()->device); layout->SetDebugName("VkRenderPassManager.TextureSetLayout"); return layout.get(); } VulkanPipelineLayout* VkRenderPassManager::GetPipelineLayout(int numLayers) { if (PipelineLayouts.size() <= (size_t)numLayers) PipelineLayouts.resize(numLayers + 1); auto &layout = PipelineLayouts[numLayers]; if (layout) return layout.get(); PipelineLayoutBuilder builder; builder.addSetLayout(DynamicSetLayout.get()); if (numLayers != 0) builder.addSetLayout(GetTextureSetLayout(numLayers)); builder.addPushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstants)); layout = builder.create(GetVulkanFrameBuffer()->device); layout->SetDebugName("VkRenderPassManager.PipelineLayout"); return layout.get(); } void VkRenderPassManager::CreateDescriptorPool() { DescriptorPoolBuilder builder; builder.addPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 3); builder.addPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); builder.addPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1); builder.setMaxSets(1); DynamicDescriptorPool = builder.create(GetVulkanFrameBuffer()->device); DynamicDescriptorPool->SetDebugName("VkRenderPassManager.DynamicDescriptorPool"); } void VkRenderPassManager::CreateDynamicSet() { DynamicSet = DynamicDescriptorPool->allocate(DynamicSetLayout.get()); if (!DynamicSet) I_FatalError("CreateDynamicSet failed.\n"); } void VkRenderPassManager::CreateNullTexture() { auto fb = GetVulkanFrameBuffer(); ImageBuilder imgbuilder; imgbuilder.setFormat(VK_FORMAT_R8G8B8A8_UNORM); imgbuilder.setSize(1, 1); imgbuilder.setUsage(VK_IMAGE_USAGE_SAMPLED_BIT); NullTexture = imgbuilder.create(fb->device); NullTexture->SetDebugName("VkRenderPassManager.NullTexture"); ImageViewBuilder viewbuilder; viewbuilder.setImage(NullTexture.get(), VK_FORMAT_R8G8B8A8_UNORM); NullTextureView = viewbuilder.create(fb->device); NullTextureView->SetDebugName("VkRenderPassManager.NullTextureView"); PipelineBarrier barrier; barrier.addImage(NullTexture.get(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT); barrier.execute(fb->GetTransferCommands(), VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); } VulkanDescriptorSet* VkRenderPassManager::GetNullTextureDescriptorSet() { if (!NullTextureDescriptorSet) { NullTextureDescriptorSet = AllocateTextureDescriptorSet(SHADER_MIN_REQUIRED_TEXTURE_LAYERS); auto fb = GetVulkanFrameBuffer(); WriteDescriptors update; for (int i = 0; i < SHADER_MIN_REQUIRED_TEXTURE_LAYERS; i++) { update.addCombinedImageSampler(NullTextureDescriptorSet.get(), i, NullTextureView.get(), fb->GetSamplerManager()->Get(CLAMP_XY_NOMIP), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); } update.updateSets(fb->device); } return NullTextureDescriptorSet.get(); } VulkanImageView* VkRenderPassManager::GetNullTextureView() { return NullTextureView.get(); } void VkRenderPassManager::UpdateDynamicSet() { auto fb = GetVulkanFrameBuffer(); // In some rare cases drawing commands may already have been created before VulkanFrameBuffer::BeginFrame is called. // Make sure there there are no active command buffers using DynamicSet when we update it: fb->GetRenderState()->EndRenderPass(); fb->WaitForCommands(false); WriteDescriptors update; update.addBuffer(DynamicSet.get(), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, fb->ViewpointUBO->mBuffer.get(), 0, sizeof(HWViewpointUniforms)); update.addBuffer(DynamicSet.get(), 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, fb->LightBufferSSO->mBuffer.get()); update.addBuffer(DynamicSet.get(), 2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, fb->MatrixBuffer->UniformBuffer->mBuffer.get(), 0, sizeof(MatricesUBO)); update.addBuffer(DynamicSet.get(), 3, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, fb->StreamBuffer->UniformBuffer->mBuffer.get(), 0, sizeof(StreamUBO)); update.addCombinedImageSampler(DynamicSet.get(), 4, fb->GetBuffers()->Shadowmap.View.get(), fb->GetBuffers()->ShadowmapSampler.get(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); update.updateSets(fb->device); } std::unique_ptr VkRenderPassManager::AllocateTextureDescriptorSet(int numLayers) { if (TextureDescriptorSetsLeft == 0 || TextureDescriptorsLeft < numLayers) { TextureDescriptorSetsLeft = 1000; TextureDescriptorsLeft = 2000; DescriptorPoolBuilder builder; builder.addPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, TextureDescriptorsLeft); builder.setMaxSets(TextureDescriptorSetsLeft); TextureDescriptorPools.push_back(builder.create(GetVulkanFrameBuffer()->device)); TextureDescriptorPools.back()->SetDebugName("VkRenderPassManager.TextureDescriptorPool"); } TextureDescriptorSetsLeft--; TextureDescriptorsLeft -= numLayers; return TextureDescriptorPools.back()->allocate(GetTextureSetLayout(numLayers)); } ///////////////////////////////////////////////////////////////////////////// VkRenderPassSetup::VkRenderPassSetup(const VkRenderPassKey &key) : PassKey(key) { } std::unique_ptr VkRenderPassSetup::CreateRenderPass(int clearTargets) { auto buffers = GetVulkanFrameBuffer()->GetBuffers(); VkFormat drawBufferFormats[] = { VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_A2R10G10B10_UNORM_PACK32 }; RenderPassBuilder builder; builder.addAttachment( PassKey.DrawBufferFormat, (VkSampleCountFlagBits)PassKey.Samples, (clearTargets & CT_Color) ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_STORE_OP_STORE, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); for (int i = 1; i < PassKey.DrawBuffers; i++) { builder.addAttachment( drawBufferFormats[i], buffers->GetSceneSamples(), (clearTargets & CT_Color) ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_STORE_OP_STORE, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); } if (PassKey.DepthStencil) { builder.addDepthStencilAttachment( buffers->SceneDepthStencilFormat, PassKey.DrawBufferFormat == VK_FORMAT_R8G8B8A8_UNORM ? VK_SAMPLE_COUNT_1_BIT : buffers->GetSceneSamples(), (clearTargets & CT_Depth) ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_STORE_OP_STORE, (clearTargets & CT_Stencil) ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_STORE_OP_STORE, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); } builder.addSubpass(); for (int i = 0; i < PassKey.DrawBuffers; i++) builder.addSubpassColorAttachmentRef(i, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); if (PassKey.DepthStencil) { builder.addSubpassDepthStencilAttachmentRef(PassKey.DrawBuffers, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); builder.addExternalSubpassDependency( VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT); } else { builder.addExternalSubpassDependency( VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT); } auto renderpass = builder.create(GetVulkanFrameBuffer()->device); renderpass->SetDebugName("VkRenderPassSetup.RenderPass"); return renderpass; } VulkanRenderPass *VkRenderPassSetup::GetRenderPass(int clearTargets) { if (!RenderPasses[clearTargets]) RenderPasses[clearTargets] = CreateRenderPass(clearTargets); return RenderPasses[clearTargets].get(); } VulkanPipeline *VkRenderPassSetup::GetPipeline(const VkPipelineKey &key) { auto &item = Pipelines[key]; if (!item) item = CreatePipeline(key); return item.get(); } std::unique_ptr VkRenderPassSetup::CreatePipeline(const VkPipelineKey &key) { auto fb = GetVulkanFrameBuffer(); GraphicsPipelineBuilder builder; VkShaderProgram *program; if (key.SpecialEffect != EFF_NONE) { program = fb->GetShaderManager()->GetEffect(key.SpecialEffect, PassKey.DrawBuffers > 1 ? GBUFFER_PASS : NORMAL_PASS); } else { program = fb->GetShaderManager()->Get(key.EffectState, key.AlphaTest, PassKey.DrawBuffers > 1 ? GBUFFER_PASS : NORMAL_PASS); } builder.addVertexShader(program->vert.get()); builder.addFragmentShader(program->frag.get()); const VkVertexFormat &vfmt = *fb->GetRenderPassManager()->GetVertexFormat(key.VertexFormat); for (int i = 0; i < vfmt.NumBindingPoints; i++) builder.addVertexBufferBinding(i, vfmt.Stride); const static VkFormat vkfmts[] = { VK_FORMAT_R32G32B32A32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32_SFLOAT, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_A2B10G10R10_SNORM_PACK32 }; bool inputLocations[6] = { false, false, false, false, false, false }; for (size_t i = 0; i < vfmt.Attrs.size(); i++) { const auto &attr = vfmt.Attrs[i]; builder.addVertexAttribute(attr.location, attr.binding, vkfmts[attr.format], attr.offset); inputLocations[attr.location] = true; } // Vulkan requires an attribute binding for each location specified in the shader for (int i = 0; i < 6; i++) { if (!inputLocations[i]) builder.addVertexAttribute(i, 0, VK_FORMAT_R32G32B32_SFLOAT, 0); } builder.addDynamicState(VK_DYNAMIC_STATE_VIEWPORT); builder.addDynamicState(VK_DYNAMIC_STATE_SCISSOR); // builder.addDynamicState(VK_DYNAMIC_STATE_LINE_WIDTH); builder.addDynamicState(VK_DYNAMIC_STATE_DEPTH_BIAS); // builder.addDynamicState(VK_DYNAMIC_STATE_BLEND_CONSTANTS); // builder.addDynamicState(VK_DYNAMIC_STATE_DEPTH_BOUNDS); // builder.addDynamicState(VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK); // builder.addDynamicState(VK_DYNAMIC_STATE_STENCIL_WRITE_MASK); builder.addDynamicState(VK_DYNAMIC_STATE_STENCIL_REFERENCE); // Note: the actual values are ignored since we use dynamic viewport+scissor states builder.setViewport(0.0f, 0.0f, 320.0f, 200.0f); builder.setScissor(0, 0, 320, 200); static const VkPrimitiveTopology vktopology[] = { VK_PRIMITIVE_TOPOLOGY_POINT_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP }; static const VkStencilOp op2vk[] = { VK_STENCIL_OP_KEEP, VK_STENCIL_OP_INCREMENT_AND_CLAMP, VK_STENCIL_OP_DECREMENT_AND_CLAMP }; static const VkCompareOp depthfunc2vk[] = { VK_COMPARE_OP_LESS, VK_COMPARE_OP_LESS_OR_EQUAL, VK_COMPARE_OP_ALWAYS }; builder.setTopology(vktopology[key.DrawType]); builder.setDepthStencilEnable(key.DepthTest, key.DepthWrite, key.StencilTest); builder.setDepthFunc(depthfunc2vk[key.DepthFunc]); builder.setDepthClampEnable(key.DepthClamp); builder.setDepthBias(key.DepthBias, 0.0f, 0.0f, 0.0f); // Note: CCW and CW is intentionally swapped here because the vulkan and opengl coordinate systems differ. // main.vp addresses this by patching up gl_Position.z, which has the side effect of flipping the sign of the front face calculations. builder.setCull(key.CullMode == Cull_None ? VK_CULL_MODE_NONE : VK_CULL_MODE_BACK_BIT, key.CullMode == Cull_CW ? VK_FRONT_FACE_COUNTER_CLOCKWISE : VK_FRONT_FACE_CLOCKWISE); builder.setColorWriteMask((VkColorComponentFlags)key.ColorMask); builder.setStencil(VK_STENCIL_OP_KEEP, op2vk[key.StencilPassOp], VK_STENCIL_OP_KEEP, VK_COMPARE_OP_EQUAL, 0xffffffff, 0xffffffff, 0); builder.setBlendMode(key.RenderStyle); builder.setSubpassColorAttachmentCount(PassKey.DrawBuffers); builder.setRasterizationSamples((VkSampleCountFlagBits)PassKey.Samples); builder.setLayout(fb->GetRenderPassManager()->GetPipelineLayout(key.NumTextureLayers)); builder.setRenderPass(GetRenderPass(0)); auto pipeline = builder.create(fb->device); pipeline->SetDebugName("VkRenderPassSetup.Pipeline"); return pipeline; }