diff --git a/src/dxvk/dxvk_shader.cpp b/src/dxvk/dxvk_shader.cpp index a3ed01d73..6fa9338ff 100644 --- a/src/dxvk/dxvk_shader.cpp +++ b/src/dxvk/dxvk_shader.cpp @@ -1,3 +1,4 @@ +#include "dxvk_device.h" #include "dxvk_shader.h" #include @@ -159,10 +160,9 @@ namespace dxvk { } - DxvkShaderModule DxvkShader::createShaderModule( - const Rc& vkd, + SpirvCodeBuffer DxvkShader::getCode( const DxvkBindingLayoutObjects* layout, - const DxvkShaderModuleCreateInfo& info) { + const DxvkShaderModuleCreateInfo& state) const { SpirvCodeBuffer spirvCode = m_code.decompress(); uint32_t* code = spirvCode.data(); @@ -180,14 +180,22 @@ namespace dxvk { // For dual-source blending we need to re-map // location 1, index 0 to location 0, index 1 - if (info.fsDualSrcBlend && m_o1IdxOffset && m_o1LocOffset) + if (state.fsDualSrcBlend && m_o1IdxOffset && m_o1LocOffset) std::swap(code[m_o1IdxOffset], code[m_o1LocOffset]); // Replace undefined input variables with zero - for (uint32_t u : bit::BitMask(info.undefinedInputs)) + for (uint32_t u : bit::BitMask(state.undefinedInputs)) eliminateInput(spirvCode, u); - return DxvkShaderModule(vkd, this, spirvCode); + return spirvCode; + } + + + DxvkShaderModule DxvkShader::createShaderModule( + const Rc& vkd, + const DxvkBindingLayoutObjects* layout, + const DxvkShaderModuleCreateInfo& info) { + return DxvkShaderModule(vkd, this, getCode(layout, info)); } @@ -402,4 +410,250 @@ namespace dxvk { } } + + DxvkShaderPipelineLibrary::DxvkShaderPipelineLibrary( + const DxvkDevice* device, + const DxvkShader* shader, + const DxvkBindingLayoutObjects* layout) + : m_device(device), m_shader(shader), m_layout(layout) { + + } + + + DxvkShaderPipelineLibrary::~DxvkShaderPipelineLibrary() { + auto vk = m_device->vkd(); + + vk->vkDestroyPipeline(vk->device(), m_pipeline, nullptr); + vk->vkDestroyPipeline(vk->device(), m_pipelineNoDepthClip, nullptr); + } + + + VkPipeline DxvkShaderPipelineLibrary::getPipelineHandle( + VkPipelineCache cache, + const DxvkShaderPipelineLibraryCompileArgs& args) { + std::lock_guard lock(m_mutex); + + VkPipeline& pipeline = (m_shader->info().stage == VK_SHADER_STAGE_VERTEX_BIT && !args.depthClipEnable) + ? m_pipelineNoDepthClip + : m_pipeline; + + if (pipeline) + return pipeline; + + switch (m_shader->info().stage) { + case VK_SHADER_STAGE_VERTEX_BIT: + pipeline = compileVertexShaderPipeline(cache, args); + break; + + case VK_SHADER_STAGE_FRAGMENT_BIT: + pipeline = compileFragmentShaderPipeline(cache); + break; + + case VK_SHADER_STAGE_COMPUTE_BIT: + pipeline = compileComputeShaderPipeline(cache); + break; + + default: + // Should be unreachable + pipeline = VK_NULL_HANDLE; + } + + return pipeline; + } + + + void DxvkShaderPipelineLibrary::compilePipeline(VkPipelineCache cache) { + // Just compile the pipeline with default args. Implicitly skips + // this step if another thread has compiled the pipeline in the + // meantime, in order to avoid duplicate work. + getPipelineHandle(cache, DxvkShaderPipelineLibraryCompileArgs()); + } + + + VkPipeline DxvkShaderPipelineLibrary::compileVertexShaderPipeline( + VkPipelineCache cache, + const DxvkShaderPipelineLibraryCompileArgs& args) { + auto vk = m_device->vkd(); + + // Set up shader stage. Do not create a shader module. + SpirvCodeBuffer spirv = m_shader->getCode(m_layout, DxvkShaderModuleCreateInfo()); + + VkShaderModuleCreateInfo codeInfo = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; + codeInfo.codeSize = spirv.size(); + codeInfo.pCode = spirv.data(); + + VkPipelineShaderStageCreateInfo stageInfo = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, &codeInfo }; + stageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + stageInfo.pName = "main"; + + // Set up dynamic state. We do not know any pipeline state + // at this time, so make as much state dynamic as we can. + std::array dynamicStates = {{ + VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT_EXT, + VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT_EXT, + VK_DYNAMIC_STATE_DEPTH_BIAS, + VK_DYNAMIC_STATE_CULL_MODE_EXT, + VK_DYNAMIC_STATE_FRONT_FACE_EXT, + }}; + + VkPipelineDynamicStateCreateInfo dyInfo = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO }; + dyInfo.dynamicStateCount = dynamicStates.size(); + dyInfo.pDynamicStates = dynamicStates.data(); + + // All viewport state is dynamic, so we do not need to initialize this. + VkPipelineViewportStateCreateInfo vpInfo = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO }; + + // Set up rasterizer state. Depth bias, cull mode and front face are all + // dynamic, but we do not have dynamic state for depth bias enablement + // with the original version of VK_EXT_extended_dynamic_state, so always + // enable that. Do not support any polygon modes other than FILL. + VkPipelineRasterizationDepthClipStateCreateInfoEXT rsDepthClipInfo = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_DEPTH_CLIP_STATE_CREATE_INFO_EXT }; + + VkPipelineRasterizationStateCreateInfo rsInfo = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; + rsInfo.depthClampEnable = VK_TRUE; + rsInfo.rasterizerDiscardEnable = VK_FALSE; + rsInfo.polygonMode = VK_POLYGON_MODE_FILL; + rsInfo.depthBiasEnable = VK_TRUE; + rsInfo.lineWidth = 1.0f; + + if (m_device->features().extDepthClipEnable.depthClipEnable) { + rsDepthClipInfo.pNext = std::exchange(rsInfo.pNext, &rsDepthClipInfo); + rsDepthClipInfo.depthClipEnable = args.depthClipEnable; + } else { + rsInfo.depthClampEnable = !args.depthClipEnable; + } + + // Only the view mask is used as input, and since we do not use MultiView, it is always 0 + VkPipelineRenderingCreateInfo rtInfo = { VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR }; + + VkGraphicsPipelineLibraryCreateInfoEXT libInfo = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT, &rtInfo }; + libInfo.flags = VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT; + + VkGraphicsPipelineCreateInfo info = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &libInfo }; + info.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR; + info.stageCount = 1; + info.pStages = &stageInfo; + info.pViewportState = &vpInfo; + info.pRasterizationState = &rsInfo; + info.pDynamicState = &dyInfo; + info.layout = m_layout->getPipelineLayout(); + info.basePipelineIndex = -1; + + VkPipeline pipeline = VK_NULL_HANDLE; + + if (vk->vkCreateGraphicsPipelines(vk->device(), cache, 1, &info, nullptr, &pipeline)) + throw DxvkError("DxvkShaderPipelineLibrary: Failed to create compute pipeline"); + + return pipeline; + } + + + VkPipeline DxvkShaderPipelineLibrary::compileFragmentShaderPipeline(VkPipelineCache cache) { + auto vk = m_device->vkd(); + + // Set up shader stage. Do not create a shader module. + SpirvCodeBuffer spirv = m_shader->getCode(m_layout, DxvkShaderModuleCreateInfo()); + + VkShaderModuleCreateInfo codeInfo = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; + codeInfo.codeSize = spirv.size(); + codeInfo.pCode = spirv.data(); + + VkPipelineShaderStageCreateInfo stageInfo = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, &codeInfo }; + stageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + stageInfo.pName = "main"; + + // Set up dynamic state. We do not know any pipeline state + // at this time, so make as much state dynamic as we can. + uint32_t dynamicStateCount = 0; + std::array dynamicStates; + + dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT; + dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT; + dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT; + dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK; + dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_WRITE_MASK; + dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_REFERENCE; + dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT; + dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_OP_EXT; + + if (m_device->features().core.features.depthBounds) { + dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT; + dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_BOUNDS; + } + + VkPipelineDynamicStateCreateInfo dyInfo = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO }; + dyInfo.dynamicStateCount = dynamicStateCount; + dyInfo.pDynamicStates = dynamicStates.data(); + + // Set up multisample state. If sample shading is enabled, assume that + // we only have one sample enabled, with a non-zero sample mask and no + // alpha-to-coverage. + uint32_t msSampleMask = 0x1; + + VkPipelineMultisampleStateCreateInfo msInfo = { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO }; + msInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + msInfo.pSampleMask = &msSampleMask; + msInfo.sampleShadingEnable = VK_TRUE; + msInfo.minSampleShading = 1.0f; + + // All depth-stencil state is dynamic, so no need to initialize this. + // Depth bounds testing is disabled on devices which don't support it. + VkPipelineDepthStencilStateCreateInfo dsInfo = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO }; + + // Only the view mask is used as input, and since we do not use MultiView, it is always 0 + VkPipelineRenderingCreateInfo rtInfo = { VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR }; + + VkGraphicsPipelineLibraryCreateInfoEXT libInfo = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT, &rtInfo }; + libInfo.flags = VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT; + + VkGraphicsPipelineCreateInfo info = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &libInfo }; + info.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR; + info.stageCount = 1; + info.pStages = &stageInfo; + info.pDepthStencilState = &dsInfo; + info.pDynamicState = &dyInfo; + info.layout = m_layout->getPipelineLayout(); + info.basePipelineIndex = -1; + + if (m_shader->flags().test(DxvkShaderFlag::HasSampleRateShading)) + info.pMultisampleState = &msInfo; + + VkPipeline pipeline = VK_NULL_HANDLE; + + if (vk->vkCreateGraphicsPipelines(vk->device(), cache, 1, &info, nullptr, &pipeline)) + throw DxvkError("DxvkShaderPipelineLibrary: Failed to create compute pipeline"); + + return pipeline; + } + + + VkPipeline DxvkShaderPipelineLibrary::compileComputeShaderPipeline(VkPipelineCache cache) { + auto vk = m_device->vkd(); + + // Set up shader stage. Do not create a shader module since we only + // ever call this if graphics pipeline libraries are supported. + SpirvCodeBuffer spirv = m_shader->getCode(m_layout, DxvkShaderModuleCreateInfo()); + + VkShaderModuleCreateInfo codeInfo = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; + codeInfo.codeSize = spirv.size(); + codeInfo.pCode = spirv.data(); + + VkPipelineShaderStageCreateInfo stageInfo = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, &codeInfo }; + stageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; + stageInfo.pName = "main"; + + // Compile the compute pipeline as normal + VkComputePipelineCreateInfo info = { VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO }; + info.stage = stageInfo; + info.layout = m_layout->getPipelineLayout(); + info.basePipelineIndex = -1; + + VkPipeline pipeline = VK_NULL_HANDLE; + + if (vk->vkCreateComputePipelines(vk->device(), cache, 1, &info, nullptr, &pipeline)) + throw DxvkError("DxvkShaderPipelineLibrary: Failed to create compute pipeline"); + + return pipeline; + } + } \ No newline at end of file diff --git a/src/dxvk/dxvk_shader.h b/src/dxvk/dxvk_shader.h index ba798aea5..40e2023bd 100644 --- a/src/dxvk/dxvk_shader.h +++ b/src/dxvk/dxvk_shader.h @@ -120,6 +120,19 @@ namespace dxvk { const DxvkBindingLayout& getBindings() const { return m_bindings; } + + /** + * \brief Patches code using given info + * + * Rewrites binding IDs and potentially fixes up other + * parts of the code depending on pipeline state. + * \param [in] layout Biding layout + * \param [in] state Pipeline state info + * \returns Uncompressed SPIR-V code buffer + */ + SpirvCodeBuffer getCode( + const DxvkBindingLayoutObjects* layout, + const DxvkShaderModuleCreateInfo& state) const; /** * \brief Creates a shader module @@ -273,5 +286,85 @@ namespace dxvk { VkPipelineShaderStageCreateInfo m_stage; }; + + + /** + * \brief Shader pipeline library compile args + */ + struct DxvkShaderPipelineLibraryCompileArgs { + VkBool32 depthClipEnable = VK_TRUE; + + bool operator == (const DxvkShaderPipelineLibraryCompileArgs& other) const { + return depthClipEnable == other.depthClipEnable; + } + + bool operator != (const DxvkShaderPipelineLibraryCompileArgs& other) const { + return !this->operator == (other); + } + }; + + + /** + * \brief Shader pipeline library + * + * Stores a pipeline object for either a complete compute + * pipeline, a pre-rasterization pipeline library consisting + * of a single vertex shader, or a fragment shader pipeline + * library. All state unknown at shader compile time will + * be made dynamic. + */ + class DxvkShaderPipelineLibrary { + + public: + + DxvkShaderPipelineLibrary( + const DxvkDevice* device, + const DxvkShader* shader, + const DxvkBindingLayoutObjects* layout); + + ~DxvkShaderPipelineLibrary(); + + /** + * \brief Queries pipeline handle for the given set of arguments + * + * Either returns an already compiled pipeline library object, or + * performs the compilation step if that has not happened yet. + * \param [in] cache Pipeline cache handle + * \param [in] args Compile arguments + * \returns Vulkan pipeline handle + */ + VkPipeline getPipelineHandle( + VkPipelineCache cache, + const DxvkShaderPipelineLibraryCompileArgs& args); + + /** + * \brief Compiles the pipeline with default arguments + * + * This is meant to be called from a worker thread in + * order to reduce the amount of work done on the app's + * main thread. + * \param [in] cache Pipeline cache handle + */ + void compilePipeline(VkPipelineCache cache); + + private: + + const DxvkDevice* m_device; + const DxvkShader* m_shader; + const DxvkBindingLayoutObjects* m_layout; + + dxvk::mutex m_mutex; + VkPipeline m_pipeline = VK_NULL_HANDLE; + VkPipeline m_pipelineNoDepthClip = VK_NULL_HANDLE; + + VkPipeline compileVertexShaderPipeline( + VkPipelineCache cache, + const DxvkShaderPipelineLibraryCompileArgs& args); + + VkPipeline compileFragmentShaderPipeline(VkPipelineCache cache); + + VkPipeline compileComputeShaderPipeline(VkPipelineCache cache); + + }; } \ No newline at end of file