diff options
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/gfx-unit-test/gfx-test-util.cpp | 12 | ||||
| -rw-r--r-- | tools/gfx-unit-test/gfx-test-util.h | 13 | ||||
| -rw-r--r-- | tools/gfx-unit-test/imported.slang | 8 | ||||
| -rw-r--r-- | tools/gfx-unit-test/importing-shader-cache-shader.slang | 15 | ||||
| -rw-r--r-- | tools/gfx-unit-test/shader-cache-shader-A.slang | 10 | ||||
| -rw-r--r-- | tools/gfx-unit-test/shader-cache-shader-B.slang | 10 | ||||
| -rw-r--r-- | tools/gfx-unit-test/shader-cache-shader-C.slang | 10 | ||||
| -rw-r--r-- | tools/gfx-unit-test/shader-cache-shader.slang | 10 | ||||
| -rw-r--r-- | tools/gfx-unit-test/shader-cache-tests.cpp | 246 | ||||
| -rw-r--r-- | tools/gfx/gfx.slang | 20 | ||||
| -rw-r--r-- | tools/gfx/persistent-shader-cache.cpp | 24 | ||||
| -rw-r--r-- | tools/gfx/persistent-shader-cache.h | 20 | ||||
| -rw-r--r-- | tools/gfx/renderer-shared.cpp | 111 | ||||
| -rw-r--r-- | tools/gfx/renderer-shared.h | 7 |
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; |
