diff options
Diffstat (limited to 'tools/gfx-unit-test')
| -rw-r--r-- | tools/gfx-unit-test/gfx-test-util.cpp | 14 | ||||
| -rw-r--r-- | tools/gfx-unit-test/gfx-test-util.h | 6 | ||||
| -rw-r--r-- | tools/gfx-unit-test/imported.slang | 8 | ||||
| -rw-r--r-- | tools/gfx-unit-test/importing-shader-cache-shader.slang | 15 | ||||
| -rw-r--r-- | tools/gfx-unit-test/multiple-entry-point-shader-cache-shader.slang | 28 | ||||
| -rw-r--r-- | tools/gfx-unit-test/shader-cache-shader-A.slang | 10 | ||||
| -rw-r--r-- | tools/gfx-unit-test/shader-cache-shader-B.slang | 10 | ||||
| -rw-r--r-- | tools/gfx-unit-test/shader-cache-shader-C.slang | 10 | ||||
| -rw-r--r-- | tools/gfx-unit-test/shader-cache-shader.slang | 10 | ||||
| -rw-r--r-- | tools/gfx-unit-test/shader-cache-tests.cpp | 624 |
10 files changed, 731 insertions, 4 deletions
diff --git a/tools/gfx-unit-test/gfx-test-util.cpp b/tools/gfx-unit-test/gfx-test-util.cpp index 9a6a7c4f6..90aec2647 100644 --- a/tools/gfx-unit-test/gfx-test-util.cpp +++ b/tools/gfx-unit-test/gfx-test-util.cpp @@ -54,6 +54,13 @@ namespace gfx_test diagnosticsBlob.writeRef()); diagnoseIfNeeded(diagnosticsBlob); SLANG_RETURN_ON_FAIL(result); + + ComPtr<slang::IComponentType> linkedProgram; + result = composedProgram->link(linkedProgram.writeRef(), diagnosticsBlob.writeRef()); + diagnoseIfNeeded(diagnosticsBlob); + SLANG_RETURN_ON_FAIL(result); + + composedProgram = linkedProgram; slangReflection = composedProgram->getLayout(); gfx::IShaderProgram::Desc programDesc = {}; @@ -169,7 +176,7 @@ namespace gfx_test compareComputeResultFuzzy(result, expectedResult, expectedBufferSize); } - Slang::ComPtr<gfx::IDevice> createTestingDevice(UnitTestContext* context, Slang::RenderApiFlag::Enum api) + Slang::ComPtr<gfx::IDevice> createTestingDevice(UnitTestContext* context, Slang::RenderApiFlag::Enum api, ISlangMutableFileSystem* fileSystem) { Slang::ComPtr<gfx::IDevice> device; gfx::IDevice::Desc deviceDesc = {}; @@ -208,6 +215,11 @@ namespace gfx_test void* extDescPtr = &extDesc; deviceDesc.extendedDescs = &extDescPtr; + if (fileSystem) + { + deviceDesc.shaderCacheFileSystem = fileSystem; + } + auto createDeviceResult = gfxCreateDevice(&deviceDesc, device.writeRef()); if (SLANG_FAILED(createDeviceResult)) { diff --git a/tools/gfx-unit-test/gfx-test-util.h b/tools/gfx-unit-test/gfx-test-util.h index 2977ac599..e3b8f493b 100644 --- a/tools/gfx-unit-test/gfx-test-util.h +++ b/tools/gfx-unit-test/gfx-test-util.h @@ -69,20 +69,20 @@ namespace gfx_test return compareComputeResult(device, buffer, 0, expectedBuffer.getBuffer(), bufferSize); } - Slang::ComPtr<gfx::IDevice> createTestingDevice(UnitTestContext* context, Slang::RenderApiFlag::Enum api); + Slang::ComPtr<gfx::IDevice> createTestingDevice(UnitTestContext* context, Slang::RenderApiFlag::Enum api, ISlangMutableFileSystem* fileSystem = nullptr); void initializeRenderDoc(); void renderDocBeginFrame(); void renderDocEndFrame(); template<typename ImplFunc> - void runTestImpl(const ImplFunc& f, UnitTestContext* context, Slang::RenderApiFlag::Enum api) + void runTestImpl(const ImplFunc& f, UnitTestContext* context, Slang::RenderApiFlag::Enum api, ISlangMutableFileSystem* fileSystem = nullptr) { if ((api & context->enabledApis) == 0) { SLANG_IGNORE_TEST } - auto device = createTestingDevice(context, api); + auto device = createTestingDevice(context, api, fileSystem); if (!device) { SLANG_IGNORE_TEST diff --git a/tools/gfx-unit-test/imported.slang b/tools/gfx-unit-test/imported.slang new file mode 100644 index 000000000..a44782390 --- /dev/null +++ b/tools/gfx-unit-test/imported.slang @@ -0,0 +1,8 @@ +struct TestFunction + { + void simpleElementAdd(RWStructuredBuffer<float> buffer, uint index) + { + var input = buffer[index]; + buffer[index] = input + 1.0f; + } + };
\ No newline at end of file diff --git a/tools/gfx-unit-test/importing-shader-cache-shader.slang b/tools/gfx-unit-test/importing-shader-cache-shader.slang new file mode 100644 index 000000000..bede42492 --- /dev/null +++ b/tools/gfx-unit-test/importing-shader-cache-shader.slang @@ -0,0 +1,15 @@ +#include "imported.slang" + + uniform RWStructuredBuffer<float> buffer; + + [shader("compute")] + [numthreads(4, 1, 1)] + void computeMain( + uint3 sv_dispatchThreadID : SV_DispatchThreadID) + { + TestFunction test; + for (uint i = 0; i < 4; ++i) + { + test.simpleElementAdd(buffer, i); + } + }
\ No newline at end of file diff --git a/tools/gfx-unit-test/multiple-entry-point-shader-cache-shader.slang b/tools/gfx-unit-test/multiple-entry-point-shader-cache-shader.slang new file mode 100644 index 000000000..9287b62ea --- /dev/null +++ b/tools/gfx-unit-test/multiple-entry-point-shader-cache-shader.slang @@ -0,0 +1,28 @@ +uniform RWStructuredBuffer<float> buffer; + +[shader("compute")] +[numthreads(4, 1, 1)] +void computeA( +uint3 sv_dispatchThreadID : SV_DispatchThreadID) +{ + var input = buffer[sv_dispatchThreadID.x]; + buffer[sv_dispatchThreadID.x] = input + 1.0f; +} + +[shader("compute")] +[numthreads(4, 1, 1)] +void computeB( +uint3 sv_dispatchThreadID : SV_DispatchThreadID) +{ + var input = buffer[sv_dispatchThreadID.x]; + buffer[sv_dispatchThreadID.x] = input + 2.0f; +} + +[shader("compute")] +[numthreads(4, 1, 1)] +void computeC( +uint3 sv_dispatchThreadID : SV_DispatchThreadID) +{ + var input = buffer[sv_dispatchThreadID.x]; + buffer[sv_dispatchThreadID.x] = input + 3.0f; +} diff --git a/tools/gfx-unit-test/shader-cache-shader-A.slang b/tools/gfx-unit-test/shader-cache-shader-A.slang new file mode 100644 index 000000000..c2dbe56f0 --- /dev/null +++ b/tools/gfx-unit-test/shader-cache-shader-A.slang @@ -0,0 +1,10 @@ +uniform RWStructuredBuffer<float> buffer; + + [shader("compute")] + [numthreads(4, 1, 1)] + void computeMain( + uint3 sv_dispatchThreadID : SV_DispatchThreadID) + { + var input = buffer[sv_dispatchThreadID.x]; + buffer[sv_dispatchThreadID.x] = input + 3.0f; + }
\ No newline at end of file diff --git a/tools/gfx-unit-test/shader-cache-shader-B.slang b/tools/gfx-unit-test/shader-cache-shader-B.slang new file mode 100644 index 000000000..341e32893 --- /dev/null +++ b/tools/gfx-unit-test/shader-cache-shader-B.slang @@ -0,0 +1,10 @@ +uniform RWStructuredBuffer<float> buffer; + + [shader("compute")] + [numthreads(4, 1, 1)] + void computeMain( + uint3 sv_dispatchThreadID : SV_DispatchThreadID) + { + var input = buffer[sv_dispatchThreadID.x]; + buffer[sv_dispatchThreadID.x] = input + 1.0f; + }
\ No newline at end of file diff --git a/tools/gfx-unit-test/shader-cache-shader-C.slang b/tools/gfx-unit-test/shader-cache-shader-C.slang new file mode 100644 index 000000000..826475761 --- /dev/null +++ b/tools/gfx-unit-test/shader-cache-shader-C.slang @@ -0,0 +1,10 @@ +uniform RWStructuredBuffer<float> buffer; + + [shader("compute")] + [numthreads(4, 1, 1)] + void computeMain( + uint3 sv_dispatchThreadID : SV_DispatchThreadID) + { + var input = buffer[sv_dispatchThreadID.x]; + buffer[sv_dispatchThreadID.x] = input + 2.0f; + }
\ No newline at end of file diff --git a/tools/gfx-unit-test/shader-cache-shader.slang b/tools/gfx-unit-test/shader-cache-shader.slang new file mode 100644 index 000000000..c2dbe56f0 --- /dev/null +++ b/tools/gfx-unit-test/shader-cache-shader.slang @@ -0,0 +1,10 @@ +uniform RWStructuredBuffer<float> buffer; + + [shader("compute")] + [numthreads(4, 1, 1)] + void computeMain( + uint3 sv_dispatchThreadID : SV_DispatchThreadID) + { + var input = buffer[sv_dispatchThreadID.x]; + buffer[sv_dispatchThreadID.x] = input + 3.0f; + }
\ No newline at end of file diff --git a/tools/gfx-unit-test/shader-cache-tests.cpp b/tools/gfx-unit-test/shader-cache-tests.cpp new file mode 100644 index 000000000..184b71c06 --- /dev/null +++ b/tools/gfx-unit-test/shader-cache-tests.cpp @@ -0,0 +1,624 @@ +#include "tools/unit-test/slang-unit-test.h" + +#include "slang-gfx.h" +#include "gfx-test-util.h" +#include "tools/gfx-util/shader-cursor.h" +#include "source/core/slang-basic.h" + +#include "source/core/slang-memory-file-system.h" +#include "source/core/slang-file-system.h" + +using namespace gfx; + +namespace gfx_test +{ + + struct BaseShaderCacheTest + { + UnitTestContext* context; + Slang::RenderApiFlag::Enum api; + + ComPtr<IDevice> device; + ComPtr<IPipelineState> pipelineState; + ComPtr<IResourceView> bufferView; + + // diskFileSystem is used to save the test shaders to disk as necessary as + // loadComputeProgram() always loads from the OS file system. cacheFileSystem + // is used to hold the shader cache in memory to avoid needing to manually erase all the + // shader cache entry files between consecutive runs of the test. + ComPtr<ISlangMutableFileSystem> diskFileSystem; + ComPtr<ISlangMutableFileSystem> cacheFileSystem; + + // Simple compute shaders we can pipe to our individual shader files for cache testing + Slang::String contentsA = Slang::String( + R"(uniform RWStructuredBuffer<float> buffer; + + [shader("compute")] + [numthreads(4, 1, 1)] + void computeMain( + uint3 sv_dispatchThreadID : SV_DispatchThreadID) + { + var input = buffer[sv_dispatchThreadID.x]; + buffer[sv_dispatchThreadID.x] = input + 1.0f; + })"); + + Slang::String contentsB = Slang::String( + R"(uniform RWStructuredBuffer<float> buffer; + + [shader("compute")] + [numthreads(4, 1, 1)] + void computeMain( + uint3 sv_dispatchThreadID : SV_DispatchThreadID) + { + var input = buffer[sv_dispatchThreadID.x]; + buffer[sv_dispatchThreadID.x] = input + 2.0f; + })"); + + Slang::String contentsC = Slang::String( + R"(uniform RWStructuredBuffer<float> buffer; + + [shader("compute")] + [numthreads(4, 1, 1)] + void computeMain( + uint3 sv_dispatchThreadID : SV_DispatchThreadID) + { + var input = buffer[sv_dispatchThreadID.x]; + buffer[sv_dispatchThreadID.x] = input + 3.0f; + })"); + + void createRequiredResources() + { + const int numberCount = 4; + float initialData[] = { 0.0f, 1.0f, 2.0f, 3.0f }; + IBufferResource::Desc bufferDesc = {}; + bufferDesc.sizeInBytes = numberCount * sizeof(float); + bufferDesc.format = gfx::Format::Unknown; + bufferDesc.elementSize = sizeof(float); + bufferDesc.allowedStates = ResourceStateSet( + ResourceState::ShaderResource, + ResourceState::UnorderedAccess, + ResourceState::CopyDestination, + ResourceState::CopySource); + bufferDesc.defaultState = ResourceState::UnorderedAccess; + bufferDesc.memoryType = MemoryType::DeviceLocal; + + ComPtr<IBufferResource> numbersBuffer; + GFX_CHECK_CALL_ABORT(device->createBufferResource( + bufferDesc, + (void*)initialData, + numbersBuffer.writeRef())); + + IResourceView::Desc viewDesc = {}; + viewDesc.type = IResourceView::Type::UnorderedAccess; + viewDesc.format = Format::Unknown; + GFX_CHECK_CALL_ABORT( + device->createBufferView(numbersBuffer, nullptr, viewDesc, bufferView.writeRef())); + } + + void freeOldResources() + { + bufferView = nullptr; + pipelineState = nullptr; + device = 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() + { + freeOldResources(); + device = createTestingDevice(context, api, cacheFileSystem); + } + + void init(ComPtr<IDevice> device, UnitTestContext* context) + { + this->device = device; + this->context = context; + switch (device->getDeviceInfo().deviceType) + { + case DeviceType::DirectX11: + api = Slang::RenderApiFlag::D3D11; + break; + case DeviceType::DirectX12: + api = Slang::RenderApiFlag::D3D12; + break; + case DeviceType::Vulkan: + api = Slang::RenderApiFlag::Vulkan; + break; + case DeviceType::CPU: + api = Slang::RenderApiFlag::CPU; + break; + case DeviceType::CUDA: + api = Slang::RenderApiFlag::CUDA; + break; + case DeviceType::OpenGl: + api = Slang::RenderApiFlag::OpenGl; + break; + default: + SLANG_IGNORE_TEST + } + + cacheFileSystem = new Slang::MemoryFileSystem(); + diskFileSystem = Slang::OSFileSystem::getMutableSingleton(); + diskFileSystem = new Slang::RelativeFileSystem(diskFileSystem, "tools/gfx-unit-test"); + } + + void submitGPUWork() + { + Slang::ComPtr<ITransientResourceHeap> transientHeap; + ITransientResourceHeap::Desc transientHeapDesc = {}; + transientHeapDesc.constantBufferSize = 4096; + GFX_CHECK_CALL_ABORT( + device->createTransientResourceHeap(transientHeapDesc, transientHeap.writeRef())); + + ICommandQueue::Desc queueDesc = { ICommandQueue::QueueType::Graphics }; + auto queue = device->createCommandQueue(queueDesc); + + auto commandBuffer = transientHeap->createCommandBuffer(); + auto encoder = commandBuffer->encodeComputeCommands(); + + auto rootObject = encoder->bindPipeline(pipelineState); + + ShaderCursor rootCursor(rootObject); + // Bind buffer view to the entry point. + rootCursor.getPath("buffer").setResource(bufferView); + + encoder->dispatchCompute(1, 1, 1); + encoder->endEncoding(); + commandBuffer->close(); + queue->executeCommandBuffer(commandBuffer); + queue->waitOnHost(); + } + }; + + // One shader file on disk, all modifications are done to the same file + struct SingleEntryShaderCache : BaseShaderCacheTest + { + void generateNewPipelineState(Slang::String shaderContents) + { + diskFileSystem->saveFile("shader-cache-shader.slang", shaderContents.getBuffer(), shaderContents.getLength()); + + ComPtr<IShaderProgram> shaderProgram; + slang::ProgramLayout* slangReflection; + GFX_CHECK_CALL_ABORT(loadComputeProgram(device, shaderProgram, "shader-cache-shader", "computeMain", slangReflection)); + + ComputePipelineStateDesc pipelineDesc = {}; + pipelineDesc.program = shaderProgram.get(); + GFX_CHECK_CALL_ABORT( + device->createComputePipelineState(pipelineDesc, pipelineState.writeRef())); + } + + void run() + { + ComPtr<IShaderCacheStatistics> shaderCacheStats; + + // Due to needing a workaround to prevent loading old, outdated modules, we need to + // recreate the device between each segment of the test. However, we need to maintain the + // same cache filesystem for the duration of the test, so the device is immediately recreated + // to ensure we can pass the filesystem all the way through. + // + // TODO: Remove the repeated generateNewDevice() and createRequiredResources() calls once + // a solution exists that allows source code changes under the same module name to be picked + // up on load. + 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); + } + }; + + // Several shader files on disk, modifications may be done to any file + struct MultipleEntryShaderCache : BaseShaderCacheTest + { + void modifyShaderA(Slang::String shaderContents) + { + diskFileSystem->saveFile("shader-cache-shader-A.slang", shaderContents.getBuffer(), shaderContents.getLength()); + } + + void modifyShaderB(Slang::String shaderContents) + { + diskFileSystem->saveFile("shader-cache-shader-B.slang", shaderContents.getBuffer(), shaderContents.getLength()); + } + + void modifyShaderC(Slang::String shaderContents) + { + diskFileSystem->saveFile("shader-cache-shader-C.slang", shaderContents.getBuffer(), shaderContents.getLength()); + } + + void generateNewPipelineState(GfxIndex shaderIndex) + { + ComPtr<IShaderProgram> shaderProgram; + slang::ProgramLayout* slangReflection; + char* shaderFilename; + switch (shaderIndex) + { + case 0: + shaderFilename = "shader-cache-shader-A"; + break; + case 1: + shaderFilename = "shader-cache-shader-B"; + break; + case 2: + shaderFilename = "shader-cache-shader-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())); + } + + void checkAllCacheEntries() + { + generateNewPipelineState(0); + submitGPUWork(); + generateNewPipelineState(1); + submitGPUWork(); + generateNewPipelineState(2); + submitGPUWork(); + } + + void run() + { + ComPtr<IShaderCacheStatistics> shaderCacheStats; + + // Due to needing a workaround to prevent loading old, outdated modules, we need to + // recreate the device between each segment of the test. However, we need to maintain the + // same cache filesystem for the duration of the test, so the device is immediately recreated + // to ensure we can pass the filesystem all the way through. + // + // TODO: Remove the repeated generateNewDevice() and createRequiredResources() calls once + // a solution exists that allows source code changes under the same module name to be picked + // up on load. + 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); + } + }; + + // One shader file on disk containing several entry points, no modifications are made to the file + struct MultipleEntryPointShader : BaseShaderCacheTest + { + void generateNewPipelineState(GfxIndex shaderIndex) + { + ComPtr<IShaderProgram> shaderProgram; + slang::ProgramLayout* slangReflection; + 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)); + + ComputePipelineStateDesc pipelineDesc = {}; + pipelineDesc.program = shaderProgram.get(); + GFX_CHECK_CALL_ABORT( + device->createComputePipelineState(pipelineDesc, pipelineState.writeRef())); + } + + void run() + { + ComPtr<IShaderCacheStatistics> shaderCacheStats; + + // Due to needing a workaround to prevent loading old, outdated modules, we need to + // recreate the device between each segment of the test. However, we need to maintain the + // same cache filesystem for the duration of the test, so the device is immediately recreated + // to ensure we can pass the filesystem all the way through. + // + // TODO: Remove the repeated generateNewDevice() and createRequiredResources() calls once + // a solution exists that allows source code changes under the same module name to be picked + // up on load. + 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); + } + }; + + // 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 + { + Slang::String importedContentsA = Slang::String( + R"(struct TestFunction + { + void simpleElementAdd(RWStructuredBuffer<float> buffer, uint index) + { + var input = buffer[index]; + buffer[index] = input + 1.0f; + } + };)"); + + Slang::String importedContentsB = Slang::String( + R"(struct TestFunction + { + void simpleElementAdd(RWStructuredBuffer<float> buffer, uint index) + { + var input = buffer[index]; + buffer[index] = input + 2.0f; + } + };)"); + + Slang::String importFile = Slang::String( + R"(import imported; + + uniform RWStructuredBuffer<float> buffer; + + [shader("compute")] + [numthreads(4, 1, 1)] + void computeMain( + uint3 sv_dispatchThreadID : SV_DispatchThreadID) + { + TestFunction test; + for (uint i = 0; i < 4; ++i) + { + test.simpleElementAdd(buffer, i); + } + })"); + + Slang::String includeFile = Slang::String( + R"(#include "imported.slang" + + uniform RWStructuredBuffer<float> buffer; + + [shader("compute")] + [numthreads(4, 1, 1)] + void computeMain( + uint3 sv_dispatchThreadID : SV_DispatchThreadID) + { + TestFunction test; + for (uint i = 0; i < 4; ++i) + { + test.simpleElementAdd(buffer, i); + } + })"); + + void initializeFiles() + { + diskFileSystem->saveFile("imported.slang", importedContentsA.getBuffer(), importedContentsA.getLength()); + diskFileSystem->saveFile("importing-shader-cache-shader.slang", importFile.getBuffer(), importFile.getLength()); + } + + void modifyImportedFile(Slang::String importedContents) + { + diskFileSystem->saveFile("imported.slang", importedContents.getBuffer(), importedContents.getLength()); + } + + void changeImportToInclude() + { + diskFileSystem->saveFile("importing-shader-cache-shader.slang", includeFile.getBuffer(), includeFile.getLength()); + } + + void generateNewPipelineState() + { + ComPtr<IShaderProgram> shaderProgram; + slang::ProgramLayout* slangReflection; + GFX_CHECK_CALL_ABORT(loadComputeProgram(device, shaderProgram, "importing-shader-cache-shader", "computeMain", slangReflection)); + + ComputePipelineStateDesc pipelineDesc = {}; + pipelineDesc.program = shaderProgram.get(); + GFX_CHECK_CALL_ABORT( + device->createComputePipelineState(pipelineDesc, pipelineState.writeRef())); + } + + void run() + { + ComPtr<IShaderCacheStatistics> shaderCacheStats; + + // Due to needing a workaround to prevent loading old, outdated modules, we need to + // recreate the device between each segment of the test. However, we need to maintain the + // same cache filesystem for the duration of the test, so the device is immediately recreated + // to ensure we can pass the filesystem all the way through. + // + // TODO: Remove the repeated generateNewDevice() and createRequiredResources() calls once + // a solution exists that allows source code changes under the same module name to be picked + // up on load. + 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); + } + }; + + template <typename T> + void shaderCacheTestImpl(ComPtr<IDevice> device, UnitTestContext* context) + { + T test; + test.init(device, context); + test.run(); + } + + SLANG_UNIT_TEST(singleEntryShaderCacheD3D12) + { + runTestImpl(shaderCacheTestImpl<SingleEntryShaderCache>, unitTestContext, Slang::RenderApiFlag::D3D12); + } + + SLANG_UNIT_TEST(singleEntryShaderCacheVulkan) + { + runTestImpl(shaderCacheTestImpl<SingleEntryShaderCache>, unitTestContext, Slang::RenderApiFlag::Vulkan); + } + + SLANG_UNIT_TEST(multipleEntryShaderCacheD3D12) + { + runTestImpl(shaderCacheTestImpl<MultipleEntryShaderCache>, unitTestContext, Slang::RenderApiFlag::D3D12); + } + + SLANG_UNIT_TEST(multipleEntryShaderCacheVulkan) + { + runTestImpl(shaderCacheTestImpl<MultipleEntryShaderCache>, unitTestContext, Slang::RenderApiFlag::Vulkan); + } + + SLANG_UNIT_TEST(multipleEntryPointShaderCacheD3D12) + { + runTestImpl(shaderCacheTestImpl<MultipleEntryPointShader>, unitTestContext, Slang::RenderApiFlag::D3D12); + } + + SLANG_UNIT_TEST(multipleEntryPointShaderCacheVulkan) + { + runTestImpl(shaderCacheTestImpl<MultipleEntryPointShader>, unitTestContext, Slang::RenderApiFlag::Vulkan); + } + + SLANG_UNIT_TEST(shaderFileImportsShaderCacheD3D12) + { + runTestImpl(shaderCacheTestImpl<ShaderFileImportsShaderCache>, unitTestContext, Slang::RenderApiFlag::D3D12); + } + + SLANG_UNIT_TEST(shaderFileImportsShaderCacheVulkan) + { + runTestImpl(shaderCacheTestImpl<ShaderFileImportsShaderCache>, unitTestContext, Slang::RenderApiFlag::Vulkan); + } +} |
