diff options
| author | skallweitNV <64953474+skallweitNV@users.noreply.github.com> | 2022-12-12 19:25:48 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-12-12 10:25:48 -0800 |
| commit | c2dc1a86ed2f5e160749fe9f99b70db6c3e4d7a6 (patch) | |
| tree | ea65b9635d892917a2420688a27c38537c4758be /tools/gfx-unit-test/shader-cache-tests.cpp | |
| parent | 8d359fc6133fa49d2d3b7f8bb4b37916e719c344 (diff) | |
Refactor shader cache (#2558)
* Fix a bug in Path::find
* Fix code formatting
* Fix LockFile and add LockFileGuard
* Add PersistentCache and unit test
* Replace file path dependency list with source file dependency list
* Add note on ordering in Module/FileDependencyList
* Remove old shader cache code
* Refactor shader cache implementation
* Temporarily skip unit tests reading/writing files
* Fix warning
* Reenable lock file test
* Rename shader cache tests and disable crashing test
* Testing
* Stop using Path::getCanonical
* Fix persistent cache lock and test
* Fix threading issues
* Move adding file dependency hashes to getEntryPointHash()
* Fix handling of #include files
* Allow specifying additional search paths for gfx testing device
* Work on shader cache tests
* Update project files
* Revive shader cache graphics tests
* Split graphics pipeline test
* Fix compilation
Diffstat (limited to 'tools/gfx-unit-test/shader-cache-tests.cpp')
| -rw-r--r-- | tools/gfx-unit-test/shader-cache-tests.cpp | 1449 |
1 files changed, 562 insertions, 887 deletions
diff --git a/tools/gfx-unit-test/shader-cache-tests.cpp b/tools/gfx-unit-test/shader-cache-tests.cpp index 486b59cda..4cccc726f 100644 --- a/tools/gfx-unit-test/shader-cache-tests.cpp +++ b/tools/gfx-unit-test/shader-cache-tests.cpp @@ -5,8 +5,7 @@ #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-io.h" #include "source/core/slang-file-system.h" #include "gfx-test-texture-util.h" @@ -17,69 +16,136 @@ using namespace Slang; namespace gfx_test { - struct BaseShaderCacheTest + struct ShaderCacheTest { UnitTestContext* context; - RenderApiFlag::Enum api; + Slang::RenderApiFlag::Enum api; + + String testDirectory; + String cacheDirectory; + + ComPtr<ISlangMutableFileSystem> diskFileSystem; + + IDevice::ShaderCacheDesc shaderCacheDesc = {}; ComPtr<IDevice> device; - ComPtr<IShaderCacheStatistics> shaderCacheStats; + ComPtr<IShaderCache> shaderCache; ComPtr<IPipelineState> pipelineState; ComPtr<IResourceView> bufferView; - 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. - // This is also used to test the file stream implementation for the cache. - // - memoryFileSystem - Used to test the fallback path for the cache in the case physical - // file paths cannot be obtained, which prevents usage of file streams. - ComPtr<ISlangMutableFileSystem> diskFileSystem; - ComPtr<ISlangMutableFileSystem> memoryFileSystem; - - // Simple compute shaders we can pipe to our individual shader files for cache testing - String contentsA = String( + String computeShaderA = String( R"( - uniform RWStructuredBuffer<float> buffer; - [shader("compute")] [numthreads(4, 1, 1)] - void computeMain( - uint3 sv_dispatchThreadID : SV_DispatchThreadID) + void main( + uint3 sv_dispatchThreadID : SV_DispatchThreadID, + uniform RWStructuredBuffer<float> buffer) { var input = buffer[sv_dispatchThreadID.x]; buffer[sv_dispatchThreadID.x] = input + 1.0f; - })"); + } + )"); - String contentsB = String( + String computeShaderB = String( R"( - uniform RWStructuredBuffer<float> buffer; - [shader("compute")] [numthreads(4, 1, 1)] - void computeMain( - uint3 sv_dispatchThreadID : SV_DispatchThreadID) + void main( + uint3 sv_dispatchThreadID : SV_DispatchThreadID, + uniform RWStructuredBuffer<float> buffer) { var input = buffer[sv_dispatchThreadID.x]; buffer[sv_dispatchThreadID.x] = input + 2.0f; - })"); + } + )"); - String contentsC = String( + String computeShaderC = String( R"( - uniform RWStructuredBuffer<float> buffer; - [shader("compute")] [numthreads(4, 1, 1)] - void computeMain( - uint3 sv_dispatchThreadID : SV_DispatchThreadID) + void main( + uint3 sv_dispatchThreadID : SV_DispatchThreadID, + uniform RWStructuredBuffer<float> buffer) { var input = buffer[sv_dispatchThreadID.x]; buffer[sv_dispatchThreadID.x] = input + 3.0f; - })"); + } + )"); + + + void removeDirectory(const String& directory) + { + auto osFileSystem = OSFileSystem::getMutableSingleton(); + + struct Context + { + ISlangMutableFileSystem *fileSystem; + const String& directory; + } context { osFileSystem, directory }; + + osFileSystem->enumeratePathContents( + directory.getBuffer(), + [](SlangPathType pathType, const char* fileName, void* userData) + { + struct Context* context = static_cast<Context *>(userData); + if (pathType == SlangPathType::SLANG_PATH_TYPE_FILE) + { + String path = Path::simplify(context->directory + "/" + fileName); + context->fileSystem->remove(path.getBuffer()); + } + }, + &context); + + osFileSystem->remove(directory.getBuffer()); + } + + void writeShader(const String& source, const String& fileName) + { + diskFileSystem->saveFile(fileName.getBuffer(), source.getBuffer(), source.getLength()); + } + + void init(UnitTestContext* context, Slang::RenderApiFlag::Enum api) + { + this->context = context; + this->api = api; + + testDirectory = Path::simplify(Path::getParentDirectory(Path::getExecutablePath()) + "/shader-cache-test"); + cacheDirectory = Path::simplify(testDirectory + "/cache"); + + // Cleanup if there are stale files from a previously aborted test. + removeDirectory(cacheDirectory); + removeDirectory(testDirectory); + + Path::createDirectory(testDirectory); + diskFileSystem = new RelativeFileSystem(OSFileSystem::getMutableSingleton(), testDirectory); + shaderCacheDesc.shaderCachePath = cacheDirectory.getBuffer(); + } + + void cleanup() + { + removeDirectory(cacheDirectory); + removeDirectory(testDirectory); + } + + template<typename Func> + void runStep(Func func) + { + List<const char*> additionalSearchPaths; + additionalSearchPaths.add(testDirectory.getBuffer()); + + runTestImpl( + [this, func] (IDevice* device, UnitTestContext* ctx) + { + this->device = device; + device->queryInterface(SLANG_UUID_IShaderCache, (void**)this->shaderCache.writeRef()); + func(); + this->device = nullptr; + this->shaderCache = nullptr; + }, + context, api, additionalSearchPaths, shaderCacheDesc); + } - void createRequiredResources() + void createComputeResources() { const int numberCount = 4; float initialData[] = { 0.0f, 1.0f, 2.0f, 3.0f }; @@ -108,57 +174,25 @@ namespace gfx_test device->createBufferView(numbersBuffer, nullptr, viewDesc, bufferView.writeRef())); } - void freeOldResources() + void freeComputeResources() { bufferView = nullptr; pipelineState = nullptr; - device = nullptr; - shaderCacheStats = nullptr; } - // TODO: This should be removed at some point. Currently exists as a workaround for module loading - // seemingly not accounting for updated shader code under the same module name with the same entry point. - void generateNewDevice() + void createComputePipeline(const char* moduleName, const char* entryPointName) { - freeOldResources(); - device = createTestingDevice(context, api, shaderCache); - } - - void init(ComPtr<IDevice> device, UnitTestContext* context) - { - this->device = device; - this->context = context; - switch (device->getDeviceInfo().deviceType) - { - case DeviceType::DirectX11: - api = RenderApiFlag::D3D11; - break; - case DeviceType::DirectX12: - api = RenderApiFlag::D3D12; - break; - case DeviceType::Vulkan: - api = RenderApiFlag::Vulkan; - break; - case DeviceType::CPU: - api = RenderApiFlag::CPU; - break; - case DeviceType::CUDA: - api = RenderApiFlag::CUDA; - break; - case DeviceType::OpenGl: - api = RenderApiFlag::OpenGl; - break; - default: - SLANG_IGNORE_TEST - } + ComPtr<IShaderProgram> shaderProgram; + slang::ProgramLayout* slangReflection; + GFX_CHECK_CALL_ABORT(loadComputeProgram(device, shaderProgram, moduleName, entryPointName, slangReflection)); - memoryFileSystem = new MemoryFileSystem(); - diskFileSystem = OSFileSystem::getMutableSingleton(); - diskFileSystem->createDirectory("tools/gfx-unit-test/shader-cache-test"); - diskFileSystem = new RelativeFileSystem(diskFileSystem, "tools/gfx-unit-test/shader-cache-test"); + ComputePipelineStateDesc pipelineDesc = {}; + pipelineDesc.program = shaderProgram.get(); + GFX_CHECK_CALL_ABORT( + device->createComputePipelineState(pipelineDesc, pipelineState.writeRef())); } - void submitGPUWork() + void dispatchComputePipeline() { ComPtr<ITransientResourceHeap> transientHeap; ITransientResourceHeap::Desc transientHeapDesc = {}; @@ -174,437 +208,284 @@ namespace gfx_test auto rootObject = encoder->bindPipeline(pipelineState); - ShaderCursor rootCursor(rootObject); + ShaderCursor entryPointCursor(rootObject->getEntryPoint(0)); + entryPointCursor.getPath("buffer").setResource(bufferView); + + // ShaderCursor rootCursor(rootObject); // Bind buffer view to the entry point. - rootCursor.getPath("buffer").setResource(bufferView); + // rootCursor.getPath("buffer").setResource(bufferView); - encoder->dispatchCompute(1, 1, 1); + encoder->dispatchCompute(4, 1, 1); encoder->endEncoding(); commandBuffer->close(); queue->executeCommandBuffer(commandBuffer); queue->waitOnHost(); - } + } - void cleanUpFiles() + void runComputePipeline(const char* moduleName, const char* entryPointName) { - freeOldResources(); - - List<String> filePaths; - diskFileSystem->enumeratePathContents( - ".", - [](SlangPathType pathType, const char* name, void* userData) - { - if (pathType == SlangPathType::SLANG_PATH_TYPE_FILE) - { - List<String>& out = *(List<String>*)userData; - out.add(String(name)); - } - }, - &filePaths); + createComputeResources(); + createComputePipeline(moduleName, entryPointName); + dispatchComputePipeline(); + freeComputeResources(); + } - for (auto file : filePaths) - { - diskFileSystem->remove(file.getBuffer()); - } - // Get a mutable singleton so we can delete the folder. - auto fileSystem = OSFileSystem::getMutableSingleton(); - fileSystem->remove("tools/gfx-unit-test/shader-cache-test"); + ShaderCacheStats getStats() + { + SLANG_ASSERT(shaderCache); + ShaderCacheStats stats; + shaderCache->getShaderCacheStats(&stats); + return stats; } - void run() + void run(UnitTestContext* context, Slang::RenderApiFlag::Enum api) { - shaderCache.shaderCacheFileSystem = diskFileSystem; - runTests(); - shaderCache.shaderCacheFileSystem = memoryFileSystem; + init(context, api); runTests(); - - cleanUpFiles(); + cleanup(); } virtual void runTests() = 0; }; - // Due to needing a workaround to prevent loading old, outdated modules, we need to - // recreate the device between each segment of the test for all tests. However, we need to maintain the - // same cache filesystem for the same duration, so the device is immediately recreated - // to ensure we can pass the filesystem all the way through. - // - // General 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. - - // One shader file on disk, all modifications are done to the same file - struct SingleEntryShaderCache : BaseShaderCacheTest + // Basic shader cache test using 3 different shader files stored on disk. + struct ShaderCacheTestBasic : ShaderCacheTest { - void generateNewPipelineState(Slang::String shaderContents) + void runTests() { - diskFileSystem->saveFile("test-tmp-single-entry.slang", shaderContents.getBuffer(), shaderContents.getLength()); + // Write shader source files. + writeShader(computeShaderA, "shader-cache-tmp-a.slang"); + writeShader(computeShaderB, "shader-cache-tmp-b.slang"); + writeShader(computeShaderC, "shader-cache-tmp-c.slang"); - ComPtr<IShaderProgram> shaderProgram; - slang::ProgramLayout* slangReflection; - GFX_CHECK_CALL_ABORT(loadComputeProgram(device, shaderProgram, "shader-cache-test/test-tmp-single-entry", "computeMain", slangReflection)); + // Cache is cold and we expect 3 misses. + runStep( + [this]() + { + runComputePipeline("shader-cache-tmp-a", "main"); + runComputePipeline("shader-cache-tmp-b", "main"); + runComputePipeline("shader-cache-tmp-c", "main"); - ComputePipelineStateDesc pipelineDesc = {}; - pipelineDesc.program = shaderProgram.get(); - GFX_CHECK_CALL_ABORT( - device->createComputePipelineState(pipelineDesc, pipelineState.writeRef())); - } + SLANG_CHECK(getStats().missCount == 3); + SLANG_CHECK(getStats().hitCount == 0); + SLANG_CHECK(getStats().entryCount == 3); + } + ); - void runTests() - { - generateNewDevice(); - createRequiredResources(); - generateNewPipelineState(contentsA); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - generateNewDevice(); - createRequiredResources(); - generateNewPipelineState(contentsA); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - generateNewDevice(); - createRequiredResources(); - generateNewPipelineState(contentsC); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 1); - } - }; + // Cache is hot and we expect 3 hits. + runStep( + [this]() + { + runComputePipeline("shader-cache-tmp-a", "main"); + runComputePipeline("shader-cache-tmp-b", "main"); + runComputePipeline("shader-cache-tmp-c", "main"); - // Several shader files on disk, modifications may be done to any file - struct MultipleEntryShaderCache : BaseShaderCacheTest - { - void modifyShaderA(String shaderContents) - { - diskFileSystem->saveFile("test-tmp-multi-entry-A.slang", shaderContents.getBuffer(), shaderContents.getLength()); - } + SLANG_CHECK(getStats().missCount == 0); + SLANG_CHECK(getStats().hitCount == 3); + SLANG_CHECK(getStats().entryCount == 3); + } + ); - void modifyShaderB(String shaderContents) - { - diskFileSystem->saveFile("test-tmp-multi-entry-B.slang", shaderContents.getBuffer(), shaderContents.getLength()); - } + // Write shader source files, all rotated by one. + writeShader(computeShaderA, "shader-cache-tmp-b.slang"); + writeShader(computeShaderB, "shader-cache-tmp-c.slang"); + writeShader(computeShaderC, "shader-cache-tmp-a.slang"); - void modifyShaderC(String shaderContents) - { - diskFileSystem->saveFile("test-tmp-multi-entry-C.slang", shaderContents.getBuffer(), shaderContents.getLength()); - } + // Cache is cold again and we expect 3 misses. + runStep( + [this]() + { + runComputePipeline("shader-cache-tmp-a", "main"); + runComputePipeline("shader-cache-tmp-b", "main"); + runComputePipeline("shader-cache-tmp-c", "main"); - void generateNewPipelineState(GfxIndex shaderIndex) - { - ComPtr<IShaderProgram> shaderProgram; - slang::ProgramLayout* slangReflection; - const char* shaderFilename; - switch (shaderIndex) - { - case 0: - shaderFilename = "shader-cache-test/test-tmp-multi-entry-A"; - break; - case 1: - shaderFilename = "shader-cache-test/test-tmp-multi-entry-B"; - break; - case 2: - shaderFilename = "shader-cache-test/test-tmp-multi-entry-C"; - break; - default: - // Should never reach this point since we wrote the test - SLANG_IGNORE_TEST; - } - GFX_CHECK_CALL_ABORT(loadComputeProgram(device, shaderProgram, shaderFilename, "computeMain", slangReflection)); - - ComputePipelineStateDesc pipelineDesc = {}; - pipelineDesc.program = shaderProgram.get(); - GFX_CHECK_CALL_ABORT( - device->createComputePipelineState(pipelineDesc, pipelineState.writeRef())); - } + SLANG_CHECK(getStats().missCount == 3); + SLANG_CHECK(getStats().hitCount == 0); + SLANG_CHECK(getStats().entryCount == 6); + } + ); - void checkAllCacheEntries() - { - generateNewPipelineState(0); - submitGPUWork(); - generateNewPipelineState(1); - submitGPUWork(); - generateNewPipelineState(2); - submitGPUWork(); - } + // Cache is hot again and we expect 3 hits. + runStep( + [this]() + { + runComputePipeline("shader-cache-tmp-a", "main"); + runComputePipeline("shader-cache-tmp-b", "main"); + runComputePipeline("shader-cache-tmp-c", "main"); - void runTests() - { - generateNewDevice(); - createRequiredResources(); - modifyShaderA(contentsA); - modifyShaderB(contentsB); - modifyShaderC(contentsC); - checkAllCacheEntries(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 3); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - generateNewDevice(); - createRequiredResources(); - checkAllCacheEntries(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 3); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - generateNewDevice(); - createRequiredResources(); - modifyShaderA(contentsB); - checkAllCacheEntries(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 2); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 1); - - generateNewDevice(); - createRequiredResources(); - modifyShaderA(contentsC); - modifyShaderB(contentsA); - modifyShaderC(contentsB); - checkAllCacheEntries(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 3); + SLANG_CHECK(getStats().missCount == 0); + SLANG_CHECK(getStats().hitCount == 3); + SLANG_CHECK(getStats().entryCount == 6); + } + ); } }; - // One shader file on disk containing several entry points, no modifications are made to the file - struct MultipleEntryPointShader : BaseShaderCacheTest + // Test one shader file on disk with multiple entry points. + struct ShaderCacheTestEntryPoint : ShaderCacheTest { - void generateNewPipelineState(GfxIndex shaderIndex) + void runTests() { - ComPtr<IShaderProgram> shaderProgram; - slang::ProgramLayout* slangReflection; - const char* entryPointName; - switch (shaderIndex) - { - case 0: - entryPointName = "computeA"; - break; - case 1: - entryPointName = "computeB"; - break; - case 2: - entryPointName = "computeC"; - break; - default: - // Should never reach this point since we wrote the test - SLANG_IGNORE_TEST; - } - GFX_CHECK_CALL_ABORT(loadComputeProgram(device, shaderProgram, "multiple-entry-point-shader-cache-shader", entryPointName, slangReflection)); + // Cache is cold and we expect 3 misses, one for each entry point. + runStep( + [this]() + { + runComputePipeline("shader-cache-multiple-entry-points", "computeA"); + runComputePipeline("shader-cache-multiple-entry-points", "computeB"); + runComputePipeline("shader-cache-multiple-entry-points", "computeC"); - ComputePipelineStateDesc pipelineDesc = {}; - pipelineDesc.program = shaderProgram.get(); - GFX_CHECK_CALL_ABORT( - device->createComputePipelineState(pipelineDesc, pipelineState.writeRef())); - } + SLANG_CHECK(getStats().missCount == 3); + SLANG_CHECK(getStats().hitCount == 0); + SLANG_CHECK(getStats().entryCount == 3); + } + ); - void runTests() - { - generateNewDevice(); - createRequiredResources(); - generateNewPipelineState(0); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - generateNewDevice(); - createRequiredResources(); - generateNewPipelineState(1); - submitGPUWork(); - generateNewPipelineState(0); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - generateNewDevice(); - createRequiredResources(); - generateNewPipelineState(2); - submitGPUWork(); - generateNewPipelineState(1); - submitGPUWork(); - generateNewPipelineState(0); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 2); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); + // Cache is hot and we expect 3 hits. + runStep( + [this]() + { + runComputePipeline("shader-cache-multiple-entry-points", "computeA"); + runComputePipeline("shader-cache-multiple-entry-points", "computeB"); + runComputePipeline("shader-cache-multiple-entry-points", "computeC"); + + SLANG_CHECK(getStats().missCount == 0); + SLANG_CHECK(getStats().hitCount == 3); + SLANG_CHECK(getStats().entryCount == 3); + } + ); } }; - // One shader file contains an import/include, direct code modifications are made to the imported file - // This test specifically checks four cases: - // 1. import w/o changes in the imported file - // 2. import w/ changes in the imported file - // 3. #include w/o changes in the included file (the included file is the same as the imported file in the prior step) - // 4. #include w/ changes in the included file - struct ShaderFileImportsShaderCache : BaseShaderCacheTest + // Test cache invalidation due to an import/include file being changed on disk. + struct ShaderCacheTestImportInclude : ShaderCacheTest { String importedContentsA = String( R"( - struct TestFunction + void processElement(RWStructuredBuffer<float> buffer, uint index) { - void simpleElementAdd(RWStructuredBuffer<float> buffer, uint index) - { - var input = buffer[index]; - buffer[index] = input + 1.0f; - } - };)"); + var input = buffer[index]; + buffer[index] = input + 1.0f; + } + )"); String importedContentsB = String( R"( - struct TestFunction + void processElement(RWStructuredBuffer<float> buffer, uint index) { - void simpleElementAdd(RWStructuredBuffer<float> buffer, uint index) - { - var input = buffer[index]; - buffer[index] = input + 2.0f; - } - };)"); + var input = buffer[index]; + buffer[index] = input + 2.0f; + } + )"); String importFile = String( R"( - import test_tmp_imported; - - uniform RWStructuredBuffer<float> buffer; + import shader_cache_tmp_imported; [shader("compute")] [numthreads(4, 1, 1)] - void computeMain( - uint3 sv_dispatchThreadID : SV_DispatchThreadID) + void main( + uint3 sv_dispatchThreadID : SV_DispatchThreadID, + uniform RWStructuredBuffer<float> buffer) { - TestFunction test; - for (uint i = 0; i < 4; ++i) - { - test.simpleElementAdd(buffer, i); - } - })"); + processElement(buffer, sv_dispatchThreadID.x); + } + )"); String includeFile = String( R"( - #include "test-tmp-imported.slang" + #include "shader-cache-tmp-imported.slang" - uniform RWStructuredBuffer<float> buffer; - [shader("compute")] [numthreads(4, 1, 1)] - void computeMain( - uint3 sv_dispatchThreadID : SV_DispatchThreadID) + void main( + uint3 sv_dispatchThreadID : SV_DispatchThreadID, + uniform RWStructuredBuffer<float> buffer) { - TestFunction test; - for (uint i = 0; i < 4; ++i) - { - test.simpleElementAdd(buffer, i); - } + processElement(buffer, sv_dispatchThreadID.x); })"); - void initializeFiles() + void runTests() { - diskFileSystem->saveFile("test-tmp-imported.slang", importedContentsA.getBuffer(), importedContentsA.getLength()); - diskFileSystem->saveFile("test-tmp-importing.slang", importFile.getBuffer(), importFile.getLength()); - } + // Write shader source files. + writeShader(importedContentsA, "shader-cache-tmp-imported.slang"); + writeShader(importFile, "shader-cache-tmp-import.slang"); + writeShader(includeFile, "shader-cache-tmp-include.slang"); - void modifyImportedFile(String importedContents) - { - diskFileSystem->saveFile("test-tmp-imported.slang", importedContents.getBuffer(), importedContents.getLength()); - } + // Cache is cold and we expect 2 misses. + runStep( + [this]() + { + runComputePipeline("shader-cache-tmp-import", "main"); + runComputePipeline("shader-cache-tmp-include", "main"); - void changeImportToInclude() - { - diskFileSystem->saveFile("test-tmp-importing.slang", includeFile.getBuffer(), includeFile.getLength()); - } + SLANG_CHECK(getStats().missCount == 2); + SLANG_CHECK(getStats().hitCount == 0); + SLANG_CHECK(getStats().entryCount == 2); + } + ); - void generateNewPipelineState() - { - ComPtr<IShaderProgram> shaderProgram; - slang::ProgramLayout* slangReflection; - GFX_CHECK_CALL_ABORT(loadComputeProgram(device, shaderProgram, "shader-cache-test/test-tmp-importing", "computeMain", slangReflection)); + // Cache is hot and we expect 2 hits. + runStep( + [this]() + { + runComputePipeline("shader-cache-tmp-import", "main"); + runComputePipeline("shader-cache-tmp-include", "main"); - ComputePipelineStateDesc pipelineDesc = {}; - pipelineDesc.program = shaderProgram.get(); - GFX_CHECK_CALL_ABORT( - device->createComputePipelineState(pipelineDesc, pipelineState.writeRef())); - } + SLANG_CHECK(getStats().missCount == 0); + SLANG_CHECK(getStats().hitCount == 2); + SLANG_CHECK(getStats().entryCount == 2); + } + ); - void runTests() - { - generateNewDevice(); - createRequiredResources(); - initializeFiles(); - generateNewPipelineState(); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - generateNewDevice(); - createRequiredResources(); - modifyImportedFile(importedContentsB); - generateNewPipelineState(); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 1); - - generateNewDevice(); - createRequiredResources(); - changeImportToInclude(); - generateNewPipelineState(); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 1); - - generateNewDevice(); - createRequiredResources(); - modifyImportedFile(importedContentsA); - generateNewPipelineState(); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 1); + // Change content of imported/included shader file. + writeShader(importedContentsB, "shader-cache-tmp-imported.slang"); + + // Cache is cold and we expect 2 misses. + runStep( + [this]() + { + runComputePipeline("shader-cache-tmp-import", "main"); + runComputePipeline("shader-cache-tmp-include", "main"); + + SLANG_CHECK(getStats().missCount == 2); + SLANG_CHECK(getStats().hitCount == 0); + SLANG_CHECK(getStats().entryCount == 4); + } + ); + + // Cache is hot and we expect 2 hits. + runStep( + [this]() + { + runComputePipeline("shader-cache-tmp-import", "main"); + runComputePipeline("shader-cache-tmp-include", "main"); + + SLANG_CHECK(getStats().missCount == 0); + SLANG_CHECK(getStats().hitCount == 2); + SLANG_CHECK(getStats().entryCount == 4); + } + ); } }; // One shader featuring multiple kinds of shader objects that can be bound. - struct SpecializationArgsEntries : BaseShaderCacheTest + struct ShaderCacheTestSpecialization : ShaderCacheTest { slang::ProgramLayout* slangReflection; + void createComputePipeline() + { + ComPtr<IShaderProgram> shaderProgram; + + GFX_CHECK_CALL_ABORT( + loadComputeProgram(device, shaderProgram, "shader-cache-specialization", "computeMain", slangReflection)); + + ComputePipelineStateDesc pipelineDesc = {}; + pipelineDesc.program = shaderProgram.get(); + GFX_CHECK_CALL_ABORT( + device->createComputePipelineState(pipelineDesc, pipelineState.writeRef())); + } + void createAddTransformer(IShaderObject** transformer) { slang::TypeReflection* addTransformerType = @@ -627,7 +508,7 @@ namespace gfx_test ShaderCursor(*transformer).getPath("c").setData(&c, sizeof(float)); } - void submitGPUWork(GfxIndex transformerType) + void dispatchComputePipeline(const char* transformerTypeName) { Slang::ComPtr<ITransientResourceHeap> transientHeap; ITransientResourceHeap::Desc transientHeapDesc = {}; @@ -643,23 +524,16 @@ namespace gfx_test auto rootObject = encoder->bindPipeline(pipelineState); - ComPtr<IShaderObject> transformer; - switch (transformerType) - { - case 0: - createAddTransformer(transformer.writeRef()); - break; - case 1: - createMulTransformer(transformer.writeRef()); - break; - default: - /* Should not get here */ - SLANG_IGNORE_TEST; - } + Slang::ComPtr<IShaderObject> transformer; + slang::TypeReflection* transformerType = slangReflection->findTypeByName(transformerTypeName); + GFX_CHECK_CALL_ABORT(device->createShaderObject( + transformerType, ShaderObjectContainerType::None, transformer.writeRef())); + + float c = 1.0f; + ShaderCursor(transformer).getPath("c").setData(&c, sizeof(float)); ShaderCursor entryPointCursor(rootObject->getEntryPoint(0)); entryPointCursor.getPath("buffer").setResource(bufferView); - entryPointCursor.getPath("transformer").setObject(transformer); encoder->dispatchCompute(1, 1, 1); @@ -669,78 +543,78 @@ namespace gfx_test queue->waitOnHost(); } - void generateNewPipelineState() + void runComputePipeline(const char* transformerTypeName) { - ComPtr<IShaderProgram> shaderProgram; - - GFX_CHECK_CALL_ABORT(loadComputeProgram(device, shaderProgram, "compute-smoke", "computeMain", slangReflection)); - - ComputePipelineStateDesc pipelineDesc = {}; - pipelineDesc.program = shaderProgram.get(); - GFX_CHECK_CALL_ABORT( - device->createComputePipelineState(pipelineDesc, pipelineState.writeRef())); - } + createComputeResources(); + createComputePipeline(); + dispatchComputePipeline(transformerTypeName); + freeComputeResources(); + } void runTests() { - generateNewDevice(); - createRequiredResources(); - generateNewPipelineState(); - submitGPUWork(0); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - generateNewDevice(); - createRequiredResources(); - generateNewPipelineState(); - submitGPUWork(1); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - } - }; + // Cache is cold and we expect 2 misses. + runStep( + [this]() + { + runComputePipeline("AddTransformer"); + runComputePipeline("MulTransformer"); - // Same gist as the multiple entry point compute shader but with a graphics - // shader file containing a vertex and fragment shader - struct Vertex - { - float position[3]; - }; + SLANG_CHECK(getStats().missCount == 2); + SLANG_CHECK(getStats().hitCount == 0); + SLANG_CHECK(getStats().entryCount == 2); + } + ); - static const int kVertexCount = 3; - static const Vertex kVertexData[kVertexCount] = - { - { 0, 0, 0.5 }, - { 1, 0, 0.5 }, - { 0, 1, 0.5 }, + // Cache is hot and we expect 2 hits. + runStep( + [this]() + { + runComputePipeline("AddTransformer"); + runComputePipeline("MulTransformer"); + + SLANG_CHECK(getStats().missCount == 0); + SLANG_CHECK(getStats().hitCount == 2); + SLANG_CHECK(getStats().entryCount == 2); + } + ); + } }; - struct GraphicsShaderCache : BaseShaderCacheTest + // Same gist as the multiple entry point compute shader but with a graphics + // shader file containing a vertex and fragment shader. + struct ShaderCacheTestGraphics : ShaderCacheTest { - const int kWidth = 256; - const int kHeight = 256; - const Format format = Format::R32G32B32A32_FLOAT; + struct Vertex + { + float position[3]; + }; - ComPtr<IShaderProgram> shaderProgram; - ComPtr<IRenderPassLayout> renderPass; - ComPtr<IFramebuffer> framebuffer; + static const int kWidth = 256; + static const int kHeight = 256; + static const Format format = Format::R32G32B32A32_FLOAT; ComPtr<IBufferResource> vertexBuffer; ComPtr<ITextureResource> colorBuffer; + ComPtr<IInputLayout> inputLayout; + ComPtr<IFramebufferLayout> framebufferLayout; + ComPtr<IRenderPassLayout> renderPass; + ComPtr<IFramebuffer> framebuffer; ComPtr<IBufferResource> createVertexBuffer(IDevice* device) { + const Vertex vertices[] = { + { 0, 0, 0.5 }, + { 1, 0, 0.5 }, + { 0, 1, 0.5 }, + }; + IBufferResource::Desc vertexBufferDesc; vertexBufferDesc.type = IResource::Type::Buffer; - vertexBufferDesc.sizeInBytes = kVertexCount * sizeof(Vertex); + vertexBufferDesc.sizeInBytes = sizeof(vertices); vertexBufferDesc.defaultState = ResourceState::VertexBuffer; vertexBufferDesc.allowedStates = ResourceState::VertexBuffer; - ComPtr<IBufferResource> vertexBuffer = device->createBufferResource(vertexBufferDesc, &kVertexData[0]); + ComPtr<IBufferResource> vertexBuffer = device->createBufferResource(vertexBufferDesc, vertices); SLANG_CHECK_ABORT(vertexBuffer != nullptr); return vertexBuffer; } @@ -761,13 +635,7 @@ namespace gfx_test return colorBuffer; } - void createShaderProgram() - { - slang::ProgramLayout* slangReflection; - GFX_CHECK_CALL_ABORT(loadGraphicsProgram(device, shaderProgram, "shader-cache-graphics", "vertexMain", "fragmentMain", slangReflection)); - } - - void createRequiredResources() + void createGraphicsResources() { VertexStreamDesc vertexStreams[] = { { sizeof(Vertex), InputSlotClass::PerVertex, 0 }, @@ -782,7 +650,7 @@ namespace gfx_test inputLayoutDesc.inputElements = inputElements; inputLayoutDesc.vertexStreamCount = SLANG_COUNT_OF(vertexStreams); inputLayoutDesc.vertexStreams = vertexStreams; - auto inputLayout = device->createInputLayout(inputLayoutDesc); + inputLayout = device->createInputLayout(inputLayoutDesc); SLANG_CHECK_ABORT(inputLayout != nullptr); vertexBuffer = createVertexBuffer(device); @@ -795,18 +663,9 @@ namespace gfx_test IFramebufferLayout::Desc framebufferLayoutDesc; framebufferLayoutDesc.renderTargetCount = 1; framebufferLayoutDesc.renderTargets = &targetLayout; - ComPtr<gfx::IFramebufferLayout> framebufferLayout = device->createFramebufferLayout(framebufferLayoutDesc); + framebufferLayout = device->createFramebufferLayout(framebufferLayoutDesc); SLANG_CHECK_ABORT(framebufferLayout != nullptr); - GraphicsPipelineStateDesc pipelineDesc = {}; - pipelineDesc.program = shaderProgram.get(); - pipelineDesc.inputLayout = inputLayout; - pipelineDesc.framebufferLayout = framebufferLayout; - pipelineDesc.depthStencil.depthTestEnable = false; - pipelineDesc.depthStencil.depthWriteEnable = false; - GFX_CHECK_CALL_ABORT( - device->createGraphicsPipelineState(pipelineDesc, pipelineState.writeRef())); - IRenderPassLayout::Desc renderPassDesc = {}; renderPassDesc.framebufferLayout = framebufferLayout; renderPassDesc.renderTargetCount = 1; @@ -833,7 +692,35 @@ namespace gfx_test GFX_CHECK_CALL_ABORT(device->createFramebuffer(framebufferDesc, framebuffer.writeRef())); } - void submitGPUWork() + void freeGraphicsResources() + { + inputLayout = nullptr; + framebufferLayout = nullptr; + renderPass = nullptr; + framebuffer = nullptr; + vertexBuffer = nullptr; + colorBuffer = nullptr; + pipelineState = nullptr; + } + + void createGraphicsPipeline() + { + ComPtr<IShaderProgram> shaderProgram; + slang::ProgramLayout* slangReflection; + GFX_CHECK_CALL_ABORT( + loadGraphicsProgram(device, shaderProgram, "shader-cache-graphics", "vertexMain", "fragmentMain", slangReflection)); + + GraphicsPipelineStateDesc pipelineDesc = {}; + pipelineDesc.program = shaderProgram.get(); + pipelineDesc.inputLayout = inputLayout; + pipelineDesc.framebufferLayout = framebufferLayout; + pipelineDesc.depthStencil.depthTestEnable = false; + pipelineDesc.depthStencil.depthWriteEnable = false; + GFX_CHECK_CALL_ABORT( + device->createGraphicsPipelineState(pipelineDesc, pipelineState.writeRef())); + } + + void dispatchGraphicsPipeline() { ComPtr<ITransientResourceHeap> transientHeap; ITransientResourceHeap::Desc transientHeapDesc = {}; @@ -857,28 +744,50 @@ namespace gfx_test encoder->setVertexBuffer(0, vertexBuffer); encoder->setPrimitiveTopology(PrimitiveTopology::TriangleList); - encoder->draw(kVertexCount); + encoder->draw(3); encoder->endEncoding(); commandBuffer->close(); queue->executeCommandBuffer(commandBuffer); queue->waitOnHost(); } + void runGraphicsPipeline() + { + createGraphicsResources(); + createGraphicsPipeline(); + dispatchGraphicsPipeline(); + freeGraphicsResources(); + } + void runTests() { - generateNewDevice(); - createShaderProgram(); - createRequiredResources(); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 2); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); + // Cache is cold and we expect 2 misses (2 entry points). + runStep( + [this]() + { + runGraphicsPipeline(); + + SLANG_CHECK(getStats().missCount == 2); + SLANG_CHECK(getStats().hitCount == 0); + SLANG_CHECK(getStats().entryCount == 2); + } + ); + + // Cache is hot and we expect 2 hits. + runStep( + [this]() + { + runGraphicsPipeline(); + + SLANG_CHECK(getStats().missCount == 0); + SLANG_CHECK(getStats().hitCount == 2); + SLANG_CHECK(getStats().entryCount == 2); + } + ); } }; - // Same as GraphicsShaderCache, but instead of having a singular file containing both a vertex and fragment shader, we + // Same as ShaderCacheTestGraphics, but instead of having a singular file containing both a vertex and fragment shader, we // now have two separate shader files, one containing the vertex shader and the other the fragment with the same // names, with the expectation that we should record cache misses for both fetches. // @@ -890,54 +799,38 @@ namespace gfx_test // // We do not actively test geometry shaders here, but it is simply an extension of this test and should be expected // to behave similarly. - struct SplitGraphicsShader : GraphicsShaderCache + struct ShaderCacheTestGraphicsSplit : ShaderCacheTestGraphics { - void createShaderProgram() - { - slang::ProgramLayout* slangReflection; - const char* moduleNames[] = { "split-graphics-vertex", "split-graphics-fragment" }; - GFX_CHECK_CALL_ABORT(loadSplitGraphicsProgram(device, shaderProgram, moduleNames, "main", "main", slangReflection)); - } - - Result loadSplitGraphicsProgram( - IDevice* device, - ComPtr<IShaderProgram>& outShaderProgram, - const char** shaderModuleNames, - const char* vertexEntryPointName, - const char* fragmentEntryPointName, - slang::ProgramLayout*& slangReflection) + void createGraphicsPipeline() { ComPtr<slang::ISession> slangSession; - SLANG_RETURN_ON_FAIL(device->getSlangSession(slangSession.writeRef())); + GFX_CHECK_CALL_ABORT(device->getSlangSession(slangSession.writeRef())); - ComPtr<slang::IBlob> diagnosticsBlob; - slang::IModule* vertexModule = slangSession->loadModule(shaderModuleNames[0], diagnosticsBlob.writeRef()); - if (!vertexModule) - return SLANG_FAIL; - slang::IModule* fragmentModule = slangSession->loadModule(shaderModuleNames[1], diagnosticsBlob.writeRef()); - if (!fragmentModule) - return SLANG_FAIL; + slang::IModule* vertexModule = slangSession->loadModule("shader-cache-graphics-vertex"); + SLANG_CHECK_ABORT(vertexModule); + slang::IModule* fragmentModule = slangSession->loadModule("shader-cache-graphics-fragment"); + SLANG_CHECK_ABORT(fragmentModule); ComPtr<slang::IEntryPoint> vertexEntryPoint; - SLANG_RETURN_ON_FAIL( - vertexModule->findEntryPointByName(vertexEntryPointName, vertexEntryPoint.writeRef())); + GFX_CHECK_CALL_ABORT( + vertexModule->findEntryPointByName("main", vertexEntryPoint.writeRef())); ComPtr<slang::IEntryPoint> fragmentEntryPoint; - SLANG_RETURN_ON_FAIL( - fragmentModule->findEntryPointByName(fragmentEntryPointName, fragmentEntryPoint.writeRef())); + GFX_CHECK_CALL_ABORT( + fragmentModule->findEntryPointByName("main", fragmentEntryPoint.writeRef())); Slang::List<slang::IComponentType*> componentTypes; componentTypes.add(vertexModule); componentTypes.add(fragmentModule); Slang::ComPtr<slang::IComponentType> composedProgram; - SlangResult result = slangSession->createCompositeComponentType( - componentTypes.getBuffer(), - componentTypes.getCount(), - composedProgram.writeRef(), - diagnosticsBlob.writeRef()); - SLANG_RETURN_ON_FAIL(result); - slangReflection = composedProgram->getLayout(); + GFX_CHECK_CALL_ABORT( + slangSession->createCompositeComponentType( + componentTypes.getBuffer(), + componentTypes.getCount(), + composedProgram.writeRef())); + + slang::ProgramLayout* slangReflection = composedProgram->getLayout(); Slang::List<slang::IComponentType*> entryPoints; entryPoints.add(vertexEntryPoint); @@ -949,263 +842,60 @@ namespace gfx_test programDesc.entryPointCount = 2; programDesc.slangEntryPoints = entryPoints.getBuffer(); - auto shaderProgram = device->createProgram(programDesc); - - outShaderProgram = shaderProgram; - return SLANG_OK; - } - - void runTests() - { - generateNewDevice(); - createShaderProgram(); - createRequiredResources(); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 2); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - } - }; - - // 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. - // - // As opening multiple streams to the same file is dependent on the OS, this test is run on the - // in-memory file system. Cache eviction policy with an on-disk file system will need to be inspected - // manually. - struct CacheWithMaxEntryLimit : MultipleEntryShaderCache - { - List<String> test0Lines; // C -> B -> A - List<String> test1Lines; // C -> B - List<String> test2Lines; // A -> B - List<String> test3Lines; // A -> C - List<String> test4Lines; // C -> B -> A - List<String> entryKeys; // C, B, A - - void getCacheFile(List<String>& lines) - { - ComPtr<ISlangBlob> contentsBlob; - memoryFileSystem->loadFile(shaderCache.cacheFilename, contentsBlob.writeRef()); - List<UnownedStringSlice> temp; - StringUtil::calcLines(UnownedStringSlice((char*)contentsBlob->getBufferPointer()), temp); - for (auto line : temp) - { - if (line.trim().getLength() != 0) - 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]); - } - - // Cache limit 3, three unique shaders - void runTest0() - { - shaderCache.entryCountLimit = 3; - generateNewDevice(); - createRequiredResources(); - modifyShaderA(contentsA); - modifyShaderB(contentsB); - modifyShaderC(contentsC); - generateNewPipelineState(0); - submitGPUWork(); - generateNewPipelineState(1); - submitGPUWork(); - generateNewPipelineState(2); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 3); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - // This needs to be called in order to force the cache file to be updated, otherwise we will - // be unable to perform the necessary checks. - freeOldResources(); - - getCacheFile(test0Lines); - SLANG_CHECK(test0Lines.getCount() == 3); - - // This segment also doubles as the point where we fetch the keys for all three shaders - // to use in later checks. - for (auto line : test0Lines) - { - List<UnownedStringSlice> digests; - StringUtil::split(line.getUnownedSlice(), ' ', digests); - if (digests.getCount() != 2) - continue; - entryKeys.add(digests[0]); - } - - ComPtr<ISlangBlob> unused; - SLANG_CHECK(SLANG_SUCCEEDED(memoryFileSystem->loadFile(entryKeys[0].getBuffer(), unused.writeRef()))); - SLANG_CHECK(SLANG_SUCCEEDED(memoryFileSystem->loadFile(entryKeys[1].getBuffer(), unused.writeRef()))); - SLANG_CHECK(SLANG_SUCCEEDED(memoryFileSystem->loadFile(entryKeys[2].getBuffer(), unused.writeRef()))); - } - - // Cache limit 2, access shaders A then B then C - void runTest1() - { - shaderCache.entryCountLimit = 2; - generateNewDevice(); - createRequiredResources(); - modifyShaderA(contentsA); - modifyShaderB(contentsB); - modifyShaderC(contentsC); - generateNewPipelineState(0); - submitGPUWork(); - generateNewPipelineState(1); - submitGPUWork(); - generateNewPipelineState(2); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 3); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - freeOldResources(); - - getCacheFile(test1Lines); - SLANG_CHECK(test1Lines.getCount() == 2); - - ComPtr<ISlangBlob> unused; - SLANG_CHECK(SLANG_SUCCEEDED(memoryFileSystem->loadFile(entryKeys[0].getBuffer(), unused.writeRef()))); - SLANG_CHECK(SLANG_SUCCEEDED(memoryFileSystem->loadFile(entryKeys[1].getBuffer(), unused.writeRef()))); - SLANG_CHECK(SLANG_FAILED(memoryFileSystem->loadFile(entryKeys[2].getBuffer(), unused.writeRef()))); - } - - // Cache limit 2, access shaders B and then A - void runTest2() - { - generateNewDevice(); - createRequiredResources(); - generateNewPipelineState(1); - submitGPUWork(); - generateNewPipelineState(0); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - freeOldResources(); - - getCacheFile(test2Lines); - SLANG_CHECK(test2Lines.getCount() == 2); - - ComPtr<ISlangBlob> unused; - SLANG_CHECK(SLANG_FAILED(memoryFileSystem->loadFile(entryKeys[0].getBuffer(), unused.writeRef()))); - SLANG_CHECK(SLANG_SUCCEEDED(memoryFileSystem->loadFile(entryKeys[1].getBuffer(), unused.writeRef()))); - SLANG_CHECK(SLANG_SUCCEEDED(memoryFileSystem->loadFile(entryKeys[2].getBuffer(), unused.writeRef()))); - } + ComPtr<IShaderProgram> shaderProgram = device->createProgram(programDesc); - // Cache limit 2, access shaders C and then A - void runTest3() - { - generateNewDevice(); - createRequiredResources(); - generateNewPipelineState(2); - submitGPUWork(); - generateNewPipelineState(0); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - freeOldResources(); - - getCacheFile(test3Lines); - SLANG_CHECK(test3Lines.getCount() == 2); - - ComPtr<ISlangBlob> unused; - SLANG_CHECK(SLANG_SUCCEEDED(memoryFileSystem->loadFile(entryKeys[0].getBuffer(), unused.writeRef()))); - SLANG_CHECK(SLANG_FAILED(memoryFileSystem->loadFile(entryKeys[1].getBuffer(), unused.writeRef()))); - SLANG_CHECK(SLANG_SUCCEEDED(memoryFileSystem->loadFile(entryKeys[2].getBuffer(), unused.writeRef()))); + GraphicsPipelineStateDesc pipelineDesc = {}; + pipelineDesc.program = shaderProgram.get(); + pipelineDesc.inputLayout = inputLayout; + pipelineDesc.framebufferLayout = framebufferLayout; + pipelineDesc.depthStencil.depthTestEnable = false; + pipelineDesc.depthStencil.depthWriteEnable = false; + GFX_CHECK_CALL_ABORT( + device->createGraphicsPipelineState(pipelineDesc, pipelineState.writeRef())); } - // Cache limit 3, access shaders A then B then C - void runTest4() + void runGraphicsPipeline() { - shaderCache.entryCountLimit = 3; - generateNewDevice(); - createRequiredResources(); - generateNewPipelineState(0); - submitGPUWork(); - generateNewPipelineState(1); - submitGPUWork(); - generateNewPipelineState(2); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 2); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - freeOldResources(); - - getCacheFile(test4Lines); - SLANG_CHECK(test4Lines.getCount() == 3); - - ComPtr<ISlangBlob> unused; - SLANG_CHECK(SLANG_SUCCEEDED(memoryFileSystem->loadFile(entryKeys[0].getBuffer(), unused.writeRef()))); - SLANG_CHECK(SLANG_SUCCEEDED(memoryFileSystem->loadFile(entryKeys[1].getBuffer(), unused.writeRef()))); - SLANG_CHECK(SLANG_SUCCEEDED(memoryFileSystem->loadFile(entryKeys[2].getBuffer(), unused.writeRef()))); - } + createGraphicsResources(); + createGraphicsPipeline(); + dispatchGraphicsPipeline(); + freeGraphicsResources(); + } void runTests() { - runTest0(); - runTest1(); - runTest2(); - runTest3(); - runTest4(); + // Cache is cold and we expect 2 misses (2 entry points). + runStep( + [this]() + { + runGraphicsPipeline(); - checkCacheFiles(); - } + SLANG_CHECK(getStats().missCount == 2); + SLANG_CHECK(getStats().hitCount == 0); + SLANG_CHECK(getStats().entryCount == 2); + } + ); - void run() - { - shaderCache.shaderCacheFileSystem = memoryFileSystem; - runTests(); + // Cache is hot and we expect 2 hits. + runStep( + [this]() + { + runGraphicsPipeline(); - cleanUpFiles(); - } + SLANG_CHECK(getStats().missCount == 0); + SLANG_CHECK(getStats().hitCount == 2); + SLANG_CHECK(getStats().entryCount == 2); + } + ); } }; - // This test is specifically for source files which live entirely in memory. The key difference between - // these and physical source files is such files have their contents hash added to the file dependencies - // list instead of a file path, meaning any given specific set of shader contents will be treated as a - // wholly unique module. - struct NonPhysicalFileDependencyEntry : BaseShaderCacheTest + // Test caching of shaders that are compiled from source strings instead of files. + struct ShaderCacheTestSourceString : ShaderCacheTest { - void generateNewPipelineState(Slang::String shaderContents) + void createComputePipeline(Slang::String shaderSource) { ComPtr<IShaderProgram> shaderProgram; - GFX_CHECK_CALL_ABORT(loadComputeProgramFromSource(device, shaderProgram, shaderContents)); + GFX_CHECK_CALL_ABORT(loadComputeProgramFromSource(device, shaderProgram, shaderSource)); ComputePipelineStateDesc pipelineDesc = {}; pipelineDesc.program = shaderProgram.get(); @@ -1213,135 +903,120 @@ namespace gfx_test device->createComputePipelineState(pipelineDesc, pipelineState.writeRef())); } - void runTests() + void runComputePipeline(Slang::String shaderSource) { - generateNewDevice(); - createRequiredResources(); - generateNewPipelineState(contentsA); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - generateNewDevice(); - createRequiredResources(); - generateNewPipelineState(contentsA); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); - - generateNewDevice(); - createRequiredResources(); - generateNewPipelineState(contentsC); - submitGPUWork(); - - device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); - SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 1); - SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); - SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); + createComputeResources(); + createComputePipeline(shaderSource); + dispatchComputePipeline(); + freeComputeResources(); } - }; - template <typename T> - void shaderCacheTestImpl(ComPtr<IDevice> device, UnitTestContext* context) - { - T test; - test.init(device, context); - test.run(); - } + void runTests() + { + // Cache is cold and we expect 3 misses. + runStep( + [this]() + { + runComputePipeline(computeShaderA); + runComputePipeline(computeShaderB); + runComputePipeline(computeShaderC); - SLANG_UNIT_TEST(singleEntryShaderCacheD3D12) - { - runTestImpl(shaderCacheTestImpl<SingleEntryShaderCache>, unitTestContext, Slang::RenderApiFlag::D3D12); - } + SLANG_CHECK(getStats().missCount == 3); + SLANG_CHECK(getStats().hitCount == 0); + SLANG_CHECK(getStats().entryCount == 3); + } + ); - SLANG_UNIT_TEST(singleEntryShaderCacheVulkan) - { - runTestImpl(shaderCacheTestImpl<SingleEntryShaderCache>, unitTestContext, Slang::RenderApiFlag::Vulkan); - } + // Cache is hot and we expect 3 hits. + runStep( + [this]() + { + runComputePipeline(computeShaderA); + runComputePipeline(computeShaderB); + runComputePipeline(computeShaderC); - SLANG_UNIT_TEST(multipleEntryShaderCacheD3D12) - { - runTestImpl(shaderCacheTestImpl<MultipleEntryShaderCache>, unitTestContext, Slang::RenderApiFlag::D3D12); - } + SLANG_CHECK(getStats().missCount == 0); + SLANG_CHECK(getStats().hitCount == 3); + SLANG_CHECK(getStats().entryCount == 3); + } + ); + } + }; - SLANG_UNIT_TEST(multipleEntryShaderCacheVulkan) + template<typename T> + void runTest(UnitTestContext* context, Slang::RenderApiFlag::Enum api) { - runTestImpl(shaderCacheTestImpl<MultipleEntryShaderCache>, unitTestContext, Slang::RenderApiFlag::Vulkan); + T test; + test.run(context, api); } - SLANG_UNIT_TEST(multipleEntryPointShaderCacheD3D12) + SLANG_UNIT_TEST(shaderCacheBasicD3D12) { - runTestImpl(shaderCacheTestImpl<MultipleEntryPointShader>, unitTestContext, Slang::RenderApiFlag::D3D12); + runTest<ShaderCacheTestBasic>(unitTestContext, Slang::RenderApiFlag::D3D12); } - SLANG_UNIT_TEST(multipleEntryPointShaderCacheVulkan) + SLANG_UNIT_TEST(shaderCacheBasicVulkan) { - runTestImpl(shaderCacheTestImpl<MultipleEntryPointShader>, unitTestContext, Slang::RenderApiFlag::Vulkan); + runTest<ShaderCacheTestBasic>(unitTestContext, Slang::RenderApiFlag::Vulkan); } - SLANG_UNIT_TEST(shaderFileImportsShaderCacheD3D12) + SLANG_UNIT_TEST(shaderCacheEntryPointD3D12) { - runTestImpl(shaderCacheTestImpl<ShaderFileImportsShaderCache>, unitTestContext, Slang::RenderApiFlag::D3D12); + runTest<ShaderCacheTestEntryPoint>(unitTestContext, Slang::RenderApiFlag::D3D12); } - SLANG_UNIT_TEST(shaderFileImportsShaderCacheVulkan) + SLANG_UNIT_TEST(shaderCacheEntryPointVulkan) { - runTestImpl(shaderCacheTestImpl<ShaderFileImportsShaderCache>, unitTestContext, Slang::RenderApiFlag::Vulkan); + runTest<ShaderCacheTestEntryPoint>(unitTestContext, Slang::RenderApiFlag::Vulkan); } - SLANG_UNIT_TEST(specializationArgsShaderCacheD3D12) + SLANG_UNIT_TEST(shaderCacheImportIncludeD3D12) { - runTestImpl(shaderCacheTestImpl<SpecializationArgsEntries>, unitTestContext, Slang::RenderApiFlag::D3D12); + runTest<ShaderCacheTestImportInclude>(unitTestContext, Slang::RenderApiFlag::D3D12); } - SLANG_UNIT_TEST(specializationArgsShaderCacheVulkan) + SLANG_UNIT_TEST(shaderCacheImportIncludeVulkan) { - runTestImpl(shaderCacheTestImpl<SpecializationArgsEntries>, unitTestContext, Slang::RenderApiFlag::Vulkan); + runTest<ShaderCacheTestImportInclude>(unitTestContext, Slang::RenderApiFlag::Vulkan); } - SLANG_UNIT_TEST(cacheEvictionPolicyD3D12) + SLANG_UNIT_TEST(shaderCacheSpecializationD3D12) { - runTestImpl(shaderCacheTestImpl<CacheWithMaxEntryLimit>, unitTestContext, Slang::RenderApiFlag::D3D12); + runTest<ShaderCacheTestSpecialization>(unitTestContext, Slang::RenderApiFlag::D3D12); } - SLANG_UNIT_TEST(cacheEvictionPolicyVulkan) + SLANG_UNIT_TEST(shaderCacheSpecializationVulkan) { - runTestImpl(shaderCacheTestImpl<CacheWithMaxEntryLimit>, unitTestContext, Slang::RenderApiFlag::Vulkan); + runTest<ShaderCacheTestSpecialization>(unitTestContext, Slang::RenderApiFlag::Vulkan); } - SLANG_UNIT_TEST(graphicsShaderCacheD3D12) + SLANG_UNIT_TEST(shaderCacheGraphicsD3D12) { - runTestImpl(shaderCacheTestImpl<GraphicsShaderCache>, unitTestContext, Slang::RenderApiFlag::D3D12); + runTest<ShaderCacheTestGraphics>(unitTestContext, Slang::RenderApiFlag::D3D12); } - SLANG_UNIT_TEST(graphicsShaderCacheVulkan) + SLANG_UNIT_TEST(shaderCacheGraphicsVulkan) { - runTestImpl(shaderCacheTestImpl<GraphicsShaderCache>, unitTestContext, Slang::RenderApiFlag::Vulkan); + runTest<ShaderCacheTestGraphics>(unitTestContext, Slang::RenderApiFlag::Vulkan); } - SLANG_UNIT_TEST(splitGraphicsShaderCacheD3D12) + SLANG_UNIT_TEST(shaderCacheGraphicsSplitD3D12) { - runTestImpl(shaderCacheTestImpl<SplitGraphicsShader>, unitTestContext, Slang::RenderApiFlag::D3D12); + runTest<ShaderCacheTestGraphicsSplit>(unitTestContext, Slang::RenderApiFlag::D3D12); } - SLANG_UNIT_TEST(splitGraphicsShaderCacheVulkan) + SLANG_UNIT_TEST(shaderCacheGraphicsSplitVulkan) { - runTestImpl(shaderCacheTestImpl<SplitGraphicsShader>, unitTestContext, Slang::RenderApiFlag::Vulkan); + runTest<ShaderCacheTestGraphicsSplit>(unitTestContext, Slang::RenderApiFlag::Vulkan); } - SLANG_UNIT_TEST(nonPhysicalFileDependenciesCacheEntryD3D12) + SLANG_UNIT_TEST(shaderCacheSourceStringD3D12) { - runTestImpl(shaderCacheTestImpl<NonPhysicalFileDependencyEntry>, unitTestContext, Slang::RenderApiFlag::D3D12); + runTest<ShaderCacheTestSourceString>(unitTestContext, Slang::RenderApiFlag::D3D12); } - SLANG_UNIT_TEST(nonPhysicalFileDependenciesCacheEntryVulkan) + SLANG_UNIT_TEST(shaderCacheSourceStringVulkan) { - runTestImpl(shaderCacheTestImpl<NonPhysicalFileDependencyEntry>, unitTestContext, Slang::RenderApiFlag::Vulkan); + runTest<ShaderCacheTestSourceString>(unitTestContext, Slang::RenderApiFlag::Vulkan); } } |
