Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/util/gpu_shader_cache.cpp
4214 views
1
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "gpu_shader_cache.h"
5
#include "gpu_device.h"
6
7
#include "common/error.h"
8
#include "common/file_system.h"
9
#include "common/heap_array.h"
10
#include "common/log.h"
11
#include "common/md5_digest.h"
12
#include "common/path.h"
13
14
#include "fmt/format.h"
15
16
#include "compress_helpers.h"
17
18
LOG_CHANNEL(GPUDevice);
19
20
#pragma pack(push, 1)
21
struct CacheFileHeader
22
{
23
u32 signature;
24
u32 render_api_version;
25
u32 cache_version;
26
};
27
struct CacheIndexEntry
28
{
29
u8 shader_type;
30
u8 shader_language;
31
u8 unused[2];
32
u32 source_length;
33
u64 source_hash_low;
34
u64 source_hash_high;
35
u64 entry_point_low;
36
u64 entry_point_high;
37
u32 file_offset;
38
u32 compressed_size;
39
u32 uncompressed_size;
40
};
41
#pragma pack(pop)
42
43
static constexpr u32 EXPECTED_SIGNATURE = 0x434B5544; // DUKC
44
45
GPUShaderCache::GPUShaderCache() = default;
46
47
GPUShaderCache::~GPUShaderCache()
48
{
49
Close();
50
}
51
52
bool GPUShaderCache::CacheIndexKey::operator==(const CacheIndexKey& key) const
53
{
54
return (std::memcmp(this, &key, sizeof(*this)) == 0);
55
}
56
57
bool GPUShaderCache::CacheIndexKey::operator!=(const CacheIndexKey& key) const
58
{
59
return (std::memcmp(this, &key, sizeof(*this)) != 0);
60
}
61
62
std::size_t GPUShaderCache::CacheIndexEntryHash::operator()(const CacheIndexKey& e) const noexcept
63
{
64
std::size_t h = 0;
65
hash_combine(h, e.entry_point_low, e.entry_point_high, e.source_hash_low, e.source_hash_high, e.source_length,
66
e.shader_type);
67
return h;
68
}
69
70
bool GPUShaderCache::Open(std::string_view base_filename, u32 render_api_version, u32 cache_version)
71
{
72
m_base_filename = base_filename;
73
m_render_api_version = render_api_version;
74
m_version = cache_version;
75
76
if (base_filename.empty())
77
return true;
78
79
const std::string index_filename = fmt::format("{}.idx", m_base_filename);
80
const std::string blob_filename = fmt::format("{}.bin", m_base_filename);
81
return ReadExisting(index_filename, blob_filename);
82
}
83
84
bool GPUShaderCache::Create()
85
{
86
const std::string index_filename = fmt::format("{}.idx", m_base_filename);
87
const std::string blob_filename = fmt::format("{}.bin", m_base_filename);
88
return CreateNew(index_filename, blob_filename);
89
}
90
91
void GPUShaderCache::Close()
92
{
93
if (m_index_file)
94
{
95
std::fclose(m_index_file);
96
m_index_file = nullptr;
97
}
98
if (m_blob_file)
99
{
100
std::fclose(m_blob_file);
101
m_blob_file = nullptr;
102
}
103
}
104
105
void GPUShaderCache::Clear()
106
{
107
if (!IsOpen())
108
return;
109
110
Close();
111
112
WARNING_LOG("Clearing shader cache at {}.", Path::GetFileName(m_base_filename));
113
114
const std::string index_filename = fmt::format("{}.idx", m_base_filename);
115
const std::string blob_filename = fmt::format("{}.bin", m_base_filename);
116
CreateNew(index_filename, blob_filename);
117
}
118
119
bool GPUShaderCache::CreateNew(const std::string& index_filename, const std::string& blob_filename)
120
{
121
if (FileSystem::FileExists(index_filename.c_str()))
122
{
123
WARNING_LOG("Removing existing index file '{}'", Path::GetFileName(index_filename));
124
FileSystem::DeleteFile(index_filename.c_str());
125
}
126
if (FileSystem::FileExists(blob_filename.c_str()))
127
{
128
WARNING_LOG("Removing existing blob file '{}'", Path::GetFileName(blob_filename));
129
FileSystem::DeleteFile(blob_filename.c_str());
130
}
131
132
m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "wb");
133
if (!m_index_file) [[unlikely]]
134
{
135
ERROR_LOG("Failed to open index file '{}' for writing", Path::GetFileName(index_filename));
136
return false;
137
}
138
139
const CacheFileHeader file_header = {
140
.signature = EXPECTED_SIGNATURE, .render_api_version = m_render_api_version, .cache_version = m_version};
141
if (std::fwrite(&file_header, sizeof(file_header), 1, m_index_file) != 1) [[unlikely]]
142
{
143
ERROR_LOG("Failed to write version to index file '{}'", Path::GetFileName(index_filename));
144
std::fclose(m_index_file);
145
m_index_file = nullptr;
146
FileSystem::DeleteFile(index_filename.c_str());
147
return false;
148
}
149
150
m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "w+b");
151
if (!m_blob_file) [[unlikely]]
152
{
153
ERROR_LOG("Failed to open blob file '{}' for writing", Path::GetFileName(blob_filename));
154
std::fclose(m_index_file);
155
m_index_file = nullptr;
156
FileSystem::DeleteFile(index_filename.c_str());
157
return false;
158
}
159
160
return true;
161
}
162
163
bool GPUShaderCache::ReadExisting(const std::string& index_filename, const std::string& blob_filename)
164
{
165
m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "r+b");
166
if (!m_index_file)
167
{
168
// special case here: when there's a sharing violation (i.e. two instances running),
169
// we don't want to blow away the cache. so just continue without a cache.
170
if (errno == EACCES)
171
{
172
WARNING_LOG("Failed to open shader cache index with EACCES, are you running two instances?");
173
return true;
174
}
175
176
return false;
177
}
178
179
CacheFileHeader file_header;
180
if (std::fread(&file_header, sizeof(file_header), 1, m_index_file) != 1 ||
181
file_header.signature != EXPECTED_SIGNATURE || file_header.render_api_version != m_render_api_version ||
182
file_header.cache_version != m_version) [[unlikely]]
183
{
184
ERROR_LOG("Bad file/data version in '{}'", Path::GetFileName(index_filename));
185
std::fclose(m_index_file);
186
m_index_file = nullptr;
187
return false;
188
}
189
190
m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "a+b");
191
if (!m_blob_file) [[unlikely]]
192
{
193
ERROR_LOG("Blob file '{}' is missing", Path::GetFileName(blob_filename));
194
std::fclose(m_index_file);
195
m_index_file = nullptr;
196
return false;
197
}
198
199
std::fseek(m_blob_file, 0, SEEK_END);
200
const u32 blob_file_size = static_cast<u32>(std::ftell(m_blob_file));
201
202
for (;;)
203
{
204
CacheIndexEntry entry;
205
if (std::fread(&entry, sizeof(entry), 1, m_index_file) != 1 ||
206
(entry.file_offset + entry.compressed_size) > blob_file_size) [[unlikely]]
207
{
208
if (std::feof(m_index_file))
209
break;
210
211
ERROR_LOG("Failed to read entry from '{}', corrupt file?", Path::GetFileName(index_filename));
212
m_index.clear();
213
std::fclose(m_blob_file);
214
m_blob_file = nullptr;
215
std::fclose(m_index_file);
216
m_index_file = nullptr;
217
return false;
218
}
219
220
const CacheIndexKey key{entry.shader_type, entry.shader_language, {},
221
entry.source_length, entry.source_hash_low, entry.source_hash_high,
222
entry.entry_point_low, entry.entry_point_high};
223
const CacheIndexData data{entry.file_offset, entry.compressed_size, entry.uncompressed_size};
224
m_index.emplace(key, data);
225
}
226
227
// ensure we don't write before seeking
228
std::fseek(m_index_file, 0, SEEK_END);
229
230
DEV_LOG("Read {} entries from '{}'", m_index.size(), Path::GetFileName(index_filename));
231
return true;
232
}
233
234
GPUShaderCache::CacheIndexKey GPUShaderCache::GetCacheKey(GPUShaderStage stage, GPUShaderLanguage language,
235
std::string_view shader_code, std::string_view entry_point)
236
{
237
union
238
{
239
struct
240
{
241
u64 hash_low;
242
u64 hash_high;
243
};
244
u8 hash[16];
245
} h;
246
247
CacheIndexKey key = {};
248
key.shader_type = static_cast<u8>(stage);
249
key.shader_language = static_cast<u8>(language);
250
251
MD5Digest digest;
252
digest.Update(shader_code.data(), static_cast<u32>(shader_code.length()));
253
digest.Final(h.hash);
254
key.source_hash_low = h.hash_low;
255
key.source_hash_high = h.hash_high;
256
key.source_length = static_cast<u32>(shader_code.length());
257
258
digest.Reset();
259
digest.Update(entry_point.data(), static_cast<u32>(entry_point.length()));
260
digest.Final(h.hash);
261
key.entry_point_low = h.hash_low;
262
key.entry_point_high = h.hash_high;
263
264
return key;
265
}
266
267
std::optional<GPUShaderCache::ShaderBinary> GPUShaderCache::Lookup(const CacheIndexKey& key)
268
{
269
std::optional<ShaderBinary> ret;
270
271
auto iter = m_index.find(key);
272
if (iter != m_index.end())
273
{
274
DynamicHeapArray<u8> compressed_data(iter->second.compressed_size);
275
276
if (std::fseek(m_blob_file, iter->second.file_offset, SEEK_SET) != 0 ||
277
std::fread(compressed_data.data(), iter->second.compressed_size, 1, m_blob_file) != 1) [[unlikely]]
278
{
279
ERROR_LOG("Read {} byte {} shader from file failed", iter->second.compressed_size,
280
GPUShader::GetStageName(static_cast<GPUShaderStage>(key.shader_type)));
281
}
282
else
283
{
284
Error error;
285
ret = CompressHelpers::DecompressBuffer(CompressHelpers::CompressType::Zstandard,
286
CompressHelpers::OptionalByteBuffer(std::move(compressed_data)),
287
iter->second.uncompressed_size, &error);
288
if (!ret.has_value()) [[unlikely]]
289
ERROR_LOG("Failed to decompress shader: {}", error.GetDescription());
290
}
291
}
292
293
return ret;
294
}
295
296
bool GPUShaderCache::Insert(const CacheIndexKey& key, const void* data, u32 data_size)
297
{
298
Error error;
299
CompressHelpers::OptionalByteBuffer compress_buffer =
300
CompressHelpers::CompressToBuffer(CompressHelpers::CompressType::Zstandard, data, data_size, -1, &error);
301
if (!compress_buffer.has_value()) [[unlikely]]
302
{
303
ERROR_LOG("Failed to compress shader: {}", error.GetDescription());
304
return false;
305
}
306
307
if (!m_blob_file || std::fseek(m_blob_file, 0, SEEK_END) != 0)
308
return false;
309
310
CacheIndexData idata;
311
idata.file_offset = static_cast<u32>(std::ftell(m_blob_file));
312
idata.compressed_size = static_cast<u32>(compress_buffer->size());
313
idata.uncompressed_size = data_size;
314
315
CacheIndexEntry entry = {};
316
entry.shader_type = static_cast<u8>(key.shader_type);
317
entry.shader_language = static_cast<u8>(key.shader_language);
318
entry.source_length = key.source_length;
319
entry.source_hash_low = key.source_hash_low;
320
entry.source_hash_high = key.source_hash_high;
321
entry.entry_point_low = key.entry_point_low;
322
entry.entry_point_high = key.entry_point_high;
323
entry.file_offset = idata.file_offset;
324
entry.compressed_size = idata.compressed_size;
325
entry.uncompressed_size = idata.uncompressed_size;
326
327
if (std::fwrite(compress_buffer->data(), compress_buffer->size(), 1, m_blob_file) != 1 ||
328
std::fflush(m_blob_file) != 0 || std::fwrite(&entry, sizeof(entry), 1, m_index_file) != 1 ||
329
std::fflush(m_index_file) != 0) [[unlikely]]
330
{
331
ERROR_LOG("Failed to write {} byte {} shader blob to file", data_size,
332
GPUShader::GetStageName(static_cast<GPUShaderStage>(key.shader_type)));
333
return false;
334
}
335
336
DEV_LOG("Cached compressed {} shader: {} -> {} bytes",
337
GPUShader::GetStageName(static_cast<GPUShaderStage>(key.shader_type)), data_size, compress_buffer->size());
338
m_index.emplace(key, idata);
339
return true;
340
}
341
342