// Copyright 2018 The SwiftShader Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "VkPipeline.hpp" #include "VkDestroy.hpp" #include "VkDevice.hpp" #include "VkPipelineCache.hpp" #include "VkPipelineLayout.hpp" #include "VkRenderPass.hpp" #include "VkShaderModule.hpp" #include "VkStringify.hpp" #include "Pipeline/ComputeProgram.hpp" #include "Pipeline/SpirvShader.hpp" #include "marl/trace.h" #include "spirv-tools/optimizer.hpp" #include namespace { // preprocessSpirv applies and freezes specializations into constants, and inlines all functions. std::vector preprocessSpirv( std::vector const &code, VkSpecializationInfo const *specializationInfo, bool optimize) { spvtools::Optimizer opt{ vk::SPIRV_VERSION }; opt.SetMessageConsumer([](spv_message_level_t level, const char *source, const spv_position_t &position, const char *message) { switch(level) { case SPV_MSG_FATAL: sw::warn("SPIR-V FATAL: %d:%d %s\n", int(position.line), int(position.column), message); case SPV_MSG_INTERNAL_ERROR: sw::warn("SPIR-V INTERNAL_ERROR: %d:%d %s\n", int(position.line), int(position.column), message); case SPV_MSG_ERROR: sw::warn("SPIR-V ERROR: %d:%d %s\n", int(position.line), int(position.column), message); case SPV_MSG_WARNING: sw::warn("SPIR-V WARNING: %d:%d %s\n", int(position.line), int(position.column), message); case SPV_MSG_INFO: sw::trace("SPIR-V INFO: %d:%d %s\n", int(position.line), int(position.column), message); case SPV_MSG_DEBUG: sw::trace("SPIR-V DEBUG: %d:%d %s\n", int(position.line), int(position.column), message); default: sw::trace("SPIR-V MESSAGE: %d:%d %s\n", int(position.line), int(position.column), message); } }); // If the pipeline uses specialization, apply the specializations before freezing if(specializationInfo) { std::unordered_map> specializations; for(auto i = 0u; i < specializationInfo->mapEntryCount; ++i) { auto const &e = specializationInfo->pMapEntries[i]; auto value_ptr = static_cast(specializationInfo->pData) + e.offset / sizeof(uint32_t); specializations.emplace(e.constantID, std::vector{ value_ptr, value_ptr + e.size / sizeof(uint32_t) }); } opt.RegisterPass(spvtools::CreateSetSpecConstantDefaultValuePass(specializations)); } if(optimize) { // Full optimization list taken from spirv-opt. opt.RegisterPerformancePasses(); } spvtools::OptimizerOptions optimizerOptions = {}; #if defined(NDEBUG) optimizerOptions.set_run_validator(false); #else optimizerOptions.set_run_validator(true); spvtools::ValidatorOptions validatorOptions = {}; validatorOptions.SetScalarBlockLayout(true); // VK_EXT_scalar_block_layout validatorOptions.SetUniformBufferStandardLayout(true); // VK_KHR_uniform_buffer_standard_layout optimizerOptions.set_validator_options(validatorOptions); #endif std::vector optimized; opt.Run(code.data(), code.size(), &optimized, optimizerOptions); if(false) { spvtools::SpirvTools core(vk::SPIRV_VERSION); std::string preOpt; core.Disassemble(code, &preOpt, SPV_BINARY_TO_TEXT_OPTION_NONE); std::string postOpt; core.Disassemble(optimized, &postOpt, SPV_BINARY_TO_TEXT_OPTION_NONE); std::cout << "PRE-OPT: " << preOpt << std::endl << "POST-OPT: " << postOpt << std::endl; } return optimized; } std::shared_ptr createShader( const vk::PipelineCache::SpirvShaderKey &key, const vk::ShaderModule *module, bool robustBufferAccess, const std::shared_ptr &dbgctx) { // Do not optimize the shader if we have a debugger context. // Optimization passes are likely to damage debug information, and reorder // instructions. const bool optimize = !dbgctx; auto code = preprocessSpirv(key.getInsns(), key.getSpecializationInfo(), optimize); ASSERT(code.size() > 0); // If the pipeline has specialization constants, assume they're unique and // use a new serial ID so the shader gets recompiled. uint32_t codeSerialID = (key.getSpecializationInfo() ? vk::ShaderModule::nextSerialID() : module->getSerialID()); // TODO(b/119409619): use allocator. return std::make_shared(codeSerialID, key.getPipelineStage(), key.getEntryPointName().c_str(), code, key.getRenderPass(), key.getSubpassIndex(), robustBufferAccess, dbgctx); } std::shared_ptr createProgram(vk::Device *device, const vk::PipelineCache::ComputeProgramKey &key) { MARL_SCOPED_EVENT("createProgram"); vk::DescriptorSet::Bindings descriptorSets; // FIXME(b/129523279): Delay code generation until invoke time. // TODO(b/119409619): use allocator. auto program = std::make_shared(device, key.getShader(), key.getLayout(), descriptorSets); program->generate(); program->finalize("ComputeProgram"); return program; } } // anonymous namespace namespace vk { Pipeline::Pipeline(PipelineLayout *layout, Device *device) : layout(layout) , device(device) , robustBufferAccess(device->getEnabledFeatures().robustBufferAccess) { layout->incRefCount(); } void Pipeline::destroy(const VkAllocationCallbacks *pAllocator) { destroyPipeline(pAllocator); vk::release(static_cast(*layout), pAllocator); } GraphicsPipeline::GraphicsPipeline(const VkGraphicsPipelineCreateInfo *pCreateInfo, void *mem, Device *device) : Pipeline(vk::Cast(pCreateInfo->layout), device) , state(device, pCreateInfo, layout, robustBufferAccess) , inputs(pCreateInfo->pVertexInputState) { } void GraphicsPipeline::destroyPipeline(const VkAllocationCallbacks *pAllocator) { vertexShader.reset(); fragmentShader.reset(); } size_t GraphicsPipeline::ComputeRequiredAllocationSize(const VkGraphicsPipelineCreateInfo *pCreateInfo) { return 0; } void GraphicsPipeline::getIndexBuffers(uint32_t count, uint32_t first, bool indexed, std::vector> *indexBuffers) const { indexBuffer.getIndexBuffers(state.getTopology(), count, first, indexed, state.hasPrimitiveRestartEnable(), indexBuffers); } bool GraphicsPipeline::containsImageWrite() const { return (vertexShader.get() && vertexShader->containsImageWrite()) || (fragmentShader.get() && fragmentShader->containsImageWrite()); } void GraphicsPipeline::setShader(const VkShaderStageFlagBits &stage, const std::shared_ptr spirvShader) { switch(stage) { case VK_SHADER_STAGE_VERTEX_BIT: ASSERT(vertexShader.get() == nullptr); vertexShader = spirvShader; break; case VK_SHADER_STAGE_FRAGMENT_BIT: ASSERT(fragmentShader.get() == nullptr); fragmentShader = spirvShader; break; default: UNSUPPORTED("Unsupported stage"); break; } } const std::shared_ptr GraphicsPipeline::getShader(const VkShaderStageFlagBits &stage) const { switch(stage) { case VK_SHADER_STAGE_VERTEX_BIT: return vertexShader; case VK_SHADER_STAGE_FRAGMENT_BIT: return fragmentShader; default: UNSUPPORTED("Unsupported stage"); return fragmentShader; } } void GraphicsPipeline::compileShaders(const VkAllocationCallbacks *pAllocator, const VkGraphicsPipelineCreateInfo *pCreateInfo, PipelineCache *pPipelineCache) { for(auto pStage = pCreateInfo->pStages; pStage != pCreateInfo->pStages + pCreateInfo->stageCount; pStage++) { if(pStage->flags != 0) { // Vulkan 1.2: "flags must be 0" UNSUPPORTED("pStage->flags %d", int(pStage->flags)); } const ShaderModule *module = vk::Cast(pStage->module); const PipelineCache::SpirvShaderKey key(pStage->stage, pStage->pName, module->getCode(), vk::Cast(pCreateInfo->renderPass), pCreateInfo->subpass, pStage->pSpecializationInfo); auto pipelineStage = key.getPipelineStage(); if(pPipelineCache) { auto shader = pPipelineCache->getOrCreateShader(key, [&] { return createShader(key, module, robustBufferAccess, device->getDebuggerContext()); }); setShader(pipelineStage, shader); } else { auto shader = createShader(key, module, robustBufferAccess, device->getDebuggerContext()); setShader(pipelineStage, shader); } } } ComputePipeline::ComputePipeline(const VkComputePipelineCreateInfo *pCreateInfo, void *mem, Device *device) : Pipeline(vk::Cast(pCreateInfo->layout), device) { } void ComputePipeline::destroyPipeline(const VkAllocationCallbacks *pAllocator) { shader.reset(); program.reset(); } size_t ComputePipeline::ComputeRequiredAllocationSize(const VkComputePipelineCreateInfo *pCreateInfo) { return 0; } void ComputePipeline::compileShaders(const VkAllocationCallbacks *pAllocator, const VkComputePipelineCreateInfo *pCreateInfo, PipelineCache *pPipelineCache) { auto &stage = pCreateInfo->stage; const ShaderModule *module = vk::Cast(stage.module); ASSERT(shader.get() == nullptr); ASSERT(program.get() == nullptr); const PipelineCache::SpirvShaderKey shaderKey( stage.stage, stage.pName, module->getCode(), nullptr, 0, stage.pSpecializationInfo); if(pPipelineCache) { shader = pPipelineCache->getOrCreateShader(shaderKey, [&] { return createShader(shaderKey, module, robustBufferAccess, device->getDebuggerContext()); }); const PipelineCache::ComputeProgramKey programKey(shader.get(), layout); program = pPipelineCache->getOrCreateComputeProgram(programKey, [&] { return createProgram(device, programKey); }); } else { shader = createShader(shaderKey, module, robustBufferAccess, device->getDebuggerContext()); const PipelineCache::ComputeProgramKey programKey(shader.get(), layout); program = createProgram(device, programKey); } } void ComputePipeline::run(uint32_t baseGroupX, uint32_t baseGroupY, uint32_t baseGroupZ, uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ, vk::DescriptorSet::Array const &descriptorSetObjects, vk::DescriptorSet::Bindings const &descriptorSets, vk::DescriptorSet::DynamicOffsets const &descriptorDynamicOffsets, vk::Pipeline::PushConstantStorage const &pushConstants) { ASSERT_OR_RETURN(program != nullptr); program->run( descriptorSetObjects, descriptorSets, descriptorDynamicOffsets, pushConstants, baseGroupX, baseGroupY, baseGroupZ, groupCountX, groupCountY, groupCountZ); } } // namespace vk