// 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 { // optimizeSpirv() applies and freezes specializations into constants, and runs spirv-opt. sw::SpirvBinary optimizeSpirv(const vk::PipelineCache::SpirvBinaryKey &key) { const sw::SpirvBinary &code = key.getBinary(); const VkSpecializationInfo *specializationInfo = key.getSpecializationInfo(); bool optimize = key.getOptimization(); 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; const uint8_t *specializationData = static_cast(specializationInfo->pData); for(uint32_t i = 0; i < specializationInfo->mapEntryCount; i++) { const VkSpecializationMapEntry &entry = specializationInfo->pMapEntries[i]; const uint8_t *value_ptr = specializationData + entry.offset; std::vector value(reinterpret_cast(value_ptr), reinterpret_cast(value_ptr + entry.size)); specializations.emplace(entry.constantID, std::move(value)); } opt.RegisterPass(spvtools::CreateSetSpecConstantDefaultValuePass(specializations)); } if(optimize) { // Remove DontInline flags so the optimizer force-inlines all functions, // as we currently don't support OpFunctionCall (b/141246700). opt.RegisterPass(spvtools::CreateRemoveDontInlinePass()); // 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 validatorOptions.SetAllowLocalSizeId(true); // VK_KHR_maintenance4 optimizerOptions.set_validator_options(validatorOptions); #endif sw::SpirvBinary optimized; opt.Run(code.data(), code.size(), &optimized, optimizerOptions); ASSERT(optimized.size() > 0); 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 createProgram(vk::Device *device, std::shared_ptr shader, const vk::PipelineLayout *layout) { MARL_SCOPED_EVENT("createProgram"); vk::DescriptorSet::Bindings descriptorSets; // TODO(b/129523279): Delay code generation until dispatch time. // TODO(b/119409619): use allocator. auto program = std::make_shared(device, shader, layout, descriptorSets); program->generate(); program->finalize("ComputeProgram"); return program; } class PipelineCreationFeedback { public: PipelineCreationFeedback(const VkGraphicsPipelineCreateInfo *pCreateInfo) : pipelineCreationFeedback(GetPipelineCreationFeedback(pCreateInfo->pNext)) { pipelineCreationBegins(); } PipelineCreationFeedback(const VkComputePipelineCreateInfo *pCreateInfo) : pipelineCreationFeedback(GetPipelineCreationFeedback(pCreateInfo->pNext)) { pipelineCreationBegins(); } ~PipelineCreationFeedback() { pipelineCreationEnds(); } void stageCreationBegins(uint32_t stage) { if(pipelineCreationFeedback && (stage < pipelineCreationFeedback->pipelineStageCreationFeedbackCount)) { // Record stage creation begin time pipelineCreationFeedback->pPipelineStageCreationFeedbacks[stage].duration = now(); } } void cacheHit(uint32_t stage) { if(pipelineCreationFeedback) { pipelineCreationFeedback->pPipelineCreationFeedback->flags |= VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT; if(stage < pipelineCreationFeedback->pipelineStageCreationFeedbackCount) { pipelineCreationFeedback->pPipelineStageCreationFeedbacks[stage].flags |= VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT; } } } void stageCreationEnds(uint32_t stage) { if(pipelineCreationFeedback && (stage < pipelineCreationFeedback->pipelineStageCreationFeedbackCount)) { pipelineCreationFeedback->pPipelineStageCreationFeedbacks[stage].flags |= VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT; pipelineCreationFeedback->pPipelineStageCreationFeedbacks[stage].duration = now() - pipelineCreationFeedback->pPipelineStageCreationFeedbacks[stage].duration; } } void pipelineCreationError() { clear(); pipelineCreationFeedback = nullptr; } private: static const VkPipelineCreationFeedbackCreateInfo *GetPipelineCreationFeedback(const void *pNext) { return vk::GetExtendedStruct(pNext, VK_STRUCTURE_TYPE_PIPELINE_CREATION_FEEDBACK_CREATE_INFO); } void pipelineCreationBegins() { if(pipelineCreationFeedback) { clear(); // Record pipeline creation begin time pipelineCreationFeedback->pPipelineCreationFeedback->duration = now(); } } void pipelineCreationEnds() { if(pipelineCreationFeedback) { pipelineCreationFeedback->pPipelineCreationFeedback->flags |= VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT; pipelineCreationFeedback->pPipelineCreationFeedback->duration = now() - pipelineCreationFeedback->pPipelineCreationFeedback->duration; } } void clear() { if(pipelineCreationFeedback) { // Clear all flags and durations pipelineCreationFeedback->pPipelineCreationFeedback->flags = 0; pipelineCreationFeedback->pPipelineCreationFeedback->duration = 0; for(uint32_t i = 0; i < pipelineCreationFeedback->pipelineStageCreationFeedbackCount; i++) { pipelineCreationFeedback->pPipelineStageCreationFeedbacks[i].flags = 0; pipelineCreationFeedback->pPipelineStageCreationFeedbacks[i].duration = 0; } } } uint64_t now() { return std::chrono::time_point_cast(std::chrono::system_clock::now()).time_since_epoch().count(); } const VkPipelineCreationFeedbackCreateInfo *pipelineCreationFeedback = nullptr; }; bool getRobustBufferAccess(VkPipelineRobustnessBufferBehaviorEXT behavior, bool inheritRobustBufferAccess) { // Based on behavior: // - : // * For pipelines, use device's robustBufferAccess // * For shaders, use pipeline's robustBufferAccess // Note that pipeline's robustBufferAccess is already set to device's if not overriden. // - Default: Use device's robustBufferAccess // - Disabled / Enabled: Override to disabled or enabled // // This function is passed "DEFAULT" when override is not provided, and // inheritRobustBufferAccess is appropriately set to the device or pipeline's // robustBufferAccess switch(behavior) { case VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DEVICE_DEFAULT_EXT: return inheritRobustBufferAccess; case VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DISABLED_EXT: return false; case VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_ROBUST_BUFFER_ACCESS_EXT: return true; default: UNSUPPORTED("Unsupported robustness behavior"); return true; } } bool getRobustBufferAccess(const VkPipelineRobustnessCreateInfoEXT *overrideRobustness, bool deviceRobustBufferAccess, bool inheritRobustBufferAccess) { VkPipelineRobustnessBufferBehaviorEXT storageBehavior = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DEVICE_DEFAULT_EXT; VkPipelineRobustnessBufferBehaviorEXT uniformBehavior = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DEVICE_DEFAULT_EXT; VkPipelineRobustnessBufferBehaviorEXT vertexBehavior = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DEVICE_DEFAULT_EXT; if(overrideRobustness) { storageBehavior = overrideRobustness->storageBuffers; uniformBehavior = overrideRobustness->uniformBuffers; vertexBehavior = overrideRobustness->vertexInputs; inheritRobustBufferAccess = deviceRobustBufferAccess; } bool storageRobustBufferAccess = getRobustBufferAccess(storageBehavior, inheritRobustBufferAccess); bool uniformRobustBufferAccess = getRobustBufferAccess(uniformBehavior, inheritRobustBufferAccess); bool vertexRobustBufferAccess = getRobustBufferAccess(vertexBehavior, inheritRobustBufferAccess); // Note: in the initial implementation, enabling robust access for any buffer enables it for // all. TODO(b/185122256) split robustBufferAccess in the pipeline and shaders into three // categories and provide robustness for storage, uniform and vertex buffers accordingly. return storageRobustBufferAccess || uniformRobustBufferAccess || vertexRobustBufferAccess; } bool getPipelineRobustBufferAccess(const void *pNext, vk::Device *device) { const VkPipelineRobustnessCreateInfoEXT *overrideRobustness = vk::GetExtendedStruct(pNext, VK_STRUCTURE_TYPE_PIPELINE_ROBUSTNESS_CREATE_INFO_EXT); const bool deviceRobustBufferAccess = device->getEnabledFeatures().robustBufferAccess; // For pipelines, there's no robustBufferAccess to inherit from. Default and no-override // both lead to using the device's robustBufferAccess. return getRobustBufferAccess(overrideRobustness, deviceRobustBufferAccess, deviceRobustBufferAccess); } bool getPipelineStageRobustBufferAccess(const void *pNext, vk::Device *device, bool pipelineRobustBufferAccess) { const VkPipelineRobustnessCreateInfoEXT *overrideRobustness = vk::GetExtendedStruct(pNext, VK_STRUCTURE_TYPE_PIPELINE_ROBUSTNESS_CREATE_INFO_EXT); const bool deviceRobustBufferAccess = device->getEnabledFeatures().robustBufferAccess; return getRobustBufferAccess(overrideRobustness, deviceRobustBufferAccess, pipelineRobustBufferAccess); } } // anonymous namespace namespace vk { Pipeline::Pipeline(PipelineLayout *layout, Device *device, bool robustBufferAccess) : layout(layout) , device(device) , robustBufferAccess(robustBufferAccess) { if(layout) { layout->incRefCount(); } } void Pipeline::destroy(const VkAllocationCallbacks *pAllocator) { destroyPipeline(pAllocator); if(layout) { vk::release(static_cast(*layout), pAllocator); } } GraphicsPipeline::GraphicsPipeline(const VkGraphicsPipelineCreateInfo *pCreateInfo, void *mem, Device *device) : Pipeline(vk::Cast(pCreateInfo->layout), device, getPipelineRobustBufferAccess(pCreateInfo->pNext, device)) , state(device, pCreateInfo, layout) { // Either the vertex input interface comes from a pipeline library, or the // VkGraphicsPipelineCreateInfo itself. Same with shaders. const auto *libraryCreateInfo = GetExtendedStruct(pCreateInfo->pNext, VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR); bool vertexInputInterfaceInLibraries = false; bool fragmentOutputInterfaceInLibraries = false; if(libraryCreateInfo) { for(uint32_t i = 0; i < libraryCreateInfo->libraryCount; ++i) { const auto *library = static_cast(vk::Cast(libraryCreateInfo->pLibraries[i])); if(library->state.hasVertexInputInterfaceState()) { inputs = library->inputs; vertexInputInterfaceInLibraries = true; } if(library->state.hasPreRasterizationState()) { vertexShader = library->vertexShader; } if(library->state.hasFragmentState()) { fragmentShader = library->fragmentShader; } if(library->state.hasFragmentOutputInterfaceState()) { memcpy(attachments.indexToLocation, library->attachments.indexToLocation, sizeof(attachments.indexToLocation)); memcpy(attachments.locationToIndex, library->attachments.locationToIndex, sizeof(attachments.locationToIndex)); fragmentOutputInterfaceInLibraries = true; } } } if(state.hasVertexInputInterfaceState() && !vertexInputInterfaceInLibraries) { inputs.initialize(pCreateInfo->pVertexInputState, pCreateInfo->pDynamicState); } if(state.hasFragmentOutputInterfaceState() && !fragmentOutputInterfaceInLibraries) { const auto *colorMapping = GetExtendedStruct(pCreateInfo->pNext, VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_LOCATION_INFO_KHR); if(colorMapping) { // Note that with VK_KHR_dynamic_rendering_local_read, if // VkRenderingAttachmentLocationInfoKHR is provided, setting an index to // VK_ATTACHMENT_UNUSED disables output for that attachment, even if write // mask is not explicitly disabled. for(uint32_t i = 0; i < sw::MAX_COLOR_BUFFERS; ++i) { attachments.indexToLocation[i] = VK_ATTACHMENT_UNUSED; attachments.locationToIndex[i] = VK_ATTACHMENT_UNUSED; } for(uint32_t i = 0; i < colorMapping->colorAttachmentCount; ++i) { const uint32_t location = colorMapping->pColorAttachmentLocations[i]; if(location != VK_ATTACHMENT_UNUSED) { attachments.indexToLocation[i] = location; attachments.locationToIndex[location] = i; } } } else { for(uint32_t i = 0; i < sw::MAX_COLOR_BUFFERS; ++i) { attachments.indexToLocation[i] = i; attachments.locationToIndex[i] = i; } } } } void GraphicsPipeline::destroyPipeline(const VkAllocationCallbacks *pAllocator) { vertexShader.reset(); fragmentShader.reset(); } size_t GraphicsPipeline::ComputeRequiredAllocationSize(const VkGraphicsPipelineCreateInfo *pCreateInfo) { return 0; } VkGraphicsPipelineLibraryFlagsEXT GraphicsPipeline::GetGraphicsPipelineSubset(const VkGraphicsPipelineCreateInfo *pCreateInfo) { const auto *libraryCreateInfo = vk::GetExtendedStruct(pCreateInfo->pNext, VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR); const auto *graphicsLibraryCreateInfo = vk::GetExtendedStruct(pCreateInfo->pNext, VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT); if(graphicsLibraryCreateInfo) { return graphicsLibraryCreateInfo->flags; } // > If this structure is omitted, and either VkGraphicsPipelineCreateInfo::flags // > includes VK_PIPELINE_CREATE_LIBRARY_BIT_KHR or the // > VkGraphicsPipelineCreateInfo::pNext chain includes a VkPipelineLibraryCreateInfoKHR // > structure with a libraryCount greater than 0, it is as if flags is 0. Otherwise if // > this structure is omitted, it is as if flags includes all possible subsets of the // > graphics pipeline (i.e. a complete graphics pipeline). // // The above basically says that when a pipeline is created: // - If not a library and not created from libraries, it's a complete pipeline (i.e. // Vulkan 1.0 pipelines) // - If only created from other libraries, no state is taken from // VkGraphicsPipelineCreateInfo. // // Otherwise the behavior when creating a library from other libraries is that some // state is taken from VkGraphicsPipelineCreateInfo and some from the libraries. const bool isLibrary = (pCreateInfo->flags & VK_PIPELINE_CREATE_LIBRARY_BIT_KHR) != 0; if(isLibrary || (libraryCreateInfo && libraryCreateInfo->libraryCount > 0)) { return 0; } return VK_GRAPHICS_PIPELINE_LIBRARY_VERTEX_INPUT_INTERFACE_BIT_EXT | VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT | VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT | VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT; } void GraphicsPipeline::getIndexBuffers(const vk::DynamicState &dynamicState, uint32_t count, uint32_t first, bool indexed, std::vector> *indexBuffers) const { const vk::VertexInputInterfaceState &vertexInputInterfaceState = state.getVertexInputInterfaceState(); const VkPrimitiveTopology topology = vertexInputInterfaceState.hasDynamicTopology() ? dynamicState.primitiveTopology : vertexInputInterfaceState.getTopology(); const bool hasPrimitiveRestartEnable = vertexInputInterfaceState.hasDynamicPrimitiveRestartEnable() ? dynamicState.primitiveRestartEnable : vertexInputInterfaceState.hasPrimitiveRestartEnable(); indexBuffer.getIndexBuffers(topology, count, first, indexed, hasPrimitiveRestartEnable, indexBuffers); } bool GraphicsPipeline::preRasterizationContainsImageWrite() const { return vertexShader.get() && vertexShader->containsImageWrite(); } bool GraphicsPipeline::fragmentContainsImageWrite() const { return 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; } } VkResult GraphicsPipeline::compileShaders(const VkAllocationCallbacks *pAllocator, const VkGraphicsPipelineCreateInfo *pCreateInfo, PipelineCache *pPipelineCache) { PipelineCreationFeedback pipelineCreationFeedback(pCreateInfo); VkGraphicsPipelineLibraryFlagsEXT pipelineSubset = GetGraphicsPipelineSubset(pCreateInfo); const bool expectVertexShader = (pipelineSubset & VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT) != 0; const bool expectFragmentShader = (pipelineSubset & VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT) != 0; const auto *inputAttachmentMapping = GetExtendedStruct(pCreateInfo->pNext, VK_STRUCTURE_TYPE_RENDERING_INPUT_ATTACHMENT_INDEX_INFO_KHR); for(uint32_t stageIndex = 0; stageIndex < pCreateInfo->stageCount; stageIndex++) { const VkPipelineShaderStageCreateInfo &stageInfo = pCreateInfo->pStages[stageIndex]; // Ignore stages that don't exist in the pipeline library. if((stageInfo.stage == VK_SHADER_STAGE_VERTEX_BIT && !expectVertexShader) || (stageInfo.stage == VK_SHADER_STAGE_FRAGMENT_BIT && !expectFragmentShader)) { continue; } pipelineCreationFeedback.stageCreationBegins(stageIndex); if((stageInfo.flags & ~(VK_PIPELINE_SHADER_STAGE_CREATE_ALLOW_VARYING_SUBGROUP_SIZE_BIT | VK_PIPELINE_SHADER_STAGE_CREATE_REQUIRE_FULL_SUBGROUPS_BIT)) != 0) { UNSUPPORTED("pStage->flags 0x%08X", int(stageInfo.flags)); } const bool optimize = true; // TODO(b/251802301): Don't optimize when debugging shaders. const ShaderModule *module = vk::Cast(stageInfo.module); // VK_EXT_graphics_pipeline_library allows VkShaderModuleCreateInfo to be chained to // VkPipelineShaderStageCreateInfo, which is used if stageInfo.module is // VK_NULL_HANDLE. VkShaderModule tempModule = {}; if(stageInfo.module == VK_NULL_HANDLE) { const auto *moduleCreateInfo = vk::GetExtendedStruct(stageInfo.pNext, VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO); ASSERT(moduleCreateInfo); VkResult createResult = vk::ShaderModule::Create(nullptr, moduleCreateInfo, &tempModule); if(createResult != VK_SUCCESS) { return createResult; } module = vk::Cast(tempModule); } const PipelineCache::SpirvBinaryKey key(module->getBinary(), stageInfo.pSpecializationInfo, robustBufferAccess, optimize); if((pCreateInfo->flags & VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_EXT) && (!pPipelineCache || !pPipelineCache->contains(key))) { pipelineCreationFeedback.pipelineCreationError(); return VK_PIPELINE_COMPILE_REQUIRED_EXT; } sw::SpirvBinary spirv; if(pPipelineCache) { auto onCacheMiss = [&] { return optimizeSpirv(key); }; auto onCacheHit = [&] { pipelineCreationFeedback.cacheHit(stageIndex); }; spirv = pPipelineCache->getOrOptimizeSpirv(key, onCacheMiss, onCacheHit); } else { spirv = optimizeSpirv(key); // If the pipeline does not have specialization constants, there's a 1-to-1 mapping between the unoptimized and optimized SPIR-V, // so we should use a 1-to-1 mapping of the identifiers to avoid JIT routine recompiles. if(!key.getSpecializationInfo()) { spirv.mapOptimizedIdentifier(key.getBinary()); } } const bool stageRobustBufferAccess = getPipelineStageRobustBufferAccess(stageInfo.pNext, device, robustBufferAccess); // TODO(b/201798871): use allocator. auto shader = std::make_shared(stageInfo.stage, stageInfo.pName, spirv, vk::Cast(pCreateInfo->renderPass), pCreateInfo->subpass, inputAttachmentMapping, stageRobustBufferAccess); setShader(stageInfo.stage, shader); pipelineCreationFeedback.stageCreationEnds(stageIndex); if(tempModule != VK_NULL_HANDLE) { vk::destroy(tempModule, nullptr); } } return VK_SUCCESS; } ComputePipeline::ComputePipeline(const VkComputePipelineCreateInfo *pCreateInfo, void *mem, Device *device) : Pipeline(vk::Cast(pCreateInfo->layout), device, getPipelineRobustBufferAccess(pCreateInfo->pNext, device)) { } void ComputePipeline::destroyPipeline(const VkAllocationCallbacks *pAllocator) { shader.reset(); program.reset(); } size_t ComputePipeline::ComputeRequiredAllocationSize(const VkComputePipelineCreateInfo *pCreateInfo) { return 0; } VkResult ComputePipeline::compileShaders(const VkAllocationCallbacks *pAllocator, const VkComputePipelineCreateInfo *pCreateInfo, PipelineCache *pPipelineCache) { PipelineCreationFeedback pipelineCreationFeedback(pCreateInfo); pipelineCreationFeedback.stageCreationBegins(0); auto &stage = pCreateInfo->stage; const ShaderModule *module = vk::Cast(stage.module); // VK_EXT_graphics_pipeline_library allows VkShaderModuleCreateInfo to be chained to // VkPipelineShaderStageCreateInfo, which is used if stageInfo.module is // VK_NULL_HANDLE. VkShaderModule tempModule = {}; if(stage.module == VK_NULL_HANDLE) { const auto *moduleCreateInfo = vk::GetExtendedStruct(stage.pNext, VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO); ASSERT(moduleCreateInfo); VkResult createResult = vk::ShaderModule::Create(nullptr, moduleCreateInfo, &tempModule); if(createResult != VK_SUCCESS) { return createResult; } module = vk::Cast(tempModule); } ASSERT(shader.get() == nullptr); ASSERT(program.get() == nullptr); const bool optimize = true; // TODO(b/251802301): Don't optimize when debugging shaders. const PipelineCache::SpirvBinaryKey shaderKey(module->getBinary(), stage.pSpecializationInfo, robustBufferAccess, optimize); if((pCreateInfo->flags & VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_EXT) && (!pPipelineCache || !pPipelineCache->contains(shaderKey))) { pipelineCreationFeedback.pipelineCreationError(); return VK_PIPELINE_COMPILE_REQUIRED_EXT; } sw::SpirvBinary spirv; if(pPipelineCache) { auto onCacheMiss = [&] { return optimizeSpirv(shaderKey); }; auto onCacheHit = [&] { pipelineCreationFeedback.cacheHit(0); }; spirv = pPipelineCache->getOrOptimizeSpirv(shaderKey, onCacheMiss, onCacheHit); } else { spirv = optimizeSpirv(shaderKey); // If the pipeline does not have specialization constants, there's a 1-to-1 mapping between the unoptimized and optimized SPIR-V, // so we should use a 1-to-1 mapping of the identifiers to avoid JIT routine recompiles. if(!shaderKey.getSpecializationInfo()) { spirv.mapOptimizedIdentifier(shaderKey.getBinary()); } } const bool stageRobustBufferAccess = getPipelineStageRobustBufferAccess(stage.pNext, device, robustBufferAccess); // TODO(b/201798871): use allocator. shader = std::make_shared(stage.stage, stage.pName, spirv, nullptr, 0, nullptr, stageRobustBufferAccess); const PipelineCache::ComputeProgramKey programKey(shader->getIdentifier(), layout->identifier); if(pPipelineCache) { program = pPipelineCache->getOrCreateComputeProgram(programKey, [&] { return createProgram(device, shader, layout); }); } else { program = createProgram(device, shader, layout); } pipelineCreationFeedback.stageCreationEnds(0); return VK_SUCCESS; } void ComputePipeline::run(uint32_t baseGroupX, uint32_t baseGroupY, uint32_t baseGroupZ, uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ, const vk::DescriptorSet::Array &descriptorSetObjects, const vk::DescriptorSet::Bindings &descriptorSets, const vk::DescriptorSet::DynamicOffsets &descriptorDynamicOffsets, const vk::Pipeline::PushConstantStorage &pushConstants) { ASSERT_OR_RETURN(program != nullptr); program->run( descriptorSetObjects, descriptorSets, descriptorDynamicOffsets, pushConstants, baseGroupX, baseGroupY, baseGroupZ, groupCountX, groupCountY, groupCountZ); } } // namespace vk