diff options
| -rw-r--r-- | build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj | 3 | ||||
| -rw-r--r-- | build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters | 9 | ||||
| -rw-r--r-- | slang.h | 20 | ||||
| -rw-r--r-- | source/slang/slang-ast-support-types.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-check-impl.h | 2 | ||||
| -rwxr-xr-x | source/slang/slang-compiler.h | 45 | ||||
| -rw-r--r-- | source/slang/slang-preprocessor.cpp | 5 | ||||
| -rw-r--r-- | source/slang/slang-preprocessor.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-serialize-type-info.h | 70 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 25 | ||||
| -rw-r--r-- | tools/gfx-unit-test/shader-cache-graphics.slang | 53 | ||||
| -rw-r--r-- | tools/gfx-unit-test/shader-cache-tests.cpp | 290 | ||||
| -rw-r--r-- | tools/gfx-unit-test/split-graphics-fragment.slang | 24 | ||||
| -rw-r--r-- | tools/gfx-unit-test/split-graphics-vertex.slang | 36 | ||||
| -rw-r--r-- | tools/gfx/persistent-shader-cache.cpp | 10 | ||||
| -rw-r--r-- | tools/gfx/persistent-shader-cache.h | 6 | ||||
| -rw-r--r-- | tools/gfx/renderer-shared.cpp | 6 |
17 files changed, 556 insertions, 52 deletions
diff --git a/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj b/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj index 588cad8bd..af9ec94a0 100644 --- a/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj +++ b/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj @@ -315,6 +315,9 @@ <None Include="..\..\..\tools\gfx-unit-test\resolve-resource-shader.slang" />
<None Include="..\..\..\tools\gfx-unit-test\root-shader-parameter.slang" />
<None Include="..\..\..\tools\gfx-unit-test\sampler-array.slang" />
+ <None Include="..\..\..\tools\gfx-unit-test\shader-cache-graphics.slang" />
+ <None Include="..\..\..\tools\gfx-unit-test\split-graphics-fragment.slang" />
+ <None Include="..\..\..\tools\gfx-unit-test\split-graphics-vertex.slang" />
<None Include="..\..\..\tools\gfx-unit-test\swapchain-shader.slang" />
<None Include="..\..\..\tools\gfx-unit-test\trivial-copy-textures.slang" />
<None Include="..\..\..\tools\gfx-unit-test\trivial-copy.slang" />
diff --git a/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters b/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters index cb19b733d..00fd1de16 100644 --- a/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters +++ b/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters @@ -142,6 +142,15 @@ <None Include="..\..\..\tools\gfx-unit-test\sampler-array.slang">
<Filter>Source Files</Filter>
</None>
+ <None Include="..\..\..\tools\gfx-unit-test\shader-cache-graphics.slang">
+ <Filter>Source Files</Filter>
+ </None>
+ <None Include="..\..\..\tools\gfx-unit-test\split-graphics-fragment.slang">
+ <Filter>Source Files</Filter>
+ </None>
+ <None Include="..\..\..\tools\gfx-unit-test\split-graphics-vertex.slang">
+ <Filter>Source Files</Filter>
+ </None>
<None Include="..\..\..\tools\gfx-unit-test\swapchain-shader.slang">
<Filter>Source Files</Filter>
</None>
@@ -4457,20 +4457,18 @@ namespace slang SlangInt targetIndex, Digest* outHash) = 0; - /** Compute the hash code of this component type's AST. This hash effectively represents - the contents of the code covered by this component type, making its use ideal when we need - to confirm whether shader code changes have occurred. For example, a shader cache needs to be - able to check when a cache entry contains out-of-date code, which can be easily detected by - comparing the AST-based hashes since any change to the shader's code will be reflected in the - AST, and subsequently, the AST-based hash. - - Not all component types will store an AST, and consequently, not all component types will have a - meaningful implementation for this function. + /** Compute the hash code of this component type's contents as indicated by the file dependencies. + This hash is ideal when we need to confirm whether shader code changes have occurred. For example, + a shader cache needs to be able to check when a cache entry contains out-of-date code, which can be + easily detected by comparing the contents-based hashes since they will directly reflect any change + to the shader's code. This function should only have a meaningful implementation in ComponentType. All other types derived - from ComponentType that also inherit from IComponentType should do nothing. + from ComponentType that also inherit from IComponentType should do nothing. However, the only component + type that should ever be hashing its contents is Module as it represents all the code in a given + translation unit. */ - virtual SLANG_NO_THROW void SLANG_MCALL computeASTBasedHash(Digest* outHash) = 0; + virtual SLANG_NO_THROW void SLANG_MCALL computeContentsBasedHash(Digest* outHash) = 0; /** Specialize the component by binding its specialization parameters to concrete arguments. diff --git a/source/slang/slang-ast-support-types.h b/source/slang/slang-ast-support-types.h index 61580ca9e..4c92810d9 100644 --- a/source/slang/slang-ast-support-types.h +++ b/source/slang/slang-ast-support-types.h @@ -1424,7 +1424,7 @@ namespace Slang Val* m_val = nullptr; }; - typedef Dictionary<Decl*, RequirementWitness> RequirementDictionary; + typedef OrderedDictionary<Decl*, RequirementWitness> RequirementDictionary; struct WitnessTable : SerialRefObject { diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index af1173051..821f785f5 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -274,7 +274,7 @@ namespace Slang }; /// Mapping from types to subtype witnesses for conformance to IDifferentiable. - Dictionary<DeclRefTypeKey, SubtypeWitness*> m_mapTypeToIDifferentiableWitness; + OrderedDictionary<DeclRefTypeKey, SubtypeWitness*> m_mapTypeToIDifferentiableWitness; /// List of external dictionaries (from imported modules) List<DeclRef<DifferentiableTypeDictionary>> m_importedDictionaries; diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index bd7cd4a18..eb6d6c1a5 100755 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -298,7 +298,7 @@ namespace Slang SlangInt entryPointIndex, SlangInt targetIndex, slang::Digest* outHash) SLANG_OVERRIDE; - SLANG_NO_THROW void SLANG_MCALL computeASTBasedHash(slang::Digest* outHash) SLANG_OVERRIDE; + SLANG_NO_THROW void SLANG_MCALL computeContentsBasedHash(slang::Digest* outHash) SLANG_OVERRIDE; /// Get the linkage (aka "session" in the public API) for this component type. Linkage* getLinkage() { return m_linkage; } @@ -313,10 +313,10 @@ namespace Slang DigestBuilder& hashBuilder, SlangInt entryPointIndex) = 0; - /// Update the hash builder with the AST contents for this component type. - /// The AST is associated with a Module component, so most derived ComponentType classes - /// will simply do nothing with this. - virtual void updateASTBasedHash(DigestBuilder& hashBuilder) = 0; + /// Update the hash builder with the source contents for this component type. + /// Module should be the only derived ComponentType class which has a meaningful + /// implementation; all others should do nothing. + virtual void updateContentsBasedHash(DigestBuilder& hashBuilder) = 0; /// Get the number of entry points linked into this component type. virtual Index getEntryPointCount() = 0; @@ -519,7 +519,7 @@ namespace Slang DigestBuilder& hashBuilder, SlangInt entryPointIndex) override; - virtual void updateASTBasedHash(DigestBuilder& hashBuilder) override; + virtual void updateContentsBasedHash(DigestBuilder& hashBuilder) override; List<RefPtr<ComponentType>> const& getChildComponents() { return m_childComponents; }; Index getChildComponentCount() { return m_childComponents.getCount(); } @@ -601,7 +601,7 @@ namespace Slang DigestBuilder& hashBuilder, SlangInt entryPointIndex) override; - virtual void updateASTBasedHash(DigestBuilder& hashBuilder) override + virtual void updateContentsBasedHash(DigestBuilder& hashBuilder) override { SLANG_UNUSED(hashBuilder); } @@ -794,7 +794,7 @@ namespace Slang DigestBuilder& hashBuilder, SlangInt entryPointIndex) override; - virtual void updateASTBasedHash(DigestBuilder& hashBuilder) override + virtual void updateContentsBasedHash(DigestBuilder& hashBuilder) override { SLANG_UNUSED(hashBuilder); } @@ -899,16 +899,16 @@ namespace Slang return Super::computeDependencyBasedHash(entryPointIndex, targetIndex, outHash); } - SLANG_NO_THROW void SLANG_MCALL computeASTBasedHash(slang::Digest* outHash) SLANG_OVERRIDE + SLANG_NO_THROW void SLANG_MCALL computeContentsBasedHash(slang::Digest* outHash) SLANG_OVERRIDE { - return Super::computeASTBasedHash(outHash); + return Super::computeContentsBasedHash(outHash); } virtual void updateDependencyBasedHash( DigestBuilder& hashBuilder, SlangInt entryPointIndex) override; - virtual void updateASTBasedHash(DigestBuilder& hashBuilder) override + virtual void updateContentsBasedHash(DigestBuilder& hashBuilder) override { SLANG_UNUSED(hashBuilder); } @@ -1126,16 +1126,16 @@ namespace Slang return Super::computeDependencyBasedHash(entryPointIndex, targetIndex, outHash); } - SLANG_NO_THROW void SLANG_MCALL computeASTBasedHash(slang::Digest* outHash) SLANG_OVERRIDE + SLANG_NO_THROW void SLANG_MCALL computeContentsBasedHash(slang::Digest* outHash) SLANG_OVERRIDE { - return Super::computeASTBasedHash(outHash); + return Super::computeContentsBasedHash(outHash); } virtual void updateDependencyBasedHash( DigestBuilder& hashBuilder, SlangInt entryPointIndex) override; - virtual void updateASTBasedHash(DigestBuilder& hashBuilder) override + virtual void updateContentsBasedHash(DigestBuilder& hashBuilder) override { SLANG_UNUSED(hashBuilder); } @@ -1322,16 +1322,16 @@ namespace Slang return Super::computeDependencyBasedHash(entryPointIndex, targetIndex, outHash); } - SLANG_NO_THROW void SLANG_MCALL computeASTBasedHash(slang::Digest* outHash) SLANG_OVERRIDE + SLANG_NO_THROW void SLANG_MCALL computeContentsBasedHash(slang::Digest* outHash) SLANG_OVERRIDE { - return Super::computeASTBasedHash(outHash); + return Super::computeContentsBasedHash(outHash); } virtual void updateDependencyBasedHash( DigestBuilder& hashBuilder, SlangInt entryPointIndex) override; - virtual void updateASTBasedHash(DigestBuilder& hashBuilder) override; + virtual void updateContentsBasedHash(DigestBuilder& hashBuilder) override; /// Create a module (initially empty). Module(Linkage* linkage, ASTBuilder* astBuilder = nullptr); @@ -1366,6 +1366,14 @@ namespace Slang /// void setIRModule(IRModule* irModule) { m_irModule = irModule; } + DigestBuilder& getContentsDigestBuilder() { return contentsBuilder; } + + /// Set the contents digest for this module. + void setContentsDigest(slang::Digest digest) { contentsDigest = digest; } + + /// Get the contents digest for this module. + slang::Digest getContentsDigest() { return contentsDigest; } + Index getEntryPointCount() SLANG_OVERRIDE { return 0; } RefPtr<EntryPoint> getEntryPoint(Index index) SLANG_OVERRIDE { SLANG_UNUSED(index); return nullptr; } String getEntryPointMangledName(Index index) SLANG_OVERRIDE { SLANG_UNUSED(index); return String(); } @@ -1474,6 +1482,9 @@ namespace Slang // and m_mangledExportSymbols holds the NodeBase* values for each index. StringSlicePool m_mangledExportPool; List<NodeBase*> m_mangledExportSymbols; + + DigestBuilder contentsBuilder; + slang::Digest contentsDigest; }; typedef Module LoadedModule; diff --git a/source/slang/slang-preprocessor.cpp b/source/slang/slang-preprocessor.cpp index fca8f5029..c977e44ab 100644 --- a/source/slang/slang-preprocessor.cpp +++ b/source/slang/slang-preprocessor.cpp @@ -32,9 +32,10 @@ void PreprocessorHandler::handleEndOfTranslationUnit(Preprocessor* preprocessor) SLANG_UNUSED(preprocessor); } -void PreprocessorHandler::handleFileDependency(String const& path) +void PreprocessorHandler::handleFileDependency(String const& path, ISlangBlob* contents) { SLANG_UNUSED(path); + SLANG_UNUSED(contents); } // In order to simplify the naming scheme, we will nest the implementaiton of the @@ -2972,7 +2973,7 @@ static SlangResult readFile( // if( auto handler = context->m_preprocessor->handler ) { - handler->handleFileDependency(path); + handler->handleFileDependency(path, *outBlob); } return SLANG_OK; diff --git a/source/slang/slang-preprocessor.h b/source/slang/slang-preprocessor.h index 4d7721d31..90e03e964 100644 --- a/source/slang/slang-preprocessor.h +++ b/source/slang/slang-preprocessor.h @@ -28,7 +28,7 @@ using preprocessor::Preprocessor; struct PreprocessorHandler { virtual void handleEndOfTranslationUnit(Preprocessor* preprocessor); - virtual void handleFileDependency(String const& path); + virtual void handleFileDependency(String const& path, ISlangBlob* contents = nullptr); }; /// Description of a preprocessor options/dependencies diff --git a/source/slang/slang-serialize-type-info.h b/source/slang/slang-serialize-type-info.h index 6258a129d..b6cea58f5 100644 --- a/source/slang/slang-serialize-type-info.h +++ b/source/slang/slang-serialize-type-info.h @@ -326,6 +326,76 @@ struct SerialTypeInfo<Dictionary<KEY, VALUE>> } }; +// OrderedDictionary +template <typename KEY, typename VALUE> +struct SerialTypeInfo<OrderedDictionary<KEY, VALUE>> +{ + typedef OrderedDictionary<KEY, VALUE> NativeType; + struct SerialType + { + SerialIndex keys; ///< Index an array + SerialIndex values; ///< Index an array + }; + + typedef typename SerialTypeInfo<KEY>::SerialType KeySerialType; + typedef typename SerialTypeInfo<VALUE>::SerialType ValueSerialType; + + enum { SerialAlignment = SLANG_ALIGN_OF(SerialIndex) }; + + static void toSerial(SerialWriter* writer, const void* native, void* serial) + { + auto& src = *(const NativeType*)native; + auto& dst = *(SerialType*)serial; + + List<KeySerialType> keys; + List<ValueSerialType> values; + + Index count = Index(src.Count()); + keys.setCount(count); + values.setCount(count); + + if (writer->getFlags() & SerialWriter::Flag::ZeroInitialize) + { + ::memset(keys.getBuffer(), 0, count * sizeof(KeySerialType)); + ::memset(values.getBuffer(), 0, count * sizeof(ValueSerialType)); + } + + Index i = 0; + for (const auto& pair : src) + { + SerialTypeInfo<KEY>::toSerial(writer, &pair.Key, &keys[i]); + SerialTypeInfo<VALUE>::toSerial(writer, &pair.Value, &values[i]); + i++; + } + + // When we add the array it is already converted to a serializable type, so add as SerialArray + dst.keys = writer->addSerialArray<KEY>(keys.getBuffer(), count); + dst.values = writer->addSerialArray<VALUE>(values.getBuffer(), count); + } + static void toNative(SerialReader* reader, const void* serial, void* native) + { + auto& src = *(const SerialType*)serial; + auto& dst = *(NativeType*)native; + + // Clear it + dst = NativeType(); + + List<KEY> keys; + List<VALUE> values; + + reader->getArray(src.keys, keys); + reader->getArray(src.values, values); + + SLANG_ASSERT(keys.getCount() == values.getCount()); + + const Index count = keys.getCount(); + for (Index i = 0; i < count; ++i) + { + dst.Add(keys[i], values[i]); + } + } +}; + // KeyValuePair template<typename KEY, typename VALUE> struct SerialTypeInfo<KeyValuePair<KEY, VALUE>> diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index c7351d6a8..361f1b193 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -1898,9 +1898,13 @@ protected: // by applications to decide when they need to "hot reload" // their shader code. // - void handleFileDependency(String const& path) SLANG_OVERRIDE + void handleFileDependency(String const& path, ISlangBlob* sourceBlob) SLANG_OVERRIDE { m_module->addFilePathDependency(path); + if (sourceBlob) + { + m_module->getContentsDigestBuilder().addToDigest(sourceBlob); + } } // The second task that this handler deals with is detecting @@ -3042,6 +3046,10 @@ RefPtr<Module> Linkage::loadModule( return nullptr; } + auto builder = module->getContentsDigestBuilder(); + builder.addToDigest(sourceBlob); + module->setContentsDigest(builder.finalize()); + return module; } @@ -3259,10 +3267,9 @@ void Module::updateDependencyBasedHash( SLANG_UNUSED(entryPointIndex); } -void Module::updateASTBasedHash(DigestBuilder& builder) +void Module::updateContentsBasedHash(DigestBuilder& builder) { - auto serializedAST = ASTSerialUtil::serializeAST(getModuleDecl()); - builder.addToDigest(serializedAST); + builder.addToDigest(getContentsDigest()); } void Module::addModuleDependency(Module* module) @@ -3491,16 +3498,18 @@ SLANG_NO_THROW void SLANG_MCALL ComponentType::computeDependencyBasedHash( // to the hash. auto entryPointName = getEntryPoint(entryPointIndex)->getName()->text; builder.addToDigest(entryPointName); + auto entryPointMangledName = getEntryPointMangledName(entryPointIndex); + builder.addToDigest(entryPointMangledName); auto entryPointNameOverride = getEntryPointNameOverride(entryPointIndex); builder.addToDigest(entryPointNameOverride); *outHash = builder.finalize(); } -SLANG_NO_THROW void SLANG_MCALL ComponentType::computeASTBasedHash(slang::Digest* outHash) +SLANG_NO_THROW void SLANG_MCALL ComponentType::computeContentsBasedHash(slang::Digest* outHash) { DigestBuilder builder; - updateASTBasedHash(builder); + updateContentsBasedHash(builder); *outHash = builder.finalize(); } @@ -3848,13 +3857,13 @@ void CompositeComponentType::updateDependencyBasedHash( } } -void CompositeComponentType::updateASTBasedHash(DigestBuilder& builder) +void CompositeComponentType::updateContentsBasedHash(DigestBuilder& builder) { auto componentCount = getChildComponentCount(); for (Index i = 0; i < componentCount; ++i) { - getChildComponent(i)->updateASTBasedHash(builder); + getChildComponent(i)->updateContentsBasedHash(builder); } } diff --git a/tools/gfx-unit-test/shader-cache-graphics.slang b/tools/gfx-unit-test/shader-cache-graphics.slang new file mode 100644 index 000000000..82d3b4e5f --- /dev/null +++ b/tools/gfx-unit-test/shader-cache-graphics.slang @@ -0,0 +1,53 @@ +// shader-cache-graphics.slang + +// Per-vertex attributes to be assembled from bound vertex buffers. +struct AssembledVertex +{ + float3 position : POSITION; +}; + +// Output of the vertex shader, and input to the fragment shader. +struct CoarseVertex +{ + float3 color; +}; + +// Output of the fragment shader +struct Fragment +{ + float4 color; +}; + +// Vertex Shader + +struct VertexStageOutput +{ + CoarseVertex coarseVertex : CoarseVertex; + float4 sv_position : SV_Position; +}; + +[shader("vertex")] +VertexStageOutput vertexMain( + AssembledVertex assembledVertex) +{ + VertexStageOutput output; + + float3 position = assembledVertex.position; + float3 color = float3(1.0, 0.0, 0.0); + + output.coarseVertex.color = color; + output.sv_position = float4(position, 1.0); + + return output; +} + +// Fragment Shader + +[shader("fragment")] +float4 fragmentMain( + CoarseVertex coarseVertex : CoarseVertex) : SV_Target +{ + float3 color = coarseVertex.color; + + return float4(color, 1.0); +} diff --git a/tools/gfx-unit-test/shader-cache-tests.cpp b/tools/gfx-unit-test/shader-cache-tests.cpp index 16de83a74..a9939d434 100644 --- a/tools/gfx-unit-test/shader-cache-tests.cpp +++ b/tools/gfx-unit-test/shader-cache-tests.cpp @@ -10,6 +10,8 @@ #include "source/core/slang-memory-file-system.h" #include "source/core/slang-file-system.h" +#include "gfx-test-texture-util.h" + using namespace gfx; using namespace Slang; @@ -676,6 +678,274 @@ namespace gfx_test } }; + // Same gist as the multiple entry point compute shader but with a graphics + // shader file containing a vertex and fragment shader + struct Vertex + { + float position[3]; + }; + + static const int kVertexCount = 3; + static const Vertex kVertexData[kVertexCount] = + { + { 0, 0, 0.5 }, + { 1, 0, 0.5 }, + { 0, 1, 0.5 }, + }; + + struct GraphicsShaderCache : BaseShaderCacheTest + { + const int kWidth = 256; + const int kHeight = 256; + const Format format = Format::R32G32B32A32_FLOAT; + + ComPtr<IShaderProgram> shaderProgram; + ComPtr<IRenderPassLayout> renderPass; + ComPtr<IFramebuffer> framebuffer; + + ComPtr<IBufferResource> vertexBuffer; + ComPtr<ITextureResource> colorBuffer; + + ComPtr<IBufferResource> createVertexBuffer(IDevice* device) + { + IBufferResource::Desc vertexBufferDesc; + vertexBufferDesc.type = IResource::Type::Buffer; + vertexBufferDesc.sizeInBytes = kVertexCount * sizeof(Vertex); + vertexBufferDesc.defaultState = ResourceState::VertexBuffer; + vertexBufferDesc.allowedStates = ResourceState::VertexBuffer; + ComPtr<IBufferResource> vertexBuffer = device->createBufferResource(vertexBufferDesc, &kVertexData[0]); + SLANG_CHECK_ABORT(vertexBuffer != nullptr); + return vertexBuffer; + } + + ComPtr<ITextureResource> createColorBuffer(IDevice* device) + { + gfx::ITextureResource::Desc colorBufferDesc; + colorBufferDesc.type = IResource::Type::Texture2D; + colorBufferDesc.size.width = kWidth; + colorBufferDesc.size.height = kHeight; + colorBufferDesc.size.depth = 1; + colorBufferDesc.numMipLevels = 1; + colorBufferDesc.format = format; + colorBufferDesc.defaultState = ResourceState::RenderTarget; + colorBufferDesc.allowedStates = { ResourceState::RenderTarget, ResourceState::CopySource }; + ComPtr<ITextureResource> colorBuffer = device->createTextureResource(colorBufferDesc, nullptr); + SLANG_CHECK_ABORT(colorBuffer != nullptr); + return colorBuffer; + } + + void createShaderProgram() + { + slang::ProgramLayout* slangReflection; + GFX_CHECK_CALL_ABORT(loadGraphicsProgram(device, shaderProgram, "shader-cache-graphics", "vertexMain", "fragmentMain", slangReflection)); + } + + void createRequiredResources() + { + VertexStreamDesc vertexStreams[] = { + { sizeof(Vertex), InputSlotClass::PerVertex, 0 }, + }; + + InputElementDesc inputElements[] = { + // Vertex buffer data + { "POSITION", 0, Format::R32G32B32_FLOAT, offsetof(Vertex, position), 0 }, + }; + IInputLayout::Desc inputLayoutDesc = {}; + inputLayoutDesc.inputElementCount = SLANG_COUNT_OF(inputElements); + inputLayoutDesc.inputElements = inputElements; + inputLayoutDesc.vertexStreamCount = SLANG_COUNT_OF(vertexStreams); + inputLayoutDesc.vertexStreams = vertexStreams; + auto inputLayout = device->createInputLayout(inputLayoutDesc); + SLANG_CHECK_ABORT(inputLayout != nullptr); + + vertexBuffer = createVertexBuffer(device); + colorBuffer = createColorBuffer(device); + + IFramebufferLayout::TargetLayout targetLayout; + targetLayout.format = format; + targetLayout.sampleCount = 1; + + IFramebufferLayout::Desc framebufferLayoutDesc; + framebufferLayoutDesc.renderTargetCount = 1; + framebufferLayoutDesc.renderTargets = &targetLayout; + ComPtr<gfx::IFramebufferLayout> framebufferLayout = device->createFramebufferLayout(framebufferLayoutDesc); + SLANG_CHECK_ABORT(framebufferLayout != nullptr); + + GraphicsPipelineStateDesc pipelineDesc = {}; + pipelineDesc.program = shaderProgram.get(); + pipelineDesc.inputLayout = inputLayout; + pipelineDesc.framebufferLayout = framebufferLayout; + pipelineDesc.depthStencil.depthTestEnable = false; + pipelineDesc.depthStencil.depthWriteEnable = false; + GFX_CHECK_CALL_ABORT( + device->createGraphicsPipelineState(pipelineDesc, pipelineState.writeRef())); + + IRenderPassLayout::Desc renderPassDesc = {}; + renderPassDesc.framebufferLayout = framebufferLayout; + renderPassDesc.renderTargetCount = 1; + IRenderPassLayout::TargetAccessDesc renderTargetAccess = {}; + renderTargetAccess.loadOp = IRenderPassLayout::TargetLoadOp::Clear; + renderTargetAccess.storeOp = IRenderPassLayout::TargetStoreOp::Store; + renderTargetAccess.initialState = ResourceState::RenderTarget; + renderTargetAccess.finalState = ResourceState::CopySource; + renderPassDesc.renderTargetAccess = &renderTargetAccess; + GFX_CHECK_CALL_ABORT(device->createRenderPassLayout(renderPassDesc, renderPass.writeRef())); + + gfx::IResourceView::Desc colorBufferViewDesc; + memset(&colorBufferViewDesc, 0, sizeof(colorBufferViewDesc)); + colorBufferViewDesc.format = format; + colorBufferViewDesc.renderTarget.shape = gfx::IResource::Type::Texture2D; + colorBufferViewDesc.type = gfx::IResourceView::Type::RenderTarget; + auto rtv = device->createTextureView(colorBuffer, colorBufferViewDesc); + + gfx::IFramebuffer::Desc framebufferDesc; + framebufferDesc.renderTargetCount = 1; + framebufferDesc.depthStencilView = nullptr; + framebufferDesc.renderTargetViews = rtv.readRef(); + framebufferDesc.layout = framebufferLayout; + GFX_CHECK_CALL_ABORT(device->createFramebuffer(framebufferDesc, framebuffer.writeRef())); + } + + void submitGPUWork() + { + 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->encodeRenderCommands(renderPass, framebuffer); + auto rootObject = encoder->bindPipeline(pipelineState); + + gfx::Viewport viewport = {}; + viewport.maxZ = 1.0f; + viewport.extentX = (float)kWidth; + viewport.extentY = (float)kHeight; + encoder->setViewportAndScissor(viewport); + + encoder->setVertexBuffer(0, vertexBuffer); + encoder->setPrimitiveTopology(PrimitiveTopology::TriangleList); + + encoder->draw(kVertexCount); + encoder->endEncoding(); + commandBuffer->close(); + queue->executeCommandBuffer(commandBuffer); + queue->waitOnHost(); + } + + void run() + { + generateNewDevice(); + createShaderProgram(); + createRequiredResources(); + submitGPUWork(); + + ComPtr<IShaderCacheStatistics> shaderCacheStats; + + device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); + SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 2); + SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); + SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); + } + }; + + // Same as GraphicsShaderCache, but instead of having a singular file containing both a vertex and fragment shader, we + // now have two separate shader files, one containing the vertex shader and the other the fragment with the same + // names, with the expectation that we should record cache misses for both fetches. + // + // This test is intended to guard against the case where vertex/fragment/geometry shaders are split across + // multiple files with the same entry point name in each file and are loaded as three separate ComponentType objects. + // In this case, the current method for cache key generation will hash in the exact same modules, file dependencies, + // entry point names, and entry point name overrides, resulting in the same dependency hash being returned for all three + // and consequently, the wrong shader code being provided when the shaders are being created. + // + // We do not actively test geometry shaders here, but it is simply an extension of this test and should be expected + // to behave similarly. + struct SplitGraphicsShader : GraphicsShaderCache + { + void createShaderProgram() + { + slang::ProgramLayout* slangReflection; + const char* moduleNames[] = { "split-graphics-vertex", "split-graphics-fragment" }; + GFX_CHECK_CALL_ABORT(loadSplitGraphicsProgram(device, shaderProgram, moduleNames, "main", "main", slangReflection)); + } + + Result loadSplitGraphicsProgram( + IDevice* device, + ComPtr<IShaderProgram>& outShaderProgram, + const char** shaderModuleNames, + const char* vertexEntryPointName, + const char* fragmentEntryPointName, + slang::ProgramLayout*& slangReflection) + { + ComPtr<slang::ISession> slangSession; + SLANG_RETURN_ON_FAIL(device->getSlangSession(slangSession.writeRef())); + + ComPtr<slang::IBlob> diagnosticsBlob; + slang::IModule* vertexModule = slangSession->loadModule(shaderModuleNames[0], diagnosticsBlob.writeRef()); + if (!vertexModule) + return SLANG_FAIL; + slang::IModule* fragmentModule = slangSession->loadModule(shaderModuleNames[1], diagnosticsBlob.writeRef()); + if (!fragmentModule) + return SLANG_FAIL; + + ComPtr<slang::IEntryPoint> vertexEntryPoint; + SLANG_RETURN_ON_FAIL( + vertexModule->findEntryPointByName(vertexEntryPointName, vertexEntryPoint.writeRef())); + + ComPtr<slang::IEntryPoint> fragmentEntryPoint; + SLANG_RETURN_ON_FAIL( + fragmentModule->findEntryPointByName(fragmentEntryPointName, fragmentEntryPoint.writeRef())); + + Slang::List<slang::IComponentType*> componentTypes; + componentTypes.add(vertexModule); + componentTypes.add(fragmentModule); + + Slang::ComPtr<slang::IComponentType> composedProgram; + SlangResult result = slangSession->createCompositeComponentType( + componentTypes.getBuffer(), + componentTypes.getCount(), + composedProgram.writeRef(), + diagnosticsBlob.writeRef()); + SLANG_RETURN_ON_FAIL(result); + slangReflection = composedProgram->getLayout(); + + Slang::List<slang::IComponentType*> entryPoints; + entryPoints.add(vertexEntryPoint); + entryPoints.add(fragmentEntryPoint); + + gfx::IShaderProgram::Desc programDesc = {}; + programDesc.slangGlobalScope = composedProgram.get(); + programDesc.linkingStyle = gfx::IShaderProgram::LinkingStyle::SeparateEntryPointCompilation; + programDesc.entryPointCount = 2; + programDesc.slangEntryPoints = entryPoints.getBuffer(); + + auto shaderProgram = device->createProgram(programDesc); + + outShaderProgram = shaderProgram; + return SLANG_OK; + } + + void run() + { + generateNewDevice(); + createShaderProgram(); + createRequiredResources(); + submitGPUWork(); + + ComPtr<IShaderCacheStatistics> shaderCacheStats; + + device->queryInterface(SLANG_UUID_IShaderCacheStatistics, (void**)shaderCacheStats.writeRef()); + SLANG_CHECK(shaderCacheStats->getCacheMissCount() == 2); + SLANG_CHECK(shaderCacheStats->getCacheHitCount() == 0); + SLANG_CHECK(shaderCacheStats->getCacheEntryDirtyCount() == 0); + } + }; + // Same as MultipleEntryShaderCache, but we now set the maximum entry count limit, so the cache // should remove entries as needed when it reaches capacity. // @@ -945,4 +1215,24 @@ namespace gfx_test { runTestImpl(shaderCacheTestImpl<CacheWithMaxEntryLimit>, unitTestContext, Slang::RenderApiFlag::Vulkan); } + + SLANG_UNIT_TEST(graphicsShaderCacheD3D12) + { + runTestImpl(shaderCacheTestImpl<GraphicsShaderCache>, unitTestContext, Slang::RenderApiFlag::D3D12); + } + + SLANG_UNIT_TEST(graphicsShaderCacheVulkan) + { + runTestImpl(shaderCacheTestImpl<GraphicsShaderCache>, unitTestContext, Slang::RenderApiFlag::Vulkan); + } + + SLANG_UNIT_TEST(splitGraphicsShaderCacheD3D12) + { + runTestImpl(shaderCacheTestImpl<SplitGraphicsShader>, unitTestContext, Slang::RenderApiFlag::D3D12); + } + + SLANG_UNIT_TEST(splitGraphicsShaderCacheVulkan) + { + runTestImpl(shaderCacheTestImpl<SplitGraphicsShader>, unitTestContext, Slang::RenderApiFlag::Vulkan); + } } diff --git a/tools/gfx-unit-test/split-graphics-fragment.slang b/tools/gfx-unit-test/split-graphics-fragment.slang new file mode 100644 index 000000000..db515a957 --- /dev/null +++ b/tools/gfx-unit-test/split-graphics-fragment.slang @@ -0,0 +1,24 @@ +// split-graphics-fragment.slang + +// Output of the vertex shader, and input to the fragment shader. +struct CoarseVertex +{ + float3 color; +}; + +// Output of the fragment shader +struct Fragment +{ + float4 color; +}; + +// Fragment Shader + +[shader("fragment")] +float4 main( + CoarseVertex coarseVertex : CoarseVertex) : SV_Target +{ + float3 color = coarseVertex.color; + + return float4(color, 1.0); +} diff --git a/tools/gfx-unit-test/split-graphics-vertex.slang b/tools/gfx-unit-test/split-graphics-vertex.slang new file mode 100644 index 000000000..615686a90 --- /dev/null +++ b/tools/gfx-unit-test/split-graphics-vertex.slang @@ -0,0 +1,36 @@ +// split-graphics-vertex.slang + +// Per-vertex attributes to be assembled from bound vertex buffers. +struct AssembledVertex +{ + float3 position : POSITION; +}; + +// Output of the vertex shader, and input to the fragment shader. +struct CoarseVertex +{ + float3 color; +}; + +// Vertex Shader + +struct VertexStageOutput +{ + CoarseVertex coarseVertex : CoarseVertex; + float4 sv_position : SV_Position; +}; + +[shader("vertex")] +VertexStageOutput main( + AssembledVertex assembledVertex) +{ + VertexStageOutput output; + + float3 position = assembledVertex.position; + float3 color = float3(1.0, 0.0, 0.0); + + output.coarseVertex.color = color; + output.sv_position = float4(position, 1.0); + + return output; +} diff --git a/tools/gfx/persistent-shader-cache.cpp b/tools/gfx/persistent-shader-cache.cpp index fdf91fbe8..c0fd3b44a 100644 --- a/tools/gfx/persistent-shader-cache.cpp +++ b/tools/gfx/persistent-shader-cache.cpp @@ -63,9 +63,9 @@ void PersistentShaderCache::loadCacheFromFile() if (digests.getCount() != 2) continue; auto dependencyDigest = DigestUtil::fromString(digests[0]); - auto astDigest = DigestUtil::fromString(digests[1]); + auto contentsDigest = DigestUtil::fromString(digests[1]); - ShaderCacheEntry entry = { dependencyDigest, astDigest }; + ShaderCacheEntry entry = { dependencyDigest, contentsDigest }; auto entryNode = entries.AddLast(entry); keyToEntry.Add(dependencyDigest, entryNode); @@ -133,7 +133,7 @@ void PersistentShaderCache::addEntry(const slang::Digest& dependencyDigest, cons void PersistentShaderCache::updateEntry( LinkedNode<ShaderCacheEntry>* entryNode, const slang::Digest& dependencyDigest, - const slang::Digest& astDigest, + const slang::Digest& contentsDigest, ISlangBlob* updatedCode) { if (!mutableShaderCacheFileSystem) @@ -143,7 +143,7 @@ void PersistentShaderCache::updateEntry( return; } - entryNode->Value.astBasedDigest = astDigest; + entryNode->Value.contentsBasedDigest = contentsDigest; mutableShaderCacheFileSystem->saveFileBlob(DigestUtil::toString(dependencyDigest).getBuffer(), updatedCode); saveCacheToFile(); @@ -162,7 +162,7 @@ void PersistentShaderCache::saveCacheToFile() { indexSb << entry.dependencyBasedDigest; indexSb << " "; - indexSb << entry.astBasedDigest; + indexSb << entry.contentsBasedDigest; indexSb << "\n"; } diff --git a/tools/gfx/persistent-shader-cache.h b/tools/gfx/persistent-shader-cache.h index 49a4a53dd..8d4ded4b9 100644 --- a/tools/gfx/persistent-shader-cache.h +++ b/tools/gfx/persistent-shader-cache.h @@ -16,7 +16,7 @@ namespace gfx struct ShaderCacheEntry { slang::Digest dependencyBasedDigest; - slang::Digest astBasedDigest; + slang::Digest contentsBasedDigest; }; class PersistentShaderCache : public RefObject @@ -32,14 +32,14 @@ public: // Add an entry to the cache with the provided key and contents hashes. If // adding an entry causes the cache to exceed size limitations, this will also // delete the least recently used entry. - void addEntry(const slang::Digest& dependencyDigest, const slang::Digest& astDigest, ISlangBlob* compiledCode); + void addEntry(const slang::Digest& dependencyDigest, const slang::Digest& contentsDigest, ISlangBlob* compiledCode); // Update the contents hash for the specified entry in the cache and update the // corresponding file on disk. void updateEntry( LinkedNode<ShaderCacheEntry>* entryNode, const slang::Digest& dependencyDigest, - const slang::Digest& astDigest, + const slang::Digest& contentsDigest, ISlangBlob* updatedCode); private: diff --git a/tools/gfx/renderer-shared.cpp b/tools/gfx/renderer-shared.cpp index c7d7933a3..65c625d5d 100644 --- a/tools/gfx/renderer-shared.cpp +++ b/tools/gfx/renderer-shared.cpp @@ -368,13 +368,13 @@ Result RendererBase::getEntryPointCodeFromShaderCache( // 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 contentsHash; - program->computeASTBasedHash(&contentsHash); + program->computeContentsBasedHash(&contentsHash); ComPtr<ISlangBlob> codeBlob; - // Query the shader cache index for an entry with shaderKey as its key. + // Query the shader cache index for an entry with shaderKey as its key. auto entry = persistentShaderCache->findEntry(shaderKey, codeBlob.writeRef()); - if (entry && contentsHash == entry->Value.astBasedDigest) + if (entry && contentsHash == entry->Value.contentsBasedDigest) { // We found the entry in the cache, and the entry's contents are up-to-date. Nothing else needs to be done. shaderCacheHitCount++; |
