summaryrefslogtreecommitdiffstats
path: root/tools/gfx/persistent-shader-cache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/gfx/persistent-shader-cache.cpp')
-rw-r--r--tools/gfx/persistent-shader-cache.cpp263
1 files changed, 195 insertions, 68 deletions
diff --git a/tools/gfx/persistent-shader-cache.cpp b/tools/gfx/persistent-shader-cache.cpp
index c0fd3b44a..54c46f9dc 100644
--- a/tools/gfx/persistent-shader-cache.cpp
+++ b/tools/gfx/persistent-shader-cache.cpp
@@ -6,9 +6,15 @@
#include "../../source/core/slang-string-util.h"
#include "../../source/core/slang-file-system.h"
+#include "../../source/core/slang-char-util.h"
+
+#include <chrono>
+
namespace gfx
{
+using namespace std::chrono;
+
PersistentShaderCache::PersistentShaderCache(const IDevice::ShaderCacheDesc& inDesc)
{
desc = inDesc;
@@ -35,52 +41,97 @@ PersistentShaderCache::PersistentShaderCache(const IDevice::ShaderCacheDesc& inD
loadCacheFromFile();
}
+PersistentShaderCache::~PersistentShaderCache()
+{
+ if (isMemoryFileSystem)
+ {
+ saveCacheToMemory();
+ }
+}
+
// Load a previous cache index saved to disk. If not found, create a new cache index
// and save it to disk as filename.
void PersistentShaderCache::loadCacheFromFile()
{
- ComPtr<ISlangBlob> indexBlob;
- if (SLANG_FAILED(desc.shaderCacheFileSystem->loadFile(desc.cacheFilename, indexBlob.writeRef())))
+ // We will need to combine the filename with the cache path in order to have the correct
+ // file path for initializing the stream. This needs to be done separately because there
+ // is no guarantee that the underlying file system is mutable.
+ String filePath;
+ if (mutableShaderCacheFileSystem)
{
- // Cache index not found, so we'll create and save a new one.
- if (mutableShaderCacheFileSystem)
+ ComPtr<ISlangBlob> fullPath;
+ if (SLANG_FAILED(mutableShaderCacheFileSystem->getPath(PathKind::OperatingSystem, desc.cacheFilename, fullPath.writeRef())))
{
- mutableShaderCacheFileSystem->saveFile(desc.cacheFilename, nullptr, 0);
+ // If we fail to obtain a physical file path, then this must be a MemoryFileSystem. In this case, file streams
+ // will not work as they require the file to be on disk, so we will rely on a fall back implementation.
+ isMemoryFileSystem = true;
+ loadCacheFromMemory();
return;
}
- // Cache index not found and we can't save a new one due to the file system being immutable.
- return;
+ filePath = String((char*)fullPath->getBufferPointer());
+ }
+ else
+ {
+ filePath = Path::combine(String(desc.shaderCachePath), String(desc.cacheFilename));
}
- auto indexString = UnownedStringSlice((char*)indexBlob->getBufferPointer());
-
- List<UnownedStringSlice> lines;
- StringUtil::calcLines(indexString, lines);
- for (auto line : lines)
+ if (SLANG_FAILED(indexStream.init(filePath, FileMode::Open, FileAccess::ReadWrite, FileShare::ReadWrite)))
{
- List<UnownedStringSlice> digests;
- StringUtil::split(line, ' ', digests); // This will return our two hashes as two elements in digests, unless we've reached the end.
- if (digests.getCount() != 2)
- continue;
- auto dependencyDigest = DigestUtil::fromString(digests[0]);
- auto contentsDigest = DigestUtil::fromString(digests[1]);
+ // If we failed to open a stream to the file, then the file does not yet exist on disk.
+ // We will create the index file if our underlying file system is mutable.
+ if (mutableShaderCacheFileSystem)
+ {
+ indexStream.init(filePath, FileMode::Create, FileAccess::ReadWrite, FileShare::ReadWrite);
+ }
+ return;
+ }
+ else
+ {
+ const auto start = indexStream.getPosition();
+ indexStream.seek(SeekOrigin::End, 0);
+ const auto end = indexStream.getPosition();
+ indexStream.seek(SeekOrigin::Start, 0);
+ const Index numEntries = (Index)(end - start) / sizeof(ShaderCacheEntry);
- ShaderCacheEntry entry = { dependencyDigest, contentsDigest };
- auto entryNode = entries.AddLast(entry);
- keyToEntry.Add(dependencyDigest, entryNode);
+ if (desc.entryCountLimit > 0 && numEntries > desc.entryCountLimit)
+ {
+ // If the size limit for the current cache is smaller than the cache that produced the file we're trying to
+ // load, re-create the entire file.
+ //
+ // FileStream does not currently have any methods for truncating an existing file, so in this case, our cache
+ // index would no longer accurately reflect the state of our cache due to the extra now-garbage lines present.
+ // While this has no impact on cache operation, it could be problematic for debugging purposes, etc.
+ indexStream.close();
+ indexStream.init(filePath, FileMode::Create, FileAccess::ReadWrite, FileShare::ReadWrite);
+ return;
+ }
+ else
+ {
+ // The cache index is not guaranteed to be ordered by most recent access, so we need a temporary list to store
+ // all the entries in order to sort them before filling in our linked list.
+ List<ShaderCacheEntry> tempEntries;
+ tempEntries.setCount(numEntries);
+ size_t bytesRead;
+ indexStream.read(tempEntries.getBuffer(), sizeof(ShaderCacheEntry) * numEntries, bytesRead);
- // If there are more entries present in the cache file than the entry count limit allows,
- // ignore the rest. Since we output the lines in the cache file in order of most to least
- // recently used, the cache's behavior will still be correct.
- if (desc.entryCountLimit > 0 && entries.Count() == desc.entryCountLimit)
- break;
+ // We will need to sort tempEntries by last accessed time before we can add entries to our linked list.
+ tempEntries.quickSort(tempEntries.getBuffer(), 0, tempEntries.getCount() - 1, [](ShaderCacheEntry a, ShaderCacheEntry b) { return a.lastAccessedTime > b.lastAccessedTime; });
+ for (auto& entry : tempEntries)
+ {
+ // If we reach this point, then the current cache is at least the same size in entries as the cache
+ // that produced the index we're reading in, so we don't need to check if we're exceeding capacity.
+ auto entryIndexNode = orderedEntries.AddLast(entries.getCount());
+ entries.add(entry);
+ keyToEntry.Add(entry.dependencyBasedDigest, entryIndexNode);
+ }
+ }
}
}
-LinkedNode<ShaderCacheEntry>* PersistentShaderCache::findEntry(const slang::Digest& key, ISlangBlob** outCompiledCode)
+ShaderCacheEntry* PersistentShaderCache::findEntry(const slang::Digest& key, ISlangBlob** outCompiledCode)
{
- LinkedNode<ShaderCacheEntry>* entryNode;
- if (!keyToEntry.TryGetValue(key, entryNode))
+ LinkedNode<Index>* entryIndexNode;
+ if (!keyToEntry.TryGetValue(key, entryIndexNode))
{
// The key was not found in the cache, so we return nullptr.
*outCompiledCode = nullptr;
@@ -90,19 +141,24 @@ LinkedNode<ShaderCacheEntry>* PersistentShaderCache::findEntry(const slang::Dige
// If the key is found, load the stored contents from disk. We then move the corresponding
// entry to the front of the linked list and update the cache file on disk
desc.shaderCacheFileSystem->loadFile(DigestUtil::toString(key).getBuffer(), outCompiledCode);
- if (entries.FirstNode() != entryNode)
+ auto index = entryIndexNode->Value;
+ entries[index].lastAccessedTime = (double)high_resolution_clock::now().time_since_epoch().count();
+ if (orderedEntries.FirstNode() != entryIndexNode)
{
- entries.RemoveFromList(entryNode);
- entries.AddFirst(entryNode);
- if (mutableShaderCacheFileSystem)
+ orderedEntries.RemoveFromList(entryIndexNode);
+ orderedEntries.AddFirst(entryIndexNode);
+ if (mutableShaderCacheFileSystem && !isMemoryFileSystem)
{
- saveCacheToFile();
+ auto offset = index * sizeof(ShaderCacheEntry);
+ indexStream.seek(SeekOrigin::Start, offset + 2 * sizeof(slang::Digest));
+ indexStream.write(&entries[index].lastAccessedTime, sizeof(double));
+ indexStream.flush();
}
}
- return entryNode;
+ return &entries[index];
}
-void PersistentShaderCache::addEntry(const slang::Digest& dependencyDigest, const slang::Digest& astDigest, ISlangBlob* compiledCode)
+void PersistentShaderCache::addEntry(const slang::Digest& dependencyDigest, const slang::Digest& contentsDigest, ISlangBlob* compiledCode)
{
if (!mutableShaderCacheFileSystem)
{
@@ -113,25 +169,45 @@ void PersistentShaderCache::addEntry(const slang::Digest& dependencyDigest, cons
// Check that we do not exceed the cache's size limit by adding another entry. If so,
// remove the least recently used entry first.
//
- // In theory, this could loop infinitely since deleteLRUEntry() immediately returns if
- // mutableShaderCacheFileSystem is not set. However, this situation is functionally impossible
- // because we check immediately before this as well.
- while (desc.entryCountLimit > 0 && entries.Count() >= desc.entryCountLimit)
+ // In theory, the cache could be more than just one entry over the entry count limit.
+ // However, this is impossible in practice because we fully re-create the entry list
+ // and cache index file if the size of the current cache is smaller than the cache
+ // that generated the index file we loaded. In any case, the initial number of entries
+ // in the cache will always be fewer than the size limit and this check will be hit
+ // on the first entry added that exceeds the cache's size.
+ Index index = entries.getCount();
+ if (desc.entryCountLimit > 0 && orderedEntries.Count() >= desc.entryCountLimit)
{
- deleteLRUEntry();
+ index = deleteLRUEntry();
}
- ShaderCacheEntry entry = { dependencyDigest, astDigest };
- auto entryNode = entries.AddFirst(entry);
+ auto lastAccessedTime = (double)high_resolution_clock::now().time_since_epoch().count();
+
+ ShaderCacheEntry entry = { dependencyDigest, contentsDigest, lastAccessedTime };
+ auto entryNode = orderedEntries.AddFirst(index);
+ if (index == entries.getCount())
+ {
+ // No entries were removed, so we can tack this entry on at the end.
+ entries.add(entry);
+ }
+ else
+ {
+ // An entry was deleted, so we overwrite that slot with the new entry.
+ entries[index] = entry;
+ }
keyToEntry.Add(dependencyDigest, entryNode);
mutableShaderCacheFileSystem->saveFileBlob(DigestUtil::toString(dependencyDigest).getBuffer(), compiledCode);
- saveCacheToFile();
+ if (!isMemoryFileSystem)
+ {
+ indexStream.seek(SeekOrigin::End, 0);
+ indexStream.write(&entry, sizeof(ShaderCacheEntry));
+ indexStream.flush();
+ }
}
void PersistentShaderCache::updateEntry(
- LinkedNode<ShaderCacheEntry>* entryNode,
const slang::Digest& dependencyDigest,
const slang::Digest& contentsDigest,
ISlangBlob* updatedCode)
@@ -143,48 +219,99 @@ void PersistentShaderCache::updateEntry(
return;
}
- entryNode->Value.contentsBasedDigest = contentsDigest;
+ // Unlike in addEntry(), we only update the contents digest here because the last accessed time will have already
+ // been updated while finding the entry.
+ auto entryIndexNode = *keyToEntry.TryGetValue(dependencyDigest);
+ auto index = entryIndexNode->Value;
+ entries[index].contentsBasedDigest = contentsDigest;
mutableShaderCacheFileSystem->saveFileBlob(DigestUtil::toString(dependencyDigest).getBuffer(), updatedCode);
- saveCacheToFile();
+ if (!isMemoryFileSystem)
+ {
+ auto offset = index * sizeof(ShaderCacheEntry);
+ indexStream.seek(SeekOrigin::Start, offset + sizeof(slang::Digest));
+ indexStream.write(&contentsDigest, sizeof(slang::Digest));
+ indexStream.flush();
+ }
}
-void PersistentShaderCache::saveCacheToFile()
+Index PersistentShaderCache::deleteLRUEntry()
{
if (!mutableShaderCacheFileSystem)
{
- // Cannot save the index to disk if the underlying file system isn't mutable.
- return;
+ // This is here as a safety precaution but should never be hit as
+ // addEntry() and its memory-based equivalent are the only functions
+ // that should call this.
+ return -1;
}
- StringBuilder indexSb;
- for (auto& entry : entries)
- {
- indexSb << entry.dependencyBasedDigest;
- indexSb << " ";
- indexSb << entry.contentsBasedDigest;
- indexSb << "\n";
- }
+ auto lruEntry = orderedEntries.LastNode();
+ auto index = lruEntry->Value;
+ auto shaderKey = entries[index].dependencyBasedDigest;
- mutableShaderCacheFileSystem->saveFile(desc.cacheFilename, indexSb.getBuffer(), indexSb.getLength());
+ keyToEntry.Remove(shaderKey);
+ mutableShaderCacheFileSystem->remove(DigestUtil::toString(shaderKey).getBuffer());
+
+ orderedEntries.Delete(lruEntry);
+ return index;
}
-void PersistentShaderCache::deleteLRUEntry()
+// An in-memory file system cannot utilize file streaming to update the index file in place.
+// Consequently, the cache index file is updated once on exit and is guaranteed to maintain the
+// correct order of entries from most to least recently used. However, any kind of interruption
+// in program execution that results in the cache destructor not being called will result in an
+// inaccurate cache index.
+//
+// These currently assume that the underlying file system must be a MemoryFileSystem as this is the
+// only in-memory file system that currently exists in Slang, which is guaranteed to be mutable.
+// Mutability checks will need to be added if this changes in the future.
+void PersistentShaderCache::loadCacheFromMemory()
{
- if (!mutableShaderCacheFileSystem)
+ ComPtr<ISlangBlob> indexBlob;
+ if (SLANG_FAILED(mutableShaderCacheFileSystem->loadFile(desc.cacheFilename, indexBlob.writeRef())))
{
- // This is here as a safety precaution but should never be hit as
- // addEntry() is the only function that should call this.
+ mutableShaderCacheFileSystem->saveFile(desc.cacheFilename, nullptr, 0);
return;
}
- auto lruEntry = entries.LastNode();
- auto shaderKey = lruEntry->Value.dependencyBasedDigest;
+ auto indexString = UnownedStringSlice((char*)indexBlob->getBufferPointer());
- keyToEntry.Remove(shaderKey);
- mutableShaderCacheFileSystem->remove(DigestUtil::toString(shaderKey).getBuffer());
+ List<UnownedStringSlice> lines;
+ StringUtil::calcLines(indexString, lines);
+ for (auto line : lines)
+ {
+ List<UnownedStringSlice> entryFields;
+ StringUtil::split(line, ' ', entryFields);
+ if (entryFields.getCount() != 2)
+ continue;
+
+ ShaderCacheEntry entry;
+ entry.dependencyBasedDigest = DigestUtil::fromString(entryFields[0]);
+ entry.contentsBasedDigest = DigestUtil::fromString(entryFields[1]);
+ entry.lastAccessedTime = 0;
+
+ auto entryNode = orderedEntries.AddLast(entries.getCount());
+ entries.add(entry);
+ keyToEntry.Add(entry.dependencyBasedDigest, entryNode);
+
+ if (desc.entryCountLimit > 0 && orderedEntries.Count() == desc.entryCountLimit)
+ break;
+ }
+}
- entries.Delete(lruEntry);
+void PersistentShaderCache::saveCacheToMemory()
+{
+ StringBuilder indexSb;
+ for (auto& entryIndex : orderedEntries)
+ {
+ auto entry = entries[entryIndex];
+ indexSb << entry.dependencyBasedDigest;
+ indexSb << " ";
+ indexSb << entry.contentsBasedDigest;
+ indexSb << "\n";
+ }
+
+ mutableShaderCacheFileSystem->saveFile(desc.cacheFilename, indexSb.getBuffer(), indexSb.getLength());
}
}