diff options
| author | lucy96chen <47800040+lucy96chen@users.noreply.github.com> | 2022-10-12 09:55:09 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-10-12 09:55:09 -0700 |
| commit | f0cd62b37c5dfbbdb3fb205f1be2b8beba0dfed4 (patch) | |
| tree | 97d031e889046ac992b729d85e2db1cd3597e317 /tools | |
| parent | 5128de89a9a8da09587f20e8fb5bc324ea14e0df (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')
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"))); + } +} |
