summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj3
-rw-r--r--build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters9
-rw-r--r--slang.h20
-rw-r--r--source/slang/slang-ast-support-types.h2
-rw-r--r--source/slang/slang-check-impl.h2
-rwxr-xr-xsource/slang/slang-compiler.h45
-rw-r--r--source/slang/slang-preprocessor.cpp5
-rw-r--r--source/slang/slang-preprocessor.h2
-rw-r--r--source/slang/slang-serialize-type-info.h70
-rw-r--r--source/slang/slang.cpp25
-rw-r--r--tools/gfx-unit-test/shader-cache-graphics.slang53
-rw-r--r--tools/gfx-unit-test/shader-cache-tests.cpp290
-rw-r--r--tools/gfx-unit-test/split-graphics-fragment.slang24
-rw-r--r--tools/gfx-unit-test/split-graphics-vertex.slang36
-rw-r--r--tools/gfx/persistent-shader-cache.cpp10
-rw-r--r--tools/gfx/persistent-shader-cache.h6
-rw-r--r--tools/gfx/renderer-shared.cpp6
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>
diff --git a/slang.h b/slang.h
index 8a0813f7b..9c85a2f74 100644
--- a/slang.h
+++ b/slang.h
@@ -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++;