summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorlucy96chen <47800040+lucy96chen@users.noreply.github.com>2022-10-12 09:55:09 -0700
committerGitHub <noreply@github.com>2022-10-12 09:55:09 -0700
commitf0cd62b37c5dfbbdb3fb205f1be2b8beba0dfed4 (patch)
tree97d031e889046ac992b729d85e2db1cd3597e317 /tools
parent5128de89a9a8da09587f20e8fb5bc324ea14e0df (diff)
Shader caching (#2432)
* Changed all getEntryPointCode calls to use RendererBase::getEntryPointCodeFromShaderCache * Hashing hooked up, tests pass but need to add more to fully test functionality * checkpoint * Checkpoint: File system creation seems functional, saving is broken * checkpoint: Fixed filename generation from MD5 hash, shader blob might be going missing ahead of pipeline state creation * Fixed a lot of bugs related to hash code generation, shader cache is likely working but needs further testing * Added workaround for module loading by re-creating the test device, shader cache test functional * Vulkan shader caching bug fixed, checkpoint commit before more refinement * pre-ToT merge checkpoint * checkpoint commit, improving cache keys * Significantly expanded items included in the dependency hash for Module; Added dependency hash functions to SpecializedComponentType and RenamedEntryPointComponentType * Temporarily disable shader cache test * Mid cleanup changes, solution successfully builds * Added several helper update functions to slang-md5 to help simplify usage; Added a function under ISession to compute a hash for all linkage-related items; Function renames and cleaned up some comments * Ran premake.bat; Renamed getASTBasedHashCode to computeASTBasedHash * Added slang unit tests for Checksum and MD5; Extended gfx shader cache test to test with multiple shader files and one shader file with multiple entry points * Solution builds and shader cache tests pass, but at least a couple other tests now failing * ran premake.bat * More cleanup changes * Added shaderCachePath field to IDevice desc in gfx.slang, gfx-smoke.slang should be functional * ran premake * cleanup changes; Adding test printf to getEntryPointCodeFromShaderCache to see if output can be seen in CI * Removed debugging printfs; Added handling for getEntryPointCode() failing * Cleanup changes; Jonathan's fixes to SerialWriter to zero initialize otherwise uninitialized memory; Change to SwizzleExpr creation to zero initialize elementCount * Changed enable_if_t to enable_if * Fixed enable_if * Added test for import vs include and changes to included and imported files; Fixed build errors in CUDA; Renamed shader cache statistics fields * cleanup changes * Readd removed file * Restructured computeDependencyBasedHash calls, added computeDependencyBasedHashImpl to all classes dervied from ComponentType * Applied same restructuring to the AST hash functions * Cleanup changes; Moved HashBuilder out to slang-digest.h and added some helper functions to streamline the process of adding items to a hash * Cleanup; Fixed incorrect expected results for shader import and include test
Diffstat (limited to 'tools')
-rw-r--r--tools/gfx-unit-test/gfx-test-util.cpp14
-rw-r--r--tools/gfx-unit-test/gfx-test-util.h6
-rw-r--r--tools/gfx-unit-test/imported.slang8
-rw-r--r--tools/gfx-unit-test/importing-shader-cache-shader.slang15
-rw-r--r--tools/gfx-unit-test/multiple-entry-point-shader-cache-shader.slang28
-rw-r--r--tools/gfx-unit-test/shader-cache-shader-A.slang10
-rw-r--r--tools/gfx-unit-test/shader-cache-shader-B.slang10
-rw-r--r--tools/gfx-unit-test/shader-cache-shader-C.slang10
-rw-r--r--tools/gfx-unit-test/shader-cache-shader.slang10
-rw-r--r--tools/gfx-unit-test/shader-cache-tests.cpp624
-rw-r--r--tools/gfx/cuda/cuda-device.cpp2
-rw-r--r--tools/gfx/d3d11/d3d11-device.cpp2
-rw-r--r--tools/gfx/d3d12/d3d12-pipeline-state.cpp4
-rw-r--r--tools/gfx/gfx.slang2
-rw-r--r--tools/gfx/open-gl/render-gl.cpp2
-rw-r--r--tools/gfx/renderer-shared.cpp176
-rw-r--r--tools/gfx/renderer-shared.h31
-rw-r--r--tools/gfx/vulkan/vk-pipeline-state.cpp6
-rw-r--r--tools/slang-unit-test/unit-test-checksum.cpp32
-rw-r--r--tools/slang-unit-test/unit-test-md5.cpp97
20 files changed, 1072 insertions, 17 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);
+ }
+}
diff --git a/tools/gfx/cuda/cuda-device.cpp b/tools/gfx/cuda/cuda-device.cpp
index be5dbbc96..32454109b 100644
--- a/tools/gfx/cuda/cuda-device.cpp
+++ b/tools/gfx/cuda/cuda-device.cpp
@@ -913,7 +913,7 @@ SLANG_NO_THROW Result SLANG_MCALL DeviceImpl::createProgram(
ComPtr<ISlangBlob> kernelCode;
ComPtr<ISlangBlob> diagnostics;
- auto compileResult = desc.slangGlobalScope->getEntryPointCode(
+ auto compileResult = getEntryPointCodeFromShaderCache(desc.slangGlobalScope,
(SlangInt)0, 0, kernelCode.writeRef(), diagnostics.writeRef());
if (diagnostics)
{
diff --git a/tools/gfx/d3d11/d3d11-device.cpp b/tools/gfx/d3d11/d3d11-device.cpp
index aa665ebd4..969eb7d1b 100644
--- a/tools/gfx/d3d11/d3d11-device.cpp
+++ b/tools/gfx/d3d11/d3d11-device.cpp
@@ -1239,7 +1239,7 @@ Result DeviceImpl::createProgram(
ComPtr<ISlangBlob> kernelCode;
ComPtr<ISlangBlob> diagnostics;
- auto compileResult = slangGlobalScope->getEntryPointCode(
+ auto compileResult = getEntryPointCodeFromShaderCache(slangGlobalScope,
(SlangInt)i, 0, kernelCode.writeRef(), diagnostics.writeRef());
if (diagnostics)
diff --git a/tools/gfx/d3d12/d3d12-pipeline-state.cpp b/tools/gfx/d3d12/d3d12-pipeline-state.cpp
index ec073bf44..adfdcd518 100644
--- a/tools/gfx/d3d12/d3d12-pipeline-state.cpp
+++ b/tools/gfx/d3d12/d3d12-pipeline-state.cpp
@@ -50,7 +50,7 @@ Result PipelineStateImpl::ensureAPIPipelineStateCreated()
auto programImpl = static_cast<ShaderProgramImpl*>(m_program.Ptr());
if (programImpl->m_shaders.getCount() == 0)
{
- SLANG_RETURN_ON_FAIL(programImpl->compileShaders());
+ SLANG_RETURN_ON_FAIL(programImpl->compileShaders(m_device));
}
if (desc.type == PipelineType::Graphics)
{
@@ -356,7 +356,7 @@ Result RayTracingPipelineStateImpl::ensureAPIPipelineStateCreated()
SlangInt entryPointIndex)
{
ComPtr<ISlangBlob> codeBlob;
- auto compileResult = component->getEntryPointCode(
+ auto compileResult = m_device->getEntryPointCodeFromShaderCache(component,
entryPointIndex, 0, codeBlob.writeRef(), diagnostics.writeRef());
if (diagnostics.get())
{
diff --git a/tools/gfx/gfx.slang b/tools/gfx/gfx.slang
index 17a38e28d..c1296b472 100644
--- a/tools/gfx/gfx.slang
+++ b/tools/gfx/gfx.slang
@@ -1733,6 +1733,8 @@ struct DeviceDesc
void *apiCommandDispatcher = nullptr;
// The slot (typically UAV) used to identify NVAPI intrinsics. If >=0 NVAPI is required.
GfxIndex nvapiExtnSlot = -1;
+ // The root directory for the shader cache.
+ NativeString shaderCachePath;
// The file system for loading cached shader kernels. The layer does not maintain a strong reference to the object,
// instead the user is responsible for holding the object alive during the lifetime of an `IDevice`.
void *shaderCacheFileSystem = nullptr;
diff --git a/tools/gfx/open-gl/render-gl.cpp b/tools/gfx/open-gl/render-gl.cpp
index 7daf577ef..fdec875f0 100644
--- a/tools/gfx/open-gl/render-gl.cpp
+++ b/tools/gfx/open-gl/render-gl.cpp
@@ -2791,7 +2791,7 @@ Result GLDevice::createProgram(
{
ComPtr<ISlangBlob> kernelCode;
ComPtr<ISlangBlob> diagnostics;
- auto compileResult = desc.slangGlobalScope->getEntryPointCode(
+ auto compileResult = getEntryPointCodeFromShaderCache(desc.slangGlobalScope,
i, 0, kernelCode.writeRef(), diagnostics.writeRef());
if (diagnostics)
{
diff --git a/tools/gfx/renderer-shared.cpp b/tools/gfx/renderer-shared.cpp
index 43629a0f2..8ed776776 100644
--- a/tools/gfx/renderer-shared.cpp
+++ b/tools/gfx/renderer-shared.cpp
@@ -3,6 +3,11 @@
#include "core/slang-io.h"
#include "core/slang-token-reader.h"
+#include "../../source/core/slang-file-system.h"
+
+#include "../../slang.h"
+#include "../../source/slang/slang-hash-utils.h"
+
using namespace Slang;
namespace gfx
@@ -23,6 +28,7 @@ const Slang::Guid GfxGUID::IID_IResource = SLANG_UUID_IResource;
const Slang::Guid GfxGUID::IID_IBufferResource = SLANG_UUID_IBufferResource;
const Slang::Guid GfxGUID::IID_ITextureResource = SLANG_UUID_ITextureResource;
const Slang::Guid GfxGUID::IID_IDevice = SLANG_UUID_IDevice;
+const Slang::Guid GfxGUID::IID_IShaderCacheStatistics = SLANG_UUID_IShaderCacheStatistics;
const Slang::Guid GfxGUID::IID_IShaderObject = SLANG_UUID_IShaderObject;
const Slang::Guid GfxGUID::IID_IRenderPassLayout = SLANG_UUID_IRenderPassLayout;
@@ -325,6 +331,129 @@ void PipelineStateBase::initializeBase(const PipelineStateDesc& inDesc)
}
}
+void updateCacheEntry(ISlangMutableFileSystem* fileSystem, slang::IBlob* compiledCode, String shaderFilename, slang::Digest ASTHash)
+{
+ auto hashSize = sizeof(slang::Digest);
+
+ auto bufferSize = hashSize + compiledCode->getBufferSize();
+ List<uint8_t> contents;
+ contents.setCount(bufferSize);
+ uint8_t* buffer = contents.begin();
+ memcpy(buffer, &ASTHash, hashSize);
+ memcpy(buffer + hashSize, (void*)compiledCode->getBufferPointer(), compiledCode->getBufferSize());
+ fileSystem->saveFile(shaderFilename.getBuffer(), buffer, bufferSize);
+}
+
+Result RendererBase::getEntryPointCodeFromShaderCache(
+ slang::IComponentType* program,
+ SlangInt entryPointIndex,
+ SlangInt targetIndex,
+ slang::IBlob** outCode,
+ slang::IBlob** outDiagnostics)
+{
+ // TODO: Need a way in filesystem to query both file size and file creation time, if cache size exceeds
+ // specified maximum size (in bytes or files) then delete oldest files - cache eviction policy
+
+ // Immediately call getEntryPointCode if no shader cache was provided on initialization
+ if (!shaderCacheFileSystem)
+ {
+ return program->getEntryPointCode(entryPointIndex, targetIndex, outCode, outDiagnostics);
+ }
+
+ // Produce a string which we can use to query the shader cache by combining two separate hashes which
+ // together comprise all the compilation arguments for this program.
+ ComPtr<slang::ISession> session;
+ getSlangSession(session.writeRef());
+
+ slang::Digest shaderKeyHash;
+ program->computeDependencyBasedHash(entryPointIndex, targetIndex, &shaderKeyHash);
+
+ StringBuilder shaderKey = hashToString(shaderKeyHash);
+
+ // Produce a hash using the AST for this program - This is needed to check whether a cache entry is effectively dirty,
+ // or to save along with the compiled code into an entry so the entry can be checked if fetched later on.
+ slang::Digest ASTHash;
+ program->computeASTBasedHash(&ASTHash);
+
+ ComPtr<ISlangBlob> codeBlob;
+
+ // Query shaderCacheFileSystem for an entry whose key matches shaderFilename
+ // - If we find it, then copy the file contents into memory and return in outCode
+ auto result = shaderCacheFileSystem->loadFile(shaderKey.getBuffer(), codeBlob.writeRef());
+
+ if (SLANG_FAILED(result))
+ {
+ // If we didn't find it, call program->getEntryPointCode() to get and return the code. We also
+ // make sure to save a new entry in the shader cache.
+ if (SLANG_SUCCEEDED(program->getEntryPointCode(entryPointIndex, targetIndex, codeBlob.writeRef(), outDiagnostics)))
+ {
+ if (mutableShaderCacheFileSystem)
+ {
+ updateCacheEntry(mutableShaderCacheFileSystem, codeBlob, shaderKey, ASTHash);
+ }
+
+ shaderCacheMissCount++;
+ }
+ else
+ {
+ // If getEntryPointCode() failed to fetch the code, we return SLANG_FAIL along with the diagnostics output
+ // in outDiagnostics.
+ return SLANG_FAIL;
+ }
+ }
+ else
+ {
+ // If the entry exists, we need to check that the entry isn't effectively dirty. Since we stored
+ // the AST hash with the compiled code, we can determine this by comparing the stored hash with the
+ // AST hash generated earlier.
+ auto entryContents = codeBlob->getBufferPointer();
+ auto hashSize = sizeof(slang::Digest);
+ if (memcmp(ASTHash.values, entryContents, hashSize) != 0)
+ {
+ // The AST hash stored in the entry does not match the AST hash generated earlier, indicating
+ // that the shader code has changed and the entry needs to be updated.
+ if (SLANG_SUCCEEDED(program->getEntryPointCode(entryPointIndex, targetIndex, codeBlob.writeRef(), outDiagnostics)))
+ {
+ if (mutableShaderCacheFileSystem)
+ {
+ updateCacheEntry(mutableShaderCacheFileSystem, codeBlob, shaderKey, ASTHash);
+ }
+
+ shaderCacheEntryDirtyCount++;
+ }
+ else
+ {
+ // If getEntryPointCode() failed to fetch the code, we return SLANG_FAIL along with the diagnostics output
+ // in outDiagnostics.
+ return SLANG_FAIL;
+ }
+ }
+ else
+ {
+ auto compiledCode = RawBlob::create((uint8_t*)codeBlob->getBufferPointer() + hashSize, codeBlob->getBufferSize() - hashSize);
+ codeBlob = compiledCode;
+
+ shaderCacheHitCount++;
+ }
+ }
+
+ *outCode = codeBlob.detach();
+ return SLANG_OK;
+}
+
+SlangResult RendererBase::queryInterface(SlangUUID const& uuid, void** outObject)
+{
+ if (uuid == GfxGUID::IID_IShaderCacheStatistics)
+ {
+ *outObject = static_cast<IShaderCacheStatistics*>(this);
+ addRef();
+ return SLANG_OK;
+ }
+
+ *outObject = getInterface(uuid);
+ return SLANG_OK;
+}
+
IDevice* gfx::RendererBase::getInterface(const Guid& guid)
{
return (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IDevice)
@@ -334,6 +463,28 @@ IDevice* gfx::RendererBase::getInterface(const Guid& guid)
SLANG_NO_THROW Result SLANG_MCALL RendererBase::initialize(const Desc& desc)
{
+ // If a shader cache file system was provided, use the provided system.
+ if (desc.shaderCacheFileSystem)
+ {
+ shaderCacheFileSystem = desc.shaderCacheFileSystem;
+ }
+ if (desc.shaderCachePath)
+ {
+ // Only a path was provided, create a RelativeFileSystem using the path
+ if (!shaderCacheFileSystem)
+ {
+ shaderCacheFileSystem = OSFileSystem::getMutableSingleton();
+ }
+ shaderCacheFileSystem = new RelativeFileSystem(shaderCacheFileSystem, desc.shaderCachePath);
+ }
+
+ // If we initialized a file system for the shader cache, check if it's mutable. If so, store a pointer
+ // to the mutable version in order to save new entries later.
+ if (shaderCacheFileSystem)
+ {
+ shaderCacheFileSystem->queryInterface(ISlangMutableFileSystem::getTypeGuid(), (void**)mutableShaderCacheFileSystem.writeRef());
+ }
+
if (desc.apiCommandDispatcher)
{
desc.apiCommandDispatcher->queryInterface(
@@ -664,7 +815,28 @@ Result RendererBase::getShaderObjectLayout(
return SLANG_OK;
}
+GfxCount RendererBase::getCacheMissCount()
+{
+ return shaderCacheMissCount;
+}
+GfxCount RendererBase::getCacheHitCount()
+{
+ return shaderCacheHitCount;
+}
+
+GfxCount RendererBase::getCacheEntryDirtyCount()
+{
+ return shaderCacheEntryDirtyCount;
+}
+
+Result RendererBase::resetCacheStatistics()
+{
+ shaderCacheMissCount = 0;
+ shaderCacheHitCount = 0;
+ shaderCacheEntryDirtyCount = 0;
+ return SLANG_OK;
+}
ShaderComponentID ShaderCache::getComponentId(slang::TypeReflection* type)
{
@@ -908,7 +1080,7 @@ void ShaderProgramBase::init(const IShaderProgram::Desc& inDesc)
}
}
-Result ShaderProgramBase::compileShaders()
+Result ShaderProgramBase::compileShaders(RendererBase* device)
{
// For a fully specialized program, read and store its kernel code in `shaderProgram`.
auto compileShader = [&](slang::EntryPointReflection* entryPointInfo,
@@ -918,7 +1090,7 @@ Result ShaderProgramBase::compileShaders()
auto stage = entryPointInfo->getStage();
ComPtr<ISlangBlob> kernelCode;
ComPtr<ISlangBlob> diagnostics;
- auto compileResult = entryPointComponent->getEntryPointCode(
+ auto compileResult = device->getEntryPointCodeFromShaderCache(entryPointComponent,
entryPointIndex, 0, kernelCode.writeRef(), diagnostics.writeRef());
if (diagnostics)
{
diff --git a/tools/gfx/renderer-shared.h b/tools/gfx/renderer-shared.h
index d0e4b52fb..a753fe017 100644
--- a/tools/gfx/renderer-shared.h
+++ b/tools/gfx/renderer-shared.h
@@ -26,6 +26,7 @@ struct GfxGUID
static const Slang::Guid IID_ITextureResource;
static const Slang::Guid IID_IInputLayout;
static const Slang::Guid IID_IDevice;
+ static const Slang::Guid IID_IShaderCacheStatistics;
static const Slang::Guid IID_IShaderObjectLayout;
static const Slang::Guid IID_IShaderObject;
static const Slang::Guid IID_IRenderPassLayout;
@@ -857,7 +858,7 @@ public:
return false;
}
- Slang::Result compileShaders();
+ Slang::Result compileShaders(RendererBase* device);
virtual Slang::Result createShaderModule(
slang::EntryPointReflection* entryPointInfo, Slang::ComPtr<ISlangBlob> kernelCode);
@@ -1211,11 +1212,12 @@ public:
// Renderer implementation shared by all platforms.
// Responsible for shader compilation, specialization and caching.
-class RendererBase : public IDevice, public Slang::ComObject
+class RendererBase : public IDevice, public IShaderCacheStatistics, public Slang::ComObject
{
friend class ShaderObjectBase;
public:
- SLANG_COM_OBJECT_IUNKNOWN_ALL
+ SLANG_COM_OBJECT_IUNKNOWN_ADD_REF
+ SLANG_COM_OBJECT_IUNKNOWN_RELEASE
virtual SLANG_NO_THROW Result SLANG_MCALL getNativeDeviceHandles(InteropHandles* outHandles) SLANG_OVERRIDE;
virtual SLANG_NO_THROW Result SLANG_MCALL getFeatures(
@@ -1224,6 +1226,8 @@ public:
virtual SLANG_NO_THROW Result SLANG_MCALL
getFormatSupportedResourceStates(Format format, ResourceStateSet* outStates) override;
virtual SLANG_NO_THROW Result SLANG_MCALL getSlangSession(slang::ISession** outSlangSession) SLANG_OVERRIDE;
+ virtual SLANG_NO_THROW SlangResult SLANG_MCALL
+ queryInterface(SlangUUID const& uuid, void** outObject) SLANG_OVERRIDE;
IDevice* getInterface(const Slang::Guid& guid);
virtual SLANG_NO_THROW Result SLANG_MCALL createTextureFromNativeHandle(
@@ -1309,6 +1313,13 @@ public:
// Provides a default implementation that returns SLANG_E_NOT_AVAILABLE.
virtual SLANG_NO_THROW Result SLANG_MCALL getTextureRowAlignment(size_t* outAlignment) override;
+ Result getEntryPointCodeFromShaderCache(
+ slang::IComponentType* program,
+ SlangInt entryPointIndex,
+ SlangInt targetIndex,
+ slang::IBlob** outCode,
+ slang::IBlob** outDiagnostics = nullptr);
+
Result getShaderObjectLayout(
slang::TypeReflection* type,
ShaderObjectContainerType container,
@@ -1347,9 +1358,23 @@ protected:
Slang::List<Slang::String> m_features;
public:
+ virtual SLANG_NO_THROW GfxCount SLANG_MCALL getCacheMissCount() override;
+ virtual SLANG_NO_THROW GfxCount SLANG_MCALL getCacheHitCount() override;
+ virtual SLANG_NO_THROW GfxCount SLANG_MCALL getCacheEntryDirtyCount() override;
+ virtual SLANG_NO_THROW Result SLANG_MCALL resetCacheStatistics() override;
+
+protected:
+ GfxCount shaderCacheMissCount = 0;
+ GfxCount shaderCacheHitCount = 0;
+ GfxCount shaderCacheEntryDirtyCount = 0;
+
+public:
SlangContext slangContext;
ShaderCache shaderCache;
+ ISlangFileSystem* shaderCacheFileSystem = nullptr;
+ ComPtr<ISlangMutableFileSystem> mutableShaderCacheFileSystem = nullptr;
+
Slang::Dictionary<slang::TypeLayoutReflection*, Slang::RefPtr<ShaderObjectLayoutBase>> m_shaderObjectLayoutCache;
Slang::ComPtr<IPipelineCreationAPIDispatcher> m_pipelineCreationAPIDispatcher;
};
diff --git a/tools/gfx/vulkan/vk-pipeline-state.cpp b/tools/gfx/vulkan/vk-pipeline-state.cpp
index 710cbdaef..06bd13197 100644
--- a/tools/gfx/vulkan/vk-pipeline-state.cpp
+++ b/tools/gfx/vulkan/vk-pipeline-state.cpp
@@ -251,7 +251,7 @@ Result PipelineStateImpl::createVKGraphicsPipelineState()
auto programImpl = static_cast<ShaderProgramImpl*>(m_program.Ptr());
if (programImpl->m_stageCreateInfos.getCount() == 0)
{
- SLANG_RETURN_ON_FAIL(programImpl->compileShaders());
+ SLANG_RETURN_ON_FAIL(programImpl->compileShaders(m_device));
}
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
@@ -281,7 +281,7 @@ Result PipelineStateImpl::createVKComputePipelineState()
auto programImpl = static_cast<ShaderProgramImpl*>(m_program.Ptr());
if (programImpl->m_stageCreateInfos.getCount() == 0)
{
- SLANG_RETURN_ON_FAIL(programImpl->compileShaders());
+ SLANG_RETURN_ON_FAIL(programImpl->compileShaders(m_device));
}
VkPipelineCache pipelineCache = VK_NULL_HANDLE;
@@ -340,7 +340,7 @@ Result RayTracingPipelineStateImpl::createVKRayTracingPipelineState()
auto programImpl = static_cast<ShaderProgramImpl*>(m_program.Ptr());
if (programImpl->m_stageCreateInfos.getCount() == 0)
{
- SLANG_RETURN_ON_FAIL(programImpl->compileShaders());
+ SLANG_RETURN_ON_FAIL(programImpl->compileShaders(m_device));
}
VkRayTracingPipelineCreateInfoKHR raytracingPipelineInfo = {
diff --git a/tools/slang-unit-test/unit-test-checksum.cpp b/tools/slang-unit-test/unit-test-checksum.cpp
new file mode 100644
index 000000000..8980bd31f
--- /dev/null
+++ b/tools/slang-unit-test/unit-test-checksum.cpp
@@ -0,0 +1,32 @@
+// unit-test-checksum.cpp
+
+#include "tools/unit-test/slang-unit-test.h"
+
+#include "../../source/slang/slang-hash-utils.h"
+
+using namespace Slang;
+
+SLANG_UNIT_TEST(checksum)
+{
+ {
+ slang::Digest testA;
+ testA.values[0] = 1;
+ testA.values[1] = 2;
+ testA.values[2] = 3;
+ testA.values[3] = 4;
+
+ String testAString = hashToString(testA);
+ SLANG_CHECK(testAString.equals(String("00000001000000020000000300000004")));
+ }
+
+ {
+ slang::Digest testC;
+ testC.values[0] = 0x11111111;
+ testC.values[1] = 0x22222222;
+ testC.values[2] = 0x33333333;
+ testC.values[3] = 0x44444444;
+
+ String testCString = hashToString(testC);
+ SLANG_CHECK(testCString.equals(String("11111111222222223333333344444444")));
+ }
+}
diff --git a/tools/slang-unit-test/unit-test-md5.cpp b/tools/slang-unit-test/unit-test-md5.cpp
new file mode 100644
index 000000000..95235e0ed
--- /dev/null
+++ b/tools/slang-unit-test/unit-test-md5.cpp
@@ -0,0 +1,97 @@
+// unit-test-md5.cpp
+#include "tools/unit-test/slang-unit-test.h"
+
+#include "../../source/core/slang-md5.h"
+#include "../../source/core/slang-string.h"
+#include "../../source/slang/slang-hash-utils.h"
+
+using namespace Slang;
+
+SLANG_UNIT_TEST(md5hash)
+{
+ {
+ // Raw numerical values, etc.
+ MD5Context testCtx;
+ MD5HashGen testHashGen;
+ testHashGen.init(&testCtx);
+
+ int64_t valueA = -1;
+ uint64_t valueB = 1;
+ testHashGen.update(&testCtx, valueA);
+ testHashGen.update(&testCtx, valueB);
+
+ slang::Digest testA;
+ testHashGen.finalize(&testCtx, &testA);
+
+ String testAString = hashToString(testA);
+ SLANG_CHECK(testAString.equals(String("E271A15BD2BD98081390630579266F74")));
+ }
+
+ {
+ // List
+ MD5Context testCtx;
+ MD5HashGen testHashGen;
+ testHashGen.init(&testCtx);
+
+ List<int64_t> listA;
+ listA.add(1);
+ listA.add(2);
+ listA.add(3);
+ listA.add(4);
+ testHashGen.update(&testCtx, listA);
+
+ slang::Digest testB;
+ testHashGen.finalize(&testCtx, &testB);
+
+ String testBString = hashToString(testB);
+ SLANG_CHECK(testBString.equals(String("8AD852437539AA78D60CF70BA5CA7BF2")));
+ }
+
+ {
+ // UnownedStringSlice
+ MD5Context testCtx;
+ MD5HashGen testHashGen;
+ testHashGen.init(&testCtx);
+
+ UnownedStringSlice stringSlice = UnownedStringSlice("String Slice Test");
+ testHashGen.update(&testCtx, stringSlice);
+
+ slang::Digest testC;
+ testHashGen.finalize(&testCtx, &testC);
+
+ String testCString = hashToString(testC);
+ SLANG_CHECK(testCString.equals(String("8EC56C5DDFA424183957CFD01633605B")));
+ }
+
+ {
+ // String
+ MD5Context testCtx;
+ MD5HashGen testHashGen;
+ testHashGen.init(&testCtx);
+
+ String str = String("String Test");
+ testHashGen.update(&testCtx, str);
+
+ slang::Digest testD;
+ testHashGen.finalize(&testCtx, &testD);
+
+ String testDString = hashToString(testD);
+ SLANG_CHECK(testDString.equals(String("CC795ADF40C7702106A5F01C24CEB0CE")));
+ }
+
+ {
+ // Hash
+ MD5Context testCtx;
+ MD5HashGen testHashGen;
+ testHashGen.init(&testCtx);
+
+ slang::Digest Hash;
+ testHashGen.update(&testCtx, Hash);
+
+ slang::Digest testE;
+ testHashGen.finalize(&testCtx, &testE);
+
+ String testEString = hashToString(testE);
+ SLANG_CHECK(testEString.equals(String("3613E74ABFF94BE42E75D279A5184823")));
+ }
+}