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.cpp316
1 files changed, 0 insertions, 316 deletions
diff --git a/tools/gfx/persistent-shader-cache.cpp b/tools/gfx/persistent-shader-cache.cpp
deleted file mode 100644
index 7dc64632b..000000000
--- a/tools/gfx/persistent-shader-cache.cpp
+++ /dev/null
@@ -1,316 +0,0 @@
-// slang-shader-cache-index.cpp
-#include "persistent-shader-cache.h"
-
-#include "../../source/core/slang-io.h"
-#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;
-
- // If a path is provided, we will want our underlying file system to be initialized using that path.
- if (desc.shaderCachePath)
- {
- if (!desc.shaderCacheFileSystem)
- {
- // Only a path was provided, so we get a mutable file system
- // using OSFileSystem::getMutableSingleton.
- desc.shaderCacheFileSystem = OSFileSystem::getMutableSingleton();
- }
- desc.shaderCacheFileSystem = new RelativeFileSystem(desc.shaderCacheFileSystem, desc.shaderCachePath);
- }
-
- // If our shader cache has an underlying file system, check if it's mutable. If so, store a pointer
- // to the mutable version for operations which require writing to disk.
- if (desc.shaderCacheFileSystem)
- {
- desc.shaderCacheFileSystem->queryInterface(ISlangMutableFileSystem::getTypeGuid(), (void**)mutableShaderCacheFileSystem.writeRef());
- }
-
- 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()
-{
- // 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)
- {
- ComPtr<ISlangBlob> fullPath;
- if (SLANG_FAILED(mutableShaderCacheFileSystem->getPath(PathKind::OperatingSystem, desc.cacheFilename, fullPath.writeRef())))
- {
- // 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;
- }
- filePath = String((char*)fullPath->getBufferPointer());
- }
- else
- {
- filePath = Path::combine(String(desc.shaderCachePath), String(desc.cacheFilename));
- }
-
- if (SLANG_FAILED(indexStream.init(filePath, FileMode::Open, FileAccess::ReadWrite, FileShare::ReadWrite)))
- {
- // 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);
-
- 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);
-
- // 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);
- }
- }
- }
-}
-
-ShaderCacheEntry* PersistentShaderCache::findEntry(const DigestType& key, ISlangBlob** outCompiledCode)
-{
- LinkedNode<Index>* entryIndexNode;
- if (!keyToEntry.TryGetValue(key, entryIndexNode))
- {
- // The key was not found in the cache, so we return nullptr.
- *outCompiledCode = nullptr;
- return nullptr;
- }
-
- // 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(key.toString().getBuffer(), outCompiledCode);
- auto index = entryIndexNode->Value;
- entries[index].lastAccessedTime = (double)high_resolution_clock::now().time_since_epoch().count();
- if (orderedEntries.FirstNode() != entryIndexNode)
- {
- orderedEntries.RemoveFromList(entryIndexNode);
- orderedEntries.AddFirst(entryIndexNode);
- if (mutableShaderCacheFileSystem && !isMemoryFileSystem)
- {
- auto offset = index * sizeof(ShaderCacheEntry);
- indexStream.seek(SeekOrigin::Start, offset + 2 * sizeof(DigestType));
- indexStream.write(&entries[index].lastAccessedTime, sizeof(double));
- indexStream.flush();
- }
- }
- return &entries[index];
-}
-
-void PersistentShaderCache::addEntry(const DigestType& dependencyDigest, const DigestType& contentsDigest, ISlangBlob* compiledCode)
-{
- if (!mutableShaderCacheFileSystem)
- {
- // Should not save new entries if the underlying file system isn't mutable.
- return;
- }
-
- // 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, 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)
- {
- index = deleteLRUEntry();
- }
-
- 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(dependencyDigest.toString().getBuffer(), compiledCode);
-
- if (!isMemoryFileSystem)
- {
- indexStream.seek(SeekOrigin::End, 0);
- indexStream.write(&entry, sizeof(ShaderCacheEntry));
- indexStream.flush();
- }
-}
-
-void PersistentShaderCache::updateEntry(
- const DigestType& dependencyDigest,
- const DigestType& contentsDigest,
- ISlangBlob* updatedCode)
-{
- if (!mutableShaderCacheFileSystem)
- {
- // Updating entries requires saving to disk in order to overwrite the old shader file
- // on disk, so we return if the underlying file system isn't mutable.
- return;
- }
-
- // 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(dependencyDigest.toString().getBuffer(), updatedCode);
-
- if (!isMemoryFileSystem)
- {
- auto offset = index * sizeof(ShaderCacheEntry);
- indexStream.seek(SeekOrigin::Start, offset + sizeof(DigestType));
- indexStream.write(&contentsDigest, sizeof(DigestType));
- indexStream.flush();
- }
-}
-
-Index PersistentShaderCache::deleteLRUEntry()
-{
- if (!mutableShaderCacheFileSystem)
- {
- // 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;
- }
-
- auto lruEntry = orderedEntries.LastNode();
- auto index = lruEntry->Value;
- auto shaderKey = entries[index].dependencyBasedDigest;
-
- keyToEntry.Remove(shaderKey);
- mutableShaderCacheFileSystem->remove(shaderKey.toString().getBuffer());
-
- orderedEntries.Delete(lruEntry);
- return index;
-}
-
-// 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()
-{
- ComPtr<ISlangBlob> indexBlob;
- if (SLANG_FAILED(mutableShaderCacheFileSystem->loadFile(desc.cacheFilename, indexBlob.writeRef())))
- {
- mutableShaderCacheFileSystem->saveFile(desc.cacheFilename, nullptr, 0);
- return;
- }
-
- auto indexString = UnownedStringSlice((char*)indexBlob->getBufferPointer());
-
- 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 = DigestType(entryFields[0]);
- entry.contentsBasedDigest = DigestType(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;
- }
-}
-
-void PersistentShaderCache::saveCacheToMemory()
-{
- StringBuilder indexSb;
- for (auto& entryIndex : orderedEntries)
- {
- auto entry = entries[entryIndex];
- indexSb << entry.dependencyBasedDigest.toString();
- indexSb << " ";
- indexSb << entry.contentsBasedDigest.toString();
- indexSb << "\n";
- }
-
- mutableShaderCacheFileSystem->saveFile(desc.cacheFilename, indexSb.getBuffer(), indexSb.getLength());
-}
-
-}