From 73615f425cfb9ff87e5415ea49818695c432d26c Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sun, 22 Feb 2026 20:07:41 +0800 Subject: [PATCH 01/28] DeviceFeatures: add SpecializationConstants field --- .../GraphicsEngine/interface/GraphicsTypes.h | 18 +++++++++++++++--- .../GraphicsEngine/src/RenderDeviceBase.cpp | 5 +++-- .../src/EngineFactoryD3D11.cpp | 4 ++-- .../src/EngineFactoryD3D12.cpp | 4 ++-- .../src/RenderDeviceGLImpl.cpp | 2 +- .../src/EngineFactoryVk.cpp | 2 +- .../src/VulkanTypeConversions.cpp | 5 +++-- .../src/EngineFactoryWebGPU.cpp | 5 +++-- 8 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Graphics/GraphicsEngine/interface/GraphicsTypes.h b/Graphics/GraphicsEngine/interface/GraphicsTypes.h index 4ef5a1e49c..305c8cfacf 100644 --- a/Graphics/GraphicsEngine/interface/GraphicsTypes.h +++ b/Graphics/GraphicsEngine/interface/GraphicsTypes.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -1856,6 +1856,17 @@ struct DeviceFeatures /// Indicates if device supports formatted buffers. DEVICE_FEATURE_STATE FormattedBuffers DEFAULT_INITIALIZER(DEVICE_FEATURE_STATE_DISABLED); + /// Indicates if device supports pipeline specialization constants. + + /// When this feature is enabled, applications can provide specialization + /// constants when creating a pipeline state to set constant values at + /// pipeline creation time. The driver may use these values to optimize + /// the compiled shader code. + /// + /// Supported by Vulkan and WebGPU backends. + /// Not supported by D3D11, D3D12, OpenGL, or Metal backends. + DEVICE_FEATURE_STATE SpecializationConstants DEFAULT_INITIALIZER(DEVICE_FEATURE_STATE_DISABLED); + #if DILIGENT_CPP_INTERFACE constexpr DeviceFeatures() noexcept {} @@ -1906,11 +1917,12 @@ struct DeviceFeatures Handler(TextureSubresourceViews) \ Handler(NativeMultiDraw) \ Handler(AsyncShaderCompilation) \ - Handler(FormattedBuffers) + Handler(FormattedBuffers) \ + Handler(SpecializationConstants) explicit constexpr DeviceFeatures(DEVICE_FEATURE_STATE State) noexcept { - static_assert(sizeof(*this) == 47, "Did you add a new feature to DeviceFeatures? Please add it to ENUMERATE_DEVICE_FEATURES."); + static_assert(sizeof(*this) == 48, "Did you add a new feature to DeviceFeatures? Please add it to ENUMERATE_DEVICE_FEATURES."); #define INIT_FEATURE(Feature) Feature = State; ENUMERATE_DEVICE_FEATURES(INIT_FEATURE) #undef INIT_FEATURE diff --git a/Graphics/GraphicsEngine/src/RenderDeviceBase.cpp b/Graphics/GraphicsEngine/src/RenderDeviceBase.cpp index 4839b901a8..4e471599cd 100644 --- a/Graphics/GraphicsEngine/src/RenderDeviceBase.cpp +++ b/Graphics/GraphicsEngine/src/RenderDeviceBase.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -120,9 +120,10 @@ DeviceFeatures EnableDeviceFeatures(const DeviceFeatures& SupportedFeatures, ENABLE_FEATURE(NativeMultiDraw, "Native multi-draw commands are"); ENABLE_FEATURE(AsyncShaderCompilation, "Async shader compilation is"); ENABLE_FEATURE(FormattedBuffers, "Formatted buffers are"); + ENABLE_FEATURE(SpecializationConstants, "Specialization constants are"); // clang-format on - ASSERT_SIZEOF(DeviceFeatures, 47, "Did you add a new feature to DeviceFeatures? Please handle its status here (if necessary)."); + ASSERT_SIZEOF(DeviceFeatures, 48, "Did you add a new feature to DeviceFeatures? Please handle its status here (if necessary)."); return EnabledFeatures; } diff --git a/Graphics/GraphicsEngineD3D11/src/EngineFactoryD3D11.cpp b/Graphics/GraphicsEngineD3D11/src/EngineFactoryD3D11.cpp index 567a444333..5c7fe7e91b 100644 --- a/Graphics/GraphicsEngineD3D11/src/EngineFactoryD3D11.cpp +++ b/Graphics/GraphicsEngineD3D11/src/EngineFactoryD3D11.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -490,7 +490,7 @@ GraphicsAdapterInfo EngineFactoryD3D11Impl::GetGraphicsAdapterInfo(void* Features.ShaderFloat16 = ShaderFloat16Supported ? DEVICE_FEATURE_STATE_ENABLED : DEVICE_FEATURE_STATE_DISABLED; } - ASSERT_SIZEOF(Features, 47, "Did you add a new feature to DeviceFeatures? Please handle its status here."); + ASSERT_SIZEOF(Features, 48, "Did you add a new feature to DeviceFeatures? Please handle its status here."); // Texture properties { diff --git a/Graphics/GraphicsEngineD3D12/src/EngineFactoryD3D12.cpp b/Graphics/GraphicsEngineD3D12/src/EngineFactoryD3D12.cpp index e159bcfc8b..f96be040c3 100644 --- a/Graphics/GraphicsEngineD3D12/src/EngineFactoryD3D12.cpp +++ b/Graphics/GraphicsEngineD3D12/src/EngineFactoryD3D12.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -1106,7 +1106,7 @@ GraphicsAdapterInfo EngineFactoryD3D12Impl::GetGraphicsAdapterInfo(void* ASSERT_SIZEOF(DrawCommandProps, 12, "Did you add a new member to DrawCommandProperties? Please initialize it here."); } - ASSERT_SIZEOF(DeviceFeatures, 47, "Did you add a new feature to DeviceFeatures? Please handle its status here."); + ASSERT_SIZEOF(DeviceFeatures, 48, "Did you add a new feature to DeviceFeatures? Please handle its status here."); return AdapterInfo; } diff --git a/Graphics/GraphicsEngineOpenGL/src/RenderDeviceGLImpl.cpp b/Graphics/GraphicsEngineOpenGL/src/RenderDeviceGLImpl.cpp index a240ab20e9..550d72437e 100644 --- a/Graphics/GraphicsEngineOpenGL/src/RenderDeviceGLImpl.cpp +++ b/Graphics/GraphicsEngineOpenGL/src/RenderDeviceGLImpl.cpp @@ -1131,7 +1131,7 @@ void RenderDeviceGLImpl::InitAdapterInfo() m_AdapterInfo.Queues[0].TextureCopyGranularity[2] = 1; } - ASSERT_SIZEOF(DeviceFeatures, 47, "Did you add a new feature to DeviceFeatures? Please handle its status here."); + ASSERT_SIZEOF(DeviceFeatures, 48, "Did you add a new feature to DeviceFeatures? Please handle its status here."); } void RenderDeviceGLImpl::FlagSupportedTexFormats() diff --git a/Graphics/GraphicsEngineVulkan/src/EngineFactoryVk.cpp b/Graphics/GraphicsEngineVulkan/src/EngineFactoryVk.cpp index d05ff9c536..bcc6245b5d 100644 --- a/Graphics/GraphicsEngineVulkan/src/EngineFactoryVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/EngineFactoryVk.cpp @@ -1284,7 +1284,7 @@ void EngineFactoryVkImpl::CreateDeviceAndContextsVk(const EngineVkCreateInfo& En LOG_ERROR_MESSAGE("Can not enable extended device features when VK_KHR_get_physical_device_properties2 extension is not supported by device"); } - ASSERT_SIZEOF(DeviceFeatures, 47, "Did you add a new feature to DeviceFeatures? Please handle its status here."); + ASSERT_SIZEOF(DeviceFeatures, 48, "Did you add a new feature to DeviceFeatures? Please handle its status here."); for (Uint32 i = 0; i < EngineCI.DeviceExtensionCount; ++i) { diff --git a/Graphics/GraphicsEngineVulkan/src/VulkanTypeConversions.cpp b/Graphics/GraphicsEngineVulkan/src/VulkanTypeConversions.cpp index ca9a27c873..1b08e0c970 100644 --- a/Graphics/GraphicsEngineVulkan/src/VulkanTypeConversions.cpp +++ b/Graphics/GraphicsEngineVulkan/src/VulkanTypeConversions.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -1968,6 +1968,7 @@ DeviceFeatures VkFeaturesToDeviceFeatures(uint32_t Features.TextureSubresourceViews = DEVICE_FEATURE_STATE_ENABLED; Features.AsyncShaderCompilation = DEVICE_FEATURE_STATE_ENABLED; Features.FormattedBuffers = DEVICE_FEATURE_STATE_ENABLED; + Features.SpecializationConstants = DEVICE_FEATURE_STATE_DISABLED; // Timestamps are not a feature and can't be disabled. They are either supported by the device, or not. Features.TimestampQueries = vkDeviceProps.limits.timestampComputeAndGraphics ? DEVICE_FEATURE_STATE_ENABLED : DEVICE_FEATURE_STATE_DISABLED; @@ -2061,7 +2062,7 @@ DeviceFeatures VkFeaturesToDeviceFeatures(uint32_t #undef INIT_FEATURE - ASSERT_SIZEOF(DeviceFeatures, 47, "Did you add a new feature to DeviceFeatures? Please handle its status here (if necessary)."); + ASSERT_SIZEOF(DeviceFeatures, 48, "Did you add a new feature to DeviceFeatures? Please handle its status here (if necessary)."); return Features; } diff --git a/Graphics/GraphicsEngineWebGPU/src/EngineFactoryWebGPU.cpp b/Graphics/GraphicsEngineWebGPU/src/EngineFactoryWebGPU.cpp index 1a584de3df..e048d094d9 100644 --- a/Graphics/GraphicsEngineWebGPU/src/EngineFactoryWebGPU.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/EngineFactoryWebGPU.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2023-2025 Diligent Graphics LLC + * Copyright 2023-2026 Diligent Graphics LLC * * You may not use this file except in compliance with the License (see License.txt). * @@ -403,13 +403,14 @@ DeviceFeatures GetSupportedFeatures(WGPUAdapter wgpuAdapter, WGPUDevice wgpuDevi Features.NativeMultiDraw = DEVICE_FEATURE_STATE_DISABLED; Features.AsyncShaderCompilation = DEVICE_FEATURE_STATE_ENABLED; Features.FormattedBuffers = DEVICE_FEATURE_STATE_DISABLED; + Features.SpecializationConstants = DEVICE_FEATURE_STATE_DISABLED; Features.TimestampQueries = CheckFeature(WGPUFeatureName_TimestampQuery); Features.DurationQueries = Features.TimestampQueries ? CheckFeature(WGPUFeatureName_ChromiumExperimentalTimestampQueryInsidePasses) : DEVICE_FEATURE_STATE_DISABLED; - ASSERT_SIZEOF(DeviceFeatures, 47, "Did you add a new feature to DeviceFeatures? Please handle its status here."); + ASSERT_SIZEOF(DeviceFeatures, 48, "Did you add a new feature to DeviceFeatures? Please handle its status here."); return Features; } From aa28c630d300dec33974af47f50bd7eceefc61ff Mon Sep 17 00:00:00 2001 From: assiduous Date: Mon, 23 Feb 2026 17:56:16 -0800 Subject: [PATCH 02/28] Disable SpecializationConstants feature on D3D11/D3D12 and GL --- .../GraphicsEngineD3DBase/include/EngineFactoryD3DBase.hpp | 3 ++- Graphics/GraphicsEngineOpenGL/src/RenderDeviceGLImpl.cpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Graphics/GraphicsEngineD3DBase/include/EngineFactoryD3DBase.hpp b/Graphics/GraphicsEngineD3DBase/include/EngineFactoryD3DBase.hpp index cf8dad758d..48b692c72b 100644 --- a/Graphics/GraphicsEngineD3DBase/include/EngineFactoryD3DBase.hpp +++ b/Graphics/GraphicsEngineD3DBase/include/EngineFactoryD3DBase.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -244,6 +244,7 @@ class EngineFactoryD3DBase : public EngineFactoryBase Features.NativeMultiDraw = DEVICE_FEATURE_STATE_DISABLED; Features.AsyncShaderCompilation = DEVICE_FEATURE_STATE_ENABLED; Features.FormattedBuffers = DEVICE_FEATURE_STATE_ENABLED; + Features.SpecializationConstants = DEVICE_FEATURE_STATE_DISABLED; } // Set memory properties diff --git a/Graphics/GraphicsEngineOpenGL/src/RenderDeviceGLImpl.cpp b/Graphics/GraphicsEngineOpenGL/src/RenderDeviceGLImpl.cpp index 550d72437e..298afaf04f 100644 --- a/Graphics/GraphicsEngineOpenGL/src/RenderDeviceGLImpl.cpp +++ b/Graphics/GraphicsEngineOpenGL/src/RenderDeviceGLImpl.cpp @@ -798,6 +798,7 @@ void RenderDeviceGLImpl::InitAdapterInfo() Features.TileShaders = DEVICE_FEATURE_STATE_DISABLED; Features.SubpassFramebufferFetch = DEVICE_FEATURE_STATE_DISABLED; Features.TextureComponentSwizzle = DEVICE_FEATURE_STATE_DISABLED; + Features.SpecializationConstants = DEVICE_FEATURE_STATE_DISABLED; { bool WireframeFillSupported = (glPolygonMode != nullptr); From 1d34246cf9061cbad996504f8d9a5891e04a265e Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sun, 22 Feb 2026 20:07:41 +0800 Subject: [PATCH 03/28] PipelineStateCreateInfo: add specialization constants --- Common/interface/HashUtils.hpp | 14 +- Graphics/GraphicsEngine.NET/Mapping.xml | 1 + .../GraphicsEngine/interface/PipelineState.h | 120 +++++++++++++++++- Graphics/GraphicsEngine/src/PSOSerializer.cpp | 28 +++- .../GraphicsEngine/src/PipelineStateBase.cpp | 62 +++++++++ .../src/Common/HashUtilsTest.cpp | 62 +++++++++ .../src/GraphicsEngine/PSOSerializerTest.cpp | 26 +++- 7 files changed, 301 insertions(+), 12 deletions(-) diff --git a/Common/interface/HashUtils.hpp b/Common/interface/HashUtils.hpp index edbb8898dc..f165bf7418 100644 --- a/Common/interface/HashUtils.hpp +++ b/Common/interface/HashUtils.hpp @@ -1176,7 +1176,8 @@ struct HashCombiner : HashCombinerBasem_Hasher( CI.PSODesc, CI.Flags, - CI.ResourceSignaturesCount); + CI.ResourceSignaturesCount, + CI.NumSpecializationConstants); if (CI.ppResourceSignatures != nullptr) { for (size_t i = 0; i < CI.ResourceSignaturesCount; ++i) @@ -1191,6 +1192,17 @@ struct HashCombiner : HashCombinerBasem_Hasher(SC.Name, SC.ShaderStages, SC.Size); + if (SC.pData != nullptr && SC.Size > 0) + this->m_Hasher.UpdateRaw(SC.pData, SC.Size); + } + } } }; diff --git a/Graphics/GraphicsEngine.NET/Mapping.xml b/Graphics/GraphicsEngine.NET/Mapping.xml index 971c774eec..b0d3b320b1 100644 --- a/Graphics/GraphicsEngine.NET/Mapping.xml +++ b/Graphics/GraphicsEngine.NET/Mapping.xml @@ -171,6 +171,7 @@ + diff --git a/Graphics/GraphicsEngine/interface/PipelineState.h b/Graphics/GraphicsEngine/interface/PipelineState.h index 0246eb8897..8ed8c55f60 100644 --- a/Graphics/GraphicsEngine/interface/PipelineState.h +++ b/Graphics/GraphicsEngine/interface/PipelineState.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -705,6 +705,89 @@ DILIGENT_TYPED_ENUM(PSO_CREATE_FLAGS, Uint32) DEFINE_FLAG_ENUM_OPERATORS(PSO_CREATE_FLAGS); +/// Pipeline state specialization constant. +/// +/// Specialization constants are shader constants whose values are provided +/// when the pipeline state is created. Their values are patched into the +/// shader bytecode at specialization time, allowing applications to create +/// pipeline variations without recompiling shaders and enabling the driver +/// to optimize the resulting shader code. +/// +/// \remarks Specialization constants are supported in Vulkan (via VkSpecializationInfo) +/// and WebGPU (via pipeline-overridable constants). Other backends +/// will reject non-empty specialization constants. +struct SpecializationConstant +{ + /// Specialization constant name as defined in the shader source. + + /// In GLSL/SPIR-V, this corresponds to the name decorated with the + /// `constant_id` layout qualifier. + /// In WGSL, this corresponds to the name of an `override` declaration. + /// + /// Must not be null. + const Char* Name DEFAULT_INITIALIZER(nullptr); + + /// Shader stages this specialization constant applies to. + + /// Specifies which shader stages use this constant. + /// The value must be a valid combination of SHADER_TYPE flags. + SHADER_TYPE ShaderStages DEFAULT_INITIALIZER(SHADER_TYPE_UNKNOWN); + + /// Size of the specialization constant data, in bytes. + + /// Must match the size of the constant's type as declared in the shader. + Uint32 Size DEFAULT_INITIALIZER(0); + + /// Pointer to the specialization constant data. + + /// The data is interpreted as raw bytes and must be at least + /// `Size` bytes long. Must not be null when Size > 0. + const void* pData DEFAULT_INITIALIZER(nullptr); + +#if DILIGENT_CPP_INTERFACE + constexpr SpecializationConstant() noexcept {} + + constexpr SpecializationConstant(const Char* _Name, + SHADER_TYPE _ShaderStages, + Uint32 _Size, + const void* _pData) noexcept : + Name {_Name }, + ShaderStages{_ShaderStages}, + Size {_Size }, + pData {_pData } + {} + + bool operator==(const SpecializationConstant& RHS) const noexcept + { + if (ShaderStages != RHS.ShaderStages || + Size != RHS.Size || + !SafeStrEqual(Name, RHS.Name)) + return false; + + if (pData != RHS.pData) + { + if (Size == 0) + return true; + + if ((pData == nullptr) != (RHS.pData == nullptr)) + return false; + + if (pData != nullptr && memcmp(pData, RHS.pData, Size) != 0) + return false; + } + + return true; + } + + bool operator!=(const SpecializationConstant& RHS) const noexcept + { + return !(*this == RHS); + } +#endif +}; +typedef struct SpecializationConstant SpecializationConstant; + + /// Pipeline state creation attributes struct PipelineStateCreateInfo { @@ -729,6 +812,22 @@ struct PipelineStateCreateInfo /// should be in it default state. IPipelineResourceSignature** ppResourceSignatures DEFAULT_INITIALIZER(nullptr); + /// The number of specialization constants. + Uint32 NumSpecializationConstants DEFAULT_INITIALIZER(0); + + /// Pointer to an array of NumSpecializationConstants SpecializationConstant + /// elements that define the values of specialization constants for the pipeline. + + /// Specialization constants allow applications to set constant values at pipeline + /// creation time, enabling the driver to optimize the compiled shader code. + /// + /// This feature requires DeviceFeatures.SpecializationConstants to be enabled. + /// If the device does not support specialization constants, non-empty arrays + /// will be rejected during pipeline creation. + /// + /// \sa SpecializationConstant + const SpecializationConstant* pSpecializationConstants DEFAULT_INITIALIZER(nullptr); + /// Optional pipeline state cache that is used to accelerate /// PSO creation. If `PSODesc.Name` is found in the cache, the cache /// data is used to create the PSO. Otherwise, the PSO @@ -751,9 +850,10 @@ struct PipelineStateCreateInfo bool operator==(const PipelineStateCreateInfo& RHS) const noexcept { - if (PSODesc != RHS.PSODesc || - Flags != RHS.Flags || - ResourceSignaturesCount != RHS.ResourceSignaturesCount) + if (PSODesc != RHS.PSODesc || + Flags != RHS.Flags || + ResourceSignaturesCount != RHS.ResourceSignaturesCount || + NumSpecializationConstants != RHS.NumSpecializationConstants) return false; if (ppResourceSignatures != RHS.ppResourceSignatures) @@ -776,6 +876,18 @@ struct PipelineStateCreateInfo } } + if (pSpecializationConstants != RHS.pSpecializationConstants) + { + if ((pSpecializationConstants == nullptr) != (RHS.pSpecializationConstants == nullptr)) + return false; + + for (Uint32 i = 0; i < NumSpecializationConstants; ++i) + { + if (pSpecializationConstants[i] != RHS.pSpecializationConstants[i]) + return false; + } + } + // Ignore PSO cache and pInternalData return true; diff --git a/Graphics/GraphicsEngine/src/PSOSerializer.cpp b/Graphics/GraphicsEngine/src/PSOSerializer.cpp index 3949e4eaba..66e3206a8c 100644 --- a/Graphics/GraphicsEngine/src/PSOSerializer.cpp +++ b/Graphics/GraphicsEngine/src/PSOSerializer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -135,8 +135,24 @@ bool PSOSerializer::SerializeCreateInfo( return false; } + // Serialize specialization constants + if (!Ser.SerializeArray(Allocator, CreateInfo.pSpecializationConstants, CreateInfo.NumSpecializationConstants, + [](Serializer& Ser, + ConstQual& SpecConst) // + { + if (!Ser(SpecConst.Name, + SpecConst.ShaderStages, + SpecConst.Size)) + return false; + + size_t DataSize = SpecConst.Size; + return Ser.SerializeBytes(SpecConst.pData, DataSize); + })) + return false; + ASSERT_SIZEOF64(ShaderResourceVariableDesc, 16, "Did you add a new member to ShaderResourceVariableDesc? Please add serialization here."); - ASSERT_SIZEOF64(PipelineStateCreateInfo, 96, "Did you add a new member to PipelineStateCreateInfo? Please add serialization here."); + ASSERT_SIZEOF64(SpecializationConstant, 24, "Did you add a new member to SpecializationConstant? Please add serialization here."); + ASSERT_SIZEOF64(PipelineStateCreateInfo, 112, "Did you add a new member to PipelineStateCreateInfo? Please add serialization here."); return true; } @@ -193,7 +209,7 @@ bool PSOSerializer::SerializeCreateInfo( // Skip NodeMask - ASSERT_SIZEOF64(GraphicsPipelineStateCreateInfo, 344, "Did you add a new member to GraphicsPipelineStateCreateInfo? Please add serialization here."); + ASSERT_SIZEOF64(GraphicsPipelineStateCreateInfo, 360, "Did you add a new member to GraphicsPipelineStateCreateInfo? Please add serialization here."); ASSERT_SIZEOF64(LayoutElement, 40, "Did you add a new member to LayoutElement? Please add serialization here."); } @@ -206,7 +222,7 @@ bool PSOSerializer::SerializeCreateInfo( { return SerializeCreateInfo(Ser, static_cast&>(CreateInfo), PRSNames, Allocator); - ASSERT_SIZEOF64(ComputePipelineStateCreateInfo, 104, "Did you add a new member to ComputePipelineStateCreateInfo? Please add serialization here."); + ASSERT_SIZEOF64(ComputePipelineStateCreateInfo, 120, "Did you add a new member to ComputePipelineStateCreateInfo? Please add serialization here."); } template @@ -224,7 +240,7 @@ bool PSOSerializer::SerializeCreateInfo( CreateInfo.TilePipeline.SampleCount, CreateInfo.TilePipeline.RTVFormats); - ASSERT_SIZEOF64(TilePipelineStateCreateInfo, 128, "Did you add a new member to TilePipelineStateCreateInfo? Please add serialization here."); + ASSERT_SIZEOF64(TilePipelineStateCreateInfo, 144, "Did you add a new member to TilePipelineStateCreateInfo? Please add serialization here."); } template @@ -336,7 +352,7 @@ bool PSOSerializer::SerializeCreateInfo( }); return res; - ASSERT_SIZEOF64(RayTracingPipelineStateCreateInfo, 168, "Did you add a new member to RayTracingPipelineStateCreateInfo? Please add serialization here."); + ASSERT_SIZEOF64(RayTracingPipelineStateCreateInfo, 184, "Did you add a new member to RayTracingPipelineStateCreateInfo? Please add serialization here."); ASSERT_SIZEOF64(RayTracingGeneralShaderGroup, 16, "Did you add a new member to RayTracingGeneralShaderGroup? Please add serialization here."); ASSERT_SIZEOF64(RayTracingTriangleHitShaderGroup, 24, "Did you add a new member to RayTracingTriangleHitShaderGroup? Please add serialization here."); ASSERT_SIZEOF64(RayTracingProceduralHitShaderGroup, 32, "Did you add a new member to RayTracingProceduralHitShaderGroup? Please add serialization here."); diff --git a/Graphics/GraphicsEngine/src/PipelineStateBase.cpp b/Graphics/GraphicsEngine/src/PipelineStateBase.cpp index 5160417b49..6f466e5608 100644 --- a/Graphics/GraphicsEngine/src/PipelineStateBase.cpp +++ b/Graphics/GraphicsEngine/src/PipelineStateBase.cpp @@ -551,6 +551,64 @@ void ValidatePipelineResourceLayoutDesc(const PipelineStateDesc& PSODesc, const LOG_ERROR_AND_THROW(GetShaderTypeLiteralName(Shader->GetDesc().ShaderType), " is not a valid type for ", ShaderName, " shader"); \ } + +void ValidateSpecializationConstants(const PipelineStateDesc& PSODesc, + const DeviceFeatures& Features, + Uint32 NumSpecializationConstants, + const SpecializationConstant* pSpecializationConstants) noexcept(false) +{ + if (NumSpecializationConstants != 0 && pSpecializationConstants == nullptr) + LOG_PSO_ERROR_AND_THROW("NumSpecializationConstants (", NumSpecializationConstants, ") is not zero, but pSpecializationConstants is null."); + + if (NumSpecializationConstants == 0) + return; + + if (Features.SpecializationConstants == DEVICE_FEATURE_STATE_DISABLED) + LOG_PSO_ERROR_AND_THROW("NumSpecializationConstants (", NumSpecializationConstants, ") is not zero, but SpecializationConstants device feature is not enabled."); + + for (Uint32 i = 0; i < NumSpecializationConstants; ++i) + { + const SpecializationConstant& SpecConst = pSpecializationConstants[i]; + + if (SpecConst.Name == nullptr) + LOG_PSO_ERROR_AND_THROW("pSpecializationConstants[", i, "].Name must not be null."); + + if (SpecConst.Name[0] == '\0') + LOG_PSO_ERROR_AND_THROW("pSpecializationConstants[", i, "].Name must not be empty."); + + if (SpecConst.ShaderStages == SHADER_TYPE_UNKNOWN) + LOG_PSO_ERROR_AND_THROW("pSpecializationConstants[", i, "].ShaderStages must not be SHADER_TYPE_UNKNOWN."); + + if (SpecConst.Size == 0) + LOG_PSO_ERROR_AND_THROW("pSpecializationConstants[", i, "].Size must not be zero."); + + if (SpecConst.pData == nullptr) + LOG_PSO_ERROR_AND_THROW("pSpecializationConstants[", i, "].pData must not be null."); + } + + { + std::unordered_multimap UniqueConstants; + for (Uint32 i = 0; i < NumSpecializationConstants; ++i) + { + const SpecializationConstant& SpecConst = pSpecializationConstants[i]; + + auto range = UniqueConstants.equal_range(SpecConst.Name); + for (auto it = range.first; it != range.second; ++it) + { + if ((it->second & SpecConst.ShaderStages) != 0) + { + LOG_PSO_ERROR_AND_THROW("Specialization constant '", SpecConst.Name, + "' is defined in overlapping shader stages (", GetShaderStagesString(SpecConst.ShaderStages), + " and ", GetShaderStagesString(it->second), + "). Multiple specialization constants with the same name are allowed, " + "but shader stages they use must not overlap."); + } + } + UniqueConstants.emplace(SpecConst.Name, SpecConst.ShaderStages); + } + } +} + void ValidateGraphicsPipelineCreateInfo(const GraphicsPipelineStateCreateInfo& CreateInfo, const IRenderDevice* pDevice) noexcept(false) { @@ -571,6 +629,7 @@ void ValidateGraphicsPipelineCreateInfo(const GraphicsPipelineStateCreateInfo& C ValidateDepthStencilDesc(PSODesc, GraphicsPipeline); ValidateGraphicsPipelineDesc(PSODesc, GraphicsPipeline, AdapterInfo.ShadingRate); ValidatePipelineResourceLayoutDesc(PSODesc, Features); + ValidateSpecializationConstants(PSODesc, Features, CreateInfo.NumSpecializationConstants, CreateInfo.pSpecializationConstants); if (PSODesc.PipelineType == PIPELINE_TYPE_GRAPHICS) @@ -686,6 +745,7 @@ void ValidateComputePipelineCreateInfo(const ComputePipelineStateCreateInfo& Cre ValidatePipelineResourceSignatures(CreateInfo, pDevice); ValidatePipelineResourceLayoutDesc(PSODesc, Features); + ValidateSpecializationConstants(PSODesc, Features, CreateInfo.NumSpecializationConstants, CreateInfo.pSpecializationConstants); if (CreateInfo.pCS == nullptr) LOG_PSO_ERROR_AND_THROW("Compute shader must not be null."); @@ -707,6 +767,7 @@ void ValidateRayTracingPipelineCreateInfo(const IRenderDevice* ValidatePipelineResourceSignatures(CreateInfo, pDevice); ValidatePipelineResourceLayoutDesc(PSODesc, DeviceInfo.Features); + ValidateSpecializationConstants(PSODesc, DeviceInfo.Features, CreateInfo.NumSpecializationConstants, CreateInfo.pSpecializationConstants); if (DeviceInfo.Type == RENDER_DEVICE_TYPE_D3D12) { @@ -795,6 +856,7 @@ void ValidateTilePipelineCreateInfo(const TilePipelineStateCreateInfo& CreateInf ValidatePipelineResourceSignatures(CreateInfo, pDevice); ValidatePipelineResourceLayoutDesc(PSODesc, Features); + ValidateSpecializationConstants(PSODesc, Features, CreateInfo.NumSpecializationConstants, CreateInfo.pSpecializationConstants); if (CreateInfo.pTS == nullptr) LOG_PSO_ERROR_AND_THROW("Tile shader must not be null."); diff --git a/Tests/DiligentCoreTest/src/Common/HashUtilsTest.cpp b/Tests/DiligentCoreTest/src/Common/HashUtilsTest.cpp index d6bc2100bf..733244ef68 100644 --- a/Tests/DiligentCoreTest/src/Common/HashUtilsTest.cpp +++ b/Tests/DiligentCoreTest/src/Common/HashUtilsTest.cpp @@ -1204,6 +1204,68 @@ void TestPipelineStateCIHasher() std::array ppSignatures{}; Helper.Get().ppResourceSignatures = ppSignatures.data(); TEST_RANGE(ResourceSignaturesCount, 1u, MAX_RESOURCE_SIGNATURES); + + // Test specialization constants hashing + { + const Uint32 Data1[] = {1, 2, 3}; + const Uint32 Data2[] = {4, 5, 6}; + const Uint32 Data3[] = {7, 8, 9}; + const SpecializationConstant SpecConsts[] = + { + {"Const1", SHADER_TYPE_VERTEX, sizeof(Uint32), &Data1[0]}, + {"Const2", SHADER_TYPE_PIXEL, sizeof(Uint32), &Data2[0]}, + {"Const3", SHADER_TYPE_GEOMETRY, sizeof(Uint32), &Data3[0]}, + }; + Helper.Get().ppResourceSignatures = nullptr; + Helper.Get().ResourceSignaturesCount = 0; + Helper.Get().pSpecializationConstants = SpecConsts; + TEST_VALUE(NumSpecializationConstants, 1u); + TEST_VALUE(NumSpecializationConstants, 2u); + TEST_VALUE(NumSpecializationConstants, 3u); + } + + // Test that different specialization constant data produces different hashes + { + const Uint32 DataA = 100; + const Uint32 DataB = 200; + const SpecializationConstant SC_A{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataA}; + const SpecializationConstant SC_B{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataB}; + + Helper.Get().NumSpecializationConstants = 1; + Helper.Get().pSpecializationConstants = &SC_A; + Helper.Add("SpecConst data A"); + + Helper.Get().pSpecializationConstants = &SC_B; + Helper.Add("SpecConst data B"); + } + + // Test that different specialization constant names produce different hashes + { + const Uint32 Data = 42; + const SpecializationConstant SC_Name1{"Alpha", SHADER_TYPE_VERTEX, sizeof(Uint32), &Data}; + const SpecializationConstant SC_Name2{"Beta", SHADER_TYPE_VERTEX, sizeof(Uint32), &Data}; + + Helper.Get().NumSpecializationConstants = 1; + Helper.Get().pSpecializationConstants = &SC_Name1; + Helper.Add("SpecConst name Alpha"); + + Helper.Get().pSpecializationConstants = &SC_Name2; + Helper.Add("SpecConst name Beta"); + } + + // Test that different specialization constant ShaderStages produce different hashes + { + const Uint32 Data = 42; + const SpecializationConstant SC_VS{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &Data}; + const SpecializationConstant SC_PS{"Const", SHADER_TYPE_PIXEL, sizeof(Uint32), &Data}; + + Helper.Get().NumSpecializationConstants = 1; + Helper.Get().pSpecializationConstants = &SC_VS; + Helper.Add("SpecConst stage VS"); + + Helper.Get().pSpecializationConstants = &SC_PS; + Helper.Add("SpecConst stage PS"); + } } TEST(Common_HashUtils, PipelineStateCIStdHash) diff --git a/Tests/DiligentCoreTest/src/GraphicsEngine/PSOSerializerTest.cpp b/Tests/DiligentCoreTest/src/GraphicsEngine/PSOSerializerTest.cpp index 5a4170be30..5953777820 100644 --- a/Tests/DiligentCoreTest/src/GraphicsEngine/PSOSerializerTest.cpp +++ b/Tests/DiligentCoreTest/src/GraphicsEngine/PSOSerializerTest.cpp @@ -271,6 +271,21 @@ static void TestSerializePSOCreateInfo(HelperType&& Helper) for (Uint32 i = 0; i < SrcPSO.ResourceSignaturesCount; ++i) SrcPRSNames[i] = PRSNames[i].c_str(); + const Uint32 SpecConstData0 = 0x12345678u; + const float SpecConstData1 = 3.14f; + const Uint32 SpecConstData2 = 42u; + + SpecializationConstant SpecConsts[] = + { + {"ConstA", SHADER_TYPE_VERTEX, sizeof(SpecConstData0), &SpecConstData0}, + {"ConstB", SHADER_TYPE_PIXEL, sizeof(SpecConstData1), &SpecConstData1}, + {"ConstC", SHADER_TYPE_GEOMETRY, sizeof(SpecConstData2), &SpecConstData2} // + }; + + SrcPSO.NumSpecializationConstants = Val(0u, _countof(SpecConsts)); + if (SrcPSO.NumSpecializationConstants > 0) + SrcPSO.pSpecializationConstants = SpecConsts; + Helper.Init(SrcPSO, Val); Serializer MSer; @@ -291,6 +306,15 @@ static void TestSerializePSOCreateInfo(HelperType&& Helper) EXPECT_TRUE(RSer.IsEnded()); EXPECT_EQ(SrcPSO, DstPSO); + if (SrcPSO.NumSpecializationConstants > 0) + { + ASSERT_NE(DstPSO.pSpecializationConstants, nullptr); + for (Uint32 i = 0; i < SrcPSO.NumSpecializationConstants; ++i) + { + EXPECT_STREQ(SrcPSO.pSpecializationConstants[i].Name, DstPSO.pSpecializationConstants[i].Name); + EXPECT_NE(SrcPSO.pSpecializationConstants[i].pData, DstPSO.pSpecializationConstants[i].pData); + } + } for (size_t i = 0; i < DstPRSNames.size(); ++i) { @@ -429,7 +453,7 @@ TEST(PSOSerializerTest, SerializeGraphicsPSOCreateInfo) GraphicsPipeline.SmplDesc.Count = Val(Uint8{0}, Uint8{64}); GraphicsPipeline.SmplDesc.Quality = Val(Uint8{0}, Uint8{8}); - ASSERT_SIZEOF64(GraphicsPipelineStateCreateInfo, 344, "Did you add a new member to GraphicsPipelineStateCreateInfo? Please add serialization test here."); + ASSERT_SIZEOF64(GraphicsPipelineStateCreateInfo, 360, "Did you add a new member to GraphicsPipelineStateCreateInfo? Please add serialization test here."); } void Measure(Serializer& Ser, const GraphicsPipelineStateCreateInfo& CI, const TPRSNames& PRSNames) From 339fa3bbfbddc236119cd6c8d74a7ccf7c80d237 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sun, 22 Feb 2026 20:07:41 +0800 Subject: [PATCH 04/28] PipelineStateCreateInfoX: add specialization constants --- .../interface/GraphicsTypesX.hpp | 42 +++++ .../src/GraphicsEngine/GraphicsTypesXTest.cpp | 157 ++++++++++++++++++ 2 files changed, 199 insertions(+) diff --git a/Graphics/GraphicsEngine/interface/GraphicsTypesX.hpp b/Graphics/GraphicsEngine/interface/GraphicsTypesX.hpp index e422373d21..cc1adf94fd 100644 --- a/Graphics/GraphicsEngine/interface/GraphicsTypesX.hpp +++ b/Graphics/GraphicsEngine/interface/GraphicsTypesX.hpp @@ -1236,6 +1236,8 @@ struct PipelineStateCreateInfoX : CreateInfoType AddSignature(CI.ppResourceSignatures[i]); if (CI.pPSOCache != nullptr) SetPipelineStateCache(CI.pPSOCache); + for (Uint32 i = 0; i < CI.NumSpecializationConstants; ++i) + AddSpecializationConstant(CI.pSpecializationConstants[i]); this->PSODesc.ResourceLayout = ResourceLayout; this->pInternalData = InternalData.get(); } @@ -1356,6 +1358,44 @@ struct PipelineStateCreateInfoX : CreateInfoType return static_cast(*this); } + DerivedType& AddSpecializationConstant(const SpecializationConstant& SpecConst) + { + SpecConstCopy.push_back(SpecConst); + SpecializationConstant& Entry = SpecConstCopy.back(); + + // Deep-copy name + if (SpecConst.Name != nullptr) + Entry.Name = StringPool.emplace(SpecConst.Name).first->c_str(); + + // Deep-copy data + if (SpecConst.Size > 0 && SpecConst.pData != nullptr) + { + const Uint8* pSrcData = static_cast(SpecConst.pData); + SpecConstDataCopy.emplace_back(pSrcData, pSrcData + SpecConst.Size); + Entry.pData = SpecConstDataCopy.back().data(); + } + else + { + SpecConstDataCopy.emplace_back(); + Entry.pData = nullptr; + } + + this->pSpecializationConstants = SpecConstCopy.data(); + this->NumSpecializationConstants = static_cast(SpecConstCopy.size()); + + return static_cast(*this); + } + + DerivedType& ClearSpecializationConstants() + { + SpecConstCopy.clear(); + SpecConstDataCopy.clear(); + this->pSpecializationConstants = nullptr; + this->NumSpecializationConstants = 0; + + return static_cast(*this); + } + protected: DerivedType& SetShader(IShader*& pDstShader, IShader* pShader) { @@ -1389,6 +1429,8 @@ struct PipelineStateCreateInfoX : CreateInfoType std::vector> Objects; std::vector Signatures; std::unique_ptr InternalData; + std::vector SpecConstCopy; + std::vector> SpecConstDataCopy; }; diff --git a/Tests/DiligentCoreTest/src/GraphicsEngine/GraphicsTypesXTest.cpp b/Tests/DiligentCoreTest/src/GraphicsEngine/GraphicsTypesXTest.cpp index 9be35211d1..a8712325b6 100644 --- a/Tests/DiligentCoreTest/src/GraphicsEngine/GraphicsTypesXTest.cpp +++ b/Tests/DiligentCoreTest/src/GraphicsEngine/GraphicsTypesXTest.cpp @@ -1138,6 +1138,163 @@ TEST(GraphicsTypesXTest, ComputePipelineStateCreateInfoX) } +TEST(GraphicsTypesXTest, SpecializationConstants) +{ + const float SpecConst1Value = 3.14f; + const Uint32 SpecConst2Value = 42; + const Int32 SpecConst3Value = -7; + + const SpecializationConstant SpecConsts[] = { + {"SC_Float", SHADER_TYPE_VERTEX, sizeof(SpecConst1Value), &SpecConst1Value}, + {"SC_Uint", SHADER_TYPE_PIXEL, sizeof(SpecConst2Value), &SpecConst2Value}, + {"SC_Int", SHADER_TYPE_COMPUTE, sizeof(SpecConst3Value), &SpecConst3Value}, + }; + + // Test copy construction, move construction, and assignment from plain struct with spec constants + { + ComputePipelineStateCreateInfo Ref; + Ref.NumSpecializationConstants = _countof(SpecConsts); + Ref.pSpecializationConstants = SpecConsts; + TestCtorsAndAssignments(Ref); + } + + // Test AddSpecializationConstant with StringPool (verifies deep copy of names and data) + { + StringPool Pool; + ComputePipelineStateCreateInfoX DescX; + DescX + .AddSpecializationConstant({Pool("SC_Float"), SHADER_TYPE_VERTEX, sizeof(SpecConst1Value), &SpecConst1Value}) + .AddSpecializationConstant({Pool("SC_Uint"), SHADER_TYPE_PIXEL, sizeof(SpecConst2Value), &SpecConst2Value}) + .AddSpecializationConstant({Pool("SC_Int"), SHADER_TYPE_COMPUTE, sizeof(SpecConst3Value), &SpecConst3Value}); + Pool.Clear(); + + ComputePipelineStateCreateInfo Ref; + Ref.NumSpecializationConstants = _countof(SpecConsts); + Ref.pSpecializationConstants = SpecConsts; + EXPECT_EQ(DescX, Ref); + } + + // Test AddSpecializationConstant deep-copies raw bytes. + { + Uint8 MutableData[] = {10, 20, 30, 40, 50}; + const Uint8 RefData[] = {10, 20, 30, 40, 50}; + + ComputePipelineStateCreateInfoX DescX; + DescX.AddSpecializationConstant({"SC_Bytes", SHADER_TYPE_PIXEL, static_cast(_countof(MutableData)), MutableData}); + + for (Uint8& Byte : MutableData) + Byte = 0; + + ASSERT_EQ(DescX.NumSpecializationConstants, 1u); + const SpecializationConstant& SpecConst = DescX.pSpecializationConstants[0]; + ASSERT_NE(SpecConst.pData, nullptr); + EXPECT_EQ(SpecConst.Size, static_cast(_countof(RefData))); + + const Uint8* pData = static_cast(SpecConst.pData); + for (size_t i = 0; i < _countof(RefData); ++i) + EXPECT_EQ(pData[i], RefData[i]); + } + + // Test create-info wrapper constructor deep-copies raw bytes. + { + const Uint8 RefData[] = {1, 2, 3, 4}; + ComputePipelineStateCreateInfoX DescX; + { + Uint8 MutableData[] = {1, 2, 3, 4}; + const SpecializationConstant SpecConst{ + "SC_CtorBytes", + SHADER_TYPE_COMPUTE, + static_cast(_countof(MutableData)), + MutableData}; + + ComputePipelineStateCreateInfo SrcCI; + SrcCI.NumSpecializationConstants = 1; + SrcCI.pSpecializationConstants = &SpecConst; + DescX = ComputePipelineStateCreateInfoX{SrcCI}; + + for (Uint8& Byte : MutableData) + Byte = 0; + } + + ASSERT_EQ(DescX.NumSpecializationConstants, 1u); + const SpecializationConstant& SpecConst = DescX.pSpecializationConstants[0]; + ASSERT_NE(SpecConst.pData, nullptr); + EXPECT_EQ(SpecConst.Size, static_cast(_countof(RefData))); + + const Uint8* pData = static_cast(SpecConst.pData); + for (size_t i = 0; i < _countof(RefData); ++i) + EXPECT_EQ(pData[i], RefData[i]); + } + + // Test ClearSpecializationConstants + { + ComputePipelineStateCreateInfoX DescX; + DescX + .AddSpecializationConstant(SpecConsts[0]) + .AddSpecializationConstant(SpecConsts[1]) + .AddSpecializationConstant(SpecConsts[2]); + EXPECT_EQ(DescX.NumSpecializationConstants, 3u); + + DescX.ClearSpecializationConstants(); + EXPECT_EQ(DescX.NumSpecializationConstants, 0u); + EXPECT_EQ(DescX.pSpecializationConstants, nullptr); + EXPECT_EQ(DescX, ComputePipelineStateCreateInfo{}); + } + + // Test copy and move survive source destruction + { + ComputePipelineStateCreateInfoX DescCopy; + ComputePipelineStateCreateInfoX DescMove; + ComputePipelineStateCreateInfo Ref; + Ref.NumSpecializationConstants = _countof(SpecConsts); + Ref.pSpecializationConstants = SpecConsts; + + { + StringPool Pool; + ComputePipelineStateCreateInfoX DescX; + DescX + .AddSpecializationConstant({Pool("SC_Float"), SHADER_TYPE_VERTEX, sizeof(SpecConst1Value), &SpecConst1Value}) + .AddSpecializationConstant({Pool("SC_Uint"), SHADER_TYPE_PIXEL, sizeof(SpecConst2Value), &SpecConst2Value}) + .AddSpecializationConstant({Pool("SC_Int"), SHADER_TYPE_COMPUTE, sizeof(SpecConst3Value), &SpecConst3Value}); + Pool.Clear(); + EXPECT_EQ(DescX, Ref); + + DescCopy = DescX; + auto DescCopy2 = DescCopy; + DescMove = std::move(DescCopy2); + } + + EXPECT_EQ(DescCopy, Ref); + EXPECT_EQ(DescMove, Ref); + + ComputePipelineStateCreateInfoX DescCopy2{DescCopy}; + ComputePipelineStateCreateInfoX DescMove2{std::move(DescMove)}; + EXPECT_EQ(DescCopy2, Ref); + EXPECT_EQ(DescMove2, Ref); + EXPECT_EQ(DescCopy2, DescMove2); + } + + // Test AddSpecializationConstant + ClearSpecializationConstants + re-add + { + ComputePipelineStateCreateInfoX DescX; + DescX.AddSpecializationConstant(SpecConsts[0]); + EXPECT_EQ(DescX.NumSpecializationConstants, 1u); + + DescX.ClearSpecializationConstants(); + EXPECT_EQ(DescX.NumSpecializationConstants, 0u); + + DescX + .AddSpecializationConstant(SpecConsts[1]) + .AddSpecializationConstant(SpecConsts[2]); + + ComputePipelineStateCreateInfo Ref; + const SpecializationConstant RefConsts[] = {SpecConsts[1], SpecConsts[2]}; + Ref.NumSpecializationConstants = _countof(RefConsts); + Ref.pSpecializationConstants = RefConsts; + EXPECT_EQ(DescX, Ref); + } +} + TEST(GraphicsTypesXTest, TilePipelineStateCreateInfoX) { { From a00b38fd8030f4a3d0374db9a5372091c8966000 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Tue, 24 Feb 2026 14:44:38 +0800 Subject: [PATCH 05/28] Add specialization constant failure tests --- .../PSOCreationFailureTest.cpp | 149 +++++++++++++++++- 1 file changed, 148 insertions(+), 1 deletion(-) diff --git a/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp b/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp index 79d614daf1..fc91cc5dc8 100644 --- a/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp +++ b/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -1612,4 +1612,151 @@ TEST_F(PSOCreationFailureTest, MissingCombinedImageSampler) "resource signature 'PSO Create Failure - Missing Combined Image Sampler' as separate image"); } + +TEST_F(PSOCreationFailureTest, SpecConst_NullPointerWithNonZeroCount) +{ + auto PsoCI{GetGraphicsPSOCreateInfo("PSO Create Failure - SpecConst NullPointerWithNonZeroCount")}; + PsoCI.NumSpecializationConstants = 1; + PsoCI.pSpecializationConstants = nullptr; + TestCreatePSOFailure(PsoCI, "pSpecializationConstants is null"); +} + +TEST_F(PSOCreationFailureTest, SpecConst_FeatureDisabled) +{ + if (GPUTestingEnvironment::GetInstance()->GetDevice()->GetDeviceInfo().Features.SpecializationConstants == DEVICE_FEATURE_STATE_ENABLED) + GTEST_SKIP() << "Specialization constants are supported by this device"; + + const float Data = 1.0f; + + SpecializationConstant SpecConsts[] = { + {"Constant0", SHADER_TYPE_VERTEX, sizeof(Data), &Data}, + }; + + auto PsoCI{GetGraphicsPSOCreateInfo("PSO Create Failure - SpecConst FeatureDisabled")}; + PsoCI.NumSpecializationConstants = _countof(SpecConsts); + PsoCI.pSpecializationConstants = SpecConsts; + TestCreatePSOFailure(PsoCI, "SpecializationConstants device feature is not enabled"); +} + +TEST_F(PSOCreationFailureTest, SpecConst_NullName) +{ + if (GPUTestingEnvironment::GetInstance()->GetDevice()->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + GTEST_SKIP() << "Specialization constants are not supported by this device"; + + const float Data = 1.0f; + + SpecializationConstant SpecConsts[] = { + {nullptr, SHADER_TYPE_VERTEX, sizeof(Data), &Data}, + }; + + auto PsoCI{GetGraphicsPSOCreateInfo("PSO Create Failure - SpecConst NullName")}; + PsoCI.NumSpecializationConstants = _countof(SpecConsts); + PsoCI.pSpecializationConstants = SpecConsts; + TestCreatePSOFailure(PsoCI, "Name must not be null"); +} + +TEST_F(PSOCreationFailureTest, SpecConst_EmptyName) +{ + if (GPUTestingEnvironment::GetInstance()->GetDevice()->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + GTEST_SKIP() << "Specialization constants are not supported by this device"; + + const float Data = 1.0f; + + SpecializationConstant SpecConsts[] = { + {"", SHADER_TYPE_VERTEX, sizeof(Data), &Data}, + }; + + auto PsoCI{GetGraphicsPSOCreateInfo("PSO Create Failure - SpecConst EmptyName")}; + PsoCI.NumSpecializationConstants = _countof(SpecConsts); + PsoCI.pSpecializationConstants = SpecConsts; + TestCreatePSOFailure(PsoCI, "Name must not be empty"); +} + +TEST_F(PSOCreationFailureTest, SpecConst_UnknownShaderStages) +{ + if (GPUTestingEnvironment::GetInstance()->GetDevice()->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + GTEST_SKIP() << "Specialization constants are not supported by this device"; + + const float Data = 1.0f; + + SpecializationConstant SpecConsts[] = { + {"Constant0", SHADER_TYPE_UNKNOWN, sizeof(Data), &Data}, + }; + + auto PsoCI{GetGraphicsPSOCreateInfo("PSO Create Failure - SpecConst UnknownShaderStages")}; + PsoCI.NumSpecializationConstants = _countof(SpecConsts); + PsoCI.pSpecializationConstants = SpecConsts; + TestCreatePSOFailure(PsoCI, "ShaderStages must not be SHADER_TYPE_UNKNOWN"); +} + +TEST_F(PSOCreationFailureTest, SpecConst_ZeroSize) +{ + if (GPUTestingEnvironment::GetInstance()->GetDevice()->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + GTEST_SKIP() << "Specialization constants are not supported by this device"; + + const float Data = 1.0f; + + SpecializationConstant SpecConsts[] = { + {"Constant0", SHADER_TYPE_VERTEX, 0, &Data}, + }; + + auto PsoCI{GetGraphicsPSOCreateInfo("PSO Create Failure - SpecConst ZeroSize")}; + PsoCI.NumSpecializationConstants = _countof(SpecConsts); + PsoCI.pSpecializationConstants = SpecConsts; + TestCreatePSOFailure(PsoCI, "Size must not be zero"); +} + +TEST_F(PSOCreationFailureTest, SpecConst_NullData) +{ + if (GPUTestingEnvironment::GetInstance()->GetDevice()->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + GTEST_SKIP() << "Specialization constants are not supported by this device"; + + SpecializationConstant SpecConsts[] = { + {"Constant0", SHADER_TYPE_VERTEX, sizeof(float), nullptr}, + }; + + auto PsoCI{GetGraphicsPSOCreateInfo("PSO Create Failure - SpecConst NullData")}; + PsoCI.NumSpecializationConstants = _countof(SpecConsts); + PsoCI.pSpecializationConstants = SpecConsts; + TestCreatePSOFailure(PsoCI, "pData must not be null"); +} + +TEST_F(PSOCreationFailureTest, SpecConst_DuplicateNameOverlappingStages) +{ + if (GPUTestingEnvironment::GetInstance()->GetDevice()->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + GTEST_SKIP() << "Specialization constants are not supported by this device"; + + const float Data = 1.0f; + + SpecializationConstant SpecConsts[] = { + {"Constant0", SHADER_TYPE_VERTEX | SHADER_TYPE_PIXEL, sizeof(Data), &Data}, + {"Constant0", SHADER_TYPE_VERTEX | SHADER_TYPE_GEOMETRY, sizeof(Data), &Data}, + }; + + auto PsoCI{GetGraphicsPSOCreateInfo("PSO Create Failure - SpecConst DuplicateNameOverlappingStages")}; + PsoCI.NumSpecializationConstants = _countof(SpecConsts); + PsoCI.pSpecializationConstants = SpecConsts; + TestCreatePSOFailure(PsoCI, "is defined in overlapping shader stages"); +} + +TEST_F(PSOCreationFailureTest, SpecConst_ErrorAtSecondElement) +{ + if (GPUTestingEnvironment::GetInstance()->GetDevice()->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + GTEST_SKIP() << "Specialization constants are not supported by this device"; + + const float Data = 1.0f; + + // First element is valid, second has null name + SpecializationConstant SpecConsts[] = { + {"Constant0", SHADER_TYPE_VERTEX, sizeof(Data), &Data}, + {nullptr, SHADER_TYPE_PIXEL, sizeof(Data), &Data}, + }; + + auto PsoCI{GetGraphicsPSOCreateInfo("PSO Create Failure - SpecConst ErrorAtSecondElement")}; + PsoCI.NumSpecializationConstants = _countof(SpecConsts); + PsoCI.pSpecializationConstants = SpecConsts; + TestCreatePSOFailure(PsoCI, "pSpecializationConstants[1].Name must not be null"); +} + + } // namespace From 072dc8e6131b52bd622d24a9df180997b4224681 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 25 Feb 2026 14:19:43 +0800 Subject: [PATCH 06/28] SPIRVShaderResources: add specialization constants support --- .../include/SPIRVShaderResources.hpp | 45 +++++++++- .../ShaderTools/src/SPIRVShaderResources.cpp | 89 ++++++++++++++++++- .../shaders/SPIRV/SpecializationConstants.psh | 19 ++++ .../ShaderTools/SPIRVShaderResourcesTest.cpp | 66 ++++++++++++++ 4 files changed, 214 insertions(+), 5 deletions(-) create mode 100644 Tests/DiligentCoreTest/assets/shaders/SPIRV/SpecializationConstants.psh diff --git a/Graphics/ShaderTools/include/SPIRVShaderResources.hpp b/Graphics/ShaderTools/include/SPIRVShaderResources.hpp index 91199b468f..708357b842 100644 --- a/Graphics/ShaderTools/include/SPIRVShaderResources.hpp +++ b/Graphics/ShaderTools/include/SPIRVShaderResources.hpp @@ -44,6 +44,7 @@ // | Accel Structs | // | Push Constants | _ _ _ m_TotalResources // | Stage Inputs | +// | Spec Constants | // | Resource Names | #include @@ -192,6 +193,28 @@ struct SPIRVShaderStageInputAttribs }; static_assert(sizeof(SPIRVShaderStageInputAttribs) % sizeof(void*) == 0, "Size of SPIRVShaderStageInputAttribs struct must be multiple of sizeof(void*)"); +// sizeof(SPIRVSpecializationConstantAttribs) == 24, msvc x64 +struct SPIRVSpecializationConstantAttribs +{ + // clang-format off + SPIRVSpecializationConstantAttribs(const char* _Name, + uint32_t _SpecId, + uint32_t _Size, + SHADER_CODE_BASIC_TYPE _BasicType) : + Name {_Name}, + SpecId {_SpecId}, + Size {_Size}, + BasicType{_BasicType} + {} + // clang-format on + + const char* const Name; + const uint32_t SpecId; + const uint32_t Size; // Byte size of the scalar type + const SHADER_CODE_BASIC_TYPE BasicType; +}; +static_assert(sizeof(SPIRVSpecializationConstantAttribs) % sizeof(void*) == 0, "Size of SPIRVSpecializationConstantAttribs struct must be multiple of sizeof(void*)"); + /// Diligent::SPIRVShaderResources class class SPIRVShaderResources { @@ -271,8 +294,9 @@ class SPIRVShaderResources Uint32 GetNumAccelStructs ()const noexcept{ return GetNumResources(ResourceClass::AccelStruct); } Uint32 GetNumPushConstants()const noexcept{ return GetNumResources(ResourceClass::PushConstant); } - Uint32 GetTotalResources() const noexcept { return m_Offsets[static_cast(ResourceClass::NumClasses)]; } - Uint32 GetNumShaderStageInputs()const noexcept { return m_NumShaderStageInputs; } + Uint32 GetTotalResources() const noexcept { return m_Offsets[static_cast(ResourceClass::NumClasses)]; } + Uint32 GetNumShaderStageInputs() const noexcept { return m_NumShaderStageInputs; } + Uint32 GetNumSpecConstants() const noexcept { return m_NumSpecConstants; } const SPIRVShaderResourceAttribs& GetUB (Uint32 n)const noexcept{ return GetResAttribs(ResourceClass::UniformBuffer, n); } const SPIRVShaderResourceAttribs& GetSB (Uint32 n)const noexcept{ return GetResAttribs(ResourceClass::StorageBuffer, n); } @@ -294,6 +318,14 @@ class SPIRVShaderResources return reinterpret_cast(ResourceMemoryEnd)[n]; } + const SPIRVSpecializationConstantAttribs& GetSpecConstant(Uint32 n) const noexcept + { + VERIFY(n < m_NumSpecConstants, "Specialization constant index (", n, ") is out of range. Total spec constant count: ", m_NumSpecConstants); + const SPIRVShaderResourceAttribs* ResourceMemoryEnd = reinterpret_cast(m_MemoryBuffer.get()) + GetTotalResources(); + const SPIRVShaderStageInputAttribs* StageInputsEnd = reinterpret_cast(ResourceMemoryEnd) + m_NumShaderStageInputs; + return reinterpret_cast(StageInputsEnd)[n]; + } + const ShaderCodeBufferDesc* GetUniformBufferDesc(Uint32 Index) const { if (Index >= GetNumUBs()) @@ -452,6 +484,7 @@ class SPIRVShaderResources void Initialize(IMemoryAllocator& Allocator, const ResourceCounters& Counters, Uint32 NumShaderStageInputs, + Uint32 NumSpecConstants, size_t ResourceNamesPoolSize, StringPool& ResourceNamesPool); @@ -486,8 +519,13 @@ class SPIRVShaderResources return const_cast(const_cast(this)->GetShaderStageInputAttribs(n)); } + SPIRVSpecializationConstantAttribs& GetSpecConstant(Uint32 n) noexcept + { + return const_cast(const_cast(this)->GetSpecConstant(n)); + } + // Memory buffer that holds all resources as continuous chunk of memory: - // | UBs | SBs | StrgImgs | SmplImgs | ACs | SepSamplers | SepImgs | Stage Inputs | Resource Names | + // | UBs | SBs | StrgImgs | SmplImgs | ACs | SepSamplers | SepImgs | Stage Inputs | Spec Constants | Resource Names | std::unique_ptr> m_MemoryBuffer; std::unique_ptr> m_UBReflectionBuffer; @@ -498,6 +536,7 @@ class SPIRVShaderResources std::array(ResourceClass::NumClasses) + 1> m_Offsets; OffsetType m_NumShaderStageInputs = 0; + OffsetType m_NumSpecConstants = 0; SHADER_TYPE m_ShaderType = SHADER_TYPE_UNKNOWN; diff --git a/Graphics/ShaderTools/src/SPIRVShaderResources.cpp b/Graphics/ShaderTools/src/SPIRVShaderResources.cpp index e548112580..3a138d1248 100644 --- a/Graphics/ShaderTools/src/SPIRVShaderResources.cpp +++ b/Graphics/ShaderTools/src/SPIRVShaderResources.cpp @@ -613,9 +613,57 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, ResCounters.NumPushConstants = static_cast(resources.push_constant_buffers.size()); static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please set the new resource type counter here"); + // Specialization constants reflection + struct SpecConstInfo + { + std::string Name; + uint32_t SpecId = 0; + uint32_t Size = 0; + SHADER_CODE_BASIC_TYPE BasicType = SHADER_CODE_BASIC_TYPE_UNKNOWN; + }; + std::vector SpecConstants; + Uint32 NumSpecConstants = 0; + + { + diligent_spirv_cross::SmallVector spec_consts = + Compiler.get_specialization_constants(); + for (const diligent_spirv_cross::SpecializationConstant& sc : spec_consts) + { + const diligent_spirv_cross::SPIRConstant& Constant = Compiler.get_constant(sc.id); + const diligent_spirv_cross::SPIRType& Type = Compiler.get_type(Constant.constant_type); + + // Only support scalar specialization constants + if (Type.vecsize != 1 || Type.columns != 1) + { + LOG_WARNING_MESSAGE("Specialization constant '", Compiler.get_name(sc.id), + "' (SpecId=", sc.constant_id, ") in shader '", CI.Name, + "' is not a scalar type and will be skipped."); + continue; + } + + SpecConstInfo Info; + Info.Name = Compiler.get_name(sc.id); + Info.SpecId = sc.constant_id; + // OpTypeBool has width==1 in SPIRV-Cross; use 4 bytes (VkBool32) for bool specialization constants + Info.Size = Type.basetype == diligent_spirv_cross::SPIRType::Boolean ? 4 : Type.width / 8; + Info.BasicType = SpirvBaseTypeToShaderCodeBasicType(Type.basetype); + + if (Info.Name.empty()) + { + LOG_WARNING_MESSAGE("Specialization constant with SpecId=", sc.constant_id, + " in shader '", CI.Name, "' has no name (OpName) and will be skipped."); + continue; + } + + ResourceNamesPoolSize += Info.Name.length() + 1; + SpecConstants.emplace_back(std::move(Info)); + } + NumSpecConstants = static_cast(SpecConstants.size()); + } + // Resource names pool is only needed to facilitate string allocation. StringPool ResourceNamesPool; - Initialize(Allocator, ResCounters, NumShaderStageInputs, ResourceNamesPoolSize, ResourceNamesPool); + Initialize(Allocator, ResCounters, NumShaderStageInputs, NumSpecConstants, ResourceNamesPoolSize, ResourceNamesPool); // Uniform buffer reflections std::vector UBReflections; @@ -842,6 +890,22 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, VERIFY_EXPR(CurrStageInput == GetNumShaderStageInputs()); } + if (!SpecConstants.empty()) + { + Uint32 CurrSpecConst = 0; + for (const SpecConstInfo& SC : SpecConstants) + { + new (&GetSpecConstant(CurrSpecConst++)) SPIRVSpecializationConstantAttribs // + { + ResourceNamesPool.CopyString(SC.Name), + SC.SpecId, + SC.Size, + SC.BasicType // + }; + } + VERIFY_EXPR(CurrSpecConst == GetNumSpecConstants()); + } + VERIFY(ResourceNamesPool.GetRemainingSize() == 0, "Names pool must be empty"); if (m_ShaderType == SHADER_TYPE_COMPUTE) @@ -862,6 +926,7 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, void SPIRVShaderResources::Initialize(IMemoryAllocator& Allocator, const ResourceCounters& Counters, Uint32 NumShaderStageInputs, + Uint32 NumSpecConstants, size_t ResourceNamesPoolSize, StringPool& ResourceNamesPool) { @@ -890,12 +955,16 @@ void SPIRVShaderResources::Initialize(IMemoryAllocator& Allocator, VERIFY(NumShaderStageInputs <= MaxOffset, "Max offset exceeded"); m_NumShaderStageInputs = static_cast(NumShaderStageInputs); + VERIFY(NumSpecConstants <= MaxOffset, "Max offset exceeded"); + m_NumSpecConstants = static_cast(NumSpecConstants); + size_t AlignedResourceNamesPoolSize = AlignUp(ResourceNamesPoolSize, sizeof(void*)); static_assert(sizeof(SPIRVShaderResourceAttribs) % sizeof(void*) == 0, "Size of SPIRVShaderResourceAttribs struct must be multiple of sizeof(void*)"); // clang-format off size_t MemorySize = GetTotalResources() * sizeof(SPIRVShaderResourceAttribs) + m_NumShaderStageInputs * sizeof(SPIRVShaderStageInputAttribs) + + m_NumSpecConstants * sizeof(SPIRVSpecializationConstantAttribs) + AlignedResourceNamesPoolSize * sizeof(char); VERIFY_EXPR(GetNumUBs() == Counters.NumUBs); @@ -917,7 +986,8 @@ void SPIRVShaderResources::Initialize(IMemoryAllocator& Allocator, m_MemoryBuffer = std::unique_ptr>(pRawMem, Allocator); char* NamesPool = reinterpret_cast(m_MemoryBuffer.get()) + GetTotalResources() * sizeof(SPIRVShaderResourceAttribs) + - m_NumShaderStageInputs * sizeof(SPIRVShaderStageInputAttribs); + m_NumShaderStageInputs * sizeof(SPIRVShaderStageInputAttribs) + + m_NumSpecConstants * sizeof(SPIRVSpecializationConstantAttribs); ResourceNamesPool.AssignMemory(NamesPool, ResourceNamesPoolSize); } } @@ -951,6 +1021,9 @@ SPIRVShaderResources::~SPIRVShaderResources() for (Uint32 n = 0; n < GetNumShaderStageInputs(); ++n) GetShaderStageInputAttribs(n).~SPIRVShaderStageInputAttribs(); + for (Uint32 n = 0; n < GetNumSpecConstants(); ++n) + GetSpecConstant(n).~SPIRVSpecializationConstantAttribs(); + for (Uint32 n = 0; n < GetNumAccelStructs(); ++n) GetAccelStruct(n).~SPIRVShaderResourceAttribs(); @@ -1191,6 +1264,18 @@ std::string SPIRVShaderResources::DumpResources() const ); VERIFY_EXPR(ResNum == GetTotalResources()); + if (GetNumSpecConstants() > 0) + { + ss << std::endl + << "Specialization constants (" << GetNumSpecConstants() << "):"; + for (Uint32 n = 0; n < GetNumSpecConstants(); ++n) + { + const auto& SC = GetSpecConstant(n); + ss << std::endl + << " '" << SC.Name << "' SpecId=" << SC.SpecId << " Size=" << SC.Size; + } + } + return ss.str(); } diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/SpecializationConstants.psh b/Tests/DiligentCoreTest/assets/shaders/SPIRV/SpecializationConstants.psh new file mode 100644 index 0000000000..9a1511e5ad --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/SpecializationConstants.psh @@ -0,0 +1,19 @@ +// Test shader for specialization constants reflection +[[vk::constant_id(0)]] const bool g_EnableFeature = true; +[[vk::constant_id(1)]] const int g_IntParam = 42; +[[vk::constant_id(2)]] const uint g_UintParam = 7; +[[vk::constant_id(3)]] const float g_FloatParam = 1.0f; + +float4 main() : SV_Target +{ + float4 result = float4(0, 0, 0, 1); + + if (g_EnableFeature) + { + result.x = float(g_IntParam); + result.y = float(g_UintParam); + result.z = g_FloatParam; + } + + return result; +} diff --git a/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp b/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp index 85ebacae12..beb60d9525 100644 --- a/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp +++ b/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp @@ -589,4 +589,70 @@ TEST_F(SPIRVShaderResourcesTest, MixedResources_DXC) TestMixedResources(SHADER_COMPILER_DXC); } + +struct SPIRVSpecConstRefAttribs +{ + const char* const Name; + const uint32_t SpecId; + const uint32_t Size; + const SHADER_CODE_BASIC_TYPE BasicType; +}; + +void TestSpecializationConstants(SHADER_COMPILER Compiler) +{ + std::vector SPIRV; + ASSERT_NO_FATAL_FAILURE(CompileSPIRV("SpecializationConstants.psh", Compiler, SHADER_TYPE_PIXEL, SHADER_SOURCE_LANGUAGE_HLSL, SPIRV)); + + if (::testing::Test::IsSkipped()) + return; + + SPIRVShaderResources::CreateInfo ResCI; + ResCI.ShaderType = SHADER_TYPE_PIXEL; + ResCI.Name = "SpecConstants test"; + SPIRVShaderResources Resources{ + GetRawAllocator(), + SPIRV, + ResCI, + }; + + LOG_INFO_MESSAGE("SPIRV Resources:\n", Resources.DumpResources()); + + const std::vector RefSpecConstants = { + {"g_EnableFeature", 0, 4, SHADER_CODE_BASIC_TYPE_BOOL}, + {"g_IntParam", 1, 4, SHADER_CODE_BASIC_TYPE_INT}, + {"g_UintParam", 2, 4, SHADER_CODE_BASIC_TYPE_UINT}, + {"g_FloatParam", 3, 4, SHADER_CODE_BASIC_TYPE_FLOAT}, + }; + + const SPIRVShaderResources& ConstResources = Resources; + EXPECT_EQ(ConstResources.GetNumSpecConstants(), static_cast(RefSpecConstants.size())); + + // Build a map from name to reference for order-independent matching + std::unordered_map RefMap; + for (const SPIRVSpecConstRefAttribs& Ref : RefSpecConstants) + RefMap[Ref.Name] = &Ref; + + for (Uint32 i = 0; i < ConstResources.GetNumSpecConstants(); ++i) + { + const SPIRVSpecializationConstantAttribs& SC = ConstResources.GetSpecConstant(i); + const auto it = RefMap.find(SC.Name); + ASSERT_NE(it, RefMap.end()) << "Specialization constant '" << SC.Name << "' is not found in the reference list"; + + const auto* pRef = it->second; + EXPECT_EQ(SC.SpecId, pRef->SpecId) << SC.Name; + EXPECT_EQ(SC.Size, pRef->Size) << SC.Name; + EXPECT_EQ(SC.BasicType, pRef->BasicType) << SC.Name; + } +} + +TEST_F(SPIRVShaderResourcesTest, SpecializationConstants_GLSLang) +{ + TestSpecializationConstants(SHADER_COMPILER_GLSLANG); +} + +TEST_F(SPIRVShaderResourcesTest, SpecializationConstants_DXC) +{ + TestSpecializationConstants(SHADER_COMPILER_DXC); +} + } // namespace From 8a7b6c3a129a5874d15448c51af8dbd98aaa2643 Mon Sep 17 00:00:00 2001 From: assiduous Date: Wed, 25 Feb 2026 22:50:48 -0800 Subject: [PATCH 07/28] SPIRVShaderResources: simplify specialization constants processing --- .../ShaderTools/src/SPIRVShaderResources.cpp | 41 ++++++++----------- .../ShaderTools/SPIRVShaderResourcesTest.cpp | 17 ++------ 2 files changed, 22 insertions(+), 36 deletions(-) diff --git a/Graphics/ShaderTools/src/SPIRVShaderResources.cpp b/Graphics/ShaderTools/src/SPIRVShaderResources.cpp index 3a138d1248..eea8caffca 100644 --- a/Graphics/ShaderTools/src/SPIRVShaderResources.cpp +++ b/Graphics/ShaderTools/src/SPIRVShaderResources.cpp @@ -614,19 +614,12 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, static_assert(Uint32{SPIRVShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please set the new resource type counter here"); // Specialization constants reflection - struct SpecConstInfo - { - std::string Name; - uint32_t SpecId = 0; - uint32_t Size = 0; - SHADER_CODE_BASIC_TYPE BasicType = SHADER_CODE_BASIC_TYPE_UNKNOWN; - }; - std::vector SpecConstants; - Uint32 NumSpecConstants = 0; - + diligent_spirv_cross::SmallVector SpecConstants; + Uint32 NumSpecConstants = 0; { diligent_spirv_cross::SmallVector spec_consts = Compiler.get_specialization_constants(); + SpecConstants.reserve(spec_consts.size()); for (const diligent_spirv_cross::SpecializationConstant& sc : spec_consts) { const diligent_spirv_cross::SPIRConstant& Constant = Compiler.get_constant(sc.id); @@ -641,22 +634,23 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, continue; } - SpecConstInfo Info; - Info.Name = Compiler.get_name(sc.id); - Info.SpecId = sc.constant_id; - // OpTypeBool has width==1 in SPIRV-Cross; use 4 bytes (VkBool32) for bool specialization constants - Info.Size = Type.basetype == diligent_spirv_cross::SPIRType::Boolean ? 4 : Type.width / 8; - Info.BasicType = SpirvBaseTypeToShaderCodeBasicType(Type.basetype); - - if (Info.Name.empty()) + const std::string& Name = Compiler.get_name(sc.id); + if (Name.empty()) { LOG_WARNING_MESSAGE("Specialization constant with SpecId=", sc.constant_id, " in shader '", CI.Name, "' has no name (OpName) and will be skipped."); continue; } - - ResourceNamesPoolSize += Info.Name.length() + 1; - SpecConstants.emplace_back(std::move(Info)); + ResourceNamesPoolSize += Name.length() + 1; + + SPIRVSpecializationConstantAttribs Info{ + Name.c_str(), + sc.constant_id, + // OpTypeBool has width==1 in SPIRV-Cross; use 4 bytes (VkBool32) for bool specialization constants + Type.basetype == diligent_spirv_cross::SPIRType::Boolean ? 4 : Type.width / 8, + SpirvBaseTypeToShaderCodeBasicType(Type.basetype), + }; + SpecConstants.push_back(Info); } NumSpecConstants = static_cast(SpecConstants.size()); } @@ -893,7 +887,7 @@ SPIRVShaderResources::SPIRVShaderResources(IMemoryAllocator& Allocator, if (!SpecConstants.empty()) { Uint32 CurrSpecConst = 0; - for (const SpecConstInfo& SC : SpecConstants) + for (const SPIRVSpecializationConstantAttribs& SC : SpecConstants) { new (&GetSpecConstant(CurrSpecConst++)) SPIRVSpecializationConstantAttribs // { @@ -961,6 +955,7 @@ void SPIRVShaderResources::Initialize(IMemoryAllocator& Allocator, size_t AlignedResourceNamesPoolSize = AlignUp(ResourceNamesPoolSize, sizeof(void*)); static_assert(sizeof(SPIRVShaderResourceAttribs) % sizeof(void*) == 0, "Size of SPIRVShaderResourceAttribs struct must be multiple of sizeof(void*)"); + static_assert(sizeof(SPIRVSpecializationConstantAttribs) % sizeof(void*) == 0, "Size of SPIRVSpecializationConstantAttribs struct must be multiple of sizeof(void*)"); // clang-format off size_t MemorySize = GetTotalResources() * sizeof(SPIRVShaderResourceAttribs) + m_NumShaderStageInputs * sizeof(SPIRVShaderStageInputAttribs) + @@ -1270,7 +1265,7 @@ std::string SPIRVShaderResources::DumpResources() const << "Specialization constants (" << GetNumSpecConstants() << "):"; for (Uint32 n = 0; n < GetNumSpecConstants(); ++n) { - const auto& SC = GetSpecConstant(n); + const SPIRVSpecializationConstantAttribs& SC = GetSpecConstant(n); ss << std::endl << " '" << SC.Name << "' SpecId=" << SC.SpecId << " Size=" << SC.Size; } diff --git a/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp b/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp index beb60d9525..e675ba28c1 100644 --- a/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp +++ b/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp @@ -589,15 +589,6 @@ TEST_F(SPIRVShaderResourcesTest, MixedResources_DXC) TestMixedResources(SHADER_COMPILER_DXC); } - -struct SPIRVSpecConstRefAttribs -{ - const char* const Name; - const uint32_t SpecId; - const uint32_t Size; - const SHADER_CODE_BASIC_TYPE BasicType; -}; - void TestSpecializationConstants(SHADER_COMPILER Compiler) { std::vector SPIRV; @@ -617,7 +608,7 @@ void TestSpecializationConstants(SHADER_COMPILER Compiler) LOG_INFO_MESSAGE("SPIRV Resources:\n", Resources.DumpResources()); - const std::vector RefSpecConstants = { + const std::vector RefSpecConstants = { {"g_EnableFeature", 0, 4, SHADER_CODE_BASIC_TYPE_BOOL}, {"g_IntParam", 1, 4, SHADER_CODE_BASIC_TYPE_INT}, {"g_UintParam", 2, 4, SHADER_CODE_BASIC_TYPE_UINT}, @@ -628,8 +619,8 @@ void TestSpecializationConstants(SHADER_COMPILER Compiler) EXPECT_EQ(ConstResources.GetNumSpecConstants(), static_cast(RefSpecConstants.size())); // Build a map from name to reference for order-independent matching - std::unordered_map RefMap; - for (const SPIRVSpecConstRefAttribs& Ref : RefSpecConstants) + std::unordered_map RefMap; + for (const SPIRVSpecializationConstantAttribs& Ref : RefSpecConstants) RefMap[Ref.Name] = &Ref; for (Uint32 i = 0; i < ConstResources.GetNumSpecConstants(); ++i) @@ -638,7 +629,7 @@ void TestSpecializationConstants(SHADER_COMPILER Compiler) const auto it = RefMap.find(SC.Name); ASSERT_NE(it, RefMap.end()) << "Specialization constant '" << SC.Name << "' is not found in the reference list"; - const auto* pRef = it->second; + const SPIRVSpecializationConstantAttribs* pRef = it->second; EXPECT_EQ(SC.SpecId, pRef->SpecId) << SC.Name; EXPECT_EQ(SC.Size, pRef->Size) << SC.Name; EXPECT_EQ(SC.BasicType, pRef->BasicType) << SC.Name; From 47e9e622af0d098afd393189989cbfac6005b208 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Thu, 26 Feb 2026 20:49:46 -0800 Subject: [PATCH 08/28] PipelineStateVk: build specialization constant data --- .../include/PipelineStateVkImpl.hpp | 8 +- .../src/PipelineStateVkImpl.cpp | 137 +++++++++++++++++- 2 files changed, 140 insertions(+), 5 deletions(-) diff --git a/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.hpp b/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.hpp index 0d7c37523e..9362b3ac20 100644 --- a/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.hpp +++ b/Graphics/GraphicsEngineVulkan/include/PipelineStateVkImpl.hpp @@ -49,6 +49,11 @@ namespace Diligent class DeviceContextVkImpl; +namespace +{ +struct ShaderStageSpecializationData; +} + /// Pipeline state object implementation in Vulkan backend. class PipelineStateVkImpl final : public PipelineStateBase { @@ -131,7 +136,8 @@ class PipelineStateVkImpl final : public PipelineStateBase template TShaderStages InitInternalObjects(const PSOCreateInfoType& CreateInfo, std::vector& vkShaderStages, - std::vector& ShaderModules) noexcept(false); + std::vector& ShaderModules, + std::vector& SpecDataPerStage) noexcept(false); void InitPipelineLayout(const PipelineStateCreateInfo& CreateInfo, TShaderStages& ShaderStages) noexcept(false); diff --git a/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp index de5becb9cd..40bcabca85 100644 --- a/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/PipelineStateVkImpl.cpp @@ -94,6 +94,127 @@ void InitPipelineShaderStages(const VulkanUtilities::LogicalDevice& VERIFY_EXPR(ShaderModules.size() == Stages.size()); } +// Per-shader-stage specialization constant data for Vulkan pipeline creation. +// Holds VkSpecializationMapEntry array, contiguous data blob, and the +// VkSpecializationInfo that references them. Lifetime must exceed the +// vkCreate*Pipelines call. +struct ShaderStageSpecializationData +{ + std::vector MapEntries; + std::vector DataBlob; + VkSpecializationInfo Info{}; +}; + +// Iterates SPIR-V reflected specialization constants and matches them to +// user-provided SpecializationConstant entries by name. If a reflected +// constant has no matching user entry the constant is silently skipped, +// which allows the user to supply a superset of constants shared across +// multiple pipelines / stages. +// +// Parameters: +// ShaderStages - shader stages extracted from the PSO create info +// NumSpecializationConstants - number of user-provided specialization constants +// pSpecializationConstants - user-provided specialization constant array +// PSODesc - pipeline state description (for error messages) +// vkStages [in/out] - VkPipelineShaderStageCreateInfo array to patch +// SpecDataPerStage [out] - per-stage specialization data (must outlive vkCreate*Pipelines) +// +// Throws on size mismatch between user-provided and reflected constants. +void BuildSpecializationData(const PipelineStateVkImpl::TShaderStages& ShaderStages, + Uint32 NumSpecializationConstants, + const SpecializationConstant* pSpecializationConstants, + const PipelineStateDesc& PSODesc, + std::vector& vkStages, + std::vector& SpecDataPerStage) +{ + if (NumSpecializationConstants == 0 || pSpecializationConstants == nullptr) + return; + + // vkStages has one entry per ShaderStageInfo::Item across all stages. + // We build one ShaderStageSpecializationData per vkStages entry. + SpecDataPerStage.resize(vkStages.size()); + + Uint32 vkStageIdx = 0; + for (const PipelineStateVkImpl::ShaderStageInfo& Stage : ShaderStages) + { + for (const PipelineStateVkImpl::ShaderStageInfo::Item& StageItem : Stage.Items) + { + VERIFY_EXPR(vkStageIdx < vkStages.size()); + + const ShaderVkImpl* pShader = StageItem.pShader; + const SPIRVShaderResources* pResources = pShader->GetShaderResources().get(); + ShaderStageSpecializationData& StageData = SpecDataPerStage[vkStageIdx]; + + for (Uint32 r = 0; r < pResources->GetNumSpecConstants(); ++r) + { + const SPIRVSpecializationConstantAttribs& Reflected = pResources->GetSpecConstant(r); + + // Search for a matching user-provided constant by name and stage flag. + const SpecializationConstant* pUserConst = nullptr; + for (Uint32 sc = 0; sc < NumSpecializationConstants; ++sc) + { + const SpecializationConstant& Candidate = pSpecializationConstants[sc]; + if ((Candidate.ShaderStages & Stage.Type) != 0 && + strcmp(Candidate.Name, Reflected.Name) == 0) + { + pUserConst = &Candidate; + break; + } + } + + // No user constant for this reflected entry -- skip silently. + if (pUserConst == nullptr) + continue; + + // The user may provide more data than the shader needs (e.g. when + // sharing a constant array across pipelines with different types). + // Only reject the case where the user provides less data than required. + if (pUserConst->Size < Reflected.Size) + { + LOG_ERROR_AND_THROW("Description of ", GetPipelineTypeString(PSODesc.PipelineType), + " PSO '", (PSODesc.Name != nullptr ? PSODesc.Name : ""), + "' is invalid: specialization constant '", pUserConst->Name, + "' in ", GetShaderTypeLiteralName(Stage.Type), + " shader '", pShader->GetDesc().Name, + "' has insufficient data: user provided ", pUserConst->Size, + " bytes, but the shader declares ", + GetShaderCodeBasicTypeString(Reflected.BasicType), + " (", Reflected.Size, " bytes)."); + } + + // Use the reflected size -- it is the actual size the shader expects. + const Uint32 ConstSize = Reflected.Size; + + // Build the map entry. + VkSpecializationMapEntry Entry{}; + Entry.constantID = Reflected.SpecId; + Entry.offset = static_cast(StageData.DataBlob.size()); + Entry.size = ConstSize; + StageData.MapEntries.push_back(Entry); + + // Append data to the blob (only the bytes the shader needs). + const Uint8* pSrcData = static_cast(pUserConst->pData); + StageData.DataBlob.insert(StageData.DataBlob.end(), pSrcData, pSrcData + ConstSize); + } + + // Populate VkSpecializationInfo if any entries were matched. + if (!StageData.MapEntries.empty()) + { + StageData.Info.mapEntryCount = static_cast(StageData.MapEntries.size()); + StageData.Info.pMapEntries = StageData.MapEntries.data(); + StageData.Info.dataSize = StageData.DataBlob.size(); + StageData.Info.pData = StageData.DataBlob.data(); + + vkStages[vkStageIdx].pSpecializationInfo = &StageData.Info; + } + + ++vkStageIdx; + } + } + + VERIFY_EXPR(vkStageIdx == vkStages.size()); +} + void CreateComputePipeline(RenderDeviceVkImpl* pDeviceVk, std::vector& Stages, @@ -955,7 +1076,8 @@ template PipelineStateVkImpl::TShaderStages PipelineStateVkImpl::InitInternalObjects( const PSOCreateInfoType& CreateInfo, std::vector& vkShaderStages, - std::vector& ShaderModules) noexcept(false) + std::vector& ShaderModules, + std::vector& SpecDataPerStage) noexcept(false) { TShaderStages ShaderStages; ExtractShaders(CreateInfo, ShaderStages, /*WaitUntilShadersReady = */ true); @@ -975,6 +1097,9 @@ PipelineStateVkImpl::TShaderStages PipelineStateVkImpl::InitInternalObjects( // Create shader modules and initialize shader stages InitPipelineShaderStages(LogicalDevice, ShaderStages, ShaderModules, vkShaderStages); + // Build per-stage specialization data and patch vkShaderStages. + BuildSpecializationData(ShaderStages, CreateInfo.NumSpecializationConstants, CreateInfo.pSpecializationConstants, m_Desc, vkShaderStages, SpecDataPerStage); + return ShaderStages; } @@ -982,8 +1107,9 @@ void PipelineStateVkImpl::InitializePipeline(const GraphicsPipelineStateCreateIn { std::vector vkShaderStages; std::vector ShaderModules; + std::vector SpecDataPerStage; - InitInternalObjects(CreateInfo, vkShaderStages, ShaderModules); + InitInternalObjects(CreateInfo, vkShaderStages, ShaderModules, SpecDataPerStage); const VkPipelineCache vkSPOCache = CreateInfo.pPSOCache != nullptr ? ClassPtrCast(CreateInfo.pPSOCache)->GetVkPipelineCache() : VK_NULL_HANDLE; CreateGraphicsPipeline(m_pDevice, vkShaderStages, m_PipelineLayout, m_Desc, m_pGraphicsPipelineData->Desc, m_Pipeline, GetRenderPassPtr(), vkSPOCache); @@ -993,8 +1119,9 @@ void PipelineStateVkImpl::InitializePipeline(const ComputePipelineStateCreateInf { std::vector vkShaderStages; std::vector ShaderModules; + std::vector SpecDataPerStage; - InitInternalObjects(CreateInfo, vkShaderStages, ShaderModules); + InitInternalObjects(CreateInfo, vkShaderStages, ShaderModules, SpecDataPerStage); const VkPipelineCache vkSPOCache = CreateInfo.pPSOCache != nullptr ? ClassPtrCast(CreateInfo.pPSOCache)->GetVkPipelineCache() : VK_NULL_HANDLE; CreateComputePipeline(m_pDevice, vkShaderStages, m_PipelineLayout, m_Desc, m_Pipeline, vkSPOCache); @@ -1006,8 +1133,10 @@ void PipelineStateVkImpl::InitializePipeline(const RayTracingPipelineStateCreate std::vector vkShaderStages; std::vector ShaderModules; + std::vector SpecDataPerStage; + + const TShaderStages ShaderStages = InitInternalObjects(CreateInfo, vkShaderStages, ShaderModules, SpecDataPerStage); - const PipelineStateVkImpl::TShaderStages ShaderStages = InitInternalObjects(CreateInfo, vkShaderStages, ShaderModules); const std::vector vkShaderGroups = BuildRTShaderGroupDescription(CreateInfo, m_pRayTracingPipelineData->NameToGroupIndex, ShaderStages); const VkPipelineCache vkSPOCache = CreateInfo.pPSOCache != nullptr ? ClassPtrCast(CreateInfo.pPSOCache)->GetVkPipelineCache() : VK_NULL_HANDLE; From 955da294be5c9bc7c3d2a547eecbd25ee4eb7582 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Thu, 26 Feb 2026 16:00:47 +0800 Subject: [PATCH 09/28] Vulkan: enable SpecializationConstants feature --- Graphics/GraphicsEngineVulkan/src/VulkanTypeConversions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Graphics/GraphicsEngineVulkan/src/VulkanTypeConversions.cpp b/Graphics/GraphicsEngineVulkan/src/VulkanTypeConversions.cpp index 1b08e0c970..3cc5812392 100644 --- a/Graphics/GraphicsEngineVulkan/src/VulkanTypeConversions.cpp +++ b/Graphics/GraphicsEngineVulkan/src/VulkanTypeConversions.cpp @@ -1968,7 +1968,7 @@ DeviceFeatures VkFeaturesToDeviceFeatures(uint32_t Features.TextureSubresourceViews = DEVICE_FEATURE_STATE_ENABLED; Features.AsyncShaderCompilation = DEVICE_FEATURE_STATE_ENABLED; Features.FormattedBuffers = DEVICE_FEATURE_STATE_ENABLED; - Features.SpecializationConstants = DEVICE_FEATURE_STATE_DISABLED; + Features.SpecializationConstants = DEVICE_FEATURE_STATE_ENABLED; // Timestamps are not a feature and can't be disabled. They are either supported by the device, or not. Features.TimestampQueries = vkDeviceProps.limits.timestampComputeAndGraphics ? DEVICE_FEATURE_STATE_ENABLED : DEVICE_FEATURE_STATE_DISABLED; From 759dacb05345dd5bf66e9e08ec2584dff6d0dd3f Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Fri, 27 Feb 2026 11:43:41 +0800 Subject: [PATCH 10/28] Add tests for specialization constants --- .../PSOCreationFailureTest.cpp | 124 ++++++ .../src/SpecializationConstantsTest.cpp | 383 ++++++++++++++++++ 2 files changed, 507 insertions(+) create mode 100644 Tests/DiligentCoreAPITest/src/SpecializationConstantsTest.cpp diff --git a/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp b/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp index fc91cc5dc8..117272e936 100644 --- a/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp +++ b/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp @@ -1759,4 +1759,128 @@ TEST_F(PSOCreationFailureTest, SpecConst_ErrorAtSecondElement) } +// --------------------------------------------------------------------------- +// Vulkan-specific tests for BuildSpecializationData (Name->SpecId matching). +// These require GLSL shaders with layout(constant_id) declarations so that +// the reflected specialization constants are available for matching. +// --------------------------------------------------------------------------- + +// Providing a user constant whose name does not match any reflected constant +// in the shader should succeed silently (the constant is simply skipped). +TEST_F(PSOCreationFailureTest, SpecConst_UnmatchedNameIsSkipped) +{ + auto* const pEnv = GPUTestingEnvironment::GetInstance(); + auto* const pDevice = pEnv->GetDevice(); + const auto& DeviceInfo = pDevice->GetDeviceInfo(); + + if (!DeviceInfo.IsVulkanDevice()) + GTEST_SKIP() << "Name->SpecId matching is currently Vulkan-only"; + if (DeviceInfo.Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + GTEST_SKIP() << "Specialization constants are not supported by this device"; + + // GLSL vertex shader with one specialization constant named "sc_Scale". + static constexpr char VSSource_GLSL[] = R"( + #version 450 + layout(constant_id = 0) const float sc_Scale = 1.0; + #ifndef GL_ES + out gl_PerVertex { vec4 gl_Position; }; + #endif + void main() + { + gl_Position = vec4(0.0, 0.0, 0.0, sc_Scale); + } + )"; + + static constexpr char PSSource_GLSL[] = R"( + #version 450 + layout(location = 0) out vec4 out_Color; + void main() + { + out_Color = vec4(0.0, 0.0, 0.0, 1.0); + } + )"; + + ShaderCreateInfo ShaderCI; + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + + RefCntAutoPtr pVS; + { + ShaderCI.Desc = {"SpecConst UnmatchedName VS", SHADER_TYPE_VERTEX, true}; + ShaderCI.Source = VSSource_GLSL; + pDevice->CreateShader(ShaderCI, &pVS); + ASSERT_TRUE(pVS); + } + + RefCntAutoPtr pPS; + { + ShaderCI.Desc = {"SpecConst UnmatchedName PS", SHADER_TYPE_PIXEL, true}; + ShaderCI.Source = PSSource_GLSL; + pDevice->CreateShader(ShaderCI, &pPS); + ASSERT_TRUE(pPS); + } + + const float Data = 2.0f; + SpecializationConstant SpecConsts[] = { + // This name does not exist in the shader; it should be silently skipped. + {"sc_NonExistent", SHADER_TYPE_VERTEX, sizeof(Data), &Data}, + }; + + auto PsoCI{GetGraphicsPSOCreateInfo("SpecConst UnmatchedNameIsSkipped")}; + PsoCI.pVS = pVS; + PsoCI.pPS = pPS; + PsoCI.NumSpecializationConstants = _countof(SpecConsts); + PsoCI.pSpecializationConstants = SpecConsts; + + RefCntAutoPtr pPSO; + pDevice->CreateGraphicsPipelineState(PsoCI, &pPSO); + EXPECT_NE(pPSO, nullptr) << "Unmatched specialization constant name should be silently skipped"; +} + +TEST_F(PSOCreationFailureTest, SpecConst_InsufficientData) +{ + auto* const pEnv = GPUTestingEnvironment::GetInstance(); + auto* const pDevice = pEnv->GetDevice(); + const auto& DeviceInfo = pDevice->GetDeviceInfo(); + + if (!DeviceInfo.IsVulkanDevice()) + GTEST_SKIP() << "Name->SpecId matching is currently Vulkan-only"; + if (DeviceInfo.Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + GTEST_SKIP() << "Specialization constants are not supported by this device"; + + // GLSL compute shader with a float specialization constant (4 bytes). + // The spec constant must be used so the compiler does not optimize it away. + static constexpr char CSSource_GLSL[] = R"( + #version 450 + layout(constant_id = 0) const float sc_Value = 1.0; + layout(rgba8, binding = 0) writeonly uniform image2D g_DummyUAV; + layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + void main() + { + imageStore(g_DummyUAV, ivec2(0), vec4(sc_Value)); + } + )"; + + ShaderCreateInfo ShaderCI; + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + ShaderCI.Desc = {"SpecConst InsufficientData CS", SHADER_TYPE_COMPUTE, true}; + ShaderCI.Source = CSSource_GLSL; + + RefCntAutoPtr pCS; + pDevice->CreateShader(ShaderCI, &pCS); + ASSERT_TRUE(pCS); + + // Provide 2 bytes for a 4-byte float constant (insufficient). + const Uint16 SmallData = 0; + SpecializationConstant SpecConsts[] = { + {"sc_Value", SHADER_TYPE_COMPUTE, sizeof(SmallData), &SmallData}, + }; + + auto PsoCI{GetComputePSOCreateInfo("PSO Create Failure - SpecConst InsufficientData")}; + PsoCI.pCS = pCS; + PsoCI.NumSpecializationConstants = _countof(SpecConsts); + PsoCI.pSpecializationConstants = SpecConsts; + TestCreatePSOFailure(PsoCI, "insufficient data"); +} + + } // namespace diff --git a/Tests/DiligentCoreAPITest/src/SpecializationConstantsTest.cpp b/Tests/DiligentCoreAPITest/src/SpecializationConstantsTest.cpp new file mode 100644 index 0000000000..6b30c2e5b3 --- /dev/null +++ b/Tests/DiligentCoreAPITest/src/SpecializationConstantsTest.cpp @@ -0,0 +1,383 @@ +/* + * Copyright 2026 Diligent Graphics LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +// Positive / functional tests for specialization constants. +// Verifies that specialization constant values affect shader output. +// +// PSO creation failure tests (validation of invalid SpecializationConstant entries) +// live in ObjectCreationFailure/PSOCreationFailureTest.cpp. + +#include "GPUTestingEnvironment.hpp" +#include "TestingSwapChainBase.hpp" +#include "GraphicsAccessories.hpp" +#include "FastRand.hpp" + +#include "gtest/gtest.h" + +namespace Diligent +{ +namespace Testing +{ +void RenderDrawCommandReference(ISwapChain* pSwapChain, const float* pClearColor = nullptr); +void ComputeShaderReference(ISwapChain* pSwapChain); +} // namespace Testing +} // namespace Diligent + +using namespace Diligent; +using namespace Diligent::Testing; + +namespace +{ + +// Spec-const shader: same gradient as FillTextureCS, but channel multipliers +// come from specialization constants. +// Reference output: vec4(vec2(xy % 256) / 256.0, 0.0, 1.0) +// Base color has non-zero B so that sc_MulB is not optimized away. +// To match: sc_MulR=1.0, sc_MulG=1.0, sc_MulB=0.0 +static constexpr char g_SpecConstComputeCS_GLSL[] = R"( + #version 450 + layout(constant_id = 0) const float sc_MulR = -1.0; + layout(constant_id = 1) const float sc_MulG = -1.0; + layout(constant_id = 2) const float sc_MulB = -1.0; + + layout(rgba8, binding = 0) writeonly uniform image2D g_tex2DUAV; + + layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; + void main() + { + ivec2 dim = imageSize(g_tex2DUAV); + ivec2 coord = ivec2(gl_GlobalInvocationID.xy); + if (coord.x >= dim.x || coord.y >= dim.y) + return; + vec2 uv = vec2(gl_GlobalInvocationID.xy % 256u) / 256.0; + // Base color has non-zero B channel so the compiler cannot + // eliminate sc_MulB as a dead specialization constant. + vec4 Color = vec4(uv.x * sc_MulR, + uv.y * sc_MulG, + uv.x * sc_MulB, + 1.0); + imageStore(g_tex2DUAV, coord, Color); + } +)"; + +// Vertex shader: hardcoded positions (same as DrawTest_ProceduralTriangleVS), +// per-vertex colors supplied via specialization constants (9 floats). +static constexpr char g_SpecConstGraphicsVS_GLSL[] = R"( + #version 450 + + #ifndef GL_ES + out gl_PerVertex { vec4 gl_Position; }; + #endif + + // Per-vertex colors as specialization constants (3 colors x RGB). + layout(constant_id = 0) const float sc_Col0_R = 0.0; + layout(constant_id = 1) const float sc_Col0_G = 0.0; + layout(constant_id = 2) const float sc_Col0_B = 0.0; + + layout(constant_id = 3) const float sc_Col1_R = 0.0; + layout(constant_id = 4) const float sc_Col1_G = 0.0; + layout(constant_id = 5) const float sc_Col1_B = 0.0; + + layout(constant_id = 6) const float sc_Col2_R = 0.0; + layout(constant_id = 7) const float sc_Col2_G = 0.0; + layout(constant_id = 8) const float sc_Col2_B = 0.0; + + layout(location = 0) out vec3 out_Color; + + void main() + { + vec4 Pos[6]; + Pos[0] = vec4(-1.0, -0.5, 0.0, 1.0); + Pos[1] = vec4(-0.5, +0.5, 0.0, 1.0); + Pos[2] = vec4( 0.0, -0.5, 0.0, 1.0); + + Pos[3] = vec4(+0.0, -0.5, 0.0, 1.0); + Pos[4] = vec4(+0.5, +0.5, 0.0, 1.0); + Pos[5] = vec4(+1.0, -0.5, 0.0, 1.0); + + vec3 Col[3]; + Col[0] = vec3(sc_Col0_R, sc_Col0_G, sc_Col0_B); + Col[1] = vec3(sc_Col1_R, sc_Col1_G, sc_Col1_B); + Col[2] = vec3(sc_Col2_R, sc_Col2_G, sc_Col2_B); + + gl_Position = Pos[gl_VertexIndex]; + out_Color = Col[gl_VertexIndex % 3]; + } +)"; + +// Fragment shader: interpolated color modulated by specialization constants. +// sc_Col0_R is shared with the vertex shader (tests cross-stage matching). +// sc_Brightness and sc_AlphaScale are PS-only. +// To match reference: sc_Col0_R = 1.0, sc_Brightness = 1.0, sc_AlphaScale = 1.0 +static constexpr char g_SpecConstGraphicsPS_GLSL[] = R"( + #version 450 + + // Shared with VS (same name, different constant_id in this module). + layout(constant_id = 0) const float sc_Col0_R = -1.0; + // PS-only constants. + layout(constant_id = 1) const float sc_Brightness = -1.0; + layout(constant_id = 2) const float sc_AlphaScale = -1.0; + + layout(location = 0) in vec3 in_Color; + layout(location = 0) out vec4 out_Color; + + void main() + { + out_Color = vec4(vec3(in_Color.r * sc_Col0_R, in_Color.gb) * sc_Brightness, + sc_AlphaScale); + } +)"; + + +class SpecializationConstants : public ::testing::Test +{ +protected: + static void TearDownTestSuite() + { + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + pEnv->Reset(); + } + + static void Present() + { + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + ISwapChain* pSwapChain = pEnv->GetSwapChain(); + + pSwapChain->Present(); + } + + static FastRandFloat sm_Rnd; +}; + +FastRandFloat SpecializationConstants::sm_Rnd{0, 0.f, 1.f}; + + +// --------------------------------------------------------------------------- +// Compute path: fill the swap chain back buffer via specialization constants. +// Uses ComputeShaderReference for the reference snapshot, just like +// InlineConstantsTest::ComputeResourceLayout. +// --------------------------------------------------------------------------- + +TEST_F(SpecializationConstants, ComputePath) +{ + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + IRenderDevice* pDevice = pEnv->GetDevice(); + IDeviceContext* pContext = pEnv->GetDeviceContext(); + ISwapChain* pSwapChain = pEnv->GetSwapChain(); + const RenderDeviceInfo& DeviceInfo = pDevice->GetDeviceInfo(); + + if (!DeviceInfo.IsVulkanDevice()) + GTEST_SKIP() << "Specialization constants are currently Vulkan-only"; + if (DeviceInfo.Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + GTEST_SKIP() << "Specialization constants are not supported by this device"; + if (!DeviceInfo.Features.ComputeShaders) + GTEST_SKIP() << "Compute shaders are not supported by this device"; + + GPUTestingEnvironment::ScopedReset EnvironmentAutoReset; + + RefCntAutoPtr pTestingSwapChain{pSwapChain, IID_TestingSwapChain}; + if (!pTestingSwapChain) + { + GTEST_SKIP() << "SpecializationConstants compute test requires testing swap chain"; + } + + const SwapChainDesc& SCDesc = pSwapChain->GetDesc(); + + // --- Reference pass: native-API compute dispatch + TakeSnapshot --- + ComputeShaderReference(pSwapChain); + + // --- Spec-const pass: same gradient, channel multipliers via specialization constants --- + { + ShaderCreateInfo ShaderCI; + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + ShaderCI.Desc = {"SpecConst Compute CS", SHADER_TYPE_COMPUTE, true}; + ShaderCI.EntryPoint = "main"; + ShaderCI.Source = g_SpecConstComputeCS_GLSL; + + RefCntAutoPtr pCS; + pDevice->CreateShader(ShaderCI, &pCS); + ASSERT_NE(pCS, nullptr); + + // Multipliers that reproduce the reference output: + // R channel = x-gradient * 1.0 + // G channel = y-gradient * 1.0 + // B channel = 0.0 * 0.0 = 0.0 + const float MulR = 1.0f; + const float MulG = 1.0f; + const float MulB = 0.0f; + SpecializationConstant SpecConsts[] = { + {"sc_MulR", SHADER_TYPE_COMPUTE, sizeof(float), &MulR}, + {"sc_MulG", SHADER_TYPE_COMPUTE, sizeof(float), &MulG}, + {"sc_MulB", SHADER_TYPE_COMPUTE, sizeof(float), &MulB}, + }; + + ComputePipelineStateCreateInfo PsoCI; + PsoCI.PSODesc.Name = "SpecConst Compute Test"; + PsoCI.PSODesc.PipelineType = PIPELINE_TYPE_COMPUTE; + PsoCI.pCS = pCS; + PsoCI.NumSpecializationConstants = _countof(SpecConsts); + PsoCI.pSpecializationConstants = SpecConsts; + + RefCntAutoPtr pPSO; + pDevice->CreateComputePipelineState(PsoCI, &pPSO); + ASSERT_NE(pPSO, nullptr); + + pPSO->GetStaticVariableByName(SHADER_TYPE_COMPUTE, "g_tex2DUAV")->Set(pTestingSwapChain->GetCurrentBackBufferUAV()); + + RefCntAutoPtr pSRB; + pPSO->CreateShaderResourceBinding(&pSRB, true); + ASSERT_NE(pSRB, nullptr); + + pContext->SetPipelineState(pPSO); + pContext->CommitShaderResources(pSRB, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + DispatchComputeAttribs DispatchAttribs; + DispatchAttribs.ThreadGroupCountX = (SCDesc.Width + 15) / 16; + DispatchAttribs.ThreadGroupCountY = (SCDesc.Height + 15) / 16; + pContext->DispatchCompute(DispatchAttribs); + } + + Present(); +} + + +// --------------------------------------------------------------------------- +// Graphics path: draw two triangles with per-vertex colors from spec constants. +// Uses RenderDrawCommandReference for the reference snapshot, just like +// DrawCommandTest and InlineConstantsTest. +// --------------------------------------------------------------------------- + +TEST_F(SpecializationConstants, GraphicsPath) +{ + GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance(); + IRenderDevice* pDevice = pEnv->GetDevice(); + IDeviceContext* pContext = pEnv->GetDeviceContext(); + ISwapChain* pSwapChain = pEnv->GetSwapChain(); + const RenderDeviceInfo& DeviceInfo = pDevice->GetDeviceInfo(); + + if (!DeviceInfo.IsVulkanDevice()) + GTEST_SKIP() << "Specialization constants are currently Vulkan-only"; + if (DeviceInfo.Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + GTEST_SKIP() << "Specialization constants are not supported by this device"; + + GPUTestingEnvironment::ScopedReset EnvironmentAutoReset; + + RefCntAutoPtr pTestingSwapChain{pSwapChain, IID_TestingSwapChain}; + if (!pTestingSwapChain) + { + GTEST_SKIP() << "SpecializationConstants graphics test requires testing swap chain"; + } + + const SwapChainDesc& SCDesc = pSwapChain->GetDesc(); + + // --- Reference pass: native-API two-triangle draw + TakeSnapshot --- + const float ClearColor[] = {sm_Rnd(), sm_Rnd(), sm_Rnd(), sm_Rnd()}; + RenderDrawCommandReference(pSwapChain, ClearColor); + + // --- Spec-const pass: same two triangles, colors via specialization constants --- + { + ITextureView* pRTVs[] = {pSwapChain->GetCurrentBackBufferRTV()}; + pContext->SetRenderTargets(1, pRTVs, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + pContext->ClearRenderTarget(pRTVs[0], ClearColor, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + ShaderCreateInfo ShaderCI; + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + + RefCntAutoPtr pVS; + { + ShaderCI.Desc = {"SpecConst Graphics VS", SHADER_TYPE_VERTEX, true}; + ShaderCI.Source = g_SpecConstGraphicsVS_GLSL; + pDevice->CreateShader(ShaderCI, &pVS); + ASSERT_NE(pVS, nullptr); + } + + RefCntAutoPtr pPS; + { + ShaderCI.Desc = {"SpecConst Graphics PS", SHADER_TYPE_PIXEL, true}; + ShaderCI.Source = g_SpecConstGraphicsPS_GLSL; + pDevice->CreateShader(ShaderCI, &pPS); + ASSERT_NE(pPS, nullptr); + } + + // Same per-vertex colors as DrawTest_ProceduralTriangleVS: + // Col[0] = (1, 0, 0) Col[1] = (0, 1, 0) Col[2] = (0, 0, 1) + const float3 Col0{1.0f, 0.0f, 0.0f}; + const float3 Col1{0.0f, 1.0f, 0.0f}; + const float3 Col2{0.0f, 0.0f, 1.0f}; + + // PS-only constants + const float Brightness = 1.0f; + const float AlphaScale = 1.0f; + + // clang-format off + SpecializationConstant SpecConsts[] = { + // sc_Col0_R is declared in both VS and PS: test cross-stage matching. + {"sc_Col0_R", SHADER_TYPE_VS_PS, sizeof(float), &Col0.r}, // Used in both VS and PS + {"sc_Col0_G", SHADER_TYPE_VS_PS, sizeof(float), &Col0.g}, // Used in VS only + {"sc_Col0_B", SHADER_TYPE_VS_PS, sizeof(float), &Col0.b}, // Used in VS only + {"sc_Col1_R", SHADER_TYPE_VERTEX, sizeof(float), &Col1.r}, + {"sc_Col1_G", SHADER_TYPE_VERTEX, sizeof(float), &Col1.g}, + {"sc_Col1_B", SHADER_TYPE_VERTEX, sizeof(float), &Col1.b}, + {"sc_Col2_R", SHADER_TYPE_VERTEX, sizeof(float), &Col2.r}, + {"sc_Col2_G", SHADER_TYPE_VERTEX, sizeof(float), &Col2.g}, + {"sc_Col2_B", SHADER_TYPE_VERTEX, sizeof(float), &Col2.b}, + // PS-only constants + {"sc_Brightness", SHADER_TYPE_PIXEL, sizeof(float), &Brightness}, + {"sc_AlphaScale", SHADER_TYPE_PIXEL, sizeof(float), &AlphaScale}, + }; + // clang-format on + + GraphicsPipelineStateCreateInfo PsoCI; + PsoCI.PSODesc.Name = "SpecConst Graphics Test"; + PsoCI.pVS = pVS; + PsoCI.pPS = pPS; + PsoCI.GraphicsPipeline.NumRenderTargets = 1; + PsoCI.GraphicsPipeline.RTVFormats[0] = SCDesc.ColorBufferFormat; + PsoCI.GraphicsPipeline.PrimitiveTopology = PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + PsoCI.GraphicsPipeline.DepthStencilDesc.DepthEnable = False; + PsoCI.NumSpecializationConstants = _countof(SpecConsts); + PsoCI.pSpecializationConstants = SpecConsts; + + RefCntAutoPtr pPSO; + pDevice->CreateGraphicsPipelineState(PsoCI, &pPSO); + ASSERT_NE(pPSO, nullptr); + + RefCntAutoPtr pSRB; + pPSO->CreateShaderResourceBinding(&pSRB, true); + ASSERT_NE(pSRB, nullptr); + + pContext->SetPipelineState(pPSO); + pContext->CommitShaderResources(pSRB, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + DrawAttribs drawAttribs{6, DRAW_FLAG_VERIFY_ALL}; + pContext->Draw(drawAttribs); + } + + Present(); +} + + +} // namespace From 2149085633749f62e8ca24096b4fabb2688a915b Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Fri, 27 Feb 2026 15:26:40 +0800 Subject: [PATCH 11/28] Add specialization constants support to WGSLShaderResource --- .../include/WGSLShaderResources.hpp | 43 +++++++++-- .../ShaderTools/src/WGSLShaderResources.cpp | 71 ++++++++++++++++++- 2 files changed, 108 insertions(+), 6 deletions(-) diff --git a/Graphics/ShaderTools/include/WGSLShaderResources.hpp b/Graphics/ShaderTools/include/WGSLShaderResources.hpp index d884dc7c40..6146536965 100644 --- a/Graphics/ShaderTools/include/WGSLShaderResources.hpp +++ b/Graphics/ShaderTools/include/WGSLShaderResources.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2024 Diligent Graphics LLC + * Copyright 2024-2026 Diligent Graphics LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,8 +32,8 @@ // WGSLShaderResources class uses continuous chunk of memory to store all resources, as follows: // // m_MemoryBuffer -// | | -// | Uniform Buffers | Storage Buffers | Textures | Storage Textures | Samplers | Ext Textures | Resource Names | +// | | +// | Uniform Buffers | Storage Buffers | Textures | Storage Textures | Samplers | Ext Textures | Spec Constants | Resource Names | #include #include @@ -159,6 +159,34 @@ struct WGSLShaderResourceAttribs static_assert(sizeof(WGSLShaderResourceAttribs) % sizeof(void*) == 0, "Size of WGSLShaderResourceAttribs struct must be a multiple of sizeof(void*)"); +struct WGSLSpecializationConstantAttribs +{ + // clang-format off + +/* 0 */const char* const Name; +/* 8 */const Uint16 OverrideId; +/* 10 */const Uint8 Type; // SHADER_CODE_BASIC_TYPE +/* 11 */const Uint8 Padding; +/* 12 */const Uint32 Reserved; +/* 16 */ // End of structure + + // clang-format on + + WGSLSpecializationConstantAttribs(const char* _Name, + Uint16 _OverrideId, + SHADER_CODE_BASIC_TYPE _Type) noexcept : + Name{_Name}, + OverrideId{_OverrideId}, + Type{static_cast(_Type)}, + Padding{0}, + Reserved{0} + {} + + SHADER_CODE_BASIC_TYPE GetType() const { return static_cast(Type); } +}; +static_assert(sizeof(WGSLSpecializationConstantAttribs) % sizeof(void*) == 0, "Size of WGSLSpecializationConstantAttribs struct must be a multiple of sizeof(void*)"); + + /// Diligent::WGSLShaderResources class class WGSLShaderResources { @@ -191,6 +219,7 @@ class WGSLShaderResources Uint32 GetNumSamplers ()const noexcept{ return (m_ExternalTextureOffset - m_SamplerOffset); } Uint32 GetNumExtTextures ()const noexcept{ return (m_TotalResources - m_ExternalTextureOffset);} Uint32 GetTotalResources ()const noexcept{ return m_TotalResources; } + Uint32 GetNumSpecConstants()const noexcept{ return m_NumSpecConstants; } const WGSLShaderResourceAttribs& GetUB (Uint32 n) const noexcept { return GetResAttribs(n, GetNumUBs(), 0 ); } const WGSLShaderResourceAttribs& GetSB (Uint32 n) const noexcept { return GetResAttribs(n, GetNumSBs(), m_StorageBufferOffset ); } @@ -200,6 +229,8 @@ class WGSLShaderResources const WGSLShaderResourceAttribs& GetExtTexture(Uint32 n) const noexcept { return GetResAttribs(n, GetNumExtTextures(), m_ExternalTextureOffset); } const WGSLShaderResourceAttribs& GetResource (Uint32 n) const noexcept { return GetResAttribs(n, GetTotalResources(), 0 ); } + const WGSLSpecializationConstantAttribs& GetSpecConstant(Uint32 n) const noexcept; + // clang-format on const ShaderCodeBufferDesc* GetUniformBufferDesc(Uint32 Index) const @@ -308,6 +339,7 @@ class WGSLShaderResources private: void Initialize(IMemoryAllocator& Allocator, const ResourceCounters& Counters, + Uint32 NumSpecConstants, size_t ResourceNamesPoolSize, StringPool& ResourceNamesPool); @@ -335,11 +367,13 @@ class WGSLShaderResources WGSLShaderResourceAttribs& GetExtTexture(Uint32 n) noexcept { return GetResAttribs(n, GetNumExtTextures(), m_ExternalTextureOffset); } WGSLShaderResourceAttribs& GetResource (Uint32 n) noexcept { return GetResAttribs(n, GetTotalResources(), 0 ); } + WGSLSpecializationConstantAttribs& GetSpecConstant(Uint32 n) noexcept; + // clang-format on private: // Memory buffer that holds all resources as continuous chunk of memory: - // | UBs | SBs | Textures | StorageTex | Samplers | ExternalTex | Resource Names | + // | UBs | SBs | Textures | StorageTex | Samplers | ExternalTex | Spec Constants | Resource Names | std::unique_ptr> m_MemoryBuffer; std::unique_ptr> m_UBReflectionBuffer; @@ -355,6 +389,7 @@ class WGSLShaderResources OffsetType m_SamplerOffset = 0; OffsetType m_ExternalTextureOffset = 0; OffsetType m_TotalResources = 0; + OffsetType m_NumSpecConstants = 0; SHADER_TYPE m_ShaderType = SHADER_TYPE_UNKNOWN; }; diff --git a/Graphics/ShaderTools/src/WGSLShaderResources.cpp b/Graphics/ShaderTools/src/WGSLShaderResources.cpp index ca77db0709..fbd417914a 100644 --- a/Graphics/ShaderTools/src/WGSLShaderResources.cpp +++ b/Graphics/ShaderTools/src/WGSLShaderResources.cpp @@ -901,9 +901,17 @@ WGSLShaderResources::WGSLShaderResources(IMemoryAllocator& Allocator, ResourceNamesPoolSize += strlen(ShaderName) + 1; ResourceNamesPoolSize += strlen(EntryPoint) + 1; + // Count override constants (specialization constants) + const auto& Overrides = EntryPoints[EntryPointIdx].overrides; + Uint32 NumSpecConstants = static_cast(Overrides.size()); + for (const auto& Override : Overrides) + { + ResourceNamesPoolSize += Override.name.length() + 1; + } + // Resource names pool is only needed to facilitate string allocation. StringPool ResourceNamesPool; - Initialize(Allocator, ResCounters, ResourceNamesPoolSize, ResourceNamesPool); + Initialize(Allocator, ResCounters, NumSpecConstants, ResourceNamesPoolSize, ResourceNamesPool); // Uniform buffer reflections std::vector UBReflections; @@ -979,6 +987,31 @@ WGSLShaderResources::WGSLShaderResources(IMemoryAllocator& Allocator, VERIFY_EXPR(CurrRes.NumSamplers == GetNumSamplers()); VERIFY_EXPR(CurrRes.NumExtTextures == GetNumExtTextures()); + // Construct specialization constant attribs + { + using TintOverrideType = tint::inspector::Override::Type; + Uint32 SpecConstIdx = 0; + for (const auto& Override : Overrides) + { + const char* SCName = ResourceNamesPool.CopyString(Override.name); + SHADER_CODE_BASIC_TYPE SCType = SHADER_CODE_BASIC_TYPE_UNKNOWN; + switch (Override.type) + { + // clang-format off + case TintOverrideType::kBool: SCType = SHADER_CODE_BASIC_TYPE_BOOL; break; + case TintOverrideType::kFloat32: SCType = SHADER_CODE_BASIC_TYPE_FLOAT; break; + case TintOverrideType::kUint32: SCType = SHADER_CODE_BASIC_TYPE_UINT; break; + case TintOverrideType::kInt32: SCType = SHADER_CODE_BASIC_TYPE_INT; break; + case TintOverrideType::kFloat16: SCType = SHADER_CODE_BASIC_TYPE_FLOAT16; break; + // clang-format on + default: + UNEXPECTED("Unexpected override type"); + } + new (&GetSpecConstant(SpecConstIdx++)) WGSLSpecializationConstantAttribs{SCName, Override.id.value, SCType}; + } + VERIFY_EXPR(SpecConstIdx == GetNumSpecConstants()); + } + if (CombinedSamplerSuffix != nullptr) { m_CombinedSamplerSuffix = ResourceNamesPool.CopyString(CombinedSamplerSuffix); @@ -1002,6 +1035,7 @@ WGSLShaderResources::WGSLShaderResources(IMemoryAllocator& Allocator, void WGSLShaderResources::Initialize(IMemoryAllocator& Allocator, const ResourceCounters& Counters, + Uint32 NumSpecConstants, size_t ResourceNamesPoolSize, StringPool& ResourceNamesPool) { @@ -1023,13 +1057,16 @@ void WGSLShaderResources::Initialize(IMemoryAllocator& Allocator, m_SamplerOffset = AdvanceOffset(Counters.NumSamplers); m_ExternalTextureOffset = AdvanceOffset(Counters.NumExtTextures); m_TotalResources = AdvanceOffset(0); + m_NumSpecConstants = static_cast(NumSpecConstants); static_assert(Uint32{WGSLShaderResourceAttribs::ResourceType::NumResourceTypes} == 13, "Please update the new resource type offset"); size_t AlignedResourceNamesPoolSize = AlignUp(ResourceNamesPoolSize, sizeof(void*)); static_assert(sizeof(WGSLShaderResourceAttribs) % sizeof(void*) == 0, "Size of WGSLShaderResourceAttribs struct must be a multiple of sizeof(void*)"); + static_assert(sizeof(WGSLSpecializationConstantAttribs) % sizeof(void*) == 0, "Size of WGSLSpecializationConstantAttribs struct must be a multiple of sizeof(void*)"); // clang-format off size_t MemorySize = m_TotalResources * sizeof(WGSLShaderResourceAttribs) + + m_NumSpecConstants * sizeof(WGSLSpecializationConstantAttribs) + AlignedResourceNamesPoolSize * sizeof(char); VERIFY_EXPR(GetNumUBs() == Counters.NumUBs); @@ -1046,7 +1083,8 @@ void WGSLShaderResources::Initialize(IMemoryAllocator& Allocator, void* pRawMem = Allocator.Allocate(MemorySize, "Memory for shader resources", __FILE__, __LINE__); m_MemoryBuffer = std::unique_ptr>(pRawMem, Allocator); char* NamesPool = reinterpret_cast(m_MemoryBuffer.get()) + - m_TotalResources * sizeof(WGSLShaderResourceAttribs); + m_TotalResources * sizeof(WGSLShaderResourceAttribs) + + m_NumSpecConstants * sizeof(WGSLSpecializationConstantAttribs); ResourceNamesPool.AssignMemory(NamesPool, ResourceNamesPoolSize); } } @@ -1055,6 +1093,21 @@ WGSLShaderResources::~WGSLShaderResources() { for (Uint32 n = 0; n < GetTotalResources(); ++n) GetResource(n).~WGSLShaderResourceAttribs(); + + for (Uint32 n = 0; n < GetNumSpecConstants(); ++n) + GetSpecConstant(n).~WGSLSpecializationConstantAttribs(); +} + +const WGSLSpecializationConstantAttribs& WGSLShaderResources::GetSpecConstant(Uint32 n) const noexcept +{ + VERIFY(n < m_NumSpecConstants, "Specialization constant index (", n, ") is out of range. Total spec constant count: ", m_NumSpecConstants); + const WGSLShaderResourceAttribs* ResourceMemoryEnd = reinterpret_cast(m_MemoryBuffer.get()) + GetTotalResources(); + return reinterpret_cast(ResourceMemoryEnd)[n]; +} + +WGSLSpecializationConstantAttribs& WGSLShaderResources::GetSpecConstant(Uint32 n) noexcept +{ + return const_cast(const_cast(this)->GetSpecConstant(n)); } std::string WGSLShaderResources::DumpResources() @@ -1173,6 +1226,20 @@ std::string WGSLShaderResources::DumpResources() ); VERIFY_EXPR(ResNum == GetTotalResources()); + if (GetNumSpecConstants() > 0) + { + ss << std::endl + << "Spec Constants (" << GetNumSpecConstants() << "):"; + for (Uint32 n = 0; n < GetNumSpecConstants(); ++n) + { + const auto& SC = GetSpecConstant(n); + ss << std::endl + << std::setw(3) << n << " Spec Constant " + << std::setw(32) << ('\'' + std::string{SC.Name} + '\'') + << " id=" << SC.OverrideId; + } + } + return ss.str(); } From 968182c3d5d028e32fadcbea9febf2395c2f47fd Mon Sep 17 00:00:00 2001 From: assiduous Date: Fri, 27 Feb 2026 08:26:05 -0800 Subject: [PATCH 12/28] WGSLShaderResources: factor out TintOverrideTypeToShaderCodeBasicType helper --- .../include/WGSLShaderResources.hpp | 7 +--- .../ShaderTools/src/WGSLShaderResources.cpp | 41 +++++++++++-------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Graphics/ShaderTools/include/WGSLShaderResources.hpp b/Graphics/ShaderTools/include/WGSLShaderResources.hpp index 6146536965..9266b31436 100644 --- a/Graphics/ShaderTools/include/WGSLShaderResources.hpp +++ b/Graphics/ShaderTools/include/WGSLShaderResources.hpp @@ -166,8 +166,7 @@ struct WGSLSpecializationConstantAttribs /* 0 */const char* const Name; /* 8 */const Uint16 OverrideId; /* 10 */const Uint8 Type; // SHADER_CODE_BASIC_TYPE -/* 11 */const Uint8 Padding; -/* 12 */const Uint32 Reserved; +/* 11 */ /* 16 */ // End of structure // clang-format on @@ -177,9 +176,7 @@ struct WGSLSpecializationConstantAttribs SHADER_CODE_BASIC_TYPE _Type) noexcept : Name{_Name}, OverrideId{_OverrideId}, - Type{static_cast(_Type)}, - Padding{0}, - Reserved{0} + Type{static_cast(_Type)} {} SHADER_CODE_BASIC_TYPE GetType() const { return static_cast(Type); } diff --git a/Graphics/ShaderTools/src/WGSLShaderResources.cpp b/Graphics/ShaderTools/src/WGSLShaderResources.cpp index fbd417914a..2ad22f43bb 100644 --- a/Graphics/ShaderTools/src/WGSLShaderResources.cpp +++ b/Graphics/ShaderTools/src/WGSLShaderResources.cpp @@ -321,6 +321,24 @@ WEB_GPU_BINDING_TYPE GetWebGPUTextureBindingType(WGSLShaderResourceAttribs::Text } } +SHADER_CODE_BASIC_TYPE TintOverrideTypeToShaderCodeBasicType(tint::inspector::Override::Type OverrideType) +{ + using TintOverrideType = tint::inspector::Override::Type; + switch (OverrideType) + { + // clang-format off + case TintOverrideType::kBool: return SHADER_CODE_BASIC_TYPE_BOOL; + case TintOverrideType::kFloat32: return SHADER_CODE_BASIC_TYPE_FLOAT; + case TintOverrideType::kUint32: return SHADER_CODE_BASIC_TYPE_UINT; + case TintOverrideType::kInt32: return SHADER_CODE_BASIC_TYPE_INT; + case TintOverrideType::kFloat16: return SHADER_CODE_BASIC_TYPE_FLOAT16; + // clang-format on + default: + UNEXPECTED("Unexpected override type"); + return SHADER_CODE_BASIC_TYPE_UNKNOWN; + } +} + } // namespace WGSLShaderResourceAttribs::WGSLShaderResourceAttribs(const char* _Name, @@ -904,7 +922,7 @@ WGSLShaderResources::WGSLShaderResources(IMemoryAllocator& Allocator, // Count override constants (specialization constants) const auto& Overrides = EntryPoints[EntryPointIdx].overrides; Uint32 NumSpecConstants = static_cast(Overrides.size()); - for (const auto& Override : Overrides) + for (const tint::inspector::Override& Override : Overrides) { ResourceNamesPoolSize += Override.name.length() + 1; } @@ -989,24 +1007,11 @@ WGSLShaderResources::WGSLShaderResources(IMemoryAllocator& Allocator, // Construct specialization constant attribs { - using TintOverrideType = tint::inspector::Override::Type; - Uint32 SpecConstIdx = 0; - for (const auto& Override : Overrides) + Uint32 SpecConstIdx = 0; + for (const tint::inspector::Override& Override : Overrides) { const char* SCName = ResourceNamesPool.CopyString(Override.name); - SHADER_CODE_BASIC_TYPE SCType = SHADER_CODE_BASIC_TYPE_UNKNOWN; - switch (Override.type) - { - // clang-format off - case TintOverrideType::kBool: SCType = SHADER_CODE_BASIC_TYPE_BOOL; break; - case TintOverrideType::kFloat32: SCType = SHADER_CODE_BASIC_TYPE_FLOAT; break; - case TintOverrideType::kUint32: SCType = SHADER_CODE_BASIC_TYPE_UINT; break; - case TintOverrideType::kInt32: SCType = SHADER_CODE_BASIC_TYPE_INT; break; - case TintOverrideType::kFloat16: SCType = SHADER_CODE_BASIC_TYPE_FLOAT16; break; - // clang-format on - default: - UNEXPECTED("Unexpected override type"); - } + SHADER_CODE_BASIC_TYPE SCType = TintOverrideTypeToShaderCodeBasicType(Override.type); new (&GetSpecConstant(SpecConstIdx++)) WGSLSpecializationConstantAttribs{SCName, Override.id.value, SCType}; } VERIFY_EXPR(SpecConstIdx == GetNumSpecConstants()); @@ -1066,7 +1071,7 @@ void WGSLShaderResources::Initialize(IMemoryAllocator& Allocator, static_assert(sizeof(WGSLSpecializationConstantAttribs) % sizeof(void*) == 0, "Size of WGSLSpecializationConstantAttribs struct must be a multiple of sizeof(void*)"); // clang-format off size_t MemorySize = m_TotalResources * sizeof(WGSLShaderResourceAttribs) + - m_NumSpecConstants * sizeof(WGSLSpecializationConstantAttribs) + + m_NumSpecConstants * sizeof(WGSLSpecializationConstantAttribs) + AlignedResourceNamesPoolSize * sizeof(char); VERIFY_EXPR(GetNumUBs() == Counters.NumUBs); From b318706e241e34ee9871e6375e92f52048454090 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Fri, 27 Feb 2026 15:27:24 +0800 Subject: [PATCH 13/28] Add specialization constant parsing tests for WGSLShaderResource --- .../ShaderTools/WGSLShaderResourcesTest.cpp | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/Tests/DiligentCoreTest/src/ShaderTools/WGSLShaderResourcesTest.cpp b/Tests/DiligentCoreTest/src/ShaderTools/WGSLShaderResourcesTest.cpp index 21369f3fe2..41a4245999 100644 --- a/Tests/DiligentCoreTest/src/ShaderTools/WGSLShaderResourcesTest.cpp +++ b/Tests/DiligentCoreTest/src/ShaderTools/WGSLShaderResourcesTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2024 Diligent Graphics LLC + * Copyright 2024-2026 Diligent Graphics LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -258,4 +258,69 @@ TEST(WGSLShaderResources, RWStructBufferArrays) }); } +TEST(WGSLShaderResources, SpecializationConstants) +{ + // WGSL source with override (specialization) constants of various types. + static constexpr char WGSL[] = R"( + override sc_float: f32 = 1.0; + override sc_int: i32 = 0; + override sc_uint: u32 = 0; + override sc_bool: bool = false; + + @group(0) @binding(0) var output: array; + + @compute @workgroup_size(1) + fn main() { + output[0] = sc_float; + output[1] = f32(sc_int); + output[2] = f32(sc_uint); + if sc_bool { + output[3] = 1.0; + } + } + )"; + + WGSLShaderResources Resources{ + GetRawAllocator(), + WGSL, + SHADER_SOURCE_LANGUAGE_WGSL, + "SpecConst test", + nullptr, // CombinedSamplerSuffix + "main", // EntryPoint + nullptr, // ArrayIndexSuffix + false, // LoadUniformBufferReflection + nullptr // ppTintOutput + }; + LOG_INFO_MESSAGE("WGSL Resources:\n", Resources.DumpResources()); + + // One storage buffer resource + EXPECT_EQ(Resources.GetTotalResources(), 1u); + EXPECT_EQ(Resources.GetNumSBs(), 1u); + + const std::vector RefSpecConstants = { + {"sc_float", 0, SHADER_CODE_BASIC_TYPE_FLOAT}, + {"sc_int", 0, SHADER_CODE_BASIC_TYPE_INT}, + {"sc_uint", 0, SHADER_CODE_BASIC_TYPE_UINT}, + {"sc_bool", 0, SHADER_CODE_BASIC_TYPE_BOOL}, + }; + + const WGSLShaderResources& ConstResources = Resources; + EXPECT_EQ(ConstResources.GetNumSpecConstants(), static_cast(RefSpecConstants.size())); + + // Build a map from name to reference for order-independent matching + std::unordered_map RefMap; + for (const WGSLSpecializationConstantAttribs& Ref : RefSpecConstants) + RefMap[Ref.Name] = &Ref; + + for (Uint32 i = 0; i < ConstResources.GetNumSpecConstants(); ++i) + { + const WGSLSpecializationConstantAttribs& SC = ConstResources.GetSpecConstant(i); + const auto it = RefMap.find(SC.Name); + ASSERT_NE(it, RefMap.end()) << "Specialization constant '" << SC.Name << "' is not found in the reference list"; + + const WGSLSpecializationConstantAttribs* pRef = it->second; + EXPECT_EQ(SC.GetType(), pRef->GetType()) << SC.Name; + } +} + } // namespace From df4eb0949479c3160ca058b0b6e912dfb2be1eb2 Mon Sep 17 00:00:00 2001 From: assiduous Date: Tue, 3 Mar 2026 19:03:59 -0800 Subject: [PATCH 14/28] Common: add Float16 type + conversions --- Common/CMakeLists.txt | 1 + Common/interface/Float16.hpp | 222 ++++++++++ .../src/Common/Float16Test.cpp | 391 ++++++++++++++++++ 3 files changed, 614 insertions(+) create mode 100644 Common/interface/Float16.hpp create mode 100644 Tests/DiligentCoreTest/src/Common/Float16Test.cpp diff --git a/Common/CMakeLists.txt b/Common/CMakeLists.txt index 22238239aa..447cdd8bb9 100644 --- a/Common/CMakeLists.txt +++ b/Common/CMakeLists.txt @@ -12,6 +12,7 @@ set(INTERFACE interface/Array2DTools.hpp interface/AsyncInitializer.hpp interface/BasicMath.hpp + interface/Float16.hpp interface/BasicFileStream.hpp interface/DataBlobImpl.hpp interface/DefaultRawMemoryAllocator.hpp diff --git a/Common/interface/Float16.hpp b/Common/interface/Float16.hpp new file mode 100644 index 0000000000..ba5820349b --- /dev/null +++ b/Common/interface/Float16.hpp @@ -0,0 +1,222 @@ +/* + * Copyright 2026 Diligent Graphics LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Diligent +{ + +class Float16 +{ +public: + constexpr Float16() noexcept = default; + constexpr explicit Float16(uint16_t Bits) noexcept : + m_Bits{Bits} + {} + + explicit Float16(float f) noexcept : + m_Bits(FloatToHalfBits(f)) + {} + + explicit Float16(double d) noexcept : + m_Bits(DoubleToHalfBits(d)) + {} + + explicit Float16(int32_t i) noexcept : + m_Bits(FloatToHalfBits(static_cast(i))) + {} + + explicit operator float() const noexcept + { + return HalfBitsToFloat(m_Bits); + } + + explicit operator double() const noexcept + { + return static_cast(HalfBitsToFloat(m_Bits)); + } + + // Int32 conversion: trunc toward 0, saturate on overflow, NaN->0 + explicit operator int32_t() const noexcept + { + const float f = HalfBitsToFloat(m_Bits); + + if (std::isnan(f)) return 0; + if (f >= static_cast(std::numeric_limits::max())) + return std::numeric_limits::max(); + if (f <= static_cast(std::numeric_limits::min())) + return std::numeric_limits::min(); + + return static_cast(f); // C++ truncates toward 0 + } + + bool IsZero() const { return (m_Bits & 0x7FFFu) == 0; } + bool Sign() const { return (m_Bits >> 15) != 0; } + uint16_t Raw() const { return m_Bits; } + + + static float HalfBitsToFloat(uint16_t h) + { + const uint32_t sign = (uint32_t(h) & 0x8000u) << 16; + const uint32_t exp = (h >> 10) & 0x1Fu; + const uint32_t mant = h & 0x03FFu; + + uint32_t fbits = 0; + + if (exp == 0) + { + if (mant == 0) + { + // +/-0 + fbits = sign; + } + else + { + // Subnormal: normalize mantissa + // value = mant * 2^-24 + // Convert to float bits by shifting into float mantissa with adjusted exponent. + uint32_t m = mant; + int e = -14; + while ((m & 0x0400u) == 0) + { + m <<= 1; + --e; + } + m &= 0x03FFu; + const uint32_t exp_f = uint32_t(e + 127); + fbits = sign | (exp_f << 23) | (m << 13); + } + } + else if (exp == 0x1F) + { + // Inf/NaN + fbits = sign | 0x7F800000u | (mant << 13); + if (mant != 0) fbits |= 0x00400000u; // Make sure it's a quiet NaN in float + } + else + { + // Normal + const uint32_t exp_f = exp + (127 - 15); + fbits = sign | (exp_f << 23) | (mant << 13); + } + + float out; + std::memcpy(&out, &fbits, sizeof(out)); + return out; + } + + static uint16_t DoubleToHalfBits(double d) + { + // Convert via float to keep code smaller; every half is exactly representable as float. + return FloatToHalfBits(static_cast(d)); + } + + // float -> half (binary16), round-to-nearest-even + static uint16_t FloatToHalfBits(float f) + { + uint32_t x; + std::memcpy(&x, &f, sizeof(x)); + + const uint32_t sign = (x >> 16) & 0x8000u; + uint32_t exp = (x >> 23) & 0xFFu; + uint32_t mant = x & 0x007FFFFFu; + + // NaN/Inf + if (exp == 0xFFu) + { + if (mant == 0) return static_cast(sign | 0x7C00u); // Inf + // Preserve some payload; ensure qNaN + uint16_t payload = static_cast(mant >> 13); + if (payload == 0) payload = 1; + return static_cast(sign | 0x7C00u | payload | 0x0200u); + } + + // Unbias exponent from float, then bias to half + int32_t e = static_cast(exp) - 127 + 15; + + // Handle subnormals/underflow + if (e <= 0) + { + if (e < -10) + { + // Too small -> signed zero + return static_cast(sign); + } + + // Make implicit leading 1 explicit + mant |= 0x00800000u; + + // Shift to subnormal half mantissa position + const int shift = 1 - e; // 1..10 + uint32_t mant_shifted = mant >> (shift + 13); + + // Round-to-nearest-even using the bits we threw away + const uint32_t round_mask = (1u << (shift + 13)) - 1u; + const uint32_t round_bits = mant & round_mask; + const uint32_t halfway = 1u << (shift + 12); + + if (round_bits > halfway || (round_bits == halfway && (mant_shifted & 1u))) + mant_shifted++; + + return static_cast(sign | static_cast(mant_shifted)); + } + + // Overflow -> Inf + if (e >= 31) + { + return static_cast(sign | 0x7C00u); + } + + // Normal case: round mantissa from 23 to 10 bits + uint32_t mant_half = mant >> 13; + const uint32_t round_bits = mant & 0x1FFFu; // lower 13 bits + + // Round-to-nearest-even + if (round_bits > 0x1000u || (round_bits == 0x1000u && (mant_half & 1u))) + { + mant_half++; + if (mant_half == 0x0400u) // mantissa overflow + { + mant_half = 0; + e++; + if (e >= 31) return static_cast(sign | 0x7C00u); + } + } + + return static_cast(sign | (static_cast(e) << 10) | static_cast(mant_half)); + } + +private: + uint16_t m_Bits{0}; +}; + +} // namespace Diligent diff --git a/Tests/DiligentCoreTest/src/Common/Float16Test.cpp b/Tests/DiligentCoreTest/src/Common/Float16Test.cpp new file mode 100644 index 0000000000..3853708b1a --- /dev/null +++ b/Tests/DiligentCoreTest/src/Common/Float16Test.cpp @@ -0,0 +1,391 @@ +/* + * Copyright 2026 Diligent Graphics LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#include "Float16.hpp" + +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include + +using namespace Diligent; + +namespace +{ + +float FloatFromBits(uint32_t bits) +{ + float f; + std::memcpy(&f, &bits, sizeof(f)); + return f; +} + +uint32_t BitsFromFloat(float f) +{ + uint32_t bits; + std::memcpy(&bits, &f, sizeof(bits)); + return bits; +} + +bool IsNegZero(float f) +{ + return f == 0.0f && ((BitsFromFloat(f) & 0x80000000u) != 0); +} + +// Your implementation ensures: +// - HalfBitsToFloat produces a quiet NaN for half NaNs +// - FloatToHalfBits produces a quiet NaN for float NaNs and forces non-zero payload +// +// This canonicalization makes exhaustive round-trip tests stable. +uint16_t CanonicalizeHalfForRoundTrip(uint16_t h) +{ + const uint16_t sign = h & 0x8000u; + const uint16_t exp = (h >> 10) & 0x1Fu; + uint16_t mant = h & 0x03FFu; + + if (exp == 0x1Fu && mant != 0) + { + // Force quiet bit (mantissa bit 9 in half = 0x0200) + mant |= 0x0200u; + // Ensure payload remains non-zero (it already is since mant != 0) + return static_cast(sign | 0x7C00u | mant); + } + return h; +} + +} // namespace + +// -------------------- Basic bit-pattern checks -------------------- + +TEST(Common_Float16, DefaultIsPositiveZero) +{ + Float16 h; + EXPECT_EQ(h.Raw(), 0x0000u); + EXPECT_TRUE(h.IsZero()); + EXPECT_FALSE(h.Sign()); + + float f = static_cast(h); + EXPECT_EQ(f, 0.0f); + EXPECT_FALSE(IsNegZero(f)); +} + +TEST(Common_Float16, NegativeZeroRoundTrip) +{ + Float16 h{static_cast(0x8000u)}; + EXPECT_TRUE(h.IsZero()); + EXPECT_TRUE(h.Sign()); + + float f = static_cast(h); + EXPECT_EQ(f, 0.0f); + EXPECT_TRUE(IsNegZero(f)); + + Float16 back{f}; + EXPECT_EQ(back.Raw(), 0x8000u); +} + +TEST(Common_Float16, OneAndMinusOne) +{ + Float16 hp{1.0f}; + Float16 hn{-1.0f}; + + EXPECT_EQ(hp.Raw(), 0x3C00u); // +1 + EXPECT_EQ(hn.Raw(), 0xBC00u); // -1 + + EXPECT_EQ(static_cast(hp), 1.0f); + EXPECT_EQ(static_cast(hn), -1.0f); +} + +TEST(Common_Float16, Infinity) +{ + Float16 pinf{std::numeric_limits::infinity()}; + Float16 ninf{-std::numeric_limits::infinity()}; + + EXPECT_EQ(pinf.Raw(), 0x7C00u); + EXPECT_EQ(ninf.Raw(), 0xFC00u); + + EXPECT_TRUE(std::isinf(static_cast(pinf))); + EXPECT_TRUE(std::isinf(static_cast(ninf))); + EXPECT_GT(static_cast(pinf), 0.0f); + EXPECT_LT(static_cast(ninf), 0.0f); + + // Half bits to float bits check + EXPECT_EQ(BitsFromFloat(static_cast(pinf)), 0x7F800000u); + EXPECT_EQ(BitsFromFloat(static_cast(ninf)), 0xFF800000u); +} + +TEST(Common_Float16, NaNFromFloat) +{ + // A signaling-ish NaN pattern in float; regardless, input is NaN. + float fnan = FloatFromBits(0x7FA12345u); + EXPECT_TRUE(std::isnan(fnan)); + + Float16 h{fnan}; + + // Must become half NaN: + EXPECT_EQ(h.Raw() & 0x7C00u, 0x7C00u); + EXPECT_NE(h.Raw() & 0x03FFu, 0u); + + // Must be quiet NaN in half (0x0200 set) + EXPECT_NE(h.Raw() & 0x0200u, 0u); + + // Converting back yields float NaN + float back = static_cast(h); + EXPECT_TRUE(std::isnan(back)); +} + +TEST(Common_Float16, NaNFromHalfBecomesQuietInFloat) +{ + // Create a half NaN with quiet bit not set (a "signaling" half NaN payload). + // exp=all 1s, mant!=0, quiet bit (0x0200) = 0 here. + Float16 h{static_cast(0x7C01u)}; + float f = static_cast(h); + EXPECT_TRUE(std::isnan(f)); + + // Ensure float NaN is quiet (bit 22 of float mantissa set). + // Note: This matches your `fbits |= 0x00400000u`. + uint32_t fb = BitsFromFloat(f); + EXPECT_EQ(fb & 0x7F800000u, 0x7F800000u); + EXPECT_NE(fb & 0x007FFFFFu, 0u); + EXPECT_NE(fb & 0x00400000u, 0u); +} + +TEST(Common_Float16, MinSubnormalAndMinNormalAndMaxNormal) +{ + // Min positive subnormal: 0x0001 = 2^-24 + Float16 min_sub{static_cast(0x0001u)}; + float f_sub = static_cast(min_sub); + EXPECT_GT(f_sub, 0.0f); + EXPECT_LT(f_sub, std::ldexp(1.0f, -13)); // definitely tiny + EXPECT_FLOAT_EQ(f_sub, std::ldexp(1.0f, -24)); + + // Min positive normal: 0x0400 = 2^-14 + Float16 min_norm{static_cast(0x0400u)}; + EXPECT_FLOAT_EQ(static_cast(min_norm), std::ldexp(1.0f, -14)); + + // Max normal: 0x7BFF = 65504 + Float16 max_norm{static_cast(0x7BFFu)}; + EXPECT_FLOAT_EQ(static_cast(max_norm), 65504.0f); + + // Constructing from float should hit same bit patterns + EXPECT_EQ(Float16{std::ldexp(1.0f, -24)}.Raw(), 0x0001u); + EXPECT_EQ(Float16{std::ldexp(1.0f, -14)}.Raw(), 0x0400u); + EXPECT_EQ(Float16{65504.0f}.Raw(), 0x7BFFu); +} + +// -------------------- Rounding tests (RN-even) -------------------- + +TEST(Common_Float16, NormalTiesToEven) +{ + // Half spacing around 1.0 is 2^-10 = 0.0009765625 + // Half ULP at 1.0 is 2^-11 = 0.00048828125 + + const float one = 1.0f; + const float half_ulp = std::ldexp(1.0f, -11); + + // Exactly halfway between 1.0 and next representable half => tie to even. + // 1.0 has mantissa LSB=0 at that boundary, so should round to 1.0. + Float16 h_tie{one + half_ulp}; + EXPECT_EQ(h_tie.Raw(), 0x3C00u); + + // Slightly above halfway should round up to 1.0009765625 (0x3C01) + Float16 h_up{std::nextafter(one + half_ulp, std::numeric_limits::infinity())}; + EXPECT_EQ(h_up.Raw(), 0x3C01u); + + // Slightly below halfway should round down + Float16 h_dn{std::nextafter(one + half_ulp, -std::numeric_limits::infinity())}; + EXPECT_EQ(h_dn.Raw(), 0x3C00u); +} + +TEST(Common_Float16, MantissaCarryIntoExponent) +{ + // Construct a float just above max mantissa for exponent corresponding to 1.x range. + // A clean case: nextafter of the largest value with mantissa all 1s before rounding. + // Pick half just below 2.0: 0x3FFF = 1.9990234375 + // Halfway to 2.0 would round with carry on mantissa overflow. + Float16 h_max_before_two{static_cast(0x3FFFu)}; + const float v = static_cast(h_max_before_two); + + // Add half of half-ULP at that exponent (still 2^-11 since exponent for 1.x) + const float half_ulp = std::ldexp(1.0f, -11); + Float16 h_tie{v + half_ulp}; + + // Tie-to-even: 0x3FFF has mantissa LSB=1, so tie should round to even -> 2.0 (0x4000) + EXPECT_EQ(h_tie.Raw(), 0x4000u); + EXPECT_FLOAT_EQ(static_cast(h_tie), 2.0f); +} + +TEST(Common_Float16, UnderflowToZeroTieToEven) +{ + // Smallest subnormal is 2^-24. + // Halfway between +0 and min subnormal is 2^-25. + // Tie-to-even should go to 0 (even). + const float halfway = std::ldexp(1.0f, -25); + Float16 h{halfway}; + EXPECT_EQ(h.Raw(), 0x0000u); + + // Slightly above should become min subnormal + Float16 hup{std::nextafter(halfway, std::numeric_limits::infinity())}; + EXPECT_EQ(hup.Raw(), 0x0001u); +} + +// -------------------- int32 conversion semantics -------------------- + +TEST(Common_Float16, Int32_NaNBecomesZero) +{ + Float16 h_nan{FloatFromBits(0x7FC00001u)}; + EXPECT_TRUE(std::isnan(static_cast(h_nan))); + EXPECT_EQ(static_cast(h_nan), 0); +} + +TEST(Common_Float16, Int32_InfinitySaturates) +{ + Float16 h_pinf{std::numeric_limits::infinity()}; + Float16 h_ninf{-std::numeric_limits::infinity()}; + + EXPECT_EQ(static_cast(h_pinf), std::numeric_limits::max()); + EXPECT_EQ(static_cast(h_ninf), std::numeric_limits::min()); +} + +TEST(Common_Float16, Int32_TruncatesTowardZero) +{ + Float16 h1{3.9f}; + Float16 h2{-3.9f}; + EXPECT_EQ(static_cast(h1), 3); + EXPECT_EQ(static_cast(h2), -3); +} + +// Note: half finite range doesn't overflow int32, but we still test saturation triggers using inf above. +// Also test exact integer representability boundaries. +TEST(Common_Float16, Int32_ExactIntegers) +{ + Float16 h0{0.0f}; + Float16 h5{5.0f}; + Float16 hm5{-5.0f}; + + EXPECT_EQ(static_cast(h0), 0); + EXPECT_EQ(static_cast(h5), 5); + EXPECT_EQ(static_cast(hm5), -5); +} + +// -------------------- Exhaustive round-trip over all half bit patterns -------------------- + +TEST(Common_Float16, HalfToFloatToHalfRoundTrip_All65536) +{ + for (uint32_t u = 0; u <= 0xFFFFu; ++u) + { + const uint16_t h0 = static_cast(u); + + Float16 a{h0}; // from raw bits + float f = static_cast(a); + + Float16 b{f}; // float -> half + uint16_t got = b.Raw(); + + const uint16_t expect = CanonicalizeHalfForRoundTrip(h0); + + // After canonicalization, the round-trip should match. + if (got != expect) + { + // Provide helpful diagnostics + ADD_FAILURE() << std::hex + << "Mismatch at half bits 0x" << h0 + << ": float bits 0x" << BitsFromFloat(f) + << ", got half 0x" << got + << ", expected 0x" << expect + << std::dec; + return; + } + + // Also sanity: IsZero/Sign should agree with bits + EXPECT_EQ(a.IsZero(), (h0 & 0x7FFFu) == 0); + EXPECT_EQ(a.Sign(), (h0 & 0x8000u) != 0); + } +} + +// -------------------- Extra: float->half->float should match "round then expand" exactly -------------------- +// This isn't exhaustive over float32 (too big), but it hits many patterns including edge cases. + +TEST(Common_Float16, ManyFloatBitPatterns) +{ + // Deterministic LCG to avoid & to keep tests reproducible. + uint32_t x = 0x12345678u; + + auto next_u32 = [&]() { + x = x * 1664525u + 1013904223u; + return x; + }; + + // Include some hand-picked edge cases first. + const uint32_t cases[] = { + 0x00000000u, 0x80000000u, // +/-0 + 0x3F800000u, 0xBF800000u, // +/-1 + 0x7F800000u, 0xFF800000u, // +/-inf + 0x7FC00000u, 0xFFC00000u, // qNaN + 0x00800000u, 0x80800000u, // min normal (float) + 0x007FFFFFu, 0x807FFFFFu, // max subnormal (float) + 0x3F000000u, 0x3F7FFFFFu, // ~0.5 to ~1 + }; + + for (uint32_t bits : cases) + { + float f = FloatFromBits(bits); + Float16 h{f}; + float back = static_cast(h); + + if (std::isnan(f)) + { + EXPECT_TRUE(std::isnan(back)); + } + else + { + // back must be exactly representable half expanded to float + // so converting back to half should recover same bits (up to NaN canonicalization which we handled above). + Float16 h2{back}; + EXPECT_EQ(h2.Raw(), h.Raw()); + } + } + + // Now pseudo-random patterns + for (int i = 0; i < 200000; ++i) + { + float f = FloatFromBits(next_u32()); + Float16 h{f}; + float back = static_cast(h); + + if (std::isnan(f)) + { + EXPECT_TRUE(std::isnan(back)); + continue; + } + + // Idempotence: once rounded to half and expanded, repeating should not change bits. + Float16 h2{back}; + EXPECT_EQ(h2.Raw(), h.Raw()); + } +} From 660fcd635b94f41be1b4cc9507154d93d1e2ca72 Mon Sep 17 00:00:00 2001 From: assiduous Date: Tue, 3 Mar 2026 19:25:23 -0800 Subject: [PATCH 15/28] GraphicsAccessories: add GetShaderCodeBasicTypeBitSize --- .../interface/GraphicsAccessories.hpp | 4 ++- .../src/GraphicsAccessories.cpp | 36 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Graphics/GraphicsAccessories/interface/GraphicsAccessories.hpp b/Graphics/GraphicsAccessories/interface/GraphicsAccessories.hpp index 823935fdb0..6b4e41f517 100644 --- a/Graphics/GraphicsAccessories/interface/GraphicsAccessories.hpp +++ b/Graphics/GraphicsAccessories/interface/GraphicsAccessories.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -495,6 +495,8 @@ const char* GetShaderCodeVariableClassString(SHADER_CODE_VARIABLE_CLASS Class); const char* GetShaderCodeBasicTypeString(SHADER_CODE_BASIC_TYPE Type); +Uint32 GetShaderCodeBasicTypeBitSize(SHADER_CODE_BASIC_TYPE Type); + /// Returns the string containing the shader buffer description. String GetShaderCodeBufferDescString(const ShaderCodeBufferDesc& Desc, size_t GlobalIdent = 0, size_t MemberIdent = 2); diff --git a/Graphics/GraphicsAccessories/src/GraphicsAccessories.cpp b/Graphics/GraphicsAccessories/src/GraphicsAccessories.cpp index 9988f3b9f8..755057f5bc 100644 --- a/Graphics/GraphicsAccessories/src/GraphicsAccessories.cpp +++ b/Graphics/GraphicsAccessories/src/GraphicsAccessories.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -1720,6 +1720,40 @@ const char* GetShaderCodeBasicTypeString(SHADER_CODE_BASIC_TYPE Type) } } +Uint32 GetShaderCodeBasicTypeBitSize(SHADER_CODE_BASIC_TYPE Type) +{ + static_assert(SHADER_CODE_BASIC_TYPE_COUNT == 21, "Did you add a new type? Please update the switch below."); + switch (Type) + { + // clang-format off + case SHADER_CODE_BASIC_TYPE_UNKNOWN: return 0; + case SHADER_CODE_BASIC_TYPE_VOID: return 0; + case SHADER_CODE_BASIC_TYPE_BOOL: return 8; + case SHADER_CODE_BASIC_TYPE_INT: return 32; + case SHADER_CODE_BASIC_TYPE_INT8: return 8; + case SHADER_CODE_BASIC_TYPE_INT16: return 16; + case SHADER_CODE_BASIC_TYPE_INT64: return 64; + case SHADER_CODE_BASIC_TYPE_UINT: return 32; + case SHADER_CODE_BASIC_TYPE_UINT8: return 8; + case SHADER_CODE_BASIC_TYPE_UINT16: return 16; + case SHADER_CODE_BASIC_TYPE_UINT64: return 64; + case SHADER_CODE_BASIC_TYPE_FLOAT: return 32; + case SHADER_CODE_BASIC_TYPE_FLOAT16: return 16; + case SHADER_CODE_BASIC_TYPE_DOUBLE: return 64; + case SHADER_CODE_BASIC_TYPE_MIN8FLOAT: return 8; + case SHADER_CODE_BASIC_TYPE_MIN10FLOAT: return 10; + case SHADER_CODE_BASIC_TYPE_MIN16FLOAT: return 16; + case SHADER_CODE_BASIC_TYPE_MIN12INT: return 12; + case SHADER_CODE_BASIC_TYPE_MIN16INT: return 16; + case SHADER_CODE_BASIC_TYPE_MIN16UINT: return 16; + case SHADER_CODE_BASIC_TYPE_STRING: return 0; + // clang-format on + default: + UNEXPECTED("Unknown/unsupported variable class"); + return 0; + } +} + static void PrintShaderCodeVariables(std::stringstream& ss, size_t LevelIdent, size_t IdentShift, const ShaderCodeVariableDesc* pVars, Uint32 NumVars) { if (pVars == nullptr || NumVars == 0) From 22d8564c6a1d9b766c761e719c667575d4328454 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sat, 28 Feb 2026 10:21:53 +0800 Subject: [PATCH 16/28] Prepare pipeline initialization constants for WebGPU pipeline state --- .../include/PipelineStateWebGPUImpl.hpp | 8 +- .../src/PipelineStateWebGPUImpl.cpp | 143 +++++++++++++++++- 2 files changed, 148 insertions(+), 3 deletions(-) diff --git a/Graphics/GraphicsEngineWebGPU/include/PipelineStateWebGPUImpl.hpp b/Graphics/GraphicsEngineWebGPU/include/PipelineStateWebGPUImpl.hpp index cf481bd238..7a18d19cb0 100644 --- a/Graphics/GraphicsEngineWebGPU/include/PipelineStateWebGPUImpl.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/PipelineStateWebGPUImpl.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 Diligent Graphics LLC + * Copyright 2023-2026 Diligent Graphics LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -85,6 +85,12 @@ class PipelineStateWebGPUImpl final : public PipelineStateBase SpecConstEntries; + ShaderStageInfo(ShaderWebGPUImpl* _pShader) : Type{_pShader->GetDesc().ShaderType}, pShader{_pShader} diff --git a/Graphics/GraphicsEngineWebGPU/src/PipelineStateWebGPUImpl.cpp b/Graphics/GraphicsEngineWebGPU/src/PipelineStateWebGPUImpl.cpp index aa2024caef..112209d205 100644 --- a/Graphics/GraphicsEngineWebGPU/src/PipelineStateWebGPUImpl.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/PipelineStateWebGPUImpl.cpp @@ -32,10 +32,125 @@ #include "RenderPassWebGPUImpl.hpp" #include "WebGPUTypeConversions.hpp" #include "WGSLUtils.hpp" +#include "Float16.hpp" namespace Diligent { +namespace +{ + +double ConvertSpecConstantToDouble(const void* pData, Uint32 DataSize, SHADER_CODE_BASIC_TYPE Type) +{ + switch (Type) + { + case SHADER_CODE_BASIC_TYPE_BOOL: + { + // WGSL bool override: any non-zero value is true. + Uint32 Val = 0; + memcpy(&Val, pData, std::min(DataSize, Uint32{4})); + return Val != 0 ? 1.0 : 0.0; + } + + case SHADER_CODE_BASIC_TYPE_INT: + { + Int32 Val = 0; + memcpy(&Val, pData, std::min(DataSize, Uint32{4})); + return static_cast(Val); + } + + case SHADER_CODE_BASIC_TYPE_UINT: + { + Uint32 Val = 0; + memcpy(&Val, pData, std::min(DataSize, Uint32{4})); + return static_cast(Val); + } + + case SHADER_CODE_BASIC_TYPE_FLOAT: + { + float Val = 0; + memcpy(&Val, pData, std::min(DataSize, Uint32{4})); + return static_cast(Val); + } + + case SHADER_CODE_BASIC_TYPE_FLOAT16: + { + Uint16 HalfBits = 0; + memcpy(&HalfBits, pData, std::min(DataSize, Uint32{2})); + return Float16::HalfBitsToFloat(HalfBits); + } + + default: + UNEXPECTED("Unexpected specialization constant type ", GetShaderCodeBasicTypeString(Type)); + return 0; + } +} + +void BuildSpecializationDataWebGPU(PipelineStateWebGPUImpl::TShaderStages& ShaderStages, + Uint32 NumSpecializationConstants, + const SpecializationConstant* pSpecializationConstants, + const PipelineStateDesc& PSODesc) +{ + if (NumSpecializationConstants == 0) + return; + + for (size_t StageIdx = 0; StageIdx < ShaderStages.size(); ++StageIdx) + { + PipelineStateWebGPUImpl::ShaderStageInfo& Stage = ShaderStages[StageIdx]; + const ShaderWebGPUImpl* pShader = Stage.pShader; + const std::shared_ptr& pShaderResources = pShader->GetShaderResources(); + + if (!pShaderResources) + continue; + + for (Uint32 r = 0; r < pShaderResources->GetNumSpecConstants(); ++r) + { + const WGSLSpecializationConstantAttribs& ReflectedSC = pShaderResources->GetSpecConstant(r); + + // Search for a matching user-provided constant by name and stage flag. + const SpecializationConstant* pUserConst = nullptr; + for (Uint32 sc = 0; sc < NumSpecializationConstants; ++sc) + { + const SpecializationConstant& Candidate = pSpecializationConstants[sc]; + if ((Candidate.ShaderStages & Stage.Type) != 0 && + strcmp(Candidate.Name, ReflectedSC.Name) == 0) + { + pUserConst = &Candidate; + break; + } + } + + // No user constant for this reflected entry -- skip silently. + if (pUserConst == nullptr) + continue; + + const SHADER_CODE_BASIC_TYPE ReflectedType = ReflectedSC.GetType(); + const Uint32 ReflectedSize = GetShaderCodeBasicTypeBitSize(ReflectedType) / 8; + + if (pUserConst->Size < ReflectedSize) + { + LOG_ERROR_AND_THROW("Description of ", GetPipelineTypeString(PSODesc.PipelineType), + " PSO '", (PSODesc.Name != nullptr ? PSODesc.Name : ""), + "' is invalid: specialization constant '", pUserConst->Name, + "' in ", GetShaderTypeLiteralName(Stage.Type), + " shader '", pShader->GetDesc().Name, + "' has insufficient data: user provided ", pUserConst->Size, + " bytes, but the shader declares ", + GetShaderCodeBasicTypeString(ReflectedType), + " (", ReflectedSize, " bytes)."); + } + + WGPUConstantEntry Entry{}; + Entry.key = GetWGPUStringView(ReflectedSC.Name); + Entry.value = ConvertSpecConstantToDouble(pUserConst->pData, pUserConst->Size, ReflectedType); + + Stage.SpecConstEntries.push_back(Entry); + } + } +} + +} // anonymous namespace + constexpr INTERFACE_ID PipelineStateWebGPUImpl::IID_InternalImpl; PipelineStateWebGPUImpl::PipelineStateWebGPUImpl(IReferenceCounters* pRefCounters, @@ -342,6 +457,15 @@ struct PipelineStateWebGPUImpl::AsyncPipelineBuilder : public ObjectBaseGetEntryPoint()); + wgpuRenderPipelineDesc.vertex.module = wgpuShaderModules[ShaderIdx].Get(); + wgpuRenderPipelineDesc.vertex.entryPoint = GetWGPUStringView(Stage.pShader->GetEntryPoint()); + wgpuRenderPipelineDesc.vertex.constantCount = Stage.SpecConstEntries.size(); + wgpuRenderPipelineDesc.vertex.constants = Stage.SpecConstEntries.empty() ? nullptr : Stage.SpecConstEntries.data(); break; case SHADER_TYPE_PIXEL: VERIFY(wgpuFragmentState.module == nullptr, "Only one vertex shader is allowed"); wgpuFragmentState.module = wgpuShaderModules[ShaderIdx].Get(); wgpuFragmentState.entryPoint = GetWGPUStringView(Stage.pShader->GetEntryPoint()); + wgpuFragmentState.constantCount = Stage.SpecConstEntries.size(); + wgpuFragmentState.constants = Stage.SpecConstEntries.empty() ? nullptr : Stage.SpecConstEntries.data(); wgpuRenderPipelineDesc.fragment = &wgpuFragmentState; break; @@ -615,6 +750,10 @@ void PipelineStateWebGPUImpl::InitializeWebGPUComputePipeline(const TShaderStage wgpuComputePipelineDesc.compute.entryPoint = GetWGPUStringView(pShaderWebGPU->GetEntryPoint()); wgpuComputePipelineDesc.layout = m_PipelineLayout.GetWebGPUPipelineLayout(); + const std::vector& SpecConstEntries = ShaderStages[0].SpecConstEntries; + wgpuComputePipelineDesc.compute.constantCount = SpecConstEntries.size(); + wgpuComputePipelineDesc.compute.constants = SpecConstEntries.empty() ? nullptr : SpecConstEntries.data(); + if (AsyncBuilder) { // The reference will be released from the callback. From 15846083f697504ace063d85a9f11e6364a14922 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sat, 28 Feb 2026 10:22:13 +0800 Subject: [PATCH 17/28] WebGPU: enable SpecializationConstants feature --- Graphics/GraphicsEngineWebGPU/src/EngineFactoryWebGPU.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Graphics/GraphicsEngineWebGPU/src/EngineFactoryWebGPU.cpp b/Graphics/GraphicsEngineWebGPU/src/EngineFactoryWebGPU.cpp index e048d094d9..083e618d81 100644 --- a/Graphics/GraphicsEngineWebGPU/src/EngineFactoryWebGPU.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/EngineFactoryWebGPU.cpp @@ -403,7 +403,7 @@ DeviceFeatures GetSupportedFeatures(WGPUAdapter wgpuAdapter, WGPUDevice wgpuDevi Features.NativeMultiDraw = DEVICE_FEATURE_STATE_DISABLED; Features.AsyncShaderCompilation = DEVICE_FEATURE_STATE_ENABLED; Features.FormattedBuffers = DEVICE_FEATURE_STATE_DISABLED; - Features.SpecializationConstants = DEVICE_FEATURE_STATE_DISABLED; + Features.SpecializationConstants = DEVICE_FEATURE_STATE_ENABLED; Features.TimestampQueries = CheckFeature(WGPUFeatureName_TimestampQuery); Features.DurationQueries = Features.TimestampQueries ? From 00eb5b12049d0c9d40be643950f19ddff2d74cad Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sat, 28 Feb 2026 10:22:57 +0800 Subject: [PATCH 18/28] Enable specialization constant tests on WebGPU --- .../PSOCreationFailureTest.cpp | 85 +++++++++--- .../src/SpecializationConstantsTest.cpp | 126 ++++++++++++++++-- 2 files changed, 183 insertions(+), 28 deletions(-) diff --git a/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp b/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp index 117272e936..c3fbca6ffd 100644 --- a/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp +++ b/Tests/DiligentCoreAPITest/src/ObjectCreationFailure/PSOCreationFailureTest.cpp @@ -1760,9 +1760,9 @@ TEST_F(PSOCreationFailureTest, SpecConst_ErrorAtSecondElement) // --------------------------------------------------------------------------- -// Vulkan-specific tests for BuildSpecializationData (Name->SpecId matching). -// These require GLSL shaders with layout(constant_id) declarations so that -// the reflected specialization constants are available for matching. +// Backend-specific tests for specialization constant matching (name-based). +// These require backend-specific shaders with specialization constant +// declarations so that reflected constants are available for matching. // --------------------------------------------------------------------------- // Providing a user constant whose name does not match any reflected constant @@ -1773,8 +1773,8 @@ TEST_F(PSOCreationFailureTest, SpecConst_UnmatchedNameIsSkipped) auto* const pDevice = pEnv->GetDevice(); const auto& DeviceInfo = pDevice->GetDeviceInfo(); - if (!DeviceInfo.IsVulkanDevice()) - GTEST_SKIP() << "Name->SpecId matching is currently Vulkan-only"; + if (!DeviceInfo.IsVulkanDevice() && !DeviceInfo.IsWebGPUDevice()) + GTEST_SKIP() << "Name-based spec constant matching requires Vulkan or WebGPU"; if (DeviceInfo.Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) GTEST_SKIP() << "Specialization constants are not supported by this device"; @@ -1800,21 +1800,56 @@ TEST_F(PSOCreationFailureTest, SpecConst_UnmatchedNameIsSkipped) } )"; + static constexpr char VSSource_WGSL[] = R"( + override sc_Scale: f32 = 1.0; + + @vertex + fn main() -> @builtin(position) vec4 { + return vec4(0.0, 0.0, 0.0, sc_Scale); + } + )"; + + static constexpr char PSSource_WGSL[] = R"( + @fragment + fn main() -> @location(0) vec4 { + return vec4(0.0, 0.0, 0.0, 1.0); + } + )"; + ShaderCreateInfo ShaderCI; - ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; RefCntAutoPtr pVS; { - ShaderCI.Desc = {"SpecConst UnmatchedName VS", SHADER_TYPE_VERTEX, true}; - ShaderCI.Source = VSSource_GLSL; + ShaderCI.Desc = {"SpecConst UnmatchedName VS", SHADER_TYPE_VERTEX, true}; + ShaderCI.EntryPoint = "main"; + if (DeviceInfo.IsWebGPUDevice()) + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCI.Source = VSSource_WGSL; + } + else + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + ShaderCI.Source = VSSource_GLSL; + } pDevice->CreateShader(ShaderCI, &pVS); ASSERT_TRUE(pVS); } RefCntAutoPtr pPS; { - ShaderCI.Desc = {"SpecConst UnmatchedName PS", SHADER_TYPE_PIXEL, true}; - ShaderCI.Source = PSSource_GLSL; + ShaderCI.Desc = {"SpecConst UnmatchedName PS", SHADER_TYPE_PIXEL, true}; + ShaderCI.EntryPoint = "main"; + if (DeviceInfo.IsWebGPUDevice()) + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCI.Source = PSSource_WGSL; + } + else + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + ShaderCI.Source = PSSource_GLSL; + } pDevice->CreateShader(ShaderCI, &pPS); ASSERT_TRUE(pPS); } @@ -1842,8 +1877,8 @@ TEST_F(PSOCreationFailureTest, SpecConst_InsufficientData) auto* const pDevice = pEnv->GetDevice(); const auto& DeviceInfo = pDevice->GetDeviceInfo(); - if (!DeviceInfo.IsVulkanDevice()) - GTEST_SKIP() << "Name->SpecId matching is currently Vulkan-only"; + if (!DeviceInfo.IsVulkanDevice() && !DeviceInfo.IsWebGPUDevice()) + GTEST_SKIP() << "Name-based spec constant matching requires Vulkan or WebGPU"; if (DeviceInfo.Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) GTEST_SKIP() << "Specialization constants are not supported by this device"; @@ -1860,10 +1895,30 @@ TEST_F(PSOCreationFailureTest, SpecConst_InsufficientData) } )"; + static constexpr char CSSource_WGSL[] = R"( + override sc_Value: f32 = 1.0; + + @group(0) @binding(0) var g_DummyUAV: texture_storage_2d; + + @compute @workgroup_size(1, 1, 1) + fn main() { + textureStore(g_DummyUAV, vec2(0), vec4(sc_Value)); + } + )"; + ShaderCreateInfo ShaderCI; - ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; - ShaderCI.Desc = {"SpecConst InsufficientData CS", SHADER_TYPE_COMPUTE, true}; - ShaderCI.Source = CSSource_GLSL; + ShaderCI.Desc = {"SpecConst InsufficientData CS", SHADER_TYPE_COMPUTE, true}; + ShaderCI.EntryPoint = "main"; + if (DeviceInfo.IsWebGPUDevice()) + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCI.Source = CSSource_WGSL; + } + else + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + ShaderCI.Source = CSSource_GLSL; + } RefCntAutoPtr pCS; pDevice->CreateShader(ShaderCI, &pCS); diff --git a/Tests/DiligentCoreAPITest/src/SpecializationConstantsTest.cpp b/Tests/DiligentCoreAPITest/src/SpecializationConstantsTest.cpp index 6b30c2e5b3..0cb35305b6 100644 --- a/Tests/DiligentCoreAPITest/src/SpecializationConstantsTest.cpp +++ b/Tests/DiligentCoreAPITest/src/SpecializationConstantsTest.cpp @@ -83,6 +83,29 @@ static constexpr char g_SpecConstComputeCS_GLSL[] = R"( } )"; +static constexpr char g_SpecConstComputeCS_WGSL[] = R"( + override sc_MulR: f32 = -1.0; + override sc_MulG: f32 = -1.0; + override sc_MulB: f32 = -1.0; + + @group(0) @binding(0) var g_tex2DUAV: texture_storage_2d; + + @compute @workgroup_size(16, 16, 1) + fn main(@builtin(global_invocation_id) gid: vec3) { + let dim = textureDimensions(g_tex2DUAV); + let coord = vec2(gid.xy); + if (coord.x >= i32(dim.x) || coord.y >= i32(dim.y)) { + return; + } + let uv = vec2(gid.xy % 256u) / 256.0; + let Color = vec4(uv.x * sc_MulR, + uv.y * sc_MulG, + uv.x * sc_MulB, + 1.0); + textureStore(g_tex2DUAV, coord, Color); + } +)"; + // Vertex shader: hardcoded positions (same as DrawTest_ProceduralTriangleVS), // per-vertex colors supplied via specialization constants (9 floats). static constexpr char g_SpecConstGraphicsVS_GLSL[] = R"( @@ -128,6 +151,47 @@ static constexpr char g_SpecConstGraphicsVS_GLSL[] = R"( } )"; +static constexpr char g_SpecConstGraphicsVS_WGSL[] = R"( + override sc_Col0_R: f32 = 0.0; + override sc_Col0_G: f32 = 0.0; + override sc_Col0_B: f32 = 0.0; + + override sc_Col1_R: f32 = 0.0; + override sc_Col1_G: f32 = 0.0; + override sc_Col1_B: f32 = 0.0; + + override sc_Col2_R: f32 = 0.0; + override sc_Col2_G: f32 = 0.0; + override sc_Col2_B: f32 = 0.0; + + struct VSOutput { + @builtin(position) Position: vec4, + @location(0) Color: vec3, + }; + + @vertex + fn main(@builtin(vertex_index) VertexIndex: u32) -> VSOutput { + var Pos: array, 6>; + Pos[0] = vec4(-1.0, -0.5, 0.0, 1.0); + Pos[1] = vec4(-0.5, 0.5, 0.0, 1.0); + Pos[2] = vec4( 0.0, -0.5, 0.0, 1.0); + + Pos[3] = vec4( 0.0, -0.5, 0.0, 1.0); + Pos[4] = vec4( 0.5, 0.5, 0.0, 1.0); + Pos[5] = vec4( 1.0, -0.5, 0.0, 1.0); + + var Col: array, 3>; + Col[0] = vec3(sc_Col0_R, sc_Col0_G, sc_Col0_B); + Col[1] = vec3(sc_Col1_R, sc_Col1_G, sc_Col1_B); + Col[2] = vec3(sc_Col2_R, sc_Col2_G, sc_Col2_B); + + var output: VSOutput; + output.Position = Pos[VertexIndex]; + output.Color = Col[VertexIndex % 3]; + return output; + } +)"; + // Fragment shader: interpolated color modulated by specialization constants. // sc_Col0_R is shared with the vertex shader (tests cross-stage matching). // sc_Brightness and sc_AlphaScale are PS-only. @@ -151,6 +215,18 @@ static constexpr char g_SpecConstGraphicsPS_GLSL[] = R"( } )"; +static constexpr char g_SpecConstGraphicsPS_WGSL[] = R"( + override sc_Col0_R: f32 = -1.0; + override sc_Brightness: f32 = -1.0; + override sc_AlphaScale: f32 = -1.0; + + @fragment + fn main(@location(0) in_Color: vec3) -> @location(0) vec4 { + return vec4(vec3(in_Color.r * sc_Col0_R, in_Color.g, in_Color.b) * sc_Brightness, + sc_AlphaScale); + } +)"; + class SpecializationConstants : public ::testing::Test { @@ -189,8 +265,6 @@ TEST_F(SpecializationConstants, ComputePath) ISwapChain* pSwapChain = pEnv->GetSwapChain(); const RenderDeviceInfo& DeviceInfo = pDevice->GetDeviceInfo(); - if (!DeviceInfo.IsVulkanDevice()) - GTEST_SKIP() << "Specialization constants are currently Vulkan-only"; if (DeviceInfo.Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) GTEST_SKIP() << "Specialization constants are not supported by this device"; if (!DeviceInfo.Features.ComputeShaders) @@ -212,10 +286,19 @@ TEST_F(SpecializationConstants, ComputePath) // --- Spec-const pass: same gradient, channel multipliers via specialization constants --- { ShaderCreateInfo ShaderCI; - ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; - ShaderCI.Desc = {"SpecConst Compute CS", SHADER_TYPE_COMPUTE, true}; - ShaderCI.EntryPoint = "main"; - ShaderCI.Source = g_SpecConstComputeCS_GLSL; + ShaderCI.Desc = {"SpecConst Compute CS", SHADER_TYPE_COMPUTE, true}; + ShaderCI.EntryPoint = "main"; + + if (DeviceInfo.IsWebGPUDevice()) + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCI.Source = g_SpecConstComputeCS_WGSL; + } + else + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + ShaderCI.Source = g_SpecConstComputeCS_GLSL; + } RefCntAutoPtr pCS; pDevice->CreateShader(ShaderCI, &pCS); @@ -278,8 +361,6 @@ TEST_F(SpecializationConstants, GraphicsPath) ISwapChain* pSwapChain = pEnv->GetSwapChain(); const RenderDeviceInfo& DeviceInfo = pDevice->GetDeviceInfo(); - if (!DeviceInfo.IsVulkanDevice()) - GTEST_SKIP() << "Specialization constants are currently Vulkan-only"; if (DeviceInfo.Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) GTEST_SKIP() << "Specialization constants are not supported by this device"; @@ -304,20 +385,39 @@ TEST_F(SpecializationConstants, GraphicsPath) pContext->ClearRenderTarget(pRTVs[0], ClearColor, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); ShaderCreateInfo ShaderCI; - ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; RefCntAutoPtr pVS; { - ShaderCI.Desc = {"SpecConst Graphics VS", SHADER_TYPE_VERTEX, true}; - ShaderCI.Source = g_SpecConstGraphicsVS_GLSL; + ShaderCI.Desc = {"SpecConst Graphics VS", SHADER_TYPE_VERTEX, true}; + ShaderCI.EntryPoint = "main"; + if (DeviceInfo.IsWebGPUDevice()) + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCI.Source = g_SpecConstGraphicsVS_WGSL; + } + else + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + ShaderCI.Source = g_SpecConstGraphicsVS_GLSL; + } pDevice->CreateShader(ShaderCI, &pVS); ASSERT_NE(pVS, nullptr); } RefCntAutoPtr pPS; { - ShaderCI.Desc = {"SpecConst Graphics PS", SHADER_TYPE_PIXEL, true}; - ShaderCI.Source = g_SpecConstGraphicsPS_GLSL; + ShaderCI.Desc = {"SpecConst Graphics PS", SHADER_TYPE_PIXEL, true}; + ShaderCI.EntryPoint = "main"; + if (DeviceInfo.IsWebGPUDevice()) + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCI.Source = g_SpecConstGraphicsPS_WGSL; + } + else + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + ShaderCI.Source = g_SpecConstGraphicsPS_GLSL; + } pDevice->CreateShader(ShaderCI, &pPS); ASSERT_NE(pPS, nullptr); } From 1b65d75979fc48aec2693395132b60e7561d6f8e Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 4 Mar 2026 15:13:49 +0800 Subject: [PATCH 19/28] Fixed an issue that Common_HashUtils.PipelineStateCIStdHash periodically failed. --- .../src/Common/HashUtilsTest.cpp | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/Tests/DiligentCoreTest/src/Common/HashUtilsTest.cpp b/Tests/DiligentCoreTest/src/Common/HashUtilsTest.cpp index 733244ef68..3477ee15c0 100644 --- a/Tests/DiligentCoreTest/src/Common/HashUtilsTest.cpp +++ b/Tests/DiligentCoreTest/src/Common/HashUtilsTest.cpp @@ -1205,17 +1205,33 @@ void TestPipelineStateCIHasher() Helper.Get().ppResourceSignatures = ppSignatures.data(); TEST_RANGE(ResourceSignaturesCount, 1u, MAX_RESOURCE_SIGNATURES); + // Keep specialization constant storage alive for the entire helper lifetime. + // StdHasherTestHelper stores shallow copies of create-info in m_Descs. + const Uint32 Data1[] = {1, 2, 3}; + const Uint32 Data2[] = {4, 5, 6}; + const Uint32 Data3[] = {7, 8, 9}; + const SpecializationConstant SpecConsts[] = + { + {"Const1", SHADER_TYPE_VERTEX, sizeof(Uint32), &Data1[0]}, + {"Const2", SHADER_TYPE_PIXEL, sizeof(Uint32), &Data2[0]}, + {"Const3", SHADER_TYPE_GEOMETRY, sizeof(Uint32), &Data3[0]}, + }; + + const Uint32 DataA = 100; + const Uint32 DataB = 200; + const SpecializationConstant SC_A{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataA}; + const SpecializationConstant SC_B{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataB}; + + const Uint32 DataName = 42; + const SpecializationConstant SC_Name1{"Alpha", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataName}; + const SpecializationConstant SC_Name2{"Beta", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataName}; + + const Uint32 DataStage = 42; + const SpecializationConstant SC_VS{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataStage}; + const SpecializationConstant SC_PS{"Const", SHADER_TYPE_PIXEL, sizeof(Uint32), &DataStage}; + // Test specialization constants hashing { - const Uint32 Data1[] = {1, 2, 3}; - const Uint32 Data2[] = {4, 5, 6}; - const Uint32 Data3[] = {7, 8, 9}; - const SpecializationConstant SpecConsts[] = - { - {"Const1", SHADER_TYPE_VERTEX, sizeof(Uint32), &Data1[0]}, - {"Const2", SHADER_TYPE_PIXEL, sizeof(Uint32), &Data2[0]}, - {"Const3", SHADER_TYPE_GEOMETRY, sizeof(Uint32), &Data3[0]}, - }; Helper.Get().ppResourceSignatures = nullptr; Helper.Get().ResourceSignaturesCount = 0; Helper.Get().pSpecializationConstants = SpecConsts; @@ -1226,11 +1242,6 @@ void TestPipelineStateCIHasher() // Test that different specialization constant data produces different hashes { - const Uint32 DataA = 100; - const Uint32 DataB = 200; - const SpecializationConstant SC_A{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataA}; - const SpecializationConstant SC_B{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataB}; - Helper.Get().NumSpecializationConstants = 1; Helper.Get().pSpecializationConstants = &SC_A; Helper.Add("SpecConst data A"); @@ -1241,10 +1252,6 @@ void TestPipelineStateCIHasher() // Test that different specialization constant names produce different hashes { - const Uint32 Data = 42; - const SpecializationConstant SC_Name1{"Alpha", SHADER_TYPE_VERTEX, sizeof(Uint32), &Data}; - const SpecializationConstant SC_Name2{"Beta", SHADER_TYPE_VERTEX, sizeof(Uint32), &Data}; - Helper.Get().NumSpecializationConstants = 1; Helper.Get().pSpecializationConstants = &SC_Name1; Helper.Add("SpecConst name Alpha"); @@ -1255,10 +1262,6 @@ void TestPipelineStateCIHasher() // Test that different specialization constant ShaderStages produce different hashes { - const Uint32 Data = 42; - const SpecializationConstant SC_VS{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &Data}; - const SpecializationConstant SC_PS{"Const", SHADER_TYPE_PIXEL, sizeof(Uint32), &Data}; - Helper.Get().NumSpecializationConstants = 1; Helper.Get().pSpecializationConstants = &SC_VS; Helper.Add("SpecConst stage VS"); From 524cfb6c992337d36954304cee18e0fade7d35f1 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 4 Mar 2026 15:14:12 +0800 Subject: [PATCH 20/28] Add SpecConstsTests in RenderStateCacheTest.cpp --- .../src/RenderStateCacheTest.cpp | 475 ++++++++++++++++++ 1 file changed, 475 insertions(+) diff --git a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp index c26c2b9f22..f67fb2185c 100644 --- a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp +++ b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp @@ -1918,4 +1918,479 @@ TEST(RenderStateCacheTest, NonSeparableProgramBindingConflict) ASSERT_EQ(pPSO->GetStatus(), PIPELINE_STATE_STATUS_READY); } +namespace SpecConstsTests +{ + +const std::string GraphicsVS_GLSL{ + R"( +#version 450 + +#ifndef GL_ES +out gl_PerVertex { vec4 gl_Position; }; +#endif + +layout(constant_id = 0) const float sc_Col0_R = -1.0; +layout(constant_id = 1) const float sc_Col0_G = -1.0; +layout(constant_id = 2) const float sc_Col0_B = -1.0; + +layout(constant_id = 3) const float sc_Col1_R = -1.0; +layout(constant_id = 4) const float sc_Col1_G = -1.0; +layout(constant_id = 5) const float sc_Col1_B = -1.0; + +layout(constant_id = 6) const float sc_Col2_R = -1.0; +layout(constant_id = 7) const float sc_Col2_G = -1.0; +layout(constant_id = 8) const float sc_Col2_B = -1.0; + +layout(location = 0) out vec3 out_Color; + +void main() +{ + vec4 Pos[6]; + Pos[0] = vec4(-1.0, -0.5, 0.0, 1.0); + Pos[1] = vec4(-0.5, +0.5, 0.0, 1.0); + Pos[2] = vec4( 0.0, -0.5, 0.0, 1.0); + + Pos[3] = vec4(+0.0, -0.5, 0.0, 1.0); + Pos[4] = vec4(+0.5, +0.5, 0.0, 1.0); + Pos[5] = vec4(+1.0, -0.5, 0.0, 1.0); + + vec3 Col[3]; + Col[0] = vec3(sc_Col0_R, sc_Col0_G, sc_Col0_B); + Col[1] = vec3(sc_Col1_R, sc_Col1_G, sc_Col1_B); + Col[2] = vec3(sc_Col2_R, sc_Col2_G, sc_Col2_B); + + gl_Position = Pos[gl_VertexIndex]; + out_Color = Col[gl_VertexIndex % 3]; +} +)"}; + +const std::string GraphicsPS_GLSL{ + R"( +#version 450 + +layout(constant_id = 0) const float sc_Col0_R = -1.0; +layout(constant_id = 1) const float sc_Brightness = -1.0; +layout(constant_id = 2) const float sc_AlphaScale = -1.0; + +layout(location = 0) in vec3 in_Color; +layout(location = 0) out vec4 out_Color; + +void main() +{ + out_Color = vec4(vec3(in_Color.r * sc_Col0_R, in_Color.g, in_Color.b) * sc_Brightness, + sc_AlphaScale); +} +)"}; + +const std::string GraphicsVS_WGSL{ + R"( +override sc_Col0_R: f32 = -1.0; +override sc_Col0_G: f32 = -1.0; +override sc_Col0_B: f32 = -1.0; + +override sc_Col1_R: f32 = -1.0; +override sc_Col1_G: f32 = -1.0; +override sc_Col1_B: f32 = -1.0; + +override sc_Col2_R: f32 = -1.0; +override sc_Col2_G: f32 = -1.0; +override sc_Col2_B: f32 = -1.0; + +struct VSOutput { + @builtin(position) Position: vec4, + @location(0) Color: vec3, +}; + +@vertex +fn main(@builtin(vertex_index) VertexIndex: u32) -> VSOutput { + var Pos: array, 6>; + Pos[0] = vec4(-1.0, -0.5, 0.0, 1.0); + Pos[1] = vec4(-0.5, 0.5, 0.0, 1.0); + Pos[2] = vec4( 0.0, -0.5, 0.0, 1.0); + + Pos[3] = vec4( 0.0, -0.5, 0.0, 1.0); + Pos[4] = vec4( 0.5, 0.5, 0.0, 1.0); + Pos[5] = vec4( 1.0, -0.5, 0.0, 1.0); + + var Col: array, 3>; + Col[0] = vec3(sc_Col0_R, sc_Col0_G, sc_Col0_B); + Col[1] = vec3(sc_Col1_R, sc_Col1_G, sc_Col1_B); + Col[2] = vec3(sc_Col2_R, sc_Col2_G, sc_Col2_B); + + var output: VSOutput; + output.Position = Pos[VertexIndex]; + output.Color = Col[VertexIndex % 3]; + return output; +} +)"}; + +const std::string GraphicsPS_WGSL{ + R"( +override sc_Col0_R: f32 = -1.0; +override sc_Brightness: f32 = -1.0; +override sc_AlphaScale: f32 = -1.0; + +@fragment +fn main(@location(0) in_Color: vec3) -> @location(0) vec4 { + return vec4(vec3(in_Color.r * sc_Col0_R, in_Color.g, in_Color.b) * sc_Brightness, + sc_AlphaScale); +} +)"}; + +struct SpecConstRefAttribs +{ + const Char* Name; + SHADER_TYPE Stage; + float RefValue; +}; + +// clang-format off +static constexpr SpecConstRefAttribs SpecConstRefDescs[] = { + {"sc_Col0_R", SHADER_TYPE_VS_PS, 1.0f}, + {"sc_Col0_G", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col0_B", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col1_R", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col1_G", SHADER_TYPE_VERTEX, 1.0f}, + {"sc_Col1_B", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col2_R", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col2_G", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col2_B", SHADER_TYPE_VERTEX, 1.0f}, + {"sc_Brightness", SHADER_TYPE_PIXEL, 1.0f}, + {"sc_AlphaScale", SHADER_TYPE_PIXEL, 1.0f}, +}; +// clang-format on + +static constexpr Uint32 SpecConstRefDescCount = _countof(SpecConstRefDescs); + +void CreateShaders(IRenderStateCache* pCache, + bool PresentInCache, + bool CompileAsync, + RefCntAutoPtr& pVS, + RefCntAutoPtr& pPS) +{ + auto* const pEnv = GPUTestingEnvironment::GetInstance(); + auto* const pDevice = pEnv->GetDevice(); + const auto& DeviceInfo = pDevice->GetDeviceInfo(); + + ShaderCreateInfo ShaderCI; + ShaderCI.CompileFlags = CompileAsync ? SHADER_COMPILE_FLAG_ASYNCHRONOUS : SHADER_COMPILE_FLAG_NONE; + + { + ShaderCI.Desc = {"SpecConsts Cache VS", SHADER_TYPE_VERTEX, true}; + ShaderCI.EntryPoint = "main"; + if (DeviceInfo.IsWebGPUDevice()) + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCI.Source = GraphicsVS_WGSL.c_str(); + } + else + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + ShaderCI.Source = GraphicsVS_GLSL.c_str(); + } + CreateShader(pCache, ShaderCI, PresentInCache, pVS); + ASSERT_TRUE(pVS); + } + + { + ShaderCI.Desc = {"SpecConsts Cache PS", SHADER_TYPE_PIXEL, true}; + ShaderCI.EntryPoint = "main"; + if (DeviceInfo.IsWebGPUDevice()) + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCI.Source = GraphicsPS_WGSL.c_str(); + } + else + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + ShaderCI.Source = GraphicsPS_GLSL.c_str(); + } + CreateShader(pCache, ShaderCI, PresentInCache, pPS); + ASSERT_TRUE(pPS); + } +} + +void CreateGraphicsPSO(IRenderStateCache* pCache, + bool PresentInCache, + bool CompileAsync, + const Char* Name, + IShader* pVS, + IShader* pPS, + const SpecializationConstant* pSpecConsts, + Uint32 NumSpecConsts, + RefCntAutoPtr& pPSO) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pDevice = pEnv->GetDevice(); + auto* pSwapChain = pEnv->GetSwapChain(); + + GraphicsPipelineStateCreateInfo PsoCI; + PsoCI.PSODesc.Name = Name; + PsoCI.Flags = CompileAsync ? PSO_CREATE_FLAG_ASYNCHRONOUS : PSO_CREATE_FLAG_NONE; + + PsoCI.pVS = pVS; + PsoCI.pPS = pPS; + + PsoCI.GraphicsPipeline.NumRenderTargets = 1; + PsoCI.GraphicsPipeline.RTVFormats[0] = pSwapChain->GetDesc().ColorBufferFormat; + PsoCI.GraphicsPipeline.PrimitiveTopology = PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + PsoCI.GraphicsPipeline.DepthStencilDesc.DepthEnable = False; + + PsoCI.pSpecializationConstants = pSpecConsts; + PsoCI.NumSpecializationConstants = NumSpecConsts; + + if (pCache != nullptr) + { + bool PSOFound = pCache->CreateGraphicsPipelineState(PsoCI, &pPSO); + if (!CompileAsync) + EXPECT_EQ(PSOFound, PresentInCache); + } + else + { + EXPECT_FALSE(PresentInCache); + pDevice->CreateGraphicsPipelineState(PsoCI, &pPSO); + } + ASSERT_NE(pPSO, nullptr); +} + +void VerifyPSO(IPipelineState* pPSO) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pCtx = pEnv->GetDeviceContext(); + auto* pSwapChain = pEnv->GetSwapChain(); + + static FastRandFloat rnd{2, 0, 1}; + const float ClearColor[] = {rnd(), rnd(), rnd(), rnd()}; + RenderDrawCommandReference(pSwapChain, ClearColor); + + ITextureView* pRTVs[] = {pSwapChain->GetCurrentBackBufferRTV()}; + pCtx->SetRenderTargets(1, pRTVs, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + pCtx->ClearRenderTarget(pRTVs[0], ClearColor, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + pCtx->SetPipelineState(pPSO); + pCtx->Draw({6, DRAW_FLAG_VERIFY_ALL}); + + pSwapChain->Present(); +} + +void TestRenderStateCaches(bool CompileAsync) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pDevice = pEnv->GetDevice(); + + if (pDevice->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + { + GTEST_SKIP() << "Specialization constants are not supported by this device"; + } + + GPUTestingEnvironment::ScopedReset AutoReset; + + SpecializationConstant SpecConsts[SpecConstRefDescCount]; + + for (size_t i = 0; i < SpecConstRefDescCount; ++i) + { + SpecConsts[i].Name = SpecConstRefDescs[i].Name; + SpecConsts[i].ShaderStages = SpecConstRefDescs[i].Stage; + SpecConsts[i].pData = &SpecConstRefDescs[i].RefValue; + SpecConsts[i].Size = sizeof(SpecConstRefDescs[i].RefValue); + } + + RefCntAutoPtr pUncachedVS, pUncachedPS; + CreateShaders(nullptr, false, false, pUncachedVS, pUncachedPS); + ASSERT_NE(pUncachedVS, nullptr); + ASSERT_NE(pUncachedPS, nullptr); + + RefCntAutoPtr pRefPSO; + CreateGraphicsPSO(nullptr, false, false, "SpecConsts Cache Test", pUncachedVS, pUncachedPS, SpecConsts, _countof(SpecConsts), pRefPSO); + ASSERT_NE(pRefPSO, nullptr); + ASSERT_EQ(pRefPSO->GetStatus(), PIPELINE_STATE_STATUS_READY); + + RefCntAutoPtr pData; + for (Uint32 pass = 0; pass < 3; ++pass) + { + // 0: empty cache + // 1: loaded cache + // 2: reloaded cache (loaded -> stored -> loaded) + + auto pCache = CreateCache(pDevice, /*HotReload=*/false, pData); + ASSERT_TRUE(pCache); + + RefCntAutoPtr pVS, pPS; + CreateShaders(pCache, pData != nullptr, CompileAsync, pVS, pPS); + ASSERT_NE(pVS, nullptr); + ASSERT_NE(pPS, nullptr); + + if (CompileAsync && pass == 0) + { + std::string MutableNames[SpecConstRefDescCount]; + float MutableValues[SpecConstRefDescCount]; + SpecializationConstant MutableSpecConsts[SpecConstRefDescCount]; + + for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) + { + MutableNames[i] = SpecConstRefDescs[i].Name; + MutableValues[i] = SpecConstRefDescs[i].RefValue; + MutableSpecConsts[i].Name = MutableNames[i].c_str(); + MutableSpecConsts[i].ShaderStages = SpecConstRefDescs[i].Stage; + MutableSpecConsts[i].pData = &MutableValues[i]; + MutableSpecConsts[i].Size = sizeof(MutableValues[i]); + } + + RefCntAutoPtr pMutablePSO; + CreateGraphicsPSO(pCache, false, CompileAsync, "SpecConsts Cache Mutable Test", pVS, pPS, MutableSpecConsts, _countof(MutableSpecConsts), pMutablePSO); + ASSERT_NE(pMutablePSO, nullptr); + + //Make sure the strings and data are properly copied in CreateGraphicsPSO. + for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) + { + MutableNames[i] = SpecConstRefDescs[(i + 1) % SpecConstRefDescCount].Name; + MutableValues[i] = SpecConstRefDescs[(i + 1) % SpecConstRefDescCount].RefValue; + } + + ASSERT_EQ(pMutablePSO->GetStatus(true), PIPELINE_STATE_STATUS_READY); + VerifyPSO(pMutablePSO); + } + + RefCntAutoPtr pPSO; + CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO); + ASSERT_NE(pPSO, nullptr); + ASSERT_EQ(pPSO->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + EXPECT_TRUE(pRefPSO->IsCompatibleWith(pPSO)); + EXPECT_TRUE(pPSO->IsCompatibleWith(pRefPSO)); + + VerifyPSO(pPSO); + + { + RefCntAutoPtr pPSO2; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO2); + ASSERT_NE(pPSO2, nullptr); + ASSERT_EQ(pPSO2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + if (!CompileAsync) + EXPECT_EQ(pPSO, pPSO2); + } + + pData.Release(); + pCache->WriteToBlob(pass == 0 ? ContentVersion : ~0u, &pData); + } +} + +void TestDistinctEntries(bool CompileAsync) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pDevice = pEnv->GetDevice(); + + if (pDevice->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + { + GTEST_SKIP() << "Specialization constants are not supported by this device"; + } + + GPUTestingEnvironment::ScopedReset AutoReset; + + RefCntAutoPtr pData; + for (Uint32 pass = 0; pass < 3; ++pass) + { + // 0: empty cache + // 1: loaded cache + // 2: reloaded cache (loaded -> stored -> loaded) + + auto pCache = CreateCache(pDevice, /*HotReload=*/false, pData); + ASSERT_TRUE(pCache); + + RefCntAutoPtr pVS, pPS; + CreateShaders(pCache, pData != nullptr, CompileAsync, pVS, pPS); + ASSERT_NE(pVS, nullptr); + ASSERT_NE(pPS, nullptr); + + float RefValuesB[SpecConstRefDescCount]; + for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) + RefValuesB[i] = SpecConstRefDescs[i].RefValue; + RefValuesB[9] = 2.0f; // sc_Brightness + + // clang-format off + + SpecializationConstant SpecConstsA[SpecConstRefDescCount]; + + for(size_t i = 0; i < SpecConstRefDescCount; ++i){ + SpecConstsA[i].Name = SpecConstRefDescs[i].Name; + SpecConstsA[i].ShaderStages = SpecConstRefDescs[i].Stage; + SpecConstsA[i].pData = &SpecConstRefDescs[i].RefValue; + SpecConstsA[i].Size = sizeof(SpecConstRefDescs[i].RefValue); + } + + SpecializationConstant SpecConstsB[SpecConstRefDescCount]; + + for(size_t i = 0; i < SpecConstRefDescCount; ++i){ + SpecConstsB[i].Name = SpecConstRefDescs[i].Name; + SpecConstsB[i].ShaderStages = SpecConstRefDescs[i].Stage; + SpecConstsB[i].pData = &RefValuesB[i]; + SpecConstsB[i].Size = sizeof(RefValuesB[i]); + } + + // clang-format on + + RefCntAutoPtr pPSO_A; + CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A); + ASSERT_NE(pPSO_A, nullptr); + ASSERT_EQ(pPSO_A->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + + RefCntAutoPtr pPSO_B; + CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B); + ASSERT_NE(pPSO_B, nullptr); + ASSERT_EQ(pPSO_B->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + + EXPECT_NE(pPSO_A, pPSO_B); + + RefCntAutoPtr pPSO_A2; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A2); + ASSERT_NE(pPSO_A2, nullptr); + ASSERT_EQ(pPSO_A2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + if (!CompileAsync) + EXPECT_EQ(pPSO_A, pPSO_A2); + + RefCntAutoPtr pPSO_B2; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B2); + ASSERT_NE(pPSO_B2, nullptr); + ASSERT_EQ(pPSO_B2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + if (!CompileAsync) + EXPECT_EQ(pPSO_B, pPSO_B2); + + RefCntAutoPtr pPSO_None; + CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, nullptr, 0, pPSO_None); + ASSERT_NE(pPSO_None, nullptr); + ASSERT_EQ(pPSO_None->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + EXPECT_NE(pPSO_None, pPSO_A); + EXPECT_NE(pPSO_None, pPSO_B); + + pData.Release(); + pCache->WriteToBlob(pass == 0 ? ContentVersion : ~0u, &pData); + } +} + +} // namespace SpecConstsTests + +TEST(RenderStateCacheTest, SpecializationConstants) +{ + SpecConstsTests::TestRenderStateCaches(/*CompileAsync = */ false); +} + +TEST(RenderStateCacheTest, SpecializationConstants_Async) +{ + SpecConstsTests::TestRenderStateCaches(/*CompileAsync = */ true); +} + +TEST(RenderStateCacheTest, SpecializationConstants_DistinctEntries) +{ + SpecConstsTests::TestDistinctEntries(/*CompileAsync = */ false); +} + +TEST(RenderStateCacheTest, SpecializationConstants_DistinctEntries_Async) +{ + SpecConstsTests::TestDistinctEntries(/*CompileAsync = */ true); +} + } // namespace From 7fdc4c2ac3ecc5d0b78b3cdc63b3fa6e67a501ab Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 4 Mar 2026 20:29:33 +0800 Subject: [PATCH 21/28] refine tests. --- .../src/RenderStateCacheTest.cpp | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp index f67fb2185c..af3ed57ce7 100644 --- a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp +++ b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp @@ -2303,36 +2303,36 @@ void TestDistinctEntries(bool CompileAsync) float RefValuesB[SpecConstRefDescCount]; for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) + { RefValuesB[i] = SpecConstRefDescs[i].RefValue; - RefValuesB[9] = 2.0f; // sc_Brightness - - // clang-format off + if (!strcmp(SpecConstRefDescs[i].Name, "sc_Brightness")) + RefValuesB[i] = 2.0f; + } SpecializationConstant SpecConstsA[SpecConstRefDescCount]; - - for(size_t i = 0; i < SpecConstRefDescCount; ++i){ - SpecConstsA[i].Name = SpecConstRefDescs[i].Name; + for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) + { + SpecConstsA[i].Name = SpecConstRefDescs[i].Name; SpecConstsA[i].ShaderStages = SpecConstRefDescs[i].Stage; - SpecConstsA[i].pData = &SpecConstRefDescs[i].RefValue; - SpecConstsA[i].Size = sizeof(SpecConstRefDescs[i].RefValue); + SpecConstsA[i].pData = &SpecConstRefDescs[i].RefValue; + SpecConstsA[i].Size = sizeof(SpecConstRefDescs[i].RefValue); } SpecializationConstant SpecConstsB[SpecConstRefDescCount]; - - for(size_t i = 0; i < SpecConstRefDescCount; ++i){ - SpecConstsB[i].Name = SpecConstRefDescs[i].Name; + for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) + { + SpecConstsB[i].Name = SpecConstRefDescs[i].Name; SpecConstsB[i].ShaderStages = SpecConstRefDescs[i].Stage; - SpecConstsB[i].pData = &RefValuesB[i]; - SpecConstsB[i].Size = sizeof(RefValuesB[i]); + SpecConstsB[i].pData = &RefValuesB[i]; + SpecConstsB[i].Size = sizeof(RefValuesB[i]); } - // clang-format on - RefCntAutoPtr pPSO_A; CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A); ASSERT_NE(pPSO_A, nullptr); ASSERT_EQ(pPSO_A->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + VerifyPSO(pPSO_A); RefCntAutoPtr pPSO_B; CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", From aec2dd3d6cd8a9b59078fe5c8ec61e746a188465 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 4 Mar 2026 20:59:17 +0800 Subject: [PATCH 22/28] refine TestRenderStateCaches. --- .../src/RenderStateCacheTest.cpp | 53 +++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp index af3ed57ce7..d177843c73 100644 --- a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp +++ b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp @@ -2118,7 +2118,8 @@ void CreateGraphicsPSO(IRenderStateCache* pCache, IShader* pPS, const SpecializationConstant* pSpecConsts, Uint32 NumSpecConsts, - RefCntAutoPtr& pPSO) + RefCntAutoPtr& pPSO, + bool* pFoundInCache = nullptr) { auto* pEnv = GPUTestingEnvironment::GetInstance(); auto* pDevice = pEnv->GetDevice(); @@ -2144,6 +2145,8 @@ void CreateGraphicsPSO(IRenderStateCache* pCache, bool PSOFound = pCache->CreateGraphicsPipelineState(PsoCI, &pPSO); if (!CompileAsync) EXPECT_EQ(PSOFound, PresentInCache); + if (pFoundInCache != nullptr) + *pFoundInCache = PSOFound; } else { @@ -2262,11 +2265,27 @@ void TestRenderStateCaches(bool CompileAsync) { RefCntAutoPtr pPSO2; - CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO2); + bool PSOFound2 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO2, &PSOFound2); + EXPECT_TRUE(PSOFound2); ASSERT_NE(pPSO2, nullptr); ASSERT_EQ(pPSO2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + + RefCntAutoPtr pPSO3; + bool PSOFound3 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO3, &PSOFound3); + EXPECT_TRUE(PSOFound3); + ASSERT_NE(pPSO3, nullptr); + ASSERT_EQ(pPSO3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + if (!CompileAsync) + { EXPECT_EQ(pPSO, pPSO2); + } + else + { + EXPECT_EQ(pPSO2, pPSO3); + } } pData.Release(); @@ -2343,20 +2362,46 @@ void TestDistinctEntries(bool CompileAsync) EXPECT_NE(pPSO_A, pPSO_B); RefCntAutoPtr pPSO_A2; + bool PSOFoundA2 = false; CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A2); + pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A2, &PSOFoundA2); + EXPECT_TRUE(PSOFoundA2); ASSERT_NE(pPSO_A2, nullptr); ASSERT_EQ(pPSO_A2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); if (!CompileAsync) EXPECT_EQ(pPSO_A, pPSO_A2); + else + { + RefCntAutoPtr pPSO_A3; + bool PSOFoundA3 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A3, &PSOFoundA3); + EXPECT_TRUE(PSOFoundA3); + ASSERT_NE(pPSO_A3, nullptr); + ASSERT_EQ(pPSO_A3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + EXPECT_EQ(pPSO_A2, pPSO_A3); + } RefCntAutoPtr pPSO_B2; + bool PSOFoundB2 = false; CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B2); + pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B2, &PSOFoundB2); + EXPECT_TRUE(PSOFoundB2); ASSERT_NE(pPSO_B2, nullptr); ASSERT_EQ(pPSO_B2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); if (!CompileAsync) EXPECT_EQ(pPSO_B, pPSO_B2); + else + { + RefCntAutoPtr pPSO_B3; + bool PSOFoundB3 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B3, &PSOFoundB3); + EXPECT_TRUE(PSOFoundB3); + ASSERT_NE(pPSO_B3, nullptr); + ASSERT_EQ(pPSO_B3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + EXPECT_EQ(pPSO_B2, pPSO_B3); + } RefCntAutoPtr pPSO_None; CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", From 221d8c53d01f788b8e75bac12aaf912a678f80a8 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 4 Mar 2026 21:14:50 +0800 Subject: [PATCH 23/28] refine TestRenderStateCaches. --- Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp index d177843c73..94b0a896a2 100644 --- a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp +++ b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp @@ -2190,7 +2190,7 @@ void TestRenderStateCaches(bool CompileAsync) SpecializationConstant SpecConsts[SpecConstRefDescCount]; - for (size_t i = 0; i < SpecConstRefDescCount; ++i) + for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) { SpecConsts[i].Name = SpecConstRefDescs[i].Name; SpecConsts[i].ShaderStages = SpecConstRefDescs[i].Stage; @@ -2243,7 +2243,7 @@ void TestRenderStateCaches(bool CompileAsync) CreateGraphicsPSO(pCache, false, CompileAsync, "SpecConsts Cache Mutable Test", pVS, pPS, MutableSpecConsts, _countof(MutableSpecConsts), pMutablePSO); ASSERT_NE(pMutablePSO, nullptr); - //Make sure the strings and data are properly copied in CreateGraphicsPSO. + // Make sure the strings and data are properly copied in CreateGraphicsPSO. for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) { MutableNames[i] = SpecConstRefDescs[(i + 1) % SpecConstRefDescCount].Name; @@ -2369,7 +2369,9 @@ void TestDistinctEntries(bool CompileAsync) ASSERT_NE(pPSO_A2, nullptr); ASSERT_EQ(pPSO_A2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); if (!CompileAsync) + { EXPECT_EQ(pPSO_A, pPSO_A2); + } else { RefCntAutoPtr pPSO_A3; @@ -2390,7 +2392,9 @@ void TestDistinctEntries(bool CompileAsync) ASSERT_NE(pPSO_B2, nullptr); ASSERT_EQ(pPSO_B2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); if (!CompileAsync) + { EXPECT_EQ(pPSO_B, pPSO_B2); + } else { RefCntAutoPtr pPSO_B3; From 47bb1f13f291e9b2e83b3e48ceb82d6ceea21258 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 4 Mar 2026 15:13:49 +0800 Subject: [PATCH 24/28] Fix intermittent failure in Common_HashUtils.PipelineStateCIStdHash --- .../src/Common/HashUtilsTest.cpp | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/Tests/DiligentCoreTest/src/Common/HashUtilsTest.cpp b/Tests/DiligentCoreTest/src/Common/HashUtilsTest.cpp index 733244ef68..3477ee15c0 100644 --- a/Tests/DiligentCoreTest/src/Common/HashUtilsTest.cpp +++ b/Tests/DiligentCoreTest/src/Common/HashUtilsTest.cpp @@ -1205,17 +1205,33 @@ void TestPipelineStateCIHasher() Helper.Get().ppResourceSignatures = ppSignatures.data(); TEST_RANGE(ResourceSignaturesCount, 1u, MAX_RESOURCE_SIGNATURES); + // Keep specialization constant storage alive for the entire helper lifetime. + // StdHasherTestHelper stores shallow copies of create-info in m_Descs. + const Uint32 Data1[] = {1, 2, 3}; + const Uint32 Data2[] = {4, 5, 6}; + const Uint32 Data3[] = {7, 8, 9}; + const SpecializationConstant SpecConsts[] = + { + {"Const1", SHADER_TYPE_VERTEX, sizeof(Uint32), &Data1[0]}, + {"Const2", SHADER_TYPE_PIXEL, sizeof(Uint32), &Data2[0]}, + {"Const3", SHADER_TYPE_GEOMETRY, sizeof(Uint32), &Data3[0]}, + }; + + const Uint32 DataA = 100; + const Uint32 DataB = 200; + const SpecializationConstant SC_A{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataA}; + const SpecializationConstant SC_B{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataB}; + + const Uint32 DataName = 42; + const SpecializationConstant SC_Name1{"Alpha", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataName}; + const SpecializationConstant SC_Name2{"Beta", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataName}; + + const Uint32 DataStage = 42; + const SpecializationConstant SC_VS{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataStage}; + const SpecializationConstant SC_PS{"Const", SHADER_TYPE_PIXEL, sizeof(Uint32), &DataStage}; + // Test specialization constants hashing { - const Uint32 Data1[] = {1, 2, 3}; - const Uint32 Data2[] = {4, 5, 6}; - const Uint32 Data3[] = {7, 8, 9}; - const SpecializationConstant SpecConsts[] = - { - {"Const1", SHADER_TYPE_VERTEX, sizeof(Uint32), &Data1[0]}, - {"Const2", SHADER_TYPE_PIXEL, sizeof(Uint32), &Data2[0]}, - {"Const3", SHADER_TYPE_GEOMETRY, sizeof(Uint32), &Data3[0]}, - }; Helper.Get().ppResourceSignatures = nullptr; Helper.Get().ResourceSignaturesCount = 0; Helper.Get().pSpecializationConstants = SpecConsts; @@ -1226,11 +1242,6 @@ void TestPipelineStateCIHasher() // Test that different specialization constant data produces different hashes { - const Uint32 DataA = 100; - const Uint32 DataB = 200; - const SpecializationConstant SC_A{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataA}; - const SpecializationConstant SC_B{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &DataB}; - Helper.Get().NumSpecializationConstants = 1; Helper.Get().pSpecializationConstants = &SC_A; Helper.Add("SpecConst data A"); @@ -1241,10 +1252,6 @@ void TestPipelineStateCIHasher() // Test that different specialization constant names produce different hashes { - const Uint32 Data = 42; - const SpecializationConstant SC_Name1{"Alpha", SHADER_TYPE_VERTEX, sizeof(Uint32), &Data}; - const SpecializationConstant SC_Name2{"Beta", SHADER_TYPE_VERTEX, sizeof(Uint32), &Data}; - Helper.Get().NumSpecializationConstants = 1; Helper.Get().pSpecializationConstants = &SC_Name1; Helper.Add("SpecConst name Alpha"); @@ -1255,10 +1262,6 @@ void TestPipelineStateCIHasher() // Test that different specialization constant ShaderStages produce different hashes { - const Uint32 Data = 42; - const SpecializationConstant SC_VS{"Const", SHADER_TYPE_VERTEX, sizeof(Uint32), &Data}; - const SpecializationConstant SC_PS{"Const", SHADER_TYPE_PIXEL, sizeof(Uint32), &Data}; - Helper.Get().NumSpecializationConstants = 1; Helper.Get().pSpecializationConstants = &SC_VS; Helper.Add("SpecConst stage VS"); From e779853d5b01297e18f16a5b6d4b10e51f4e3987 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 4 Mar 2026 21:14:50 +0800 Subject: [PATCH 25/28] Add render state cache tests for specialization constants --- .../src/RenderStateCacheTest.cpp | 526 +++++++++++++++++- 1 file changed, 525 insertions(+), 1 deletion(-) diff --git a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp index c26c2b9f22..b022be1576 100644 --- a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp +++ b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1918,4 +1918,528 @@ TEST(RenderStateCacheTest, NonSeparableProgramBindingConflict) ASSERT_EQ(pPSO->GetStatus(), PIPELINE_STATE_STATUS_READY); } +namespace SpecConstsTests +{ + +const std::string GraphicsVS_GLSL{ + R"( +#version 450 + +#ifndef GL_ES +out gl_PerVertex { vec4 gl_Position; }; +#endif + +layout(constant_id = 0) const float sc_Col0_R = -1.0; +layout(constant_id = 1) const float sc_Col0_G = -1.0; +layout(constant_id = 2) const float sc_Col0_B = -1.0; + +layout(constant_id = 3) const float sc_Col1_R = -1.0; +layout(constant_id = 4) const float sc_Col1_G = -1.0; +layout(constant_id = 5) const float sc_Col1_B = -1.0; + +layout(constant_id = 6) const float sc_Col2_R = -1.0; +layout(constant_id = 7) const float sc_Col2_G = -1.0; +layout(constant_id = 8) const float sc_Col2_B = -1.0; + +layout(location = 0) out vec3 out_Color; + +void main() +{ + vec4 Pos[6]; + Pos[0] = vec4(-1.0, -0.5, 0.0, 1.0); + Pos[1] = vec4(-0.5, +0.5, 0.0, 1.0); + Pos[2] = vec4( 0.0, -0.5, 0.0, 1.0); + + Pos[3] = vec4(+0.0, -0.5, 0.0, 1.0); + Pos[4] = vec4(+0.5, +0.5, 0.0, 1.0); + Pos[5] = vec4(+1.0, -0.5, 0.0, 1.0); + + vec3 Col[3]; + Col[0] = vec3(sc_Col0_R, sc_Col0_G, sc_Col0_B); + Col[1] = vec3(sc_Col1_R, sc_Col1_G, sc_Col1_B); + Col[2] = vec3(sc_Col2_R, sc_Col2_G, sc_Col2_B); + + gl_Position = Pos[gl_VertexIndex]; + out_Color = Col[gl_VertexIndex % 3]; +} +)"}; + +const std::string GraphicsPS_GLSL{ + R"( +#version 450 + +layout(constant_id = 0) const float sc_Col0_R = -1.0; +layout(constant_id = 1) const float sc_Brightness = -1.0; +layout(constant_id = 2) const float sc_AlphaScale = -1.0; + +layout(location = 0) in vec3 in_Color; +layout(location = 0) out vec4 out_Color; + +void main() +{ + out_Color = vec4(vec3(in_Color.r * sc_Col0_R, in_Color.g, in_Color.b) * sc_Brightness, + sc_AlphaScale); +} +)"}; + +const std::string GraphicsVS_WGSL{ + R"( +override sc_Col0_R: f32 = -1.0; +override sc_Col0_G: f32 = -1.0; +override sc_Col0_B: f32 = -1.0; + +override sc_Col1_R: f32 = -1.0; +override sc_Col1_G: f32 = -1.0; +override sc_Col1_B: f32 = -1.0; + +override sc_Col2_R: f32 = -1.0; +override sc_Col2_G: f32 = -1.0; +override sc_Col2_B: f32 = -1.0; + +struct VSOutput { + @builtin(position) Position: vec4, + @location(0) Color: vec3, +}; + +@vertex +fn main(@builtin(vertex_index) VertexIndex: u32) -> VSOutput { + var Pos: array, 6>; + Pos[0] = vec4(-1.0, -0.5, 0.0, 1.0); + Pos[1] = vec4(-0.5, 0.5, 0.0, 1.0); + Pos[2] = vec4( 0.0, -0.5, 0.0, 1.0); + + Pos[3] = vec4( 0.0, -0.5, 0.0, 1.0); + Pos[4] = vec4( 0.5, 0.5, 0.0, 1.0); + Pos[5] = vec4( 1.0, -0.5, 0.0, 1.0); + + var Col: array, 3>; + Col[0] = vec3(sc_Col0_R, sc_Col0_G, sc_Col0_B); + Col[1] = vec3(sc_Col1_R, sc_Col1_G, sc_Col1_B); + Col[2] = vec3(sc_Col2_R, sc_Col2_G, sc_Col2_B); + + var output: VSOutput; + output.Position = Pos[VertexIndex]; + output.Color = Col[VertexIndex % 3]; + return output; +} +)"}; + +const std::string GraphicsPS_WGSL{ + R"( +override sc_Col0_R: f32 = -1.0; +override sc_Brightness: f32 = -1.0; +override sc_AlphaScale: f32 = -1.0; + +@fragment +fn main(@location(0) in_Color: vec3) -> @location(0) vec4 { + return vec4(vec3(in_Color.r * sc_Col0_R, in_Color.g, in_Color.b) * sc_Brightness, + sc_AlphaScale); +} +)"}; + +struct SpecConstRefAttribs +{ + const Char* Name; + SHADER_TYPE Stage; + float RefValue; +}; + +// clang-format off +static constexpr SpecConstRefAttribs SpecConstRefDescs[] = { + {"sc_Col0_R", SHADER_TYPE_VS_PS, 1.0f}, + {"sc_Col0_G", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col0_B", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col1_R", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col1_G", SHADER_TYPE_VERTEX, 1.0f}, + {"sc_Col1_B", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col2_R", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col2_G", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col2_B", SHADER_TYPE_VERTEX, 1.0f}, + {"sc_Brightness", SHADER_TYPE_PIXEL, 1.0f}, + {"sc_AlphaScale", SHADER_TYPE_PIXEL, 1.0f}, +}; +// clang-format on + +static constexpr Uint32 SpecConstRefDescCount = _countof(SpecConstRefDescs); + +void CreateShaders(IRenderStateCache* pCache, + bool PresentInCache, + bool CompileAsync, + RefCntAutoPtr& pVS, + RefCntAutoPtr& pPS) +{ + auto* const pEnv = GPUTestingEnvironment::GetInstance(); + auto* const pDevice = pEnv->GetDevice(); + const auto& DeviceInfo = pDevice->GetDeviceInfo(); + + ShaderCreateInfo ShaderCI; + ShaderCI.CompileFlags = CompileAsync ? SHADER_COMPILE_FLAG_ASYNCHRONOUS : SHADER_COMPILE_FLAG_NONE; + + { + ShaderCI.Desc = {"SpecConsts Cache VS", SHADER_TYPE_VERTEX, true}; + ShaderCI.EntryPoint = "main"; + if (DeviceInfo.IsWebGPUDevice()) + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCI.Source = GraphicsVS_WGSL.c_str(); + } + else + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + ShaderCI.Source = GraphicsVS_GLSL.c_str(); + } + CreateShader(pCache, ShaderCI, PresentInCache, pVS); + ASSERT_TRUE(pVS); + } + + { + ShaderCI.Desc = {"SpecConsts Cache PS", SHADER_TYPE_PIXEL, true}; + ShaderCI.EntryPoint = "main"; + if (DeviceInfo.IsWebGPUDevice()) + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCI.Source = GraphicsPS_WGSL.c_str(); + } + else + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + ShaderCI.Source = GraphicsPS_GLSL.c_str(); + } + CreateShader(pCache, ShaderCI, PresentInCache, pPS); + ASSERT_TRUE(pPS); + } +} + +void CreateGraphicsPSO(IRenderStateCache* pCache, + bool PresentInCache, + bool CompileAsync, + const Char* Name, + IShader* pVS, + IShader* pPS, + const SpecializationConstant* pSpecConsts, + Uint32 NumSpecConsts, + RefCntAutoPtr& pPSO, + bool* pFoundInCache = nullptr) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pDevice = pEnv->GetDevice(); + auto* pSwapChain = pEnv->GetSwapChain(); + + GraphicsPipelineStateCreateInfo PsoCI; + PsoCI.PSODesc.Name = Name; + PsoCI.Flags = CompileAsync ? PSO_CREATE_FLAG_ASYNCHRONOUS : PSO_CREATE_FLAG_NONE; + + PsoCI.pVS = pVS; + PsoCI.pPS = pPS; + + PsoCI.GraphicsPipeline.NumRenderTargets = 1; + PsoCI.GraphicsPipeline.RTVFormats[0] = pSwapChain->GetDesc().ColorBufferFormat; + PsoCI.GraphicsPipeline.PrimitiveTopology = PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + PsoCI.GraphicsPipeline.DepthStencilDesc.DepthEnable = False; + + PsoCI.pSpecializationConstants = pSpecConsts; + PsoCI.NumSpecializationConstants = NumSpecConsts; + + if (pCache != nullptr) + { + bool PSOFound = pCache->CreateGraphicsPipelineState(PsoCI, &pPSO); + if (!CompileAsync) + EXPECT_EQ(PSOFound, PresentInCache); + if (pFoundInCache != nullptr) + *pFoundInCache = PSOFound; + } + else + { + EXPECT_FALSE(PresentInCache); + pDevice->CreateGraphicsPipelineState(PsoCI, &pPSO); + } + ASSERT_NE(pPSO, nullptr); +} + +void VerifyPSO(IPipelineState* pPSO) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pCtx = pEnv->GetDeviceContext(); + auto* pSwapChain = pEnv->GetSwapChain(); + + static FastRandFloat rnd{2, 0, 1}; + const float ClearColor[] = {rnd(), rnd(), rnd(), rnd()}; + RenderDrawCommandReference(pSwapChain, ClearColor); + + ITextureView* pRTVs[] = {pSwapChain->GetCurrentBackBufferRTV()}; + pCtx->SetRenderTargets(1, pRTVs, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + pCtx->ClearRenderTarget(pRTVs[0], ClearColor, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + pCtx->SetPipelineState(pPSO); + pCtx->Draw({6, DRAW_FLAG_VERIFY_ALL}); + + pSwapChain->Present(); +} + +void TestRenderStateCaches(bool CompileAsync) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pDevice = pEnv->GetDevice(); + + if (pDevice->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + { + GTEST_SKIP() << "Specialization constants are not supported by this device"; + } + + GPUTestingEnvironment::ScopedReset AutoReset; + + SpecializationConstant SpecConsts[SpecConstRefDescCount]; + + for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) + { + SpecConsts[i].Name = SpecConstRefDescs[i].Name; + SpecConsts[i].ShaderStages = SpecConstRefDescs[i].Stage; + SpecConsts[i].pData = &SpecConstRefDescs[i].RefValue; + SpecConsts[i].Size = sizeof(SpecConstRefDescs[i].RefValue); + } + + RefCntAutoPtr pUncachedVS, pUncachedPS; + CreateShaders(nullptr, false, false, pUncachedVS, pUncachedPS); + ASSERT_NE(pUncachedVS, nullptr); + ASSERT_NE(pUncachedPS, nullptr); + + RefCntAutoPtr pRefPSO; + CreateGraphicsPSO(nullptr, false, false, "SpecConsts Cache Test", pUncachedVS, pUncachedPS, SpecConsts, _countof(SpecConsts), pRefPSO); + ASSERT_NE(pRefPSO, nullptr); + ASSERT_EQ(pRefPSO->GetStatus(), PIPELINE_STATE_STATUS_READY); + + RefCntAutoPtr pData; + for (Uint32 pass = 0; pass < 3; ++pass) + { + // 0: empty cache + // 1: loaded cache + // 2: reloaded cache (loaded -> stored -> loaded) + + auto pCache = CreateCache(pDevice, /*HotReload=*/false, pData); + ASSERT_TRUE(pCache); + + RefCntAutoPtr pVS, pPS; + CreateShaders(pCache, pData != nullptr, CompileAsync, pVS, pPS); + ASSERT_NE(pVS, nullptr); + ASSERT_NE(pPS, nullptr); + + if (CompileAsync && pass == 0) + { + std::string MutableNames[SpecConstRefDescCount]; + float MutableValues[SpecConstRefDescCount]; + SpecializationConstant MutableSpecConsts[SpecConstRefDescCount]; + + for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) + { + MutableNames[i] = SpecConstRefDescs[i].Name; + MutableValues[i] = SpecConstRefDescs[i].RefValue; + MutableSpecConsts[i].Name = MutableNames[i].c_str(); + MutableSpecConsts[i].ShaderStages = SpecConstRefDescs[i].Stage; + MutableSpecConsts[i].pData = &MutableValues[i]; + MutableSpecConsts[i].Size = sizeof(MutableValues[i]); + } + + RefCntAutoPtr pMutablePSO; + CreateGraphicsPSO(pCache, false, CompileAsync, "SpecConsts Cache Mutable Test", pVS, pPS, MutableSpecConsts, _countof(MutableSpecConsts), pMutablePSO); + ASSERT_NE(pMutablePSO, nullptr); + + // Make sure the strings and data are properly copied in CreateGraphicsPSO. + for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) + { + MutableNames[i] = SpecConstRefDescs[(i + 1) % SpecConstRefDescCount].Name; + MutableValues[i] = SpecConstRefDescs[(i + 1) % SpecConstRefDescCount].RefValue; + } + + ASSERT_EQ(pMutablePSO->GetStatus(true), PIPELINE_STATE_STATUS_READY); + VerifyPSO(pMutablePSO); + } + + RefCntAutoPtr pPSO; + CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO); + ASSERT_NE(pPSO, nullptr); + ASSERT_EQ(pPSO->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + EXPECT_TRUE(pRefPSO->IsCompatibleWith(pPSO)); + EXPECT_TRUE(pPSO->IsCompatibleWith(pRefPSO)); + + VerifyPSO(pPSO); + + { + RefCntAutoPtr pPSO2; + bool PSOFound2 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO2, &PSOFound2); + EXPECT_TRUE(PSOFound2); + ASSERT_NE(pPSO2, nullptr); + ASSERT_EQ(pPSO2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + + RefCntAutoPtr pPSO3; + bool PSOFound3 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO3, &PSOFound3); + EXPECT_TRUE(PSOFound3); + ASSERT_NE(pPSO3, nullptr); + ASSERT_EQ(pPSO3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + + if (!CompileAsync) + { + EXPECT_EQ(pPSO, pPSO2); + } + else + { + EXPECT_EQ(pPSO2, pPSO3); + } + } + + pData.Release(); + pCache->WriteToBlob(pass == 0 ? ContentVersion : ~0u, &pData); + } +} + +void TestDistinctEntries(bool CompileAsync) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pDevice = pEnv->GetDevice(); + + if (pDevice->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + { + GTEST_SKIP() << "Specialization constants are not supported by this device"; + } + + GPUTestingEnvironment::ScopedReset AutoReset; + + RefCntAutoPtr pData; + for (Uint32 pass = 0; pass < 3; ++pass) + { + // 0: empty cache + // 1: loaded cache + // 2: reloaded cache (loaded -> stored -> loaded) + + auto pCache = CreateCache(pDevice, /*HotReload=*/false, pData); + ASSERT_TRUE(pCache); + + RefCntAutoPtr pVS, pPS; + CreateShaders(pCache, pData != nullptr, CompileAsync, pVS, pPS); + ASSERT_NE(pVS, nullptr); + ASSERT_NE(pPS, nullptr); + + float RefValuesB[SpecConstRefDescCount]; + for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) + { + RefValuesB[i] = SpecConstRefDescs[i].RefValue; + if (!strcmp(SpecConstRefDescs[i].Name, "sc_Brightness")) + RefValuesB[i] = 2.0f; + } + + SpecializationConstant SpecConstsA[SpecConstRefDescCount]; + for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) + { + SpecConstsA[i].Name = SpecConstRefDescs[i].Name; + SpecConstsA[i].ShaderStages = SpecConstRefDescs[i].Stage; + SpecConstsA[i].pData = &SpecConstRefDescs[i].RefValue; + SpecConstsA[i].Size = sizeof(SpecConstRefDescs[i].RefValue); + } + + SpecializationConstant SpecConstsB[SpecConstRefDescCount]; + for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) + { + SpecConstsB[i].Name = SpecConstRefDescs[i].Name; + SpecConstsB[i].ShaderStages = SpecConstRefDescs[i].Stage; + SpecConstsB[i].pData = &RefValuesB[i]; + SpecConstsB[i].Size = sizeof(RefValuesB[i]); + } + + RefCntAutoPtr pPSO_A; + CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A); + ASSERT_NE(pPSO_A, nullptr); + ASSERT_EQ(pPSO_A->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + VerifyPSO(pPSO_A); + + RefCntAutoPtr pPSO_B; + CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B); + ASSERT_NE(pPSO_B, nullptr); + ASSERT_EQ(pPSO_B->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + + EXPECT_NE(pPSO_A, pPSO_B); + + RefCntAutoPtr pPSO_A2; + bool PSOFoundA2 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A2, &PSOFoundA2); + EXPECT_TRUE(PSOFoundA2); + ASSERT_NE(pPSO_A2, nullptr); + ASSERT_EQ(pPSO_A2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + if (!CompileAsync) + { + EXPECT_EQ(pPSO_A, pPSO_A2); + } + else + { + RefCntAutoPtr pPSO_A3; + bool PSOFoundA3 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A3, &PSOFoundA3); + EXPECT_TRUE(PSOFoundA3); + ASSERT_NE(pPSO_A3, nullptr); + ASSERT_EQ(pPSO_A3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + EXPECT_EQ(pPSO_A2, pPSO_A3); + } + + RefCntAutoPtr pPSO_B2; + bool PSOFoundB2 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B2, &PSOFoundB2); + EXPECT_TRUE(PSOFoundB2); + ASSERT_NE(pPSO_B2, nullptr); + ASSERT_EQ(pPSO_B2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + if (!CompileAsync) + { + EXPECT_EQ(pPSO_B, pPSO_B2); + } + else + { + RefCntAutoPtr pPSO_B3; + bool PSOFoundB3 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B3, &PSOFoundB3); + EXPECT_TRUE(PSOFoundB3); + ASSERT_NE(pPSO_B3, nullptr); + ASSERT_EQ(pPSO_B3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + EXPECT_EQ(pPSO_B2, pPSO_B3); + } + + RefCntAutoPtr pPSO_None; + CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, nullptr, 0, pPSO_None); + ASSERT_NE(pPSO_None, nullptr); + ASSERT_EQ(pPSO_None->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + EXPECT_NE(pPSO_None, pPSO_A); + EXPECT_NE(pPSO_None, pPSO_B); + + pData.Release(); + pCache->WriteToBlob(pass == 0 ? ContentVersion : ~0u, &pData); + } +} + +} // namespace SpecConstsTests + +TEST(RenderStateCacheTest, SpecializationConstants) +{ + SpecConstsTests::TestRenderStateCaches(/*CompileAsync = */ false); +} + +TEST(RenderStateCacheTest, SpecializationConstants_Async) +{ + SpecConstsTests::TestRenderStateCaches(/*CompileAsync = */ true); +} + +TEST(RenderStateCacheTest, SpecializationConstants_DistinctEntries) +{ + SpecConstsTests::TestDistinctEntries(/*CompileAsync = */ false); +} + +TEST(RenderStateCacheTest, SpecializationConstants_DistinctEntries_Async) +{ + SpecConstsTests::TestDistinctEntries(/*CompileAsync = */ true); +} + } // namespace From 67cb3763b600288cf604610aad551b24cce81679 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Thu, 5 Mar 2026 12:29:50 +0800 Subject: [PATCH 26/28] Move tests from RenderStateCacheTest to SpecializationConstantTest. This way all shaders and reference data will not need to be duplicated. --- .../src/RenderStateCacheTest.cpp | 524 ------------------ .../src/SpecializationConstantsTest.cpp | 507 +++++++++++++++-- 2 files changed, 468 insertions(+), 563 deletions(-) diff --git a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp index 94b0a896a2..c26c2b9f22 100644 --- a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp +++ b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp @@ -1918,528 +1918,4 @@ TEST(RenderStateCacheTest, NonSeparableProgramBindingConflict) ASSERT_EQ(pPSO->GetStatus(), PIPELINE_STATE_STATUS_READY); } -namespace SpecConstsTests -{ - -const std::string GraphicsVS_GLSL{ - R"( -#version 450 - -#ifndef GL_ES -out gl_PerVertex { vec4 gl_Position; }; -#endif - -layout(constant_id = 0) const float sc_Col0_R = -1.0; -layout(constant_id = 1) const float sc_Col0_G = -1.0; -layout(constant_id = 2) const float sc_Col0_B = -1.0; - -layout(constant_id = 3) const float sc_Col1_R = -1.0; -layout(constant_id = 4) const float sc_Col1_G = -1.0; -layout(constant_id = 5) const float sc_Col1_B = -1.0; - -layout(constant_id = 6) const float sc_Col2_R = -1.0; -layout(constant_id = 7) const float sc_Col2_G = -1.0; -layout(constant_id = 8) const float sc_Col2_B = -1.0; - -layout(location = 0) out vec3 out_Color; - -void main() -{ - vec4 Pos[6]; - Pos[0] = vec4(-1.0, -0.5, 0.0, 1.0); - Pos[1] = vec4(-0.5, +0.5, 0.0, 1.0); - Pos[2] = vec4( 0.0, -0.5, 0.0, 1.0); - - Pos[3] = vec4(+0.0, -0.5, 0.0, 1.0); - Pos[4] = vec4(+0.5, +0.5, 0.0, 1.0); - Pos[5] = vec4(+1.0, -0.5, 0.0, 1.0); - - vec3 Col[3]; - Col[0] = vec3(sc_Col0_R, sc_Col0_G, sc_Col0_B); - Col[1] = vec3(sc_Col1_R, sc_Col1_G, sc_Col1_B); - Col[2] = vec3(sc_Col2_R, sc_Col2_G, sc_Col2_B); - - gl_Position = Pos[gl_VertexIndex]; - out_Color = Col[gl_VertexIndex % 3]; -} -)"}; - -const std::string GraphicsPS_GLSL{ - R"( -#version 450 - -layout(constant_id = 0) const float sc_Col0_R = -1.0; -layout(constant_id = 1) const float sc_Brightness = -1.0; -layout(constant_id = 2) const float sc_AlphaScale = -1.0; - -layout(location = 0) in vec3 in_Color; -layout(location = 0) out vec4 out_Color; - -void main() -{ - out_Color = vec4(vec3(in_Color.r * sc_Col0_R, in_Color.g, in_Color.b) * sc_Brightness, - sc_AlphaScale); -} -)"}; - -const std::string GraphicsVS_WGSL{ - R"( -override sc_Col0_R: f32 = -1.0; -override sc_Col0_G: f32 = -1.0; -override sc_Col0_B: f32 = -1.0; - -override sc_Col1_R: f32 = -1.0; -override sc_Col1_G: f32 = -1.0; -override sc_Col1_B: f32 = -1.0; - -override sc_Col2_R: f32 = -1.0; -override sc_Col2_G: f32 = -1.0; -override sc_Col2_B: f32 = -1.0; - -struct VSOutput { - @builtin(position) Position: vec4, - @location(0) Color: vec3, -}; - -@vertex -fn main(@builtin(vertex_index) VertexIndex: u32) -> VSOutput { - var Pos: array, 6>; - Pos[0] = vec4(-1.0, -0.5, 0.0, 1.0); - Pos[1] = vec4(-0.5, 0.5, 0.0, 1.0); - Pos[2] = vec4( 0.0, -0.5, 0.0, 1.0); - - Pos[3] = vec4( 0.0, -0.5, 0.0, 1.0); - Pos[4] = vec4( 0.5, 0.5, 0.0, 1.0); - Pos[5] = vec4( 1.0, -0.5, 0.0, 1.0); - - var Col: array, 3>; - Col[0] = vec3(sc_Col0_R, sc_Col0_G, sc_Col0_B); - Col[1] = vec3(sc_Col1_R, sc_Col1_G, sc_Col1_B); - Col[2] = vec3(sc_Col2_R, sc_Col2_G, sc_Col2_B); - - var output: VSOutput; - output.Position = Pos[VertexIndex]; - output.Color = Col[VertexIndex % 3]; - return output; -} -)"}; - -const std::string GraphicsPS_WGSL{ - R"( -override sc_Col0_R: f32 = -1.0; -override sc_Brightness: f32 = -1.0; -override sc_AlphaScale: f32 = -1.0; - -@fragment -fn main(@location(0) in_Color: vec3) -> @location(0) vec4 { - return vec4(vec3(in_Color.r * sc_Col0_R, in_Color.g, in_Color.b) * sc_Brightness, - sc_AlphaScale); -} -)"}; - -struct SpecConstRefAttribs -{ - const Char* Name; - SHADER_TYPE Stage; - float RefValue; -}; - -// clang-format off -static constexpr SpecConstRefAttribs SpecConstRefDescs[] = { - {"sc_Col0_R", SHADER_TYPE_VS_PS, 1.0f}, - {"sc_Col0_G", SHADER_TYPE_VERTEX, 0.0f}, - {"sc_Col0_B", SHADER_TYPE_VERTEX, 0.0f}, - {"sc_Col1_R", SHADER_TYPE_VERTEX, 0.0f}, - {"sc_Col1_G", SHADER_TYPE_VERTEX, 1.0f}, - {"sc_Col1_B", SHADER_TYPE_VERTEX, 0.0f}, - {"sc_Col2_R", SHADER_TYPE_VERTEX, 0.0f}, - {"sc_Col2_G", SHADER_TYPE_VERTEX, 0.0f}, - {"sc_Col2_B", SHADER_TYPE_VERTEX, 1.0f}, - {"sc_Brightness", SHADER_TYPE_PIXEL, 1.0f}, - {"sc_AlphaScale", SHADER_TYPE_PIXEL, 1.0f}, -}; -// clang-format on - -static constexpr Uint32 SpecConstRefDescCount = _countof(SpecConstRefDescs); - -void CreateShaders(IRenderStateCache* pCache, - bool PresentInCache, - bool CompileAsync, - RefCntAutoPtr& pVS, - RefCntAutoPtr& pPS) -{ - auto* const pEnv = GPUTestingEnvironment::GetInstance(); - auto* const pDevice = pEnv->GetDevice(); - const auto& DeviceInfo = pDevice->GetDeviceInfo(); - - ShaderCreateInfo ShaderCI; - ShaderCI.CompileFlags = CompileAsync ? SHADER_COMPILE_FLAG_ASYNCHRONOUS : SHADER_COMPILE_FLAG_NONE; - - { - ShaderCI.Desc = {"SpecConsts Cache VS", SHADER_TYPE_VERTEX, true}; - ShaderCI.EntryPoint = "main"; - if (DeviceInfo.IsWebGPUDevice()) - { - ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; - ShaderCI.Source = GraphicsVS_WGSL.c_str(); - } - else - { - ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; - ShaderCI.Source = GraphicsVS_GLSL.c_str(); - } - CreateShader(pCache, ShaderCI, PresentInCache, pVS); - ASSERT_TRUE(pVS); - } - - { - ShaderCI.Desc = {"SpecConsts Cache PS", SHADER_TYPE_PIXEL, true}; - ShaderCI.EntryPoint = "main"; - if (DeviceInfo.IsWebGPUDevice()) - { - ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; - ShaderCI.Source = GraphicsPS_WGSL.c_str(); - } - else - { - ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; - ShaderCI.Source = GraphicsPS_GLSL.c_str(); - } - CreateShader(pCache, ShaderCI, PresentInCache, pPS); - ASSERT_TRUE(pPS); - } -} - -void CreateGraphicsPSO(IRenderStateCache* pCache, - bool PresentInCache, - bool CompileAsync, - const Char* Name, - IShader* pVS, - IShader* pPS, - const SpecializationConstant* pSpecConsts, - Uint32 NumSpecConsts, - RefCntAutoPtr& pPSO, - bool* pFoundInCache = nullptr) -{ - auto* pEnv = GPUTestingEnvironment::GetInstance(); - auto* pDevice = pEnv->GetDevice(); - auto* pSwapChain = pEnv->GetSwapChain(); - - GraphicsPipelineStateCreateInfo PsoCI; - PsoCI.PSODesc.Name = Name; - PsoCI.Flags = CompileAsync ? PSO_CREATE_FLAG_ASYNCHRONOUS : PSO_CREATE_FLAG_NONE; - - PsoCI.pVS = pVS; - PsoCI.pPS = pPS; - - PsoCI.GraphicsPipeline.NumRenderTargets = 1; - PsoCI.GraphicsPipeline.RTVFormats[0] = pSwapChain->GetDesc().ColorBufferFormat; - PsoCI.GraphicsPipeline.PrimitiveTopology = PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - PsoCI.GraphicsPipeline.DepthStencilDesc.DepthEnable = False; - - PsoCI.pSpecializationConstants = pSpecConsts; - PsoCI.NumSpecializationConstants = NumSpecConsts; - - if (pCache != nullptr) - { - bool PSOFound = pCache->CreateGraphicsPipelineState(PsoCI, &pPSO); - if (!CompileAsync) - EXPECT_EQ(PSOFound, PresentInCache); - if (pFoundInCache != nullptr) - *pFoundInCache = PSOFound; - } - else - { - EXPECT_FALSE(PresentInCache); - pDevice->CreateGraphicsPipelineState(PsoCI, &pPSO); - } - ASSERT_NE(pPSO, nullptr); -} - -void VerifyPSO(IPipelineState* pPSO) -{ - auto* pEnv = GPUTestingEnvironment::GetInstance(); - auto* pCtx = pEnv->GetDeviceContext(); - auto* pSwapChain = pEnv->GetSwapChain(); - - static FastRandFloat rnd{2, 0, 1}; - const float ClearColor[] = {rnd(), rnd(), rnd(), rnd()}; - RenderDrawCommandReference(pSwapChain, ClearColor); - - ITextureView* pRTVs[] = {pSwapChain->GetCurrentBackBufferRTV()}; - pCtx->SetRenderTargets(1, pRTVs, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); - pCtx->ClearRenderTarget(pRTVs[0], ClearColor, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); - - pCtx->SetPipelineState(pPSO); - pCtx->Draw({6, DRAW_FLAG_VERIFY_ALL}); - - pSwapChain->Present(); -} - -void TestRenderStateCaches(bool CompileAsync) -{ - auto* pEnv = GPUTestingEnvironment::GetInstance(); - auto* pDevice = pEnv->GetDevice(); - - if (pDevice->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) - { - GTEST_SKIP() << "Specialization constants are not supported by this device"; - } - - GPUTestingEnvironment::ScopedReset AutoReset; - - SpecializationConstant SpecConsts[SpecConstRefDescCount]; - - for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) - { - SpecConsts[i].Name = SpecConstRefDescs[i].Name; - SpecConsts[i].ShaderStages = SpecConstRefDescs[i].Stage; - SpecConsts[i].pData = &SpecConstRefDescs[i].RefValue; - SpecConsts[i].Size = sizeof(SpecConstRefDescs[i].RefValue); - } - - RefCntAutoPtr pUncachedVS, pUncachedPS; - CreateShaders(nullptr, false, false, pUncachedVS, pUncachedPS); - ASSERT_NE(pUncachedVS, nullptr); - ASSERT_NE(pUncachedPS, nullptr); - - RefCntAutoPtr pRefPSO; - CreateGraphicsPSO(nullptr, false, false, "SpecConsts Cache Test", pUncachedVS, pUncachedPS, SpecConsts, _countof(SpecConsts), pRefPSO); - ASSERT_NE(pRefPSO, nullptr); - ASSERT_EQ(pRefPSO->GetStatus(), PIPELINE_STATE_STATUS_READY); - - RefCntAutoPtr pData; - for (Uint32 pass = 0; pass < 3; ++pass) - { - // 0: empty cache - // 1: loaded cache - // 2: reloaded cache (loaded -> stored -> loaded) - - auto pCache = CreateCache(pDevice, /*HotReload=*/false, pData); - ASSERT_TRUE(pCache); - - RefCntAutoPtr pVS, pPS; - CreateShaders(pCache, pData != nullptr, CompileAsync, pVS, pPS); - ASSERT_NE(pVS, nullptr); - ASSERT_NE(pPS, nullptr); - - if (CompileAsync && pass == 0) - { - std::string MutableNames[SpecConstRefDescCount]; - float MutableValues[SpecConstRefDescCount]; - SpecializationConstant MutableSpecConsts[SpecConstRefDescCount]; - - for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) - { - MutableNames[i] = SpecConstRefDescs[i].Name; - MutableValues[i] = SpecConstRefDescs[i].RefValue; - MutableSpecConsts[i].Name = MutableNames[i].c_str(); - MutableSpecConsts[i].ShaderStages = SpecConstRefDescs[i].Stage; - MutableSpecConsts[i].pData = &MutableValues[i]; - MutableSpecConsts[i].Size = sizeof(MutableValues[i]); - } - - RefCntAutoPtr pMutablePSO; - CreateGraphicsPSO(pCache, false, CompileAsync, "SpecConsts Cache Mutable Test", pVS, pPS, MutableSpecConsts, _countof(MutableSpecConsts), pMutablePSO); - ASSERT_NE(pMutablePSO, nullptr); - - // Make sure the strings and data are properly copied in CreateGraphicsPSO. - for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) - { - MutableNames[i] = SpecConstRefDescs[(i + 1) % SpecConstRefDescCount].Name; - MutableValues[i] = SpecConstRefDescs[(i + 1) % SpecConstRefDescCount].RefValue; - } - - ASSERT_EQ(pMutablePSO->GetStatus(true), PIPELINE_STATE_STATUS_READY); - VerifyPSO(pMutablePSO); - } - - RefCntAutoPtr pPSO; - CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO); - ASSERT_NE(pPSO, nullptr); - ASSERT_EQ(pPSO->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - EXPECT_TRUE(pRefPSO->IsCompatibleWith(pPSO)); - EXPECT_TRUE(pPSO->IsCompatibleWith(pRefPSO)); - - VerifyPSO(pPSO); - - { - RefCntAutoPtr pPSO2; - bool PSOFound2 = false; - CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO2, &PSOFound2); - EXPECT_TRUE(PSOFound2); - ASSERT_NE(pPSO2, nullptr); - ASSERT_EQ(pPSO2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - - RefCntAutoPtr pPSO3; - bool PSOFound3 = false; - CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO3, &PSOFound3); - EXPECT_TRUE(PSOFound3); - ASSERT_NE(pPSO3, nullptr); - ASSERT_EQ(pPSO3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - - if (!CompileAsync) - { - EXPECT_EQ(pPSO, pPSO2); - } - else - { - EXPECT_EQ(pPSO2, pPSO3); - } - } - - pData.Release(); - pCache->WriteToBlob(pass == 0 ? ContentVersion : ~0u, &pData); - } -} - -void TestDistinctEntries(bool CompileAsync) -{ - auto* pEnv = GPUTestingEnvironment::GetInstance(); - auto* pDevice = pEnv->GetDevice(); - - if (pDevice->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) - { - GTEST_SKIP() << "Specialization constants are not supported by this device"; - } - - GPUTestingEnvironment::ScopedReset AutoReset; - - RefCntAutoPtr pData; - for (Uint32 pass = 0; pass < 3; ++pass) - { - // 0: empty cache - // 1: loaded cache - // 2: reloaded cache (loaded -> stored -> loaded) - - auto pCache = CreateCache(pDevice, /*HotReload=*/false, pData); - ASSERT_TRUE(pCache); - - RefCntAutoPtr pVS, pPS; - CreateShaders(pCache, pData != nullptr, CompileAsync, pVS, pPS); - ASSERT_NE(pVS, nullptr); - ASSERT_NE(pPS, nullptr); - - float RefValuesB[SpecConstRefDescCount]; - for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) - { - RefValuesB[i] = SpecConstRefDescs[i].RefValue; - if (!strcmp(SpecConstRefDescs[i].Name, "sc_Brightness")) - RefValuesB[i] = 2.0f; - } - - SpecializationConstant SpecConstsA[SpecConstRefDescCount]; - for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) - { - SpecConstsA[i].Name = SpecConstRefDescs[i].Name; - SpecConstsA[i].ShaderStages = SpecConstRefDescs[i].Stage; - SpecConstsA[i].pData = &SpecConstRefDescs[i].RefValue; - SpecConstsA[i].Size = sizeof(SpecConstRefDescs[i].RefValue); - } - - SpecializationConstant SpecConstsB[SpecConstRefDescCount]; - for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) - { - SpecConstsB[i].Name = SpecConstRefDescs[i].Name; - SpecConstsB[i].ShaderStages = SpecConstRefDescs[i].Stage; - SpecConstsB[i].pData = &RefValuesB[i]; - SpecConstsB[i].Size = sizeof(RefValuesB[i]); - } - - RefCntAutoPtr pPSO_A; - CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A); - ASSERT_NE(pPSO_A, nullptr); - ASSERT_EQ(pPSO_A->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - VerifyPSO(pPSO_A); - - RefCntAutoPtr pPSO_B; - CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B); - ASSERT_NE(pPSO_B, nullptr); - ASSERT_EQ(pPSO_B->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - - EXPECT_NE(pPSO_A, pPSO_B); - - RefCntAutoPtr pPSO_A2; - bool PSOFoundA2 = false; - CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A2, &PSOFoundA2); - EXPECT_TRUE(PSOFoundA2); - ASSERT_NE(pPSO_A2, nullptr); - ASSERT_EQ(pPSO_A2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - if (!CompileAsync) - { - EXPECT_EQ(pPSO_A, pPSO_A2); - } - else - { - RefCntAutoPtr pPSO_A3; - bool PSOFoundA3 = false; - CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A3, &PSOFoundA3); - EXPECT_TRUE(PSOFoundA3); - ASSERT_NE(pPSO_A3, nullptr); - ASSERT_EQ(pPSO_A3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - EXPECT_EQ(pPSO_A2, pPSO_A3); - } - - RefCntAutoPtr pPSO_B2; - bool PSOFoundB2 = false; - CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B2, &PSOFoundB2); - EXPECT_TRUE(PSOFoundB2); - ASSERT_NE(pPSO_B2, nullptr); - ASSERT_EQ(pPSO_B2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - if (!CompileAsync) - { - EXPECT_EQ(pPSO_B, pPSO_B2); - } - else - { - RefCntAutoPtr pPSO_B3; - bool PSOFoundB3 = false; - CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B3, &PSOFoundB3); - EXPECT_TRUE(PSOFoundB3); - ASSERT_NE(pPSO_B3, nullptr); - ASSERT_EQ(pPSO_B3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - EXPECT_EQ(pPSO_B2, pPSO_B3); - } - - RefCntAutoPtr pPSO_None; - CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, nullptr, 0, pPSO_None); - ASSERT_NE(pPSO_None, nullptr); - ASSERT_EQ(pPSO_None->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - EXPECT_NE(pPSO_None, pPSO_A); - EXPECT_NE(pPSO_None, pPSO_B); - - pData.Release(); - pCache->WriteToBlob(pass == 0 ? ContentVersion : ~0u, &pData); - } -} - -} // namespace SpecConstsTests - -TEST(RenderStateCacheTest, SpecializationConstants) -{ - SpecConstsTests::TestRenderStateCaches(/*CompileAsync = */ false); -} - -TEST(RenderStateCacheTest, SpecializationConstants_Async) -{ - SpecConstsTests::TestRenderStateCaches(/*CompileAsync = */ true); -} - -TEST(RenderStateCacheTest, SpecializationConstants_DistinctEntries) -{ - SpecConstsTests::TestDistinctEntries(/*CompileAsync = */ false); -} - -TEST(RenderStateCacheTest, SpecializationConstants_DistinctEntries_Async) -{ - SpecConstsTests::TestDistinctEntries(/*CompileAsync = */ true); -} - } // namespace diff --git a/Tests/DiligentCoreAPITest/src/SpecializationConstantsTest.cpp b/Tests/DiligentCoreAPITest/src/SpecializationConstantsTest.cpp index 0cb35305b6..d291ad1757 100644 --- a/Tests/DiligentCoreAPITest/src/SpecializationConstantsTest.cpp +++ b/Tests/DiligentCoreAPITest/src/SpecializationConstantsTest.cpp @@ -30,10 +30,15 @@ // PSO creation failure tests (validation of invalid SpecializationConstant entries) // live in ObjectCreationFailure/PSOCreationFailureTest.cpp. +#include +#include + #include "GPUTestingEnvironment.hpp" #include "TestingSwapChainBase.hpp" #include "GraphicsAccessories.hpp" #include "FastRand.hpp" +#include "RenderStateCache.h" +#include "RenderStateCache.hpp" #include "gtest/gtest.h" @@ -57,7 +62,7 @@ namespace // Reference output: vec4(vec2(xy % 256) / 256.0, 0.0, 1.0) // Base color has non-zero B so that sc_MulB is not optimized away. // To match: sc_MulR=1.0, sc_MulG=1.0, sc_MulB=0.0 -static constexpr char g_SpecConstComputeCS_GLSL[] = R"( +static constexpr char ComputeCS_GLSL[] = R"( #version 450 layout(constant_id = 0) const float sc_MulR = -1.0; layout(constant_id = 1) const float sc_MulG = -1.0; @@ -83,7 +88,7 @@ static constexpr char g_SpecConstComputeCS_GLSL[] = R"( } )"; -static constexpr char g_SpecConstComputeCS_WGSL[] = R"( +static constexpr char ComputeCS_WGSL[] = R"( override sc_MulR: f32 = -1.0; override sc_MulG: f32 = -1.0; override sc_MulB: f32 = -1.0; @@ -108,7 +113,7 @@ static constexpr char g_SpecConstComputeCS_WGSL[] = R"( // Vertex shader: hardcoded positions (same as DrawTest_ProceduralTriangleVS), // per-vertex colors supplied via specialization constants (9 floats). -static constexpr char g_SpecConstGraphicsVS_GLSL[] = R"( +static constexpr char GraphicsVS_GLSL[] = R"( #version 450 #ifndef GL_ES @@ -151,7 +156,7 @@ static constexpr char g_SpecConstGraphicsVS_GLSL[] = R"( } )"; -static constexpr char g_SpecConstGraphicsVS_WGSL[] = R"( +static constexpr char GraphicsVS_WGSL[] = R"( override sc_Col0_R: f32 = 0.0; override sc_Col0_G: f32 = 0.0; override sc_Col0_B: f32 = 0.0; @@ -196,7 +201,7 @@ static constexpr char g_SpecConstGraphicsVS_WGSL[] = R"( // sc_Col0_R is shared with the vertex shader (tests cross-stage matching). // sc_Brightness and sc_AlphaScale are PS-only. // To match reference: sc_Col0_R = 1.0, sc_Brightness = 1.0, sc_AlphaScale = 1.0 -static constexpr char g_SpecConstGraphicsPS_GLSL[] = R"( +static constexpr char GraphicsPS_GLSL[] = R"( #version 450 // Shared with VS (same name, different constant_id in this module). @@ -215,7 +220,7 @@ static constexpr char g_SpecConstGraphicsPS_GLSL[] = R"( } )"; -static constexpr char g_SpecConstGraphicsPS_WGSL[] = R"( +static constexpr char GraphicsPS_WGSL[] = R"( override sc_Col0_R: f32 = -1.0; override sc_Brightness: f32 = -1.0; override sc_AlphaScale: f32 = -1.0; @@ -227,6 +232,85 @@ static constexpr char g_SpecConstGraphicsPS_WGSL[] = R"( } )"; +static constexpr Uint32 ContentVersion = 987; + +struct SpecConstRefAttribs +{ + const Char* Name; + SHADER_TYPE Stage; + float RefValue; +}; + +// clang-format off +static constexpr SpecConstRefAttribs g_SpecConstRefDescs[] = { + {"sc_Col0_R", SHADER_TYPE_VS_PS, 1.0f}, + {"sc_Col0_G", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col0_B", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col1_R", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col1_G", SHADER_TYPE_VERTEX, 1.0f}, + {"sc_Col1_B", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col2_R", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col2_G", SHADER_TYPE_VERTEX, 0.0f}, + {"sc_Col2_B", SHADER_TYPE_VERTEX, 1.0f}, + {"sc_Brightness", SHADER_TYPE_PIXEL, 1.0f}, + {"sc_AlphaScale", SHADER_TYPE_PIXEL, 1.0f}, +}; +// clang-format on + +static constexpr Uint32 g_SpecConstRefDescCount = _countof(g_SpecConstRefDescs); + +void InitializeSpecConsts(const SpecConstRefAttribs* pRefDescs, + Uint32 RefDescCount, + SpecializationConstant* pSpecConsts) +{ + for (Uint32 i = 0; i < RefDescCount; ++i) + { + pSpecConsts[i].Name = pRefDescs[i].Name; + pSpecConsts[i].ShaderStages = pRefDescs[i].Stage; + pSpecConsts[i].pData = &pRefDescs[i].RefValue; + pSpecConsts[i].Size = sizeof(pRefDescs[i].RefValue); + } +} + +RefCntAutoPtr CreateCache(IRenderDevice* pDevice, bool HotReload, IDataBlob* pCacheData = nullptr) +{ + RenderStateCacheCreateInfo CacheCI{ + pDevice, + GPUTestingEnvironment::GetInstance()->GetArchiverFactory(), + RENDER_STATE_CACHE_LOG_LEVEL_VERBOSE, + RENDER_STATE_CACHE_FILE_HASH_MODE_BY_CONTENT, + HotReload, + /*OptimizeGLShaders=*/true, + }; + + RefCntAutoPtr pCache; + CreateRenderStateCache(CacheCI, &pCache); + + if (pCacheData != nullptr) + pCache->Load(pCacheData, ContentVersion); + + return pCache; +} + +void CreateShader(IRenderStateCache* pCache, + const ShaderCreateInfo& ShaderCI, + bool PresentInCache, + RefCntAutoPtr& pShader) +{ + auto* const pEnv = GPUTestingEnvironment::GetInstance(); + auto* const pDevice = pEnv->GetDevice(); + + if (pCache != nullptr) + { + EXPECT_EQ(pCache->CreateShader(ShaderCI, &pShader), PresentInCache); + } + else + { + pDevice->CreateShader(ShaderCI, &pShader); + EXPECT_EQ(PresentInCache, false); + } + ASSERT_TRUE(pShader); +} class SpecializationConstants : public ::testing::Test { @@ -292,12 +376,12 @@ TEST_F(SpecializationConstants, ComputePath) if (DeviceInfo.IsWebGPUDevice()) { ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; - ShaderCI.Source = g_SpecConstComputeCS_WGSL; + ShaderCI.Source = ComputeCS_WGSL; } else { ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; - ShaderCI.Source = g_SpecConstComputeCS_GLSL; + ShaderCI.Source = ComputeCS_GLSL; } RefCntAutoPtr pCS; @@ -393,12 +477,12 @@ TEST_F(SpecializationConstants, GraphicsPath) if (DeviceInfo.IsWebGPUDevice()) { ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; - ShaderCI.Source = g_SpecConstGraphicsVS_WGSL; + ShaderCI.Source = GraphicsVS_WGSL; } else { ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; - ShaderCI.Source = g_SpecConstGraphicsVS_GLSL; + ShaderCI.Source = GraphicsVS_GLSL; } pDevice->CreateShader(ShaderCI, &pVS); ASSERT_NE(pVS, nullptr); @@ -411,44 +495,19 @@ TEST_F(SpecializationConstants, GraphicsPath) if (DeviceInfo.IsWebGPUDevice()) { ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; - ShaderCI.Source = g_SpecConstGraphicsPS_WGSL; + ShaderCI.Source = GraphicsPS_WGSL; } else { ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; - ShaderCI.Source = g_SpecConstGraphicsPS_GLSL; + ShaderCI.Source = GraphicsPS_GLSL; } pDevice->CreateShader(ShaderCI, &pPS); ASSERT_NE(pPS, nullptr); } - // Same per-vertex colors as DrawTest_ProceduralTriangleVS: - // Col[0] = (1, 0, 0) Col[1] = (0, 1, 0) Col[2] = (0, 0, 1) - const float3 Col0{1.0f, 0.0f, 0.0f}; - const float3 Col1{0.0f, 1.0f, 0.0f}; - const float3 Col2{0.0f, 0.0f, 1.0f}; - - // PS-only constants - const float Brightness = 1.0f; - const float AlphaScale = 1.0f; - - // clang-format off - SpecializationConstant SpecConsts[] = { - // sc_Col0_R is declared in both VS and PS: test cross-stage matching. - {"sc_Col0_R", SHADER_TYPE_VS_PS, sizeof(float), &Col0.r}, // Used in both VS and PS - {"sc_Col0_G", SHADER_TYPE_VS_PS, sizeof(float), &Col0.g}, // Used in VS only - {"sc_Col0_B", SHADER_TYPE_VS_PS, sizeof(float), &Col0.b}, // Used in VS only - {"sc_Col1_R", SHADER_TYPE_VERTEX, sizeof(float), &Col1.r}, - {"sc_Col1_G", SHADER_TYPE_VERTEX, sizeof(float), &Col1.g}, - {"sc_Col1_B", SHADER_TYPE_VERTEX, sizeof(float), &Col1.b}, - {"sc_Col2_R", SHADER_TYPE_VERTEX, sizeof(float), &Col2.r}, - {"sc_Col2_G", SHADER_TYPE_VERTEX, sizeof(float), &Col2.g}, - {"sc_Col2_B", SHADER_TYPE_VERTEX, sizeof(float), &Col2.b}, - // PS-only constants - {"sc_Brightness", SHADER_TYPE_PIXEL, sizeof(float), &Brightness}, - {"sc_AlphaScale", SHADER_TYPE_PIXEL, sizeof(float), &AlphaScale}, - }; - // clang-format on + SpecializationConstant SpecConsts[g_SpecConstRefDescCount]; + InitializeSpecConsts(g_SpecConstRefDescs, g_SpecConstRefDescCount, SpecConsts); GraphicsPipelineStateCreateInfo PsoCI; PsoCI.PSODesc.Name = "SpecConst Graphics Test"; @@ -479,5 +538,375 @@ TEST_F(SpecializationConstants, GraphicsPath) Present(); } +namespace RenderStateCacheTests +{ + +void CreateShaders(IRenderStateCache* pCache, + bool PresentInCache, + bool CompileAsync, + RefCntAutoPtr& pVS, + RefCntAutoPtr& pPS) +{ + auto* const pEnv = GPUTestingEnvironment::GetInstance(); + auto* const pDevice = pEnv->GetDevice(); + const auto& DeviceInfo = pDevice->GetDeviceInfo(); + + ShaderCreateInfo ShaderCI; + ShaderCI.CompileFlags = CompileAsync ? SHADER_COMPILE_FLAG_ASYNCHRONOUS : SHADER_COMPILE_FLAG_NONE; + + { + ShaderCI.Desc = {"SpecConsts Cache VS", SHADER_TYPE_VERTEX, true}; + ShaderCI.EntryPoint = "main"; + if (DeviceInfo.IsWebGPUDevice()) + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCI.Source = GraphicsVS_WGSL; + } + else + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + ShaderCI.Source = GraphicsVS_GLSL; + } + CreateShader(pCache, ShaderCI, PresentInCache, pVS); + ASSERT_TRUE(pVS); + } + + { + ShaderCI.Desc = {"SpecConsts Cache PS", SHADER_TYPE_PIXEL, true}; + ShaderCI.EntryPoint = "main"; + if (DeviceInfo.IsWebGPUDevice()) + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; + ShaderCI.Source = GraphicsPS_WGSL; + } + else + { + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; + ShaderCI.Source = GraphicsPS_GLSL; + } + CreateShader(pCache, ShaderCI, PresentInCache, pPS); + ASSERT_TRUE(pPS); + } +} + +void CreateGraphicsPSO(IRenderStateCache* pCache, + bool PresentInCache, + bool CompileAsync, + const Char* Name, + IShader* pVS, + IShader* pPS, + const SpecializationConstant* pSpecConsts, + Uint32 NumSpecConsts, + RefCntAutoPtr& pPSO, + bool* pFoundInCache = nullptr) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pDevice = pEnv->GetDevice(); + auto* pSwapChain = pEnv->GetSwapChain(); + + GraphicsPipelineStateCreateInfo PsoCI; + PsoCI.PSODesc.Name = Name; + PsoCI.Flags = CompileAsync ? PSO_CREATE_FLAG_ASYNCHRONOUS : PSO_CREATE_FLAG_NONE; + + PsoCI.pVS = pVS; + PsoCI.pPS = pPS; + + PsoCI.GraphicsPipeline.NumRenderTargets = 1; + PsoCI.GraphicsPipeline.RTVFormats[0] = pSwapChain->GetDesc().ColorBufferFormat; + PsoCI.GraphicsPipeline.PrimitiveTopology = PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + PsoCI.GraphicsPipeline.DepthStencilDesc.DepthEnable = False; + + PsoCI.pSpecializationConstants = pSpecConsts; + PsoCI.NumSpecializationConstants = NumSpecConsts; + + if (pCache != nullptr) + { + bool PSOFound = pCache->CreateGraphicsPipelineState(PsoCI, &pPSO); + if (!CompileAsync) + EXPECT_EQ(PSOFound, PresentInCache); + if (pFoundInCache != nullptr) + *pFoundInCache = PSOFound; + } + else + { + EXPECT_FALSE(PresentInCache); + pDevice->CreateGraphicsPipelineState(PsoCI, &pPSO); + } + ASSERT_NE(pPSO, nullptr); +} + +void VerifyPSO(IPipelineState* pPSO) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pCtx = pEnv->GetDeviceContext(); + auto* pSwapChain = pEnv->GetSwapChain(); + + static FastRandFloat rnd{2, 0, 1}; + const float ClearColor[] = {rnd(), rnd(), rnd(), rnd()}; + RenderDrawCommandReference(pSwapChain, ClearColor); + + ITextureView* pRTVs[] = {pSwapChain->GetCurrentBackBufferRTV()}; + pCtx->SetRenderTargets(1, pRTVs, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + pCtx->ClearRenderTarget(pRTVs[0], ClearColor, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + + pCtx->SetPipelineState(pPSO); + pCtx->Draw({6, DRAW_FLAG_VERIFY_ALL}); + + pSwapChain->Present(); +} + +void TestCaches(bool CompileAsync) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pDevice = pEnv->GetDevice(); + + if (pDevice->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + { + GTEST_SKIP() << "Specialization constants are not supported by this device"; + } + + GPUTestingEnvironment::ScopedReset AutoReset; + + SpecializationConstant SpecConsts[g_SpecConstRefDescCount]; + InitializeSpecConsts(g_SpecConstRefDescs, g_SpecConstRefDescCount, SpecConsts); + + RefCntAutoPtr pUncachedVS, pUncachedPS; + CreateShaders(nullptr, false, false, pUncachedVS, pUncachedPS); + ASSERT_NE(pUncachedVS, nullptr); + ASSERT_NE(pUncachedPS, nullptr); + + RefCntAutoPtr pRefPSO; + CreateGraphicsPSO(nullptr, false, false, "SpecConsts Cache Test", pUncachedVS, pUncachedPS, SpecConsts, _countof(SpecConsts), pRefPSO); + ASSERT_NE(pRefPSO, nullptr); + ASSERT_EQ(pRefPSO->GetStatus(), PIPELINE_STATE_STATUS_READY); + + RefCntAutoPtr pData; + for (Uint32 pass = 0; pass < 3; ++pass) + { + // 0: empty cache + // 1: loaded cache + // 2: reloaded cache (loaded -> stored -> loaded) + + auto pCache = CreateCache(pDevice, /*HotReload=*/false, pData); + ASSERT_TRUE(pCache); + + RefCntAutoPtr pVS, pPS; + CreateShaders(pCache, pData != nullptr, CompileAsync, pVS, pPS); + ASSERT_NE(pVS, nullptr); + ASSERT_NE(pPS, nullptr); + + if (CompileAsync && pass == 0) + { + std::string MutableNames[g_SpecConstRefDescCount]; + float MutableValues[g_SpecConstRefDescCount]; + SpecializationConstant MutableSpecConsts[g_SpecConstRefDescCount]; + + for (Uint32 i = 0; i < g_SpecConstRefDescCount; ++i) + { + MutableNames[i] = g_SpecConstRefDescs[i].Name; + MutableValues[i] = g_SpecConstRefDescs[i].RefValue; + MutableSpecConsts[i].Name = MutableNames[i].c_str(); + MutableSpecConsts[i].ShaderStages = g_SpecConstRefDescs[i].Stage; + MutableSpecConsts[i].pData = &MutableValues[i]; + MutableSpecConsts[i].Size = sizeof(MutableValues[i]); + } + + RefCntAutoPtr pMutablePSO; + CreateGraphicsPSO(pCache, false, CompileAsync, "SpecConsts Cache Mutable Test", pVS, pPS, MutableSpecConsts, _countof(MutableSpecConsts), pMutablePSO); + ASSERT_NE(pMutablePSO, nullptr); + + // Make sure the strings and data are properly copied in CreateGraphicsPSO. + for (Uint32 i = 0; i < g_SpecConstRefDescCount; ++i) + { + MutableNames[i] = g_SpecConstRefDescs[(i + 1) % g_SpecConstRefDescCount].Name; + MutableValues[i] = g_SpecConstRefDescs[(i + 1) % g_SpecConstRefDescCount].RefValue; + } + + ASSERT_EQ(pMutablePSO->GetStatus(true), PIPELINE_STATE_STATUS_READY); + VerifyPSO(pMutablePSO); + } + + RefCntAutoPtr pPSO; + CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO); + ASSERT_NE(pPSO, nullptr); + ASSERT_EQ(pPSO->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + EXPECT_TRUE(pRefPSO->IsCompatibleWith(pPSO)); + EXPECT_TRUE(pPSO->IsCompatibleWith(pRefPSO)); + + VerifyPSO(pPSO); + + { + RefCntAutoPtr pPSO2; + bool PSOFound2 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO2, &PSOFound2); + EXPECT_TRUE(PSOFound2); + ASSERT_NE(pPSO2, nullptr); + ASSERT_EQ(pPSO2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + + RefCntAutoPtr pPSO3; + bool PSOFound3 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO3, &PSOFound3); + EXPECT_TRUE(PSOFound3); + ASSERT_NE(pPSO3, nullptr); + ASSERT_EQ(pPSO3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + + if (!CompileAsync) + { + EXPECT_EQ(pPSO, pPSO2); + } + else + { + EXPECT_EQ(pPSO2, pPSO3); + } + } + + pData.Release(); + pCache->WriteToBlob(pass == 0 ? ContentVersion : ~0u, &pData); + } +} + +void TestDistinctEntries(bool CompileAsync) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pDevice = pEnv->GetDevice(); + + if (pDevice->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) + { + GTEST_SKIP() << "Specialization constants are not supported by this device"; + } + + GPUTestingEnvironment::ScopedReset AutoReset; + + RefCntAutoPtr pData; + for (Uint32 pass = 0; pass < 3; ++pass) + { + // 0: empty cache + // 1: loaded cache + // 2: reloaded cache (loaded -> stored -> loaded) + + auto pCache = CreateCache(pDevice, /*HotReload=*/false, pData); + ASSERT_TRUE(pCache); + + RefCntAutoPtr pVS, pPS; + CreateShaders(pCache, pData != nullptr, CompileAsync, pVS, pPS); + ASSERT_NE(pVS, nullptr); + ASSERT_NE(pPS, nullptr); + + float RefValuesB[g_SpecConstRefDescCount]; + for (Uint32 i = 0; i < g_SpecConstRefDescCount; ++i) + { + RefValuesB[i] = g_SpecConstRefDescs[i].RefValue; + if (strcmp(g_SpecConstRefDescs[i].Name, "sc_Brightness") == 0) + RefValuesB[i] = 2.0f; + } + + SpecializationConstant SpecConstsA[g_SpecConstRefDescCount]; + InitializeSpecConsts(g_SpecConstRefDescs, g_SpecConstRefDescCount, SpecConstsA); + + SpecializationConstant SpecConstsB[g_SpecConstRefDescCount]; + for (Uint32 i = 0; i < g_SpecConstRefDescCount; ++i) + { + SpecConstsB[i].Name = g_SpecConstRefDescs[i].Name; + SpecConstsB[i].ShaderStages = g_SpecConstRefDescs[i].Stage; + SpecConstsB[i].pData = &RefValuesB[i]; + SpecConstsB[i].Size = sizeof(RefValuesB[i]); + } + + RefCntAutoPtr pPSO_A; + CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A); + ASSERT_NE(pPSO_A, nullptr); + ASSERT_EQ(pPSO_A->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + VerifyPSO(pPSO_A); + + RefCntAutoPtr pPSO_B; + CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B); + ASSERT_NE(pPSO_B, nullptr); + ASSERT_EQ(pPSO_B->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + + EXPECT_NE(pPSO_A, pPSO_B); + + RefCntAutoPtr pPSO_A2; + bool PSOFoundA2 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A2, &PSOFoundA2); + EXPECT_TRUE(PSOFoundA2); + ASSERT_NE(pPSO_A2, nullptr); + ASSERT_EQ(pPSO_A2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + if (!CompileAsync) + { + EXPECT_EQ(pPSO_A, pPSO_A2); + } + else + { + RefCntAutoPtr pPSO_A3; + bool PSOFoundA3 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A3, &PSOFoundA3); + EXPECT_TRUE(PSOFoundA3); + ASSERT_NE(pPSO_A3, nullptr); + ASSERT_EQ(pPSO_A3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + EXPECT_EQ(pPSO_A2, pPSO_A3); + } + + RefCntAutoPtr pPSO_B2; + bool PSOFoundB2 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B2, &PSOFoundB2); + EXPECT_TRUE(PSOFoundB2); + ASSERT_NE(pPSO_B2, nullptr); + ASSERT_EQ(pPSO_B2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + if (!CompileAsync) + { + EXPECT_EQ(pPSO_B, pPSO_B2); + } + else + { + RefCntAutoPtr pPSO_B3; + bool PSOFoundB3 = false; + CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B3, &PSOFoundB3); + EXPECT_TRUE(PSOFoundB3); + ASSERT_NE(pPSO_B3, nullptr); + ASSERT_EQ(pPSO_B3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + EXPECT_EQ(pPSO_B2, pPSO_B3); + } + + RefCntAutoPtr pPSO_None; + CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", + pVS, pPS, nullptr, 0, pPSO_None); + ASSERT_NE(pPSO_None, nullptr); + ASSERT_EQ(pPSO_None->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); + EXPECT_NE(pPSO_None, pPSO_A); + EXPECT_NE(pPSO_None, pPSO_B); + + pData.Release(); + pCache->WriteToBlob(pass == 0 ? ContentVersion : ~0u, &pData); + } +} + +} // namespace RenderStateCacheTests + +TEST(RenderStateCacheTest, SpecializationConstants) +{ + RenderStateCacheTests::TestCaches(/*CompileAsync = */ false); +} + +TEST(RenderStateCacheTest, SpecializationConstants_Async) +{ + RenderStateCacheTests::TestCaches(/*CompileAsync = */ true); +} + +TEST(RenderStateCacheTest, SpecializationConstants_DistinctEntries) +{ + RenderStateCacheTests::TestDistinctEntries(/*CompileAsync = */ false); +} + +TEST(RenderStateCacheTest, SpecializationConstants_DistinctEntries_Async) +{ + RenderStateCacheTests::TestDistinctEntries(/*CompileAsync = */ true); +} + } // namespace From e846d6626328209f6cc0688e1d055e3bcad194ab Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Thu, 5 Mar 2026 12:32:37 +0800 Subject: [PATCH 27/28] remove them from RenderStateCacheTest --- .../src/RenderStateCacheTest.cpp | 526 +----------------- 1 file changed, 1 insertion(+), 525 deletions(-) diff --git a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp index b022be1576..c26c2b9f22 100644 --- a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp +++ b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2026 Diligent Graphics LLC + * Copyright 2019-2025 Diligent Graphics LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1918,528 +1918,4 @@ TEST(RenderStateCacheTest, NonSeparableProgramBindingConflict) ASSERT_EQ(pPSO->GetStatus(), PIPELINE_STATE_STATUS_READY); } -namespace SpecConstsTests -{ - -const std::string GraphicsVS_GLSL{ - R"( -#version 450 - -#ifndef GL_ES -out gl_PerVertex { vec4 gl_Position; }; -#endif - -layout(constant_id = 0) const float sc_Col0_R = -1.0; -layout(constant_id = 1) const float sc_Col0_G = -1.0; -layout(constant_id = 2) const float sc_Col0_B = -1.0; - -layout(constant_id = 3) const float sc_Col1_R = -1.0; -layout(constant_id = 4) const float sc_Col1_G = -1.0; -layout(constant_id = 5) const float sc_Col1_B = -1.0; - -layout(constant_id = 6) const float sc_Col2_R = -1.0; -layout(constant_id = 7) const float sc_Col2_G = -1.0; -layout(constant_id = 8) const float sc_Col2_B = -1.0; - -layout(location = 0) out vec3 out_Color; - -void main() -{ - vec4 Pos[6]; - Pos[0] = vec4(-1.0, -0.5, 0.0, 1.0); - Pos[1] = vec4(-0.5, +0.5, 0.0, 1.0); - Pos[2] = vec4( 0.0, -0.5, 0.0, 1.0); - - Pos[3] = vec4(+0.0, -0.5, 0.0, 1.0); - Pos[4] = vec4(+0.5, +0.5, 0.0, 1.0); - Pos[5] = vec4(+1.0, -0.5, 0.0, 1.0); - - vec3 Col[3]; - Col[0] = vec3(sc_Col0_R, sc_Col0_G, sc_Col0_B); - Col[1] = vec3(sc_Col1_R, sc_Col1_G, sc_Col1_B); - Col[2] = vec3(sc_Col2_R, sc_Col2_G, sc_Col2_B); - - gl_Position = Pos[gl_VertexIndex]; - out_Color = Col[gl_VertexIndex % 3]; -} -)"}; - -const std::string GraphicsPS_GLSL{ - R"( -#version 450 - -layout(constant_id = 0) const float sc_Col0_R = -1.0; -layout(constant_id = 1) const float sc_Brightness = -1.0; -layout(constant_id = 2) const float sc_AlphaScale = -1.0; - -layout(location = 0) in vec3 in_Color; -layout(location = 0) out vec4 out_Color; - -void main() -{ - out_Color = vec4(vec3(in_Color.r * sc_Col0_R, in_Color.g, in_Color.b) * sc_Brightness, - sc_AlphaScale); -} -)"}; - -const std::string GraphicsVS_WGSL{ - R"( -override sc_Col0_R: f32 = -1.0; -override sc_Col0_G: f32 = -1.0; -override sc_Col0_B: f32 = -1.0; - -override sc_Col1_R: f32 = -1.0; -override sc_Col1_G: f32 = -1.0; -override sc_Col1_B: f32 = -1.0; - -override sc_Col2_R: f32 = -1.0; -override sc_Col2_G: f32 = -1.0; -override sc_Col2_B: f32 = -1.0; - -struct VSOutput { - @builtin(position) Position: vec4, - @location(0) Color: vec3, -}; - -@vertex -fn main(@builtin(vertex_index) VertexIndex: u32) -> VSOutput { - var Pos: array, 6>; - Pos[0] = vec4(-1.0, -0.5, 0.0, 1.0); - Pos[1] = vec4(-0.5, 0.5, 0.0, 1.0); - Pos[2] = vec4( 0.0, -0.5, 0.0, 1.0); - - Pos[3] = vec4( 0.0, -0.5, 0.0, 1.0); - Pos[4] = vec4( 0.5, 0.5, 0.0, 1.0); - Pos[5] = vec4( 1.0, -0.5, 0.0, 1.0); - - var Col: array, 3>; - Col[0] = vec3(sc_Col0_R, sc_Col0_G, sc_Col0_B); - Col[1] = vec3(sc_Col1_R, sc_Col1_G, sc_Col1_B); - Col[2] = vec3(sc_Col2_R, sc_Col2_G, sc_Col2_B); - - var output: VSOutput; - output.Position = Pos[VertexIndex]; - output.Color = Col[VertexIndex % 3]; - return output; -} -)"}; - -const std::string GraphicsPS_WGSL{ - R"( -override sc_Col0_R: f32 = -1.0; -override sc_Brightness: f32 = -1.0; -override sc_AlphaScale: f32 = -1.0; - -@fragment -fn main(@location(0) in_Color: vec3) -> @location(0) vec4 { - return vec4(vec3(in_Color.r * sc_Col0_R, in_Color.g, in_Color.b) * sc_Brightness, - sc_AlphaScale); -} -)"}; - -struct SpecConstRefAttribs -{ - const Char* Name; - SHADER_TYPE Stage; - float RefValue; -}; - -// clang-format off -static constexpr SpecConstRefAttribs SpecConstRefDescs[] = { - {"sc_Col0_R", SHADER_TYPE_VS_PS, 1.0f}, - {"sc_Col0_G", SHADER_TYPE_VERTEX, 0.0f}, - {"sc_Col0_B", SHADER_TYPE_VERTEX, 0.0f}, - {"sc_Col1_R", SHADER_TYPE_VERTEX, 0.0f}, - {"sc_Col1_G", SHADER_TYPE_VERTEX, 1.0f}, - {"sc_Col1_B", SHADER_TYPE_VERTEX, 0.0f}, - {"sc_Col2_R", SHADER_TYPE_VERTEX, 0.0f}, - {"sc_Col2_G", SHADER_TYPE_VERTEX, 0.0f}, - {"sc_Col2_B", SHADER_TYPE_VERTEX, 1.0f}, - {"sc_Brightness", SHADER_TYPE_PIXEL, 1.0f}, - {"sc_AlphaScale", SHADER_TYPE_PIXEL, 1.0f}, -}; -// clang-format on - -static constexpr Uint32 SpecConstRefDescCount = _countof(SpecConstRefDescs); - -void CreateShaders(IRenderStateCache* pCache, - bool PresentInCache, - bool CompileAsync, - RefCntAutoPtr& pVS, - RefCntAutoPtr& pPS) -{ - auto* const pEnv = GPUTestingEnvironment::GetInstance(); - auto* const pDevice = pEnv->GetDevice(); - const auto& DeviceInfo = pDevice->GetDeviceInfo(); - - ShaderCreateInfo ShaderCI; - ShaderCI.CompileFlags = CompileAsync ? SHADER_COMPILE_FLAG_ASYNCHRONOUS : SHADER_COMPILE_FLAG_NONE; - - { - ShaderCI.Desc = {"SpecConsts Cache VS", SHADER_TYPE_VERTEX, true}; - ShaderCI.EntryPoint = "main"; - if (DeviceInfo.IsWebGPUDevice()) - { - ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; - ShaderCI.Source = GraphicsVS_WGSL.c_str(); - } - else - { - ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; - ShaderCI.Source = GraphicsVS_GLSL.c_str(); - } - CreateShader(pCache, ShaderCI, PresentInCache, pVS); - ASSERT_TRUE(pVS); - } - - { - ShaderCI.Desc = {"SpecConsts Cache PS", SHADER_TYPE_PIXEL, true}; - ShaderCI.EntryPoint = "main"; - if (DeviceInfo.IsWebGPUDevice()) - { - ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_WGSL; - ShaderCI.Source = GraphicsPS_WGSL.c_str(); - } - else - { - ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_GLSL_VERBATIM; - ShaderCI.Source = GraphicsPS_GLSL.c_str(); - } - CreateShader(pCache, ShaderCI, PresentInCache, pPS); - ASSERT_TRUE(pPS); - } -} - -void CreateGraphicsPSO(IRenderStateCache* pCache, - bool PresentInCache, - bool CompileAsync, - const Char* Name, - IShader* pVS, - IShader* pPS, - const SpecializationConstant* pSpecConsts, - Uint32 NumSpecConsts, - RefCntAutoPtr& pPSO, - bool* pFoundInCache = nullptr) -{ - auto* pEnv = GPUTestingEnvironment::GetInstance(); - auto* pDevice = pEnv->GetDevice(); - auto* pSwapChain = pEnv->GetSwapChain(); - - GraphicsPipelineStateCreateInfo PsoCI; - PsoCI.PSODesc.Name = Name; - PsoCI.Flags = CompileAsync ? PSO_CREATE_FLAG_ASYNCHRONOUS : PSO_CREATE_FLAG_NONE; - - PsoCI.pVS = pVS; - PsoCI.pPS = pPS; - - PsoCI.GraphicsPipeline.NumRenderTargets = 1; - PsoCI.GraphicsPipeline.RTVFormats[0] = pSwapChain->GetDesc().ColorBufferFormat; - PsoCI.GraphicsPipeline.PrimitiveTopology = PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - PsoCI.GraphicsPipeline.DepthStencilDesc.DepthEnable = False; - - PsoCI.pSpecializationConstants = pSpecConsts; - PsoCI.NumSpecializationConstants = NumSpecConsts; - - if (pCache != nullptr) - { - bool PSOFound = pCache->CreateGraphicsPipelineState(PsoCI, &pPSO); - if (!CompileAsync) - EXPECT_EQ(PSOFound, PresentInCache); - if (pFoundInCache != nullptr) - *pFoundInCache = PSOFound; - } - else - { - EXPECT_FALSE(PresentInCache); - pDevice->CreateGraphicsPipelineState(PsoCI, &pPSO); - } - ASSERT_NE(pPSO, nullptr); -} - -void VerifyPSO(IPipelineState* pPSO) -{ - auto* pEnv = GPUTestingEnvironment::GetInstance(); - auto* pCtx = pEnv->GetDeviceContext(); - auto* pSwapChain = pEnv->GetSwapChain(); - - static FastRandFloat rnd{2, 0, 1}; - const float ClearColor[] = {rnd(), rnd(), rnd(), rnd()}; - RenderDrawCommandReference(pSwapChain, ClearColor); - - ITextureView* pRTVs[] = {pSwapChain->GetCurrentBackBufferRTV()}; - pCtx->SetRenderTargets(1, pRTVs, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); - pCtx->ClearRenderTarget(pRTVs[0], ClearColor, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); - - pCtx->SetPipelineState(pPSO); - pCtx->Draw({6, DRAW_FLAG_VERIFY_ALL}); - - pSwapChain->Present(); -} - -void TestRenderStateCaches(bool CompileAsync) -{ - auto* pEnv = GPUTestingEnvironment::GetInstance(); - auto* pDevice = pEnv->GetDevice(); - - if (pDevice->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) - { - GTEST_SKIP() << "Specialization constants are not supported by this device"; - } - - GPUTestingEnvironment::ScopedReset AutoReset; - - SpecializationConstant SpecConsts[SpecConstRefDescCount]; - - for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) - { - SpecConsts[i].Name = SpecConstRefDescs[i].Name; - SpecConsts[i].ShaderStages = SpecConstRefDescs[i].Stage; - SpecConsts[i].pData = &SpecConstRefDescs[i].RefValue; - SpecConsts[i].Size = sizeof(SpecConstRefDescs[i].RefValue); - } - - RefCntAutoPtr pUncachedVS, pUncachedPS; - CreateShaders(nullptr, false, false, pUncachedVS, pUncachedPS); - ASSERT_NE(pUncachedVS, nullptr); - ASSERT_NE(pUncachedPS, nullptr); - - RefCntAutoPtr pRefPSO; - CreateGraphicsPSO(nullptr, false, false, "SpecConsts Cache Test", pUncachedVS, pUncachedPS, SpecConsts, _countof(SpecConsts), pRefPSO); - ASSERT_NE(pRefPSO, nullptr); - ASSERT_EQ(pRefPSO->GetStatus(), PIPELINE_STATE_STATUS_READY); - - RefCntAutoPtr pData; - for (Uint32 pass = 0; pass < 3; ++pass) - { - // 0: empty cache - // 1: loaded cache - // 2: reloaded cache (loaded -> stored -> loaded) - - auto pCache = CreateCache(pDevice, /*HotReload=*/false, pData); - ASSERT_TRUE(pCache); - - RefCntAutoPtr pVS, pPS; - CreateShaders(pCache, pData != nullptr, CompileAsync, pVS, pPS); - ASSERT_NE(pVS, nullptr); - ASSERT_NE(pPS, nullptr); - - if (CompileAsync && pass == 0) - { - std::string MutableNames[SpecConstRefDescCount]; - float MutableValues[SpecConstRefDescCount]; - SpecializationConstant MutableSpecConsts[SpecConstRefDescCount]; - - for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) - { - MutableNames[i] = SpecConstRefDescs[i].Name; - MutableValues[i] = SpecConstRefDescs[i].RefValue; - MutableSpecConsts[i].Name = MutableNames[i].c_str(); - MutableSpecConsts[i].ShaderStages = SpecConstRefDescs[i].Stage; - MutableSpecConsts[i].pData = &MutableValues[i]; - MutableSpecConsts[i].Size = sizeof(MutableValues[i]); - } - - RefCntAutoPtr pMutablePSO; - CreateGraphicsPSO(pCache, false, CompileAsync, "SpecConsts Cache Mutable Test", pVS, pPS, MutableSpecConsts, _countof(MutableSpecConsts), pMutablePSO); - ASSERT_NE(pMutablePSO, nullptr); - - // Make sure the strings and data are properly copied in CreateGraphicsPSO. - for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) - { - MutableNames[i] = SpecConstRefDescs[(i + 1) % SpecConstRefDescCount].Name; - MutableValues[i] = SpecConstRefDescs[(i + 1) % SpecConstRefDescCount].RefValue; - } - - ASSERT_EQ(pMutablePSO->GetStatus(true), PIPELINE_STATE_STATUS_READY); - VerifyPSO(pMutablePSO); - } - - RefCntAutoPtr pPSO; - CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO); - ASSERT_NE(pPSO, nullptr); - ASSERT_EQ(pPSO->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - EXPECT_TRUE(pRefPSO->IsCompatibleWith(pPSO)); - EXPECT_TRUE(pPSO->IsCompatibleWith(pRefPSO)); - - VerifyPSO(pPSO); - - { - RefCntAutoPtr pPSO2; - bool PSOFound2 = false; - CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO2, &PSOFound2); - EXPECT_TRUE(PSOFound2); - ASSERT_NE(pPSO2, nullptr); - ASSERT_EQ(pPSO2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - - RefCntAutoPtr pPSO3; - bool PSOFound3 = false; - CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Cache Test", pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO3, &PSOFound3); - EXPECT_TRUE(PSOFound3); - ASSERT_NE(pPSO3, nullptr); - ASSERT_EQ(pPSO3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - - if (!CompileAsync) - { - EXPECT_EQ(pPSO, pPSO2); - } - else - { - EXPECT_EQ(pPSO2, pPSO3); - } - } - - pData.Release(); - pCache->WriteToBlob(pass == 0 ? ContentVersion : ~0u, &pData); - } -} - -void TestDistinctEntries(bool CompileAsync) -{ - auto* pEnv = GPUTestingEnvironment::GetInstance(); - auto* pDevice = pEnv->GetDevice(); - - if (pDevice->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) - { - GTEST_SKIP() << "Specialization constants are not supported by this device"; - } - - GPUTestingEnvironment::ScopedReset AutoReset; - - RefCntAutoPtr pData; - for (Uint32 pass = 0; pass < 3; ++pass) - { - // 0: empty cache - // 1: loaded cache - // 2: reloaded cache (loaded -> stored -> loaded) - - auto pCache = CreateCache(pDevice, /*HotReload=*/false, pData); - ASSERT_TRUE(pCache); - - RefCntAutoPtr pVS, pPS; - CreateShaders(pCache, pData != nullptr, CompileAsync, pVS, pPS); - ASSERT_NE(pVS, nullptr); - ASSERT_NE(pPS, nullptr); - - float RefValuesB[SpecConstRefDescCount]; - for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) - { - RefValuesB[i] = SpecConstRefDescs[i].RefValue; - if (!strcmp(SpecConstRefDescs[i].Name, "sc_Brightness")) - RefValuesB[i] = 2.0f; - } - - SpecializationConstant SpecConstsA[SpecConstRefDescCount]; - for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) - { - SpecConstsA[i].Name = SpecConstRefDescs[i].Name; - SpecConstsA[i].ShaderStages = SpecConstRefDescs[i].Stage; - SpecConstsA[i].pData = &SpecConstRefDescs[i].RefValue; - SpecConstsA[i].Size = sizeof(SpecConstRefDescs[i].RefValue); - } - - SpecializationConstant SpecConstsB[SpecConstRefDescCount]; - for (Uint32 i = 0; i < SpecConstRefDescCount; ++i) - { - SpecConstsB[i].Name = SpecConstRefDescs[i].Name; - SpecConstsB[i].ShaderStages = SpecConstRefDescs[i].Stage; - SpecConstsB[i].pData = &RefValuesB[i]; - SpecConstsB[i].Size = sizeof(RefValuesB[i]); - } - - RefCntAutoPtr pPSO_A; - CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A); - ASSERT_NE(pPSO_A, nullptr); - ASSERT_EQ(pPSO_A->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - VerifyPSO(pPSO_A); - - RefCntAutoPtr pPSO_B; - CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B); - ASSERT_NE(pPSO_B, nullptr); - ASSERT_EQ(pPSO_B->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - - EXPECT_NE(pPSO_A, pPSO_B); - - RefCntAutoPtr pPSO_A2; - bool PSOFoundA2 = false; - CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A2, &PSOFoundA2); - EXPECT_TRUE(PSOFoundA2); - ASSERT_NE(pPSO_A2, nullptr); - ASSERT_EQ(pPSO_A2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - if (!CompileAsync) - { - EXPECT_EQ(pPSO_A, pPSO_A2); - } - else - { - RefCntAutoPtr pPSO_A3; - bool PSOFoundA3 = false; - CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, SpecConstsA, _countof(SpecConstsA), pPSO_A3, &PSOFoundA3); - EXPECT_TRUE(PSOFoundA3); - ASSERT_NE(pPSO_A3, nullptr); - ASSERT_EQ(pPSO_A3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - EXPECT_EQ(pPSO_A2, pPSO_A3); - } - - RefCntAutoPtr pPSO_B2; - bool PSOFoundB2 = false; - CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B2, &PSOFoundB2); - EXPECT_TRUE(PSOFoundB2); - ASSERT_NE(pPSO_B2, nullptr); - ASSERT_EQ(pPSO_B2->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - if (!CompileAsync) - { - EXPECT_EQ(pPSO_B, pPSO_B2); - } - else - { - RefCntAutoPtr pPSO_B3; - bool PSOFoundB3 = false; - CreateGraphicsPSO(pCache, true, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, SpecConstsB, _countof(SpecConstsB), pPSO_B3, &PSOFoundB3); - EXPECT_TRUE(PSOFoundB3); - ASSERT_NE(pPSO_B3, nullptr); - ASSERT_EQ(pPSO_B3->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - EXPECT_EQ(pPSO_B2, pPSO_B3); - } - - RefCntAutoPtr pPSO_None; - CreateGraphicsPSO(pCache, pData != nullptr, CompileAsync, "SpecConsts Distinct Test", - pVS, pPS, nullptr, 0, pPSO_None); - ASSERT_NE(pPSO_None, nullptr); - ASSERT_EQ(pPSO_None->GetStatus(CompileAsync), PIPELINE_STATE_STATUS_READY); - EXPECT_NE(pPSO_None, pPSO_A); - EXPECT_NE(pPSO_None, pPSO_B); - - pData.Release(); - pCache->WriteToBlob(pass == 0 ? ContentVersion : ~0u, &pData); - } -} - -} // namespace SpecConstsTests - -TEST(RenderStateCacheTest, SpecializationConstants) -{ - SpecConstsTests::TestRenderStateCaches(/*CompileAsync = */ false); -} - -TEST(RenderStateCacheTest, SpecializationConstants_Async) -{ - SpecConstsTests::TestRenderStateCaches(/*CompileAsync = */ true); -} - -TEST(RenderStateCacheTest, SpecializationConstants_DistinctEntries) -{ - SpecConstsTests::TestDistinctEntries(/*CompileAsync = */ false); -} - -TEST(RenderStateCacheTest, SpecializationConstants_DistinctEntries_Async) -{ - SpecConstsTests::TestDistinctEntries(/*CompileAsync = */ true); -} - } // namespace From a1ccce83ecb8126ff3a25fb8a77cd81f5bf94c0b Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Thu, 5 Mar 2026 12:33:07 +0800 Subject: [PATCH 28/28] 2026 --- Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp index c26c2b9f22..be94d064ca 100644 --- a/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp +++ b/Tests/DiligentCoreAPITest/src/RenderStateCacheTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.