summaryrefslogtreecommitdiffstats
path: root/tools/gfx-unit-test/shader-cache-tests.cpp
diff options
context:
space:
mode:
authorskallweitNV <64953474+skallweitNV@users.noreply.github.com>2022-12-12 19:25:48 +0100
committerGitHub <noreply@github.com>2022-12-12 10:25:48 -0800
commitc2dc1a86ed2f5e160749fe9f99b70db6c3e4d7a6 (patch)
treeea65b9635d892917a2420688a27c38537c4758be /tools/gfx-unit-test/shader-cache-tests.cpp
parent8d359fc6133fa49d2d3b7f8bb4b37916e719c344 (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.cpp1449
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);
}
}