summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/gfx-unit-test/gfx-test-util.cpp12
-rw-r--r--tools/gfx-unit-test/gfx-test-util.h13
-rw-r--r--tools/gfx-unit-test/imported.slang8
-rw-r--r--tools/gfx-unit-test/importing-shader-cache-shader.slang15
-rw-r--r--tools/gfx-unit-test/shader-cache-shader-A.slang10
-rw-r--r--tools/gfx-unit-test/shader-cache-shader-B.slang10
-rw-r--r--tools/gfx-unit-test/shader-cache-shader-C.slang10
-rw-r--r--tools/gfx-unit-test/shader-cache-shader.slang10
-rw-r--r--tools/gfx-unit-test/shader-cache-tests.cpp246
-rw-r--r--tools/gfx/gfx.slang20
-rw-r--r--tools/gfx/persistent-shader-cache.cpp24
-rw-r--r--tools/gfx/persistent-shader-cache.h20
-rw-r--r--tools/gfx/renderer-shared.cpp111
-rw-r--r--tools/gfx/renderer-shared.h7
14 files changed, 280 insertions, 236 deletions
diff --git a/tools/gfx-unit-test/gfx-test-util.cpp b/tools/gfx-unit-test/gfx-test-util.cpp
index 90aec2647..fa0745872 100644
--- a/tools/gfx-unit-test/gfx-test-util.cpp
+++ b/tools/gfx-unit-test/gfx-test-util.cpp
@@ -176,7 +176,10 @@ namespace gfx_test
compareComputeResultFuzzy(result, expectedResult, expectedBufferSize);
}
- Slang::ComPtr<gfx::IDevice> createTestingDevice(UnitTestContext* context, Slang::RenderApiFlag::Enum api, ISlangMutableFileSystem* fileSystem)
+ Slang::ComPtr<gfx::IDevice> createTestingDevice(
+ UnitTestContext* context,
+ Slang::RenderApiFlag::Enum api,
+ gfx::IDevice::ShaderCacheDesc shaderCache)
{
Slang::ComPtr<gfx::IDevice> device;
gfx::IDevice::Desc deviceDesc = {};
@@ -208,6 +211,8 @@ namespace gfx_test
deviceDesc.slang.searchPathCount = (SlangInt)SLANG_COUNT_OF(searchPaths);
deviceDesc.slang.searchPaths = searchPaths;
+ deviceDesc.shaderCache = shaderCache;
+
gfx::D3D12DeviceExtendedDesc extDesc = {};
extDesc.rootParameterShaderAttributeName = "root";
@@ -215,11 +220,6 @@ namespace gfx_test
void* extDescPtr = &extDesc;
deviceDesc.extendedDescs = &extDescPtr;
- if (fileSystem)
- {
- deviceDesc.shaderCacheFileSystem = fileSystem;
- }
-
auto createDeviceResult = gfxCreateDevice(&deviceDesc, device.writeRef());
if (SLANG_FAILED(createDeviceResult))
{
diff --git a/tools/gfx-unit-test/gfx-test-util.h b/tools/gfx-unit-test/gfx-test-util.h
index e3b8f493b..5175366c4 100644
--- a/tools/gfx-unit-test/gfx-test-util.h
+++ b/tools/gfx-unit-test/gfx-test-util.h
@@ -69,20 +69,27 @@ namespace gfx_test
return compareComputeResult(device, buffer, 0, expectedBuffer.getBuffer(), bufferSize);
}
- Slang::ComPtr<gfx::IDevice> createTestingDevice(UnitTestContext* context, Slang::RenderApiFlag::Enum api, ISlangMutableFileSystem* fileSystem = nullptr);
+ Slang::ComPtr<gfx::IDevice> createTestingDevice(
+ UnitTestContext* context,
+ Slang::RenderApiFlag::Enum api,
+ gfx::IDevice::ShaderCacheDesc shaderCache = {});
void initializeRenderDoc();
void renderDocBeginFrame();
void renderDocEndFrame();
template<typename ImplFunc>
- void runTestImpl(const ImplFunc& f, UnitTestContext* context, Slang::RenderApiFlag::Enum api, ISlangMutableFileSystem* fileSystem = nullptr)
+ void runTestImpl(
+ const ImplFunc& f,
+ UnitTestContext* context,
+ Slang::RenderApiFlag::Enum api,
+ gfx::IDevice::ShaderCacheDesc shaderCache = {})
{
if ((api & context->enabledApis) == 0)
{
SLANG_IGNORE_TEST
}
- auto device = createTestingDevice(context, api, fileSystem);
+ auto device = createTestingDevice(context, api, shaderCache);
if (!device)
{
SLANG_IGNORE_TEST
diff --git a/tools/gfx-unit-test/imported.slang b/tools/gfx-unit-test/imported.slang
deleted file mode 100644
index a44782390..000000000
--- a/tools/gfx-unit-test/imported.slang
+++ /dev/null
@@ -1,8 +0,0 @@
-struct TestFunction
- {
- void simpleElementAdd(RWStructuredBuffer<float> buffer, uint index)
- {
- var input = buffer[index];
- buffer[index] = input + 1.0f;
- }
- }; \ No newline at end of file
diff --git a/tools/gfx-unit-test/importing-shader-cache-shader.slang b/tools/gfx-unit-test/importing-shader-cache-shader.slang
deleted file mode 100644
index bede42492..000000000
--- a/tools/gfx-unit-test/importing-shader-cache-shader.slang
+++ /dev/null
@@ -1,15 +0,0 @@
-#include "imported.slang"
-
- uniform RWStructuredBuffer<float> buffer;
-
- [shader("compute")]
- [numthreads(4, 1, 1)]
- void computeMain(
- uint3 sv_dispatchThreadID : SV_DispatchThreadID)
- {
- TestFunction test;
- for (uint i = 0; i < 4; ++i)
- {
- test.simpleElementAdd(buffer, i);
- }
- } \ No newline at end of file
diff --git a/tools/gfx-unit-test/shader-cache-shader-A.slang b/tools/gfx-unit-test/shader-cache-shader-A.slang
deleted file mode 100644
index c2dbe56f0..000000000
--- a/tools/gfx-unit-test/shader-cache-shader-A.slang
+++ /dev/null
@@ -1,10 +0,0 @@
-uniform RWStructuredBuffer<float> buffer;
-
- [shader("compute")]
- [numthreads(4, 1, 1)]
- void computeMain(
- uint3 sv_dispatchThreadID : SV_DispatchThreadID)
- {
- var input = buffer[sv_dispatchThreadID.x];
- buffer[sv_dispatchThreadID.x] = input + 3.0f;
- } \ No newline at end of file
diff --git a/tools/gfx-unit-test/shader-cache-shader-B.slang b/tools/gfx-unit-test/shader-cache-shader-B.slang
deleted file mode 100644
index 341e32893..000000000
--- a/tools/gfx-unit-test/shader-cache-shader-B.slang
+++ /dev/null
@@ -1,10 +0,0 @@
-uniform RWStructuredBuffer<float> buffer;
-
- [shader("compute")]
- [numthreads(4, 1, 1)]
- void computeMain(
- uint3 sv_dispatchThreadID : SV_DispatchThreadID)
- {
- var input = buffer[sv_dispatchThreadID.x];
- buffer[sv_dispatchThreadID.x] = input + 1.0f;
- } \ No newline at end of file
diff --git a/tools/gfx-unit-test/shader-cache-shader-C.slang b/tools/gfx-unit-test/shader-cache-shader-C.slang
deleted file mode 100644
index 826475761..000000000
--- a/tools/gfx-unit-test/shader-cache-shader-C.slang
+++ /dev/null
@@ -1,10 +0,0 @@
-uniform RWStructuredBuffer<float> buffer;
-
- [shader("compute")]
- [numthreads(4, 1, 1)]
- void computeMain(
- uint3 sv_dispatchThreadID : SV_DispatchThreadID)
- {
- var input = buffer[sv_dispatchThreadID.x];
- buffer[sv_dispatchThreadID.x] = input + 2.0f;
- } \ No newline at end of file
diff --git a/tools/gfx-unit-test/shader-cache-shader.slang b/tools/gfx-unit-test/shader-cache-shader.slang
deleted file mode 100644
index 341e32893..000000000
--- a/tools/gfx-unit-test/shader-cache-shader.slang
+++ /dev/null
@@ -1,10 +0,0 @@
-uniform RWStructuredBuffer<float> buffer;
-
- [shader("compute")]
- [numthreads(4, 1, 1)]
- void computeMain(
- uint3 sv_dispatchThreadID : SV_DispatchThreadID)
- {
- var input = buffer[sv_dispatchThreadID.x];
- buffer[sv_dispatchThreadID.x] = input + 1.0f;
- } \ No newline at end of file
diff --git a/tools/gfx-unit-test/shader-cache-tests.cpp b/tools/gfx-unit-test/shader-cache-tests.cpp
index 67d7289aa..19e3ffb85 100644
--- a/tools/gfx-unit-test/shader-cache-tests.cpp
+++ b/tools/gfx-unit-test/shader-cache-tests.cpp
@@ -4,11 +4,13 @@
#include "gfx-test-util.h"
#include "tools/gfx-util/shader-cursor.h"
#include "source/core/slang-basic.h"
+#include "source/core/slang-string-util.h"
#include "source/core/slang-memory-file-system.h"
#include "source/core/slang-file-system.h"
using namespace gfx;
+using namespace Slang;
namespace gfx_test
{
@@ -16,22 +18,29 @@ namespace gfx_test
struct BaseShaderCacheTest
{
UnitTestContext* context;
- Slang::RenderApiFlag::Enum api;
+ RenderApiFlag::Enum api;
ComPtr<IDevice> device;
ComPtr<IPipelineState> pipelineState;
ComPtr<IResourceView> bufferView;
- // diskFileSystem is used to save the test shaders to disk as necessary as
- // loadComputeProgram() always loads from the OS file system. cacheFileSystem
- // is used to hold the shader cache in memory to avoid needing to manually erase all the
- // shader cache entry files between consecutive runs of the test.
+ IDevice::ShaderCacheDesc shaderCache = {};
+
+ // Two file systems in order to get around problems posed by the testing framework.
+ //
+ // - diskFileSystem - Used to save any files that must exist on disk for subsequent
+ // save/load function calls (most prominently loadComputeProgram()) to pick up.
+ // - cacheFileSystem - Used to hold for the actual cache for all tests. This removes the need to
+ // manually clean out old cache files from previous test runs as it is
+ // located in-memory. This is the file system passed to device creation
+ // as part of the shader cache desc.
ComPtr<ISlangMutableFileSystem> diskFileSystem;
ComPtr<ISlangMutableFileSystem> cacheFileSystem;
// Simple compute shaders we can pipe to our individual shader files for cache testing
- Slang::String contentsA = Slang::String(
- R"(uniform RWStructuredBuffer<float> buffer;
+ String contentsA = String(
+ R"(
+ uniform RWStructuredBuffer<float> buffer;
[shader("compute")]
[numthreads(4, 1, 1)]
@@ -42,8 +51,9 @@ namespace gfx_test
buffer[sv_dispatchThreadID.x] = input + 1.0f;
})");
- Slang::String contentsB = Slang::String(
- R"(uniform RWStructuredBuffer<float> buffer;
+ String contentsB = String(
+ R"(
+ uniform RWStructuredBuffer<float> buffer;
[shader("compute")]
[numthreads(4, 1, 1)]
@@ -54,8 +64,9 @@ namespace gfx_test
buffer[sv_dispatchThreadID.x] = input + 2.0f;
})");
- Slang::String contentsC = Slang::String(
- R"(uniform RWStructuredBuffer<float> buffer;
+ String contentsC = String(
+ R"(
+ uniform RWStructuredBuffer<float> buffer;
[shader("compute")]
[numthreads(4, 1, 1)]
@@ -72,7 +83,7 @@ namespace gfx_test
float initialData[] = { 0.0f, 1.0f, 2.0f, 3.0f };
IBufferResource::Desc bufferDesc = {};
bufferDesc.sizeInBytes = numberCount * sizeof(float);
- bufferDesc.format = gfx::Format::Unknown;
+ bufferDesc.format = Format::Unknown;
bufferDesc.elementSize = sizeof(float);
bufferDesc.allowedStates = ResourceStateSet(
ResourceState::ShaderResource,
@@ -107,7 +118,7 @@ namespace gfx_test
void generateNewDevice()
{
freeOldResources();
- device = createTestingDevice(context, api, cacheFileSystem);
+ device = createTestingDevice(context, api, shaderCache);
}
void init(ComPtr<IDevice> device, UnitTestContext* context)
@@ -117,35 +128,37 @@ namespace gfx_test
switch (device->getDeviceInfo().deviceType)
{
case DeviceType::DirectX11:
- api = Slang::RenderApiFlag::D3D11;
+ api = RenderApiFlag::D3D11;
break;
case DeviceType::DirectX12:
- api = Slang::RenderApiFlag::D3D12;
+ api = RenderApiFlag::D3D12;
break;
case DeviceType::Vulkan:
- api = Slang::RenderApiFlag::Vulkan;
+ api = RenderApiFlag::Vulkan;
break;
case DeviceType::CPU:
- api = Slang::RenderApiFlag::CPU;
+ api = RenderApiFlag::CPU;
break;
case DeviceType::CUDA:
- api = Slang::RenderApiFlag::CUDA;
+ api = RenderApiFlag::CUDA;
break;
case DeviceType::OpenGl:
- api = Slang::RenderApiFlag::OpenGl;
+ api = RenderApiFlag::OpenGl;
break;
default:
SLANG_IGNORE_TEST
}
- cacheFileSystem = new Slang::MemoryFileSystem();
- diskFileSystem = Slang::OSFileSystem::getMutableSingleton();
- diskFileSystem = new Slang::RelativeFileSystem(diskFileSystem, "tools/gfx-unit-test");
+ cacheFileSystem = new MemoryFileSystem();
+ diskFileSystem = OSFileSystem::getMutableSingleton();
+ diskFileSystem = new RelativeFileSystem(diskFileSystem, "tools/gfx-unit-test");
+
+ shaderCache.shaderCacheFileSystem = cacheFileSystem;
}
void submitGPUWork()
{
- Slang::ComPtr<ITransientResourceHeap> transientHeap;
+ ComPtr<ITransientResourceHeap> transientHeap;
ITransientResourceHeap::Desc transientHeapDesc = {};
transientHeapDesc.constantBufferSize = 4096;
GFX_CHECK_CALL_ABORT(
@@ -176,11 +189,11 @@ namespace gfx_test
{
void generateNewPipelineState(Slang::String shaderContents)
{
- diskFileSystem->saveFile("shader-cache-shader.slang", shaderContents.getBuffer(), shaderContents.getLength());
+ diskFileSystem->saveFile("test-tmp-single-entry.slang", shaderContents.getBuffer(), shaderContents.getLength());
ComPtr<IShaderProgram> shaderProgram;
slang::ProgramLayout* slangReflection;
- GFX_CHECK_CALL_ABORT(loadComputeProgram(device, shaderProgram, "shader-cache-shader", "computeMain", slangReflection));
+ GFX_CHECK_CALL_ABORT(loadComputeProgram(device, shaderProgram, "test-tmp-single-entry", "computeMain", slangReflection));
ComputePipelineStateDesc pipelineDesc = {};
pipelineDesc.program = shaderProgram.get();
@@ -235,19 +248,19 @@ namespace gfx_test
// Several shader files on disk, modifications may be done to any file
struct MultipleEntryShaderCache : BaseShaderCacheTest
{
- void modifyShaderA(Slang::String shaderContents)
+ void modifyShaderA(String shaderContents)
{
- diskFileSystem->saveFile("shader-cache-shader-A.slang", shaderContents.getBuffer(), shaderContents.getLength());
+ diskFileSystem->saveFile("test-tmp-multi-entry-A.slang", shaderContents.getBuffer(), shaderContents.getLength());
}
- void modifyShaderB(Slang::String shaderContents)
+ void modifyShaderB(String shaderContents)
{
- diskFileSystem->saveFile("shader-cache-shader-B.slang", shaderContents.getBuffer(), shaderContents.getLength());
+ diskFileSystem->saveFile("test-tmp-multi-entry-B.slang", shaderContents.getBuffer(), shaderContents.getLength());
}
- void modifyShaderC(Slang::String shaderContents)
+ void modifyShaderC(String shaderContents)
{
- diskFileSystem->saveFile("shader-cache-shader-C.slang", shaderContents.getBuffer(), shaderContents.getLength());
+ diskFileSystem->saveFile("test-tmp-multi-entry-C.slang", shaderContents.getBuffer(), shaderContents.getLength());
}
void generateNewPipelineState(GfxIndex shaderIndex)
@@ -258,13 +271,13 @@ namespace gfx_test
switch (shaderIndex)
{
case 0:
- shaderFilename = "shader-cache-shader-A";
+ shaderFilename = "test-tmp-multi-entry-A";
break;
case 1:
- shaderFilename = "shader-cache-shader-B";
+ shaderFilename = "test-tmp-multi-entry-B";
break;
case 2:
- shaderFilename = "shader-cache-shader-C";
+ shaderFilename = "test-tmp-multi-entry-C";
break;
default:
// Should never reach this point since we wrote the test
@@ -434,8 +447,9 @@ namespace gfx_test
// 4. #include w/ changes in the included file
struct ShaderFileImportsShaderCache : BaseShaderCacheTest
{
- Slang::String importedContentsA = Slang::String(
- R"(struct TestFunction
+ String importedContentsA = String(
+ R"(
+ struct TestFunction
{
void simpleElementAdd(RWStructuredBuffer<float> buffer, uint index)
{
@@ -444,8 +458,9 @@ namespace gfx_test
}
};)");
- Slang::String importedContentsB = Slang::String(
- R"(struct TestFunction
+ String importedContentsB = String(
+ R"(
+ struct TestFunction
{
void simpleElementAdd(RWStructuredBuffer<float> buffer, uint index)
{
@@ -454,8 +469,9 @@ namespace gfx_test
}
};)");
- Slang::String importFile = Slang::String(
- R"(import imported;
+ String importFile = String(
+ R"(
+ import test_tmp_imported;
uniform RWStructuredBuffer<float> buffer;
@@ -471,8 +487,9 @@ namespace gfx_test
}
})");
- Slang::String includeFile = Slang::String(
- R"(#include "imported.slang"
+ String includeFile = String(
+ R"(
+ #include "test-tmp-imported.slang"
uniform RWStructuredBuffer<float> buffer;
@@ -490,25 +507,25 @@ namespace gfx_test
void initializeFiles()
{
- diskFileSystem->saveFile("imported.slang", importedContentsA.getBuffer(), importedContentsA.getLength());
- diskFileSystem->saveFile("importing-shader-cache-shader.slang", importFile.getBuffer(), importFile.getLength());
+ diskFileSystem->saveFile("test-tmp-imported.slang", importedContentsA.getBuffer(), importedContentsA.getLength());
+ diskFileSystem->saveFile("test-tmp-importing.slang", importFile.getBuffer(), importFile.getLength());
}
- void modifyImportedFile(Slang::String importedContents)
+ void modifyImportedFile(String importedContents)
{
- diskFileSystem->saveFile("imported.slang", importedContents.getBuffer(), importedContents.getLength());
+ diskFileSystem->saveFile("test-tmp-imported.slang", importedContents.getBuffer(), importedContents.getLength());
}
void changeImportToInclude()
{
- diskFileSystem->saveFile("importing-shader-cache-shader.slang", includeFile.getBuffer(), includeFile.getLength());
+ diskFileSystem->saveFile("test-tmp-importing.slang", includeFile.getBuffer(), includeFile.getLength());
}
void generateNewPipelineState()
{
ComPtr<IShaderProgram> shaderProgram;
slang::ProgramLayout* slangReflection;
- GFX_CHECK_CALL_ABORT(loadComputeProgram(device, shaderProgram, "importing-shader-cache-shader", "computeMain", slangReflection));
+ GFX_CHECK_CALL_ABORT(loadComputeProgram(device, shaderProgram, "test-tmp-importing", "computeMain", slangReflection));
ComputePipelineStateDesc pipelineDesc = {};
pipelineDesc.program = shaderProgram.get();
@@ -689,6 +706,129 @@ namespace gfx_test
}
};
+ // Same as MultipleEntryShaderCache, but we now set the maximum entry count limit, so the cache
+ // should remove entries as needed when it reaches capacity.
+ //
+ // This test does not modify shaders as other tests already test this, instead focusing on checking
+ // that entries are correctly removed as cache limits are reached and that entries are always in
+ // the right order.
+ struct CacheWithMaxEntryLimit : MultipleEntryShaderCache
+ {
+ List<String> test1Lines; // C -> B
+ List<String> test2Lines; // A -> B
+ List<String> test3Lines; // A -> C
+ List<String> test4Lines; // C -> B -> A
+
+ void getCacheFile(List<String>& lines)
+ {
+ ComPtr<ISlangBlob> contentsBlob;
+ cacheFileSystem->loadFile(shaderCache.cacheFilename, contentsBlob.writeRef());
+ List<UnownedStringSlice> temp;
+ StringUtil::calcLines(UnownedStringSlice((char*)contentsBlob->getBufferPointer()), temp);
+ for (auto line : temp)
+ {
+ lines.add(line);
+ }
+ }
+
+ // Check the correctness of the cache's entries by comparing the order of entries in the
+ // current state of the cache with what we expect.
+ void checkCacheFiles()
+ {
+ // Check that shader A appears where we expect it to.
+ SLANG_CHECK(test2Lines[0] == test3Lines[0]);
+ SLANG_CHECK(test2Lines[0] == test4Lines[2]);
+
+ // Check that shader B appears where we expect it to.
+ SLANG_CHECK(test1Lines[1] == test2Lines[1]);
+ SLANG_CHECK(test1Lines[1] == test4Lines[1]);
+
+ // Check that shader C appears where we expect it to.
+ SLANG_CHECK(test1Lines[0] == test3Lines[1]);
+ SLANG_CHECK(test1Lines[0] == test4Lines[0]);
+ }
+
+ void run()
+ {
+ ComPtr<IShaderCacheStatistics> shaderCacheStats;
+
+ // Due to needing a workaround to prevent loading old, outdated modules, we need to
+ // recreate the device between each segment of the test. However, we need to maintain the
+ // same cache filesystem for the duration of the test, so the device is immediately recreated
+ // to ensure we can pass the filesystem all the way through.
+ //
+ // TODO: Remove the repeated generateNewDevice() and createRequiredResources() calls once
+ // a solution exists that allows source code changes under the same module name to be picked
+ // up on load.
+ shaderCache.entryCountLimit = 2;
+ generateNewDevice();
+ createRequiredResources();
+ modifyShaderA(contentsA);
+ modifyShaderB(contentsB);
+ modifyShaderC(contentsC);
+ generateNewPipelineState(0);
+ submitGPUWork();
+ generateNewPipelineState(1);
+ submitGPUWork();
+ generateNewPipelineState(2);
+ submitGPUWork();
+
+ // Cache limit 2, three unique shaders
+ device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef());
+ SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 3);
+ SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0);
+ SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0);
+ getCacheFile(test1Lines);
+
+ generateNewDevice();
+ createRequiredResources();
+ generateNewPipelineState(1);
+ submitGPUWork();
+ generateNewPipelineState(0);
+ submitGPUWork();
+
+ // Cache limit 2, access shaders B and then A
+ device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef());
+ SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1);
+ SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 1);
+ SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0);
+ getCacheFile(test2Lines);
+
+ generateNewDevice();
+ createRequiredResources();
+ generateNewPipelineState(2);
+ submitGPUWork();
+ generateNewPipelineState(0);
+ submitGPUWork();
+
+ // Cache limit 2, access shaders C and then A
+ device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef());
+ SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1);
+ SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 1);
+ SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0);
+ getCacheFile(test3Lines);
+
+ shaderCache.entryCountLimit = 3;
+ generateNewDevice();
+ createRequiredResources();
+ generateNewPipelineState(0);
+ submitGPUWork();
+ generateNewPipelineState(1);
+ submitGPUWork();
+ generateNewPipelineState(2);
+ submitGPUWork();
+
+ // Cache limit 3, access shaders A then B then C
+ device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef());
+ SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1);
+ SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 2);
+ SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0);
+ getCacheFile(test4Lines);
+
+ checkCacheFiles();
+ }
+ };
+
template <typename T>
void shaderCacheTestImpl(ComPtr<IDevice> device, UnitTestContext* context)
{
@@ -746,4 +886,14 @@ namespace gfx_test
{
runTestImpl(shaderCacheTestImpl<SpecializationArgsEntries>, unitTestContext, Slang::RenderApiFlag::Vulkan);
}
+
+ SLANG_UNIT_TEST(cacheEvictionPolicyD3D12)
+ {
+ runTestImpl(shaderCacheTestImpl<CacheWithMaxEntryLimit>, unitTestContext, Slang::RenderApiFlag::D3D12);
+ }
+
+ SLANG_UNIT_TEST(cacheEvictionPolicyVulkan)
+ {
+ runTestImpl(shaderCacheTestImpl<CacheWithMaxEntryLimit>, unitTestContext, Slang::RenderApiFlag::Vulkan);
+ }
}
diff --git a/tools/gfx/gfx.slang b/tools/gfx/gfx.slang
index 13359334f..b4bd76470 100644
--- a/tools/gfx/gfx.slang
+++ b/tools/gfx/gfx.slang
@@ -1710,6 +1710,19 @@ struct SlangDesc
slang::SlangLineDirectiveMode lineDirectiveMode = slang::SlangLineDirectiveMode::SLANG_LINE_DIRECTIVE_MODE_DEFAULT;
};
+struct ShaderCacheDesc
+{
+ // The filename for the file the cache's state should be saved to or loaded from.
+ NativeString cacheFilename = "cache.txt";
+ // The root directory for the shader cache.
+ NativeString shaderCachePath;
+ // The file system for loading cached shader kernels. The layer does not maintain a strong reference to the object,
+ // instead the user is responsible for holding the object alive during the lifetime of an `IDevice`.
+ void* shaderCacheFileSystem = nullptr;
+ // The maximum number of entries stored in the cache.
+ GfxCount entryCountLimit = 0;
+};
+
struct DeviceInteropHandles
{
InteropHandle handles[3] = {};
@@ -1733,11 +1746,8 @@ struct DeviceDesc
void *apiCommandDispatcher = nullptr;
// The slot (typically UAV) used to identify NVAPI intrinsics. If >=0 NVAPI is required.
GfxIndex nvapiExtnSlot = -1;
- // The root directory for the shader cache.
- NativeString shaderCachePath;
- // The file system for loading cached shader kernels. The layer does not maintain a strong reference to the object,
- // instead the user is responsible for holding the object alive during the lifetime of an `IDevice`.
- void *shaderCacheFileSystem = nullptr;
+ // Configurations for the shader cache.
+ ShaderCacheDesc shaderCache = {};
// Configurations for Slang compiler.
SlangDesc slang = {};
diff --git a/tools/gfx/persistent-shader-cache.cpp b/tools/gfx/persistent-shader-cache.cpp
index a378e0f4f..55a95a257 100644
--- a/tools/gfx/persistent-shader-cache.cpp
+++ b/tools/gfx/persistent-shader-cache.cpp
@@ -6,14 +6,15 @@
#include "../../source/core/slang-string-util.h"
#include "../../source/core/slang-file-system.h"
-namespace Slang
+namespace gfx
{
-PersistentShaderCache::PersistentShaderCache(const Desc& inDesc)
+PersistentShaderCache::PersistentShaderCache(const IDevice::ShaderCacheDesc& inDesc)
{
desc = inDesc;
- if (!desc.shaderCachePath.getBuffer())
+ // If a path is provided, we will want our underlying file system to be initialized using that path.
+ if (desc.shaderCachePath)
{
if (!desc.shaderCacheFileSystem)
{
@@ -25,7 +26,7 @@ PersistentShaderCache::PersistentShaderCache(const Desc& inDesc)
}
// If our shader cache has an underlying file system, check if it's mutable. If so, store a pointer
- // to the mutable version in order to save new entries later.
+ // to the mutable version for operations which require writing to disk.
if (desc.shaderCacheFileSystem)
{
desc.shaderCacheFileSystem->queryInterface(ISlangMutableFileSystem::getTypeGuid(), (void**)mutableShaderCacheFileSystem.writeRef());
@@ -39,27 +40,28 @@ PersistentShaderCache::PersistentShaderCache(const Desc& inDesc)
void PersistentShaderCache::loadCacheFromFile()
{
ComPtr<ISlangBlob> indexBlob;
- if (SLANG_FAILED(desc.shaderCacheFileSystem->loadFile(desc.cacheFilename.getBuffer(), indexBlob.writeRef())))
+ if (SLANG_FAILED(desc.shaderCacheFileSystem->loadFile(desc.cacheFilename, indexBlob.writeRef())))
{
// Cache index not found, so we'll create and save a new one.
if (mutableShaderCacheFileSystem)
{
- mutableShaderCacheFileSystem->saveFile(desc.cacheFilename.getBuffer(), nullptr, 0);
+ mutableShaderCacheFileSystem->saveFile(desc.cacheFilename, nullptr, 0);
return;
}
// Cache index not found and we can't save a new one due to the file system being immutable.
return;
}
- String indexString;
- File::readAllText(desc.cacheFilename, indexString);
+ auto indexString = UnownedStringSlice((char*)indexBlob->getBufferPointer());
List<UnownedStringSlice> lines;
- StringUtil::calcLines(indexString.getUnownedSlice(), lines);
+ StringUtil::calcLines(indexString, lines);
for (auto line : lines)
{
List<UnownedStringSlice> digests;
- StringUtil::split(line, ' ', digests); // This will return our two hashes as two elements in 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 astDigest = DigestUtil::fromString(digests[1]);
@@ -158,7 +160,7 @@ void PersistentShaderCache::saveCacheToFile()
indexSb << "\n";
}
- mutableShaderCacheFileSystem->saveFile(desc.cacheFilename.getBuffer(), indexSb.getBuffer(), indexSb.getLength());
+ mutableShaderCacheFileSystem->saveFile(desc.cacheFilename, indexSb.getBuffer(), indexSb.getLength());
}
void PersistentShaderCache::deleteLRUEntry()
diff --git a/tools/gfx/persistent-shader-cache.h b/tools/gfx/persistent-shader-cache.h
index 55738a9af..49a4a53dd 100644
--- a/tools/gfx/persistent-shader-cache.h
+++ b/tools/gfx/persistent-shader-cache.h
@@ -1,34 +1,28 @@
// slang-shader-cache-index.h
#pragma once
#include "../../slang.h"
+#include "../../slang-gfx.h"
#include "../../slang-com-ptr.h"
#include "../../source/core/slang-string.h"
#include "../../source/core/slang-dictionary.h"
#include "../../source/core/slang-linked-list.h"
-namespace Slang
+namespace gfx
{
+ using namespace Slang;
+
struct ShaderCacheEntry
{
slang::Digest dependencyBasedDigest;
slang::Digest astBasedDigest;
};
-class PersistentShaderCache
+class PersistentShaderCache : public RefObject
{
public:
- // TODO: Remove in integration PR in favor of new ShaderCacheDesc in slang-gfx.h
- struct Desc
- {
- String cacheFilename;
- String shaderCachePath;
- SlangInt entryCountLimit = 1000;
- ISlangFileSystem* shaderCacheFileSystem = nullptr;
- };
-
- PersistentShaderCache(const Desc& inDesc);
+ PersistentShaderCache(const IDevice::ShaderCacheDesc& inDesc);
// Fetch the cache entry corresponding to the provided key. If found, move the entry to
// the front of entries and return the entry and the corresponding compiled code in
@@ -62,7 +56,7 @@ private:
void deleteLRUEntry();
// The shader cache's description.
- Desc desc;
+ IDevice::ShaderCacheDesc desc;
// Dictionary mapping each shader's key to its corresponding node (entry) in the list
// of entries.
diff --git a/tools/gfx/renderer-shared.cpp b/tools/gfx/renderer-shared.cpp
index 48cd6907f..c7d7933a3 100644
--- a/tools/gfx/renderer-shared.cpp
+++ b/tools/gfx/renderer-shared.cpp
@@ -351,11 +351,8 @@ Result RendererBase::getEntryPointCodeFromShaderCache(
slang::IBlob** outCode,
slang::IBlob** outDiagnostics)
{
- // TODO: Need a way in filesystem to query both file size and file creation time, if cache size exceeds
- // specified maximum size (in bytes or files) then delete oldest files - cache eviction policy
-
- // Immediately call getEntryPointCode if no shader cache was provided on initialization
- if (!shaderCacheFileSystem)
+ // Immediately call getEntryPointCode if no shader cache has been initialized
+ if (!persistentShaderCache)
{
return program->getEntryPointCode(entryPointIndex, targetIndex, outCode, outDiagnostics);
}
@@ -365,75 +362,40 @@ Result RendererBase::getEntryPointCodeFromShaderCache(
ComPtr<slang::ISession> session;
getSlangSession(session.writeRef());
- slang::Digest shaderKeyHash;
- program->computeDependencyBasedHash(entryPointIndex, targetIndex, &shaderKeyHash);
-
- String shaderKey = DigestUtil::toString(shaderKeyHash);
+ slang::Digest shaderKey;
+ program->computeDependencyBasedHash(entryPointIndex, targetIndex, &shaderKey);
// Produce a hash using the AST for this program - This is needed to check whether a cache entry is effectively dirty,
// or to save along with the compiled code into an entry so the entry can be checked if fetched later on.
- slang::Digest ASTHash;
- program->computeASTBasedHash(&ASTHash);
-
+ slang::Digest contentsHash;
+ program->computeASTBasedHash(&contentsHash);
+
ComPtr<ISlangBlob> codeBlob;
- // Query shaderCacheFileSystem for an entry whose key matches shaderFilename
- // - If we find it, then copy the file contents into memory and return in outCode
- auto result = shaderCacheFileSystem->loadFile(shaderKey.getBuffer(), codeBlob.writeRef());
-
- if (SLANG_FAILED(result))
+ // Query the shader cache index for an entry with shaderKey as its key.
+ auto entry = persistentShaderCache->findEntry(shaderKey, codeBlob.writeRef());
+ if (entry && contentsHash == entry->Value.astBasedDigest)
{
- // If we didn't find it, call program->getEntryPointCode() to get and return the code. We also
- // make sure to save a new entry in the shader cache.
- if (SLANG_SUCCEEDED(program->getEntryPointCode(entryPointIndex, targetIndex, codeBlob.writeRef(), outDiagnostics)))
- {
- if (mutableShaderCacheFileSystem)
- {
- updateCacheEntry(mutableShaderCacheFileSystem, codeBlob, shaderKey, ASTHash);
- }
-
- shaderCacheMissCount++;
- }
- else
- {
- // If getEntryPointCode() failed to fetch the code, we return SLANG_FAIL along with the diagnostics output
- // in outDiagnostics.
- return SLANG_FAIL;
- }
+ // We found the entry in the cache, and the entry's contents are up-to-date. Nothing else needs to be done.
+ shaderCacheHitCount++;
}
else
{
- // If the entry exists, we need to check that the entry isn't effectively dirty. Since we stored
- // the AST hash with the compiled code, we can determine this by comparing the stored hash with the
- // AST hash generated earlier.
- auto entryContents = codeBlob->getBufferPointer();
- auto hashSize = sizeof(slang::Digest);
- if (memcmp(ASTHash.values, entryContents, hashSize) != 0)
- {
- // The AST hash stored in the entry does not match the AST hash generated earlier, indicating
- // that the shader code has changed and the entry needs to be updated.
- if (SLANG_SUCCEEDED(program->getEntryPointCode(entryPointIndex, targetIndex, codeBlob.writeRef(), outDiagnostics)))
- {
- if (mutableShaderCacheFileSystem)
- {
- updateCacheEntry(mutableShaderCacheFileSystem, codeBlob, shaderKey, ASTHash);
- }
+ // There are two possibilities: the entry does not exist in the cache, or the entry's contents are out-of-date.
+ // Both will require calling getEntryPointCode() in order to fetch the correct compiled code, so we'll do that now.
+ SLANG_RETURN_ON_FAIL(program->getEntryPointCode(entryPointIndex, targetIndex, codeBlob.writeRef(), outDiagnostics));
- shaderCacheEntryDirtyCount++;
- }
- else
- {
- // If getEntryPointCode() failed to fetch the code, we return SLANG_FAIL along with the diagnostics output
- // in outDiagnostics.
- return SLANG_FAIL;
- }
+ // If the entry was not found in the cache, let's add it. Otherwise, the entry's contents were out-of-date, so let's
+ // update the entry with the updated contents.
+ if (!entry)
+ {
+ persistentShaderCache->addEntry(shaderKey, contentsHash, codeBlob);
+ shaderCacheMissCount++;
}
else
{
- auto compiledCode = RawBlob::create((uint8_t*)codeBlob->getBufferPointer() + hashSize, codeBlob->getBufferSize() - hashSize);
- codeBlob = compiledCode;
-
- shaderCacheHitCount++;
+ persistentShaderCache->updateEntry(entry, shaderKey, contentsHash, codeBlob);
+ shaderCacheEntryDirtyCount++;
}
}
@@ -463,29 +425,12 @@ IDevice* gfx::RendererBase::getInterface(const Guid& guid)
SLANG_NO_THROW Result SLANG_MCALL RendererBase::initialize(const Desc& desc)
{
- // TODO: This logic for initalizing the shader cache has been replicated inside
- // the constructor for ShaderCacheIndex. Remove when ShaderCacheIndex is integrated in.
-
- // If a shader cache file system was provided, use the provided system.
- if (desc.shaderCacheFileSystem)
- {
- shaderCacheFileSystem = desc.shaderCacheFileSystem;
- }
- if (desc.shaderCachePath)
- {
- // Only a path was provided, create a RelativeFileSystem using the path
- if (!shaderCacheFileSystem)
- {
- shaderCacheFileSystem = OSFileSystem::getMutableSingleton();
- }
- shaderCacheFileSystem = new RelativeFileSystem(shaderCacheFileSystem, desc.shaderCachePath);
- }
-
- // If we initialized a file system for the shader cache, check if it's mutable. If so, store a pointer
- // to the mutable version in order to save new entries later.
- if (shaderCacheFileSystem)
+ auto cacheDesc = desc.shaderCache;
+ // We only want to initialize the shader cache if either a shader cache path or file system
+ // was provided.
+ if (cacheDesc.shaderCachePath || cacheDesc.shaderCacheFileSystem)
{
- shaderCacheFileSystem->queryInterface(ISlangMutableFileSystem::getTypeGuid(), (void**)mutableShaderCacheFileSystem.writeRef());
+ persistentShaderCache = new PersistentShaderCache(desc.shaderCache);
}
if (desc.apiCommandDispatcher)
diff --git a/tools/gfx/renderer-shared.h b/tools/gfx/renderer-shared.h
index fc1c35d20..01111e292 100644
--- a/tools/gfx/renderer-shared.h
+++ b/tools/gfx/renderer-shared.h
@@ -5,6 +5,8 @@
#include "core/slang-basic.h"
#include "core/slang-com-object.h"
+#include "persistent-shader-cache.h"
+
#include "resource-desc-utils.h"
namespace gfx
@@ -1372,10 +1374,7 @@ public:
SlangContext slangContext;
ShaderCache shaderCache;
- // TODO: These should be removed when ShaderCacheIndex is ready to be integrated. ShaderCacheIndex
- // will be responsible for keeping track of the underlying filesystem for the cache.
- ISlangFileSystem* shaderCacheFileSystem = nullptr;
- ComPtr<ISlangMutableFileSystem> mutableShaderCacheFileSystem = nullptr;
+ RefPtr<PersistentShaderCache> persistentShaderCache = nullptr;
Slang::Dictionary<slang::TypeLayoutReflection*, Slang::RefPtr<ShaderObjectLayoutBase>> m_shaderObjectLayoutCache;
Slang::ComPtr<IPipelineCreationAPIDispatcher> m_pipelineCreationAPIDispatcher;