| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // FLUTTER_NOLINT: https://github.com/flutter/flutter/issues/123467 |
| |
| #include "impeller/renderer/backend/vulkan/pipeline_cache_vk.h" |
| |
| #include <sstream> |
| |
| #include "flutter/fml/mapping.h" |
| #include "impeller/base/validation.h" |
| |
| namespace impeller { |
| |
| static constexpr const char* kPipelineCacheFileName = |
| "flutter.impeller.vkcache"; |
| |
| static bool VerifyExistingCache(const fml::Mapping& mapping, |
| const CapabilitiesVK& caps) { |
| return true; |
| } |
| |
| static std::shared_ptr<fml::Mapping> DecorateCacheWithMetadata( |
| std::shared_ptr<fml::Mapping> data) { |
| return data; |
| } |
| |
| static std::unique_ptr<fml::Mapping> RemoveMetadataFromCache( |
| std::unique_ptr<fml::Mapping> data) { |
| return data; |
| } |
| |
| static std::unique_ptr<fml::Mapping> OpenCacheFile( |
| const fml::UniqueFD& base_directory, |
| const std::string& cache_file_name, |
| const CapabilitiesVK& caps) { |
| if (!base_directory.is_valid()) { |
| return nullptr; |
| } |
| std::unique_ptr<fml::Mapping> mapping = |
| fml::FileMapping::CreateReadOnly(base_directory, cache_file_name); |
| if (!mapping) { |
| return nullptr; |
| } |
| if (!VerifyExistingCache(*mapping, caps)) { |
| return nullptr; |
| } |
| mapping = RemoveMetadataFromCache(std::move(mapping)); |
| if (!mapping) { |
| return nullptr; |
| } |
| return mapping; |
| } |
| |
| PipelineCacheVK::PipelineCacheVK(std::shared_ptr<const Capabilities> caps, |
| std::shared_ptr<DeviceHolder> device_holder, |
| fml::UniqueFD cache_directory) |
| : caps_(std::move(caps)), |
| device_holder_(device_holder), |
| cache_directory_(std::move(cache_directory)) { |
| if (!caps_ || !device_holder->GetDevice()) { |
| return; |
| } |
| |
| const auto& vk_caps = CapabilitiesVK::Cast(*caps_); |
| |
| auto existing_cache_data = |
| OpenCacheFile(cache_directory_, kPipelineCacheFileName, vk_caps); |
| |
| vk::PipelineCacheCreateInfo cache_info; |
| if (existing_cache_data) { |
| cache_info.initialDataSize = existing_cache_data->GetSize(); |
| cache_info.pInitialData = existing_cache_data->GetMapping(); |
| } |
| |
| auto [result, existing_cache] = |
| device_holder->GetDevice().createPipelineCacheUnique(cache_info); |
| |
| if (result == vk::Result::eSuccess) { |
| cache_ = std::move(existing_cache); |
| } else { |
| // Even though we perform consistency checks because we don't trust the |
| // driver, the driver may have additional information that may cause it to |
| // reject the cache too. |
| FML_LOG(INFO) << "Existing pipeline cache was invalid: " |
| << vk::to_string(result) << ". Starting with a fresh cache."; |
| cache_info.pInitialData = nullptr; |
| cache_info.initialDataSize = 0u; |
| auto [result2, new_cache] = |
| device_holder->GetDevice().createPipelineCacheUnique(cache_info); |
| if (result2 == vk::Result::eSuccess) { |
| cache_ = std::move(new_cache); |
| } else { |
| VALIDATION_LOG << "Could not create new pipeline cache: " |
| << vk::to_string(result2); |
| } |
| } |
| |
| is_valid_ = !!cache_; |
| } |
| |
| PipelineCacheVK::~PipelineCacheVK() { |
| std::shared_ptr<DeviceHolder> device_holder = device_holder_.lock(); |
| if (device_holder) { |
| cache_.reset(); |
| } else { |
| cache_.release(); |
| } |
| } |
| |
| bool PipelineCacheVK::IsValid() const { |
| return is_valid_; |
| } |
| |
| vk::UniquePipeline PipelineCacheVK::CreatePipeline( |
| const vk::GraphicsPipelineCreateInfo& info) { |
| std::shared_ptr<DeviceHolder> strong_device = device_holder_.lock(); |
| if (!strong_device) { |
| return {}; |
| } |
| |
| auto [result, pipeline] = |
| strong_device->GetDevice().createGraphicsPipelineUnique(*cache_, info); |
| if (result != vk::Result::eSuccess) { |
| VALIDATION_LOG << "Could not create graphics pipeline: " |
| << vk::to_string(result); |
| } |
| return std::move(pipeline); |
| } |
| |
| vk::UniquePipeline PipelineCacheVK::CreatePipeline( |
| const vk::ComputePipelineCreateInfo& info) { |
| std::shared_ptr<DeviceHolder> strong_device = device_holder_.lock(); |
| if (!strong_device) { |
| return {}; |
| } |
| |
| auto [result, pipeline] = |
| strong_device->GetDevice().createComputePipelineUnique(*cache_, info); |
| if (result != vk::Result::eSuccess) { |
| VALIDATION_LOG << "Could not create compute pipeline: " |
| << vk::to_string(result); |
| } |
| return std::move(pipeline); |
| } |
| |
| std::shared_ptr<fml::Mapping> PipelineCacheVK::CopyPipelineCacheData() const { |
| std::shared_ptr<DeviceHolder> strong_device = device_holder_.lock(); |
| if (!strong_device) { |
| return nullptr; |
| } |
| |
| if (!IsValid()) { |
| return nullptr; |
| } |
| auto [result, data] = |
| strong_device->GetDevice().getPipelineCacheData(*cache_); |
| if (result != vk::Result::eSuccess) { |
| VALIDATION_LOG << "Could not get pipeline cache data to persist."; |
| return nullptr; |
| } |
| auto shared_data = std::make_shared<std::vector<uint8_t>>(); |
| std::swap(*shared_data, data); |
| return std::make_shared<fml::NonOwnedMapping>( |
| shared_data->data(), shared_data->size(), [shared_data](auto, auto) {}); |
| } |
| |
| void PipelineCacheVK::PersistCacheToDisk() const { |
| if (!cache_directory_.is_valid()) { |
| return; |
| } |
| auto data = CopyPipelineCacheData(); |
| if (!data) { |
| VALIDATION_LOG << "Could not copy pipeline cache data."; |
| return; |
| } |
| data = DecorateCacheWithMetadata(std::move(data)); |
| if (!data) { |
| VALIDATION_LOG |
| << "Could not decorate pipeline cache with additional metadata."; |
| return; |
| } |
| if (!fml::WriteAtomically(cache_directory_, kPipelineCacheFileName, *data)) { |
| VALIDATION_LOG << "Could not persist pipeline cache to disk."; |
| return; |
| } |
| } |
| |
| const CapabilitiesVK* PipelineCacheVK::GetCapabilities() const { |
| return CapabilitiesVK::Cast(caps_.get()); |
| } |
| |
| } // namespace impeller |