Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions impeller/renderer/backend/vulkan/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ impeller_component("vulkan") {
"gpu_tracer_vk.cc",
"gpu_tracer_vk.h",
"limits_vk.h",
"pipeline_cache_data_vk.cc",
"pipeline_cache_data_vk.h",
"pipeline_cache_vk.cc",
"pipeline_cache_vk.h",
"pipeline_library_vk.cc",
Expand Down
132 changes: 132 additions & 0 deletions impeller/renderer/backend/vulkan/pipeline_cache_data_vk.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// 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.

#include "impeller/renderer/backend/vulkan/pipeline_cache_data_vk.h"

#include "flutter/fml/file.h"
#include "impeller/base/allocation.h"
#include "impeller/base/validation.h"

namespace impeller {

static constexpr const char* kPipelineCacheFileName =
"flutter.impeller.vkcache";

// Inspired by
// https://medium.com/@zeuxcg/creating-a-robust-pipeline-cache-with-vulkan-961d09416cda.
struct PipelineCacheHeader {
// This can be used by Impeller to manually invalidate all old caches.
uint32_t magic = 0xC0DEF00D;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jtmcdole this is almost you

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol yeah! The other one I had in mind was 0xFA51F00D (fast-food for our pipelines) but the T didn't read right.

// Notably, this field is missing from checks the Vulkan driver performs. For
// drivers that don't correctly check the UUID, explicitly disregarding caches
// generated by previous driver versions sidesteps some landmines.
uint32_t driver_version = 0;
uint32_t vendor_id = 0;
uint32_t device_id = 0;
// If applications are published as 32-bit and updated via the app store to be
// 64-bits, this check comes in handy to disregard previous caches.
uint32_t abi = sizeof(void*);
uint8_t uuid[VK_UUID_SIZE] = {};
uint64_t data_size = 0;

PipelineCacheHeader() = default;

explicit PipelineCacheHeader(const VkPhysicalDeviceProperties& props,
uint64_t p_data_size)
: driver_version(props.driverVersion),
vendor_id(props.vendorID),
device_id(props.deviceID),
data_size(p_data_size) {
std::memcpy(uuid, props.pipelineCacheUUID, VK_UUID_SIZE);
}

bool IsCompatibleWith(const PipelineCacheHeader& o) const {
// Check for everything but the data size.
return magic == o.magic && //
driver_version == o.driver_version && //
vendor_id == o.vendor_id && //
device_id == o.device_id && //
abi == o.abi && //
std::memcmp(uuid, o.uuid, VK_UUID_SIZE) == 0;
}
};

bool PipelineCacheDataPersist(const fml::UniqueFD& cache_directory,
const VkPhysicalDeviceProperties& props,
const vk::UniquePipelineCache& cache) {
if (!cache_directory.is_valid()) {
return false;
}
size_t data_size = 0u;
if (cache.getOwner().getPipelineCacheData(*cache, &data_size, nullptr) !=
vk::Result::eSuccess) {
VALIDATION_LOG << "Could not fetch pipeline cache size.";
return false;
}
if (data_size == 0u) {
return true;
}
auto allocation = std::make_shared<Allocation>();
if (!allocation->Truncate(Bytes{sizeof(PipelineCacheHeader) + data_size})) {
VALIDATION_LOG << "Could not allocate pipeline cache data staging buffer.";
return false;
}
const auto header = PipelineCacheHeader{props, data_size};
std::memcpy(allocation->GetBuffer(), &header, sizeof(header));
if (cache.getOwner().getPipelineCacheData(
*cache, &data_size, allocation->GetBuffer() + sizeof(header)) !=
vk::Result::eSuccess) {
VALIDATION_LOG << "Could not copy pipeline cache data.";
return false;
}

auto allocation_mapping = CreateMappingFromAllocation(allocation);
if (!allocation_mapping) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if we had a unique_ptr variant for when we don't need shared ownership like here. Not blocking.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we get concepts, I'd like to apply one for any pointer like thing that can be moved. So it would work for both.

return false;
}
if (!fml::WriteAtomically(cache_directory, kPipelineCacheFileName,
*allocation_mapping)) {
VALIDATION_LOG << "Could not write cache file to disk.";
return false;
}
return true;
}

std::unique_ptr<fml::Mapping> PipelineCacheDataRetrieve(
const fml::UniqueFD& cache_directory,
const VkPhysicalDeviceProperties& props) {
if (!cache_directory.is_valid()) {
return nullptr;
}
std::shared_ptr<fml::FileMapping> on_disk_data =
fml::FileMapping::CreateReadOnly(cache_directory, kPipelineCacheFileName);
if (!on_disk_data) {
return nullptr;
}
if (on_disk_data->GetSize() < sizeof(PipelineCacheHeader)) {
VALIDATION_LOG << "Pipeline cache data size is too small.";
return nullptr;
}
auto on_disk_header = PipelineCacheHeader{};
std::memcpy(&on_disk_header, //
on_disk_data->GetMapping(), //
sizeof(on_disk_header) //
);
const auto current_header = PipelineCacheHeader{props, 0u};
if (!on_disk_header.IsCompatibleWith(current_header)) {
FML_LOG(WARNING)
<< "Persisted pipeline cache is not compatible with current "
"Vulkan context. Ignoring.";
return nullptr;
}
// Zero sized data is known to cause issues.
if (on_disk_header.data_size == 0u) {
return nullptr;
}
return std::make_unique<fml::NonOwnedMapping>(
on_disk_data->GetMapping() + sizeof(on_disk_header),
on_disk_header.data_size, [on_disk_data](auto, auto) {});
}

} // namespace impeller
51 changes: 51 additions & 0 deletions impeller/renderer/backend/vulkan/pipeline_cache_data_vk.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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.

