From 71b801cb6812c3b234acf4fe8940c95612745337 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 21 Oct 2026 12:25:56 +0800 Subject: [PATCH] fix(webgpu): preserve shader entry points in render state cache archives Co-Authored-By: Codex --- .../include/ShaderWebGPUImpl.hpp | 3 +- .../interface/ShaderWebGPU.h | 3 + .../interface/GraphicsUtilities.h | 4 + .../GraphicsTools/src/GraphicsUtilities.cpp | 5 + .../src/GraphicsUtilitiesWebGPU.cpp | 9 ++ .../src/RenderStateCacheImpl.cpp | 1 + .../src/RenderStateCacheTest.cpp | 141 ++++++++++++++++++ 7 files changed, 165 insertions(+), 1 deletion(-) diff --git a/Graphics/GraphicsEngineWebGPU/include/ShaderWebGPUImpl.hpp b/Graphics/GraphicsEngineWebGPU/include/ShaderWebGPUImpl.hpp index c82cd77fbd..91a8c66c9f 100644 --- a/Graphics/GraphicsEngineWebGPU/include/ShaderWebGPUImpl.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/ShaderWebGPUImpl.hpp @@ -87,7 +87,8 @@ class ShaderWebGPUImpl final : public ShaderBase return m_pShaderResources->GetEmulatedArrayIndexSuffix(); } - const char* GetEntryPoint() const; + /// Implementation of IShaderWebGPU::GetEntryPoint(). + const char* DILIGENT_CALL_TYPE GetEntryPoint() const override final; const std::shared_ptr& GetShaderResources() const { diff --git a/Graphics/GraphicsEngineWebGPU/interface/ShaderWebGPU.h b/Graphics/GraphicsEngineWebGPU/interface/ShaderWebGPU.h index 3d3c9198ab..a9ac5ed898 100644 --- a/Graphics/GraphicsEngineWebGPU/interface/ShaderWebGPU.h +++ b/Graphics/GraphicsEngineWebGPU/interface/ShaderWebGPU.h @@ -63,6 +63,9 @@ class IShaderWebGPU : public IShader /// This value is defined by `ShaderCI.WebGPUEmulatedArrayIndexSuffix`, /// see Diligent::ShaderCreateInfo::WebGPUEmulatedArrayIndexSuffix. virtual const char* DILIGENT_CALL_TYPE GetEmulatedArrayIndexSuffix() const = 0; + + /// Returns the name of the selected entry point in the WGSL source code. + virtual const char* DILIGENT_CALL_TYPE GetEntryPoint() const = 0; }; #endif diff --git a/Graphics/GraphicsTools/interface/GraphicsUtilities.h b/Graphics/GraphicsTools/interface/GraphicsUtilities.h index c0398fa8cc..81130801ff 100644 --- a/Graphics/GraphicsTools/interface/GraphicsUtilities.h +++ b/Graphics/GraphicsTools/interface/GraphicsUtilities.h @@ -227,6 +227,10 @@ IBufferView* DILIGENT_GLOBAL_FUNCTION(GetBufferDefaultUAV)(IObject* pBuffer); /// For other shader types, returns null. const char* DILIGENT_GLOBAL_FUNCTION(GetWebGPUEmulatedArrayIndexSuffix)(IShader* pShader); +/// For WebGPU shaders, returns the name of the selected entry point in the WGSL source code. +/// For other shader types, returns null. +const char* DILIGENT_GLOBAL_FUNCTION(GetWebGPUShaderEntryPoint)(IShader* pShader); + /// Returns the native texture format (e.g. DXGI_FORMAT, VkFormat) for the given texture format and device type. int64_t DILIGENT_GLOBAL_FUNCTION(GetNativeTextureFormat)(TEXTURE_FORMAT TexFormat, enum RENDER_DEVICE_TYPE DeviceType); diff --git a/Graphics/GraphicsTools/src/GraphicsUtilities.cpp b/Graphics/GraphicsTools/src/GraphicsUtilities.cpp index 2114b07279..e3f0da5dff 100644 --- a/Graphics/GraphicsTools/src/GraphicsUtilities.cpp +++ b/Graphics/GraphicsTools/src/GraphicsUtilities.cpp @@ -578,6 +578,11 @@ const char* GetWebGPUEmulatedArrayIndexSuffix(IShader* pShader) { return nullptr; } + +const char* GetWebGPUShaderEntryPoint(IShader* pShader) +{ + return nullptr; +} #endif int64_t GetNativeTextureFormat(TEXTURE_FORMAT TexFormat, RENDER_DEVICE_TYPE DeviceType) diff --git a/Graphics/GraphicsTools/src/GraphicsUtilitiesWebGPU.cpp b/Graphics/GraphicsTools/src/GraphicsUtilitiesWebGPU.cpp index 47201a94ad..5748bd5abd 100644 --- a/Graphics/GraphicsTools/src/GraphicsUtilitiesWebGPU.cpp +++ b/Graphics/GraphicsTools/src/GraphicsUtilitiesWebGPU.cpp @@ -49,6 +49,15 @@ const char* GetWebGPUEmulatedArrayIndexSuffix(IShader* pShader) return nullptr; } +const char* GetWebGPUShaderEntryPoint(IShader* pShader) +{ + if (RefCntAutoPtr pShaderWGPU{pShader, IID_ShaderWebGPU}) + { + return pShaderWGPU->GetEntryPoint(); + } + return nullptr; +} + int64_t GetNativeTextureFormatWebGPU(TEXTURE_FORMAT TexFormat) { return static_cast(TextureFormatToWGPUFormat(TexFormat)); diff --git a/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp b/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp index abd42b858e..eda3846308 100644 --- a/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp +++ b/Graphics/GraphicsTools/src/RenderStateCacheImpl.cpp @@ -594,6 +594,7 @@ struct RenderStateCacheImpl::SerializedPsoCIWrapperBase ShaderCI.Source = static_cast(ShaderCI.ByteCode); ShaderCI.ByteCode = nullptr; ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCI.EntryPoint = GetWebGPUShaderEntryPoint(pShader); ShaderCI.WebGPUEmulatedArrayIndexSuffix = GetWebGPUEmulatedArrayIndexSuffix(pShader); } ShaderArchiveInfo ArchiveInfo; diff --git a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp index a96ca8ff9c..ed509a699f 100644 --- a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp +++ b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp @@ -847,6 +847,147 @@ void TestGraphicsPSO(bool UseRenderPass, bool CompileAsync = false) } } +void TestWebGPUEntryPoints(bool LoadShadersFromArchive, bool MultipleEntryPoints) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pDevice = pEnv->GetDevice(); + if (!pDevice->GetDeviceInfo().IsWebGPUDevice()) + GTEST_SKIP() << "WGSL entry points require WebGPU"; + if (pEnv->GetArchiverFactory() == nullptr) + GTEST_SKIP() << "Archiver library is not loaded"; + + GPUTestingEnvironment::ScopedReset AutoReset; + + const SHADER_TYPE ShaderTypes[] = {SHADER_TYPE_VERTEX, SHADER_TYPE_PIXEL, SHADER_TYPE_COMPUTE}; + const char* EntryPoints[] = {"vertexMain", "fragmentMain", "computeMain"}; + const char* Sources[] = { + "@vertex fn vertexMain() -> @builtin(position) vec4f { return vec4f(0.0, 0.0, 0.0, 1.0); }", + "@fragment fn fragmentMain() -> @location(0) vec4f { return vec4f(1.0); }", + "@compute @workgroup_size(1) fn computeMain() {}", + }; + const char* OtherEntryPoints[] = { + "@group(0) @binding(0) var unusedValue: vec4f;\n" + "@vertex fn unusedVertex() -> @builtin(position) vec4f { return unusedValue; }\n", + "@group(0) @binding(0) var unusedValue: vec4f;\n" + "@fragment fn unusedFragment() -> @location(0) vec4f { return unusedValue; }\n", + "@group(0) @binding(0) var unusedValue: array;\n" + "@compute @workgroup_size(1) fn unusedCompute() { unusedValue[0] = 1u; }\n", + }; + + std::string ShaderSources[_countof(ShaderTypes)]; + ShaderCreateInfo ShaderCIs[_countof(ShaderTypes)]; + for (Uint32 i = 0; i < _countof(ShaderTypes); ++i) + { + // Put another entry point of the same stage first, so inferring a single + // entry point or selecting the first one cannot replace the selected name. + ShaderSources[i] = MultipleEntryPoints ? OtherEntryPoints[i] : ""; + ShaderSources[i] += Sources[i]; + ShaderCIs[i].Desc = {EntryPoints[i], ShaderTypes[i]}; + ShaderCIs[i].SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCIs[i].Source = ShaderSources[i].c_str(); + ShaderCIs[i].EntryPoint = EntryPoints[i]; + } + + RefCntAutoPtr pData; + if (LoadShadersFromArchive) + { + // Save shaders without a PSO. Reloading them must exercise device-shader + // reserialization when the PSO is first added to the new cache. + auto pCache = CreateCache(pDevice, false); + ASSERT_NE(nullptr, pCache); + for (const auto& ShaderCI : ShaderCIs) + { + RefCntAutoPtr pShader; + EXPECT_FALSE(pCache->CreateShader(ShaderCI, &pShader)); + ASSERT_NE(nullptr, pShader); + ASSERT_EQ(SHADER_STATUS_READY, pShader->GetStatus()); + } + ASSERT_TRUE(pCache->WriteToBlob(ContentVersion, &pData)); + ASSERT_NE(nullptr, pData); + } + + for (Uint32 Pass = 0; Pass < 2; ++Pass) + { + SCOPED_TRACE(Pass); + auto pCache = CreateCache(pDevice, false, pData); + ASSERT_NE(nullptr, pCache); + + RefCntAutoPtr Shaders[_countof(ShaderTypes)]; + for (Uint32 i = 0; i < _countof(ShaderTypes); ++i) + { + if (LoadShadersFromArchive) + EXPECT_TRUE(pCache->CreateShader(ShaderCIs[i], &Shaders[i])); + else + pDevice->CreateShader(ShaderCIs[i], &Shaders[i]); + ASSERT_NE(nullptr, Shaders[i]); + ASSERT_EQ(SHADER_STATUS_READY, Shaders[i]->GetStatus()); + } + + GraphicsPipelineStateCreateInfo GraphicsCI; + GraphicsCI.PSODesc.Name = "RenderStateCache - WGSL Graphics"; + GraphicsCI.GraphicsPipeline.NumRenderTargets = 1; + GraphicsCI.GraphicsPipeline.RTVFormats[0] = pEnv->GetSwapChain()->GetDesc().ColorBufferFormat; + GraphicsCI.GraphicsPipeline.PrimitiveTopology = PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + GraphicsCI.GraphicsPipeline.RasterizerDesc.CullMode = CULL_MODE_NONE; + GraphicsCI.GraphicsPipeline.DepthStencilDesc.DepthEnable = False; + GraphicsCI.pVS = Shaders[0]; + GraphicsCI.pPS = Shaders[1]; + + RefCntAutoPtr pGraphicsPSO; + EXPECT_EQ(Pass != 0, pCache->CreateGraphicsPipelineState(GraphicsCI, &pGraphicsPSO)); + ASSERT_NE(nullptr, pGraphicsPSO); + ASSERT_EQ(PIPELINE_STATE_STATUS_READY, pGraphicsPSO->GetStatus()); + + ComputePipelineStateCreateInfo ComputeCI; + ComputeCI.PSODesc.Name = "RenderStateCache - WGSL Compute"; + ComputeCI.PSODesc.PipelineType = PIPELINE_TYPE_COMPUTE; + ComputeCI.pCS = Shaders[2]; + + RefCntAutoPtr pComputePSO; + EXPECT_EQ(Pass != 0, pCache->CreateComputePipelineState(ComputeCI, &pComputePSO)); + ASSERT_NE(nullptr, pComputePSO); + ASSERT_EQ(PIPELINE_STATE_STATUS_READY, pComputePSO->GetStatus()); + + // Only the unused entry points access resources. Selecting one of them + // while serializing would change the restored pipeline's resource layout. + for (IPipelineState* pPSO : {pGraphicsPSO.RawPtr(), pComputePSO.RawPtr()}) + { + for (Uint32 i = 0; i < pPSO->GetResourceSignatureCount(); ++i) + { + if (auto* pSignature = pPSO->GetResourceSignature(i)) + EXPECT_EQ(0u, pSignature->GetDesc().NumResources); + } + } + + // A ready device PSO alone is insufficient: serialization failures are + // caught by the cache. Require archive hits after destroying this cache. + RefCntAutoPtr pNextData; + ASSERT_TRUE(pCache->WriteToBlob(ContentVersion, &pNextData)); + ASSERT_NE(nullptr, pNextData); + pData = std::move(pNextData); + } +} + +TEST(RenderStateCacheTest, WebGPUEntryPoints_DeviceShaders) +{ + TestWebGPUEntryPoints(/*LoadShadersFromArchive = */ false, /*MultipleEntryPoints = */ false); +} + +TEST(RenderStateCacheTest, WebGPUEntryPoints_ArchivedShaders) +{ + TestWebGPUEntryPoints(/*LoadShadersFromArchive = */ true, /*MultipleEntryPoints = */ false); +} + +TEST(RenderStateCacheTest, WebGPUEntryPoints_MultipleDeviceShaders) +{ + TestWebGPUEntryPoints(/*LoadShadersFromArchive = */ false, /*MultipleEntryPoints = */ true); +} + +TEST(RenderStateCacheTest, WebGPUEntryPoints_MultipleArchivedShaders) +{ + TestWebGPUEntryPoints(/*LoadShadersFromArchive = */ true, /*MultipleEntryPoints = */ true); +} + TEST(RenderStateCacheTest, CreateGraphicsPSO) { TestGraphicsPSO(/*UseRenderPass = */ false);