Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit f8d553a

Browse files
authored
[Impeller] Perform integrity checks for Vulkan pipeline caches. (#54654)
Fixes flutter/flutter#128126 I scared myself looking into the recent Vulkan driver issues and decided to fix this to follow best-practices. In addition to the comments, see the linked issue for the article on how this works. I didn't perform the data hashing because we use ::rename in fml::WriteAtomically and I am not as concerned about that. But we can add it later if needed. We also don't have a good utility to hash data. This also gets rid of one intermediate allocation. We could also write directly into the file mapping but FML has no utilities to msync. Something to fix later if needed.
1 parent 2d7a269 commit f8d553a

File tree

8 files changed

+395
-81
lines changed

8 files changed

+395
-81
lines changed

ci/licenses_golden/excluded_files

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@
194194
../../../flutter/impeller/renderer/backend/vulkan/descriptor_pool_vk_unittests.cc
195195
../../../flutter/impeller/renderer/backend/vulkan/driver_info_vk_unittests.cc
196196
../../../flutter/impeller/renderer/backend/vulkan/fence_waiter_vk_unittests.cc
197+
../../../flutter/impeller/renderer/backend/vulkan/pipeline_cache_data_vk_unittests.cc
197198
../../../flutter/impeller/renderer/backend/vulkan/render_pass_builder_vk_unittests.cc
198199
../../../flutter/impeller/renderer/backend/vulkan/render_pass_cache_unittests.cc
199200
../../../flutter/impeller/renderer/backend/vulkan/resource_manager_vk_unittests.cc

ci/licenses_golden/licenses_flutter

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43109,6 +43109,8 @@ ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/formats_vk.cc + ../../
4310943109
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/formats_vk.h + ../../../flutter/LICENSE
4311043110
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/gpu_tracer_vk.cc + ../../../flutter/LICENSE
4311143111
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/gpu_tracer_vk.h + ../../../flutter/LICENSE
43112+
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_cache_data_vk.cc + ../../../flutter/LICENSE
43113+
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_cache_data_vk.h + ../../../flutter/LICENSE
4311243114
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_cache_vk.cc + ../../../flutter/LICENSE
4311343115
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_cache_vk.h + ../../../flutter/LICENSE
4311443116
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.cc + ../../../flutter/LICENSE
@@ -45991,6 +45993,8 @@ FILE: ../../../flutter/impeller/renderer/backend/vulkan/formats_vk.h
4599145993
FILE: ../../../flutter/impeller/renderer/backend/vulkan/gpu_tracer_vk.cc
4599245994
FILE: ../../../flutter/impeller/renderer/backend/vulkan/gpu_tracer_vk.h
4599345995
FILE: ../../../flutter/impeller/renderer/backend/vulkan/limits_vk.h
45996+
FILE: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_cache_data_vk.cc
45997+
FILE: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_cache_data_vk.h
4599445998
FILE: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_cache_vk.cc
4599545999
FILE: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_cache_vk.h
4599646000
FILE: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.cc

impeller/renderer/backend/vulkan/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ impeller_component("vulkan_unittests") {
1515
"descriptor_pool_vk_unittests.cc",
1616
"driver_info_vk_unittests.cc",
1717
"fence_waiter_vk_unittests.cc",
18+
"pipeline_cache_data_vk_unittests.cc",
1819
"render_pass_builder_vk_unittests.cc",
1920
"render_pass_cache_unittests.cc",
2021
"resource_manager_vk_unittests.cc",
@@ -71,6 +72,8 @@ impeller_component("vulkan") {
7172
"gpu_tracer_vk.cc",
7273
"gpu_tracer_vk.h",
7374
"limits_vk.h",
75+
"pipeline_cache_data_vk.cc",
76+
"pipeline_cache_data_vk.h",
7477
"pipeline_cache_vk.cc",
7578
"pipeline_cache_vk.h",
7679
"pipeline_library_vk.cc",
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "impeller/renderer/backend/vulkan/pipeline_cache_data_vk.h"
6+
7+
#include "flutter/fml/file.h"
8+
#include "impeller/base/allocation.h"
9+
#include "impeller/base/validation.h"
10+
11+
namespace impeller {
12+
13+
static constexpr const char* kPipelineCacheFileName =
14+
"flutter.impeller.vkcache";
15+
16+
bool PipelineCacheDataPersist(const fml::UniqueFD& cache_directory,
17+
const VkPhysicalDeviceProperties& props,
18+
const vk::UniquePipelineCache& cache) {
19+
if (!cache_directory.is_valid()) {
20+
return false;
21+
}
22+
size_t data_size = 0u;
23+
if (cache.getOwner().getPipelineCacheData(*cache, &data_size, nullptr) !=
24+
vk::Result::eSuccess) {
25+
VALIDATION_LOG << "Could not fetch pipeline cache size.";
26+
return false;
27+
}
28+
if (data_size == 0u) {
29+
return true;
30+
}
31+
auto allocation = std::make_shared<Allocation>();
32+
if (!allocation->Truncate(Bytes{sizeof(PipelineCacheHeaderVK) + data_size},
33+
false)) {
34+
VALIDATION_LOG << "Could not allocate pipeline cache data staging buffer.";
35+
return false;
36+
}
37+
const auto header = PipelineCacheHeaderVK{props, data_size};
38+
std::memcpy(allocation->GetBuffer(), &header, sizeof(header));
39+
if (cache.getOwner().getPipelineCacheData(
40+
*cache, &data_size, allocation->GetBuffer() + sizeof(header)) !=
41+
vk::Result::eSuccess) {
42+
VALIDATION_LOG << "Could not copy pipeline cache data.";
43+
return false;
44+
}
45+
46+
auto allocation_mapping = CreateMappingFromAllocation(allocation);
47+
if (!allocation_mapping) {
48+
return false;
49+
}
50+
if (!fml::WriteAtomically(cache_directory, kPipelineCacheFileName,
51+
*allocation_mapping)) {
52+
VALIDATION_LOG << "Could not write cache file to disk.";
53+
return false;
54+
}
55+
return true;
56+
}
57+
58+
std::unique_ptr<fml::Mapping> PipelineCacheDataRetrieve(
59+
const fml::UniqueFD& cache_directory,
60+
const VkPhysicalDeviceProperties& props) {
61+
if (!cache_directory.is_valid()) {
62+
return nullptr;
63+
}
64+
std::shared_ptr<fml::FileMapping> on_disk_data =
65+
fml::FileMapping::CreateReadOnly(cache_directory, kPipelineCacheFileName);
66+
if (!on_disk_data) {
67+
return nullptr;
68+
}
69+
if (on_disk_data->GetSize() < sizeof(PipelineCacheHeaderVK)) {
70+
VALIDATION_LOG << "Pipeline cache data size is too small.";
71+
return nullptr;
72+
}
73+
auto on_disk_header = PipelineCacheHeaderVK{};
74+
std::memcpy(&on_disk_header, //
75+
on_disk_data->GetMapping(), //
76+
sizeof(on_disk_header) //
77+
);
78+
const auto current_header = PipelineCacheHeaderVK{props, 0u};
79+
if (!on_disk_header.IsCompatibleWith(current_header)) {
80+
FML_LOG(WARNING)
81+
<< "Persisted pipeline cache is not compatible with current "
82+
"Vulkan context. Ignoring.";
83+
return nullptr;
84+
}
85+
// Zero sized data is known to cause issues.
86+
if (on_disk_header.data_size == 0u) {
87+
return nullptr;
88+
}
89+
return std::make_unique<fml::NonOwnedMapping>(
90+
on_disk_data->GetMapping() + sizeof(on_disk_header),
91+
on_disk_header.data_size, [on_disk_data](auto, auto) {});
92+
}
93+
94+
PipelineCacheHeaderVK::PipelineCacheHeaderVK() = default;
95+
96+
PipelineCacheHeaderVK::PipelineCacheHeaderVK(
97+
const VkPhysicalDeviceProperties& props,
98+
uint64_t p_data_size)
99+
: driver_version(props.driverVersion),
100+
vendor_id(props.vendorID),
101+
device_id(props.deviceID),
102+
data_size(p_data_size) {
103+
std::memcpy(uuid, props.pipelineCacheUUID, VK_UUID_SIZE);
104+
}
105+
106+
bool PipelineCacheHeaderVK::IsCompatibleWith(
107+
const PipelineCacheHeaderVK& o) const {
108+
// Check for everything but the data size.
109+
return magic == o.magic && //
110+
driver_version == o.driver_version && //
111+
vendor_id == o.vendor_id && //
112+
device_id == o.device_id && //
113+
abi == o.abi && //
114+
std::memcmp(uuid, o.uuid, VK_UUID_SIZE) == 0;
115+
}
116+
117+
} // namespace impeller
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_CACHE_DATA_VK_H_
6+
#define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_CACHE_DATA_VK_H_
7+
8+
#include "flutter/fml/mapping.h"
9+
#include "flutter/fml/unique_fd.h"
10+
#include "impeller/renderer/backend/vulkan/vk.h"
11+
12+
namespace impeller {
13+
14+
//------------------------------------------------------------------------------
15+
/// @brief An Impeller specific header prepended to all pipeline cache
16+
/// information that is persisted on disk. This information is used
17+
/// to perform additional integrity checks that may have been missed
18+
/// by the Vulkan driver.
19+
///
20+
/// Inspired by
21+
/// https://medium.com/@zeuxcg/creating-a-robust-pipeline-cache-with-vulkan-961d09416cda.
22+
///
23+
struct PipelineCacheHeaderVK {
24+
// This can be used by Impeller to manually invalidate all old caches.
25+
uint32_t magic = 0xC0DEF00D;
26+
// Notably, this field is missing from checks the Vulkan driver performs. For
27+
// drivers that don't correctly check the UUID, explicitly disregarding caches
28+
// generated by previous driver versions sidesteps some landmines.
29+
uint32_t driver_version = 0;
30+
uint32_t vendor_id = 0;
31+
uint32_t device_id = 0;
32+
// If applications are published as 32-bit and updated via the app store to be
33+
// 64-bits, this check comes in handy to disregard previous caches.
34+
uint32_t abi = sizeof(void*);
35+
uint8_t uuid[VK_UUID_SIZE] = {};
36+
uint64_t data_size = 0;
37+
38+
//----------------------------------------------------------------------------
39+
/// @brief Constructs a new empty instance.
40+
///
41+
PipelineCacheHeaderVK();
42+
43+
//----------------------------------------------------------------------------
44+
/// @brief Constructs a new instance that will be compatible with the
45+
/// given physical device properties.
46+
///
47+
/// @param[in] props The properties.
48+
/// @param[in] p_data_size The data size.
49+
///
50+
explicit PipelineCacheHeaderVK(const VkPhysicalDeviceProperties& props,
51+
uint64_t p_data_size);
52+
53+
//----------------------------------------------------------------------------
54+
/// @brief Determines whether the specified o is compatible with.
55+
///
56+
/// The size of the data following the header may be different and
57+
/// is not part of compatibility checks.
58+
///
59+
/// @param[in] other The other header.
60+
///
61+
/// @return True if the specified header is compatible with this one,
62+
/// False otherwise. The size of the data following the header may
63+
/// be different.
64+
///
65+
bool IsCompatibleWith(const PipelineCacheHeaderVK& other) const;
66+
};
67+
68+
//------------------------------------------------------------------------------
69+
/// @brief Persist the pipeline cache to a file in the given cache
70+
/// directory. This function performs integrity checks the Vulkan
71+
/// driver may have missed.
72+
///
73+
/// @warning The pipeline cache must be externally synchronized for most
74+
/// complete results. If additional pipelines are being created
75+
/// while this function is executing, this function may fail to
76+
/// persist data.
77+
///
78+
/// @param[in] cache_directory The cache directory
79+
/// @param[in] props The physical device properties
80+
/// @param[in] cache The cache
81+
///
82+
/// @return If the cache data could be persisted to disk.
83+
///
84+
bool PipelineCacheDataPersist(const fml::UniqueFD& cache_directory,
85+
const VkPhysicalDeviceProperties& props,
86+
const vk::UniquePipelineCache& cache);
87+
88+
//------------------------------------------------------------------------------
89+
/// @brief Retrieve the previously persisted pipeline cache data. This
90+
/// function provides integrity checks the Vulkan driver may have
91+
/// missed.
92+
///
93+
/// The data is stripped of any additional headers that perform
94+
/// integrity checks. It can be used directly to construct a
95+
/// pre-initialized Vulkan pipeline cache.
96+
///
97+
/// @param[in] cache_directory The cache directory
98+
/// @param[in] props The properties
99+
///
100+
/// @return The cache data if it was found and checked to have passed
101+
/// additional integrity checks.
102+
///
103+
std::unique_ptr<fml::Mapping> PipelineCacheDataRetrieve(
104+
const fml::UniqueFD& cache_directory,
105+
const VkPhysicalDeviceProperties& props);
106+
107+
} // namespace impeller
108+
109+
#endif // FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_CACHE_DATA_VK_H_

0 commit comments

Comments
 (0)