#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_CACHE_DATA_VK_H_
#define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_CACHE_DATA_VK_H_

#include "flutter/fml/mapping.h"
#include "flutter/fml/unique_fd.h"
#include "impeller/renderer/backend/vulkan/vk.h"

namespace impeller {

//------------------------------------------------------------------------------
/// @brief Persist the pipeline cache to a file in the given cache
/// directory. This function performs integrity checks the Vulkan
/// driver may have missed.
///
/// @warning The pipeline cache must be externally synchronized for most
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left this in warning in there because it is theoretically possible if we are still in the middle of constructing pipelines. The failure mode is not fatal though and we only persist after a set number of frames. So I'm not too worried about this.

/// complete results. If additional pipelines are being created
/// while this function is executing, this function may fail to
/// persist data.
///
/// @param[in] cache_directory The cache directory
/// @param[in] props The physical device properties
/// @param[in] cache The cache
///
/// @return If the cache data could be persisted to disk.
///
bool PipelineCacheDataPersist(const fml::UniqueFD& cache_directory,
const VkPhysicalDeviceProperties& props,
const vk::UniquePipelineCache& cache);

//------------------------------------------------------------------------------
/// @brief Retrieve the previously persisted pipeline cache data. This
/// function provides integrity checks the Vulkan driver may have
/// missed.
///
/// @param[in] cache_directory The cache directory
/// @param[in] props The properties
///
/// @return The cache data if it was found and checked to have passed
/// additional integrity checks.
///
std::unique_ptr<fml::Mapping> PipelineCacheDataRetrieve(
const fml::UniqueFD& cache_directory,
const VkPhysicalDeviceProperties& props);

} // namespace impeller

#endif // FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_CACHE_DATA_VK_H_
92 changes: 13 additions & 79 deletions impeller/renderer/backend/vulkan/pipeline_cache_vk.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,50 +9,12 @@
#include <sstream>

#include "flutter/fml/mapping.h"
#include "impeller/base/allocation_size.h"
#include "impeller/base/validation.h"
#include "impeller/renderer/backend/vulkan/pipeline_cache_data_vk.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<DeviceHolderVK> device_holder,
fml::UniqueFD cache_directory)
Expand All @@ -65,8 +27,8 @@ PipelineCacheVK::PipelineCacheVK(std::shared_ptr<const Capabilities> caps,

const auto& vk_caps = CapabilitiesVK::Cast(*caps_);

auto existing_cache_data =
OpenCacheFile(cache_directory_, kPipelineCacheFileName, vk_caps);
auto existing_cache_data = PipelineCacheDataRetrieve(
cache_directory_, vk_caps.GetPhysicalDeviceProperties());

vk::PipelineCacheCreateInfo cache_info;
if (existing_cache_data) {
Expand All @@ -79,6 +41,9 @@ PipelineCacheVK::PipelineCacheVK(std::shared_ptr<const Capabilities> caps,

if (result == vk::Result::eSuccess) {
cache_ = std::move(existing_cache);
FML_LOG(INFO)
<< Bytes{cache_info.initialDataSize}.ConvertTo<MegaBytes>().GetSize()
<< " MB of data was used to construct a pipeline 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
Expand Down Expand Up @@ -145,46 +110,15 @@ vk::UniquePipeline PipelineCacheVK::CreatePipeline(
return std::move(pipeline);
}

std::shared_ptr<fml::Mapping> PipelineCacheVK::CopyPipelineCacheData() const {
std::shared_ptr<DeviceHolderVK> 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.";
if (!is_valid_) {
return;
}
const auto& vk_caps = CapabilitiesVK::Cast(*caps_);
PipelineCacheDataPersist(cache_directory_, //
vk_caps.GetPhysicalDeviceProperties(), //
cache_ //
);
}

const CapabilitiesVK* PipelineCacheVK::GetCapabilities() const {
Expand Down
2 changes: 0 additions & 2 deletions impeller/renderer/backend/vulkan/pipeline_cache_vk.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ class PipelineCacheVK {
vk::UniquePipelineCache cache_;
bool is_valid_ = false;

std::shared_ptr<fml::Mapping> CopyPipelineCacheData() const;

PipelineCacheVK(const PipelineCacheVK&) = delete;

PipelineCacheVK& operator=(const PipelineCacheVK&) = delete;
Expand Down