Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
73615f4
DeviceFeatures: add SpecializationConstants field
hzqst Feb 22, 2026
aa28c63
Disable SpecializationConstants feature on D3D11/D3D12 and GL
TheMostDiligent Feb 24, 2026
1d34246
PipelineStateCreateInfo: add specialization constants
hzqst Feb 22, 2026
339fa3b
PipelineStateCreateInfoX: add specialization constants
hzqst Feb 22, 2026
a00b38f
Add specialization constant failure tests
hzqst Feb 24, 2026
072dc8e
SPIRVShaderResources: add specialization constants support
hzqst Feb 25, 2026
8a7b6c3
SPIRVShaderResources: simplify specialization constants processing
TheMostDiligent Feb 26, 2026
47e9e62
PipelineStateVk: build specialization constant data
hzqst Feb 27, 2026
955da29
Vulkan: enable SpecializationConstants feature
hzqst Feb 26, 2026
759dacb
Add tests for specialization constants
hzqst Feb 27, 2026
2149085
Add specialization constants support to WGSLShaderResource
hzqst Feb 27, 2026
968182c
WGSLShaderResources: factor out TintOverrideTypeToShaderCodeBasicType…
TheMostDiligent Feb 27, 2026
b318706
Add specialization constant parsing tests for WGSLShaderResource
hzqst Feb 27, 2026
df4eb09
Common: add Float16 type + conversions
TheMostDiligent Mar 4, 2026
660fcd6
GraphicsAccessories: add GetShaderCodeBasicTypeBitSize
TheMostDiligent Mar 4, 2026
22d8564
Prepare pipeline initialization constants for WebGPU pipeline state
hzqst Feb 28, 2026
1584608
WebGPU: enable SpecializationConstants feature
hzqst Feb 28, 2026
00eb5b1
Enable specialization constant tests on WebGPU
hzqst Feb 28, 2026
1b65d75
Fixed an issue that Common_HashUtils.PipelineStateCIStdHash periodica…
hzqst Mar 4, 2026
524cfb6
Add SpecConstsTests in RenderStateCacheTest.cpp
hzqst Mar 4, 2026
7fdc4c2
refine tests.
hzqst Mar 4, 2026
aec2dd3
refine TestRenderStateCaches.
hzqst Mar 4, 2026
221d8c5
refine TestRenderStateCaches.
hzqst Mar 4, 2026
47bb1f1
Fix intermittent failure in Common_HashUtils.PipelineStateCIStdHash
hzqst Mar 4, 2026
e779853
Add render state cache tests for specialization constants
hzqst Mar 4, 2026
67cb376
Move tests from RenderStateCacheTest to SpecializationConstantTest. T…
hzqst Mar 5, 2026
1d4e0de
Merge branch 'specialization_constants' of https://github.com/Diligen…
hzqst Mar 5, 2026
73c61ef
Merge branch 'specialization_constants' of https://github.com/hzqst/D…
hzqst Mar 5, 2026
e846d66
remove them from RenderStateCacheTest
hzqst Mar 5, 2026
a1ccce8
2026
hzqst Mar 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
222 changes: 222 additions & 0 deletions Common/interface/Float16.hpp
Original file line number Diff line number Diff line change
@@ -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 <cstdint>
#include <cstring>
#include <cmath>
#include <limits>
#include <type_traits>

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<float>(i)))
{}

explicit operator float() const noexcept
{
return HalfBitsToFloat(m_Bits);
}

explicit operator double() const noexcept
{
return static_cast<double>(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<float>(std::numeric_limits<int32_t>::max()))
return std::numeric_limits<int32_t>::max();
if (f <= static_cast<float>(std::numeric_limits<int32_t>::min()))
return std::numeric_limits<int32_t>::min();

return static_cast<int32_t>(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<float>(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<uint16_t>(sign | 0x7C00u); // Inf
// Preserve some payload; ensure qNaN
uint16_t payload = static_cast<uint16_t>(mant >> 13);
if (payload == 0) payload = 1;
return static_cast<uint16_t>(sign | 0x7C00u | payload | 0x0200u);
}

// Unbias exponent from float, then bias to half
int32_t e = static_cast<int32_t>(exp) - 127 + 15;

// Handle subnormals/underflow
if (e <= 0)
{
if (e < -10)
{
// Too small -> signed zero
return static_cast<uint16_t>(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<uint16_t>(sign | static_cast<uint16_t>(mant_shifted));
}

// Overflow -> Inf
if (e >= 31)
{
return static_cast<uint16_t>(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<uint16_t>(sign | 0x7C00u);
}
}

return static_cast<uint16_t>(sign | (static_cast<uint16_t>(e) << 10) | static_cast<uint16_t>(mant_half));
}

private:
uint16_t m_Bits{0};
};

} // namespace Diligent
14 changes: 13 additions & 1 deletion Common/interface/HashUtils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1176,7 +1176,8 @@ struct HashCombiner<HasherType, PipelineStateCreateInfo> : HashCombinerBase<Hash
this->m_Hasher(
CI.PSODesc,
CI.Flags,
CI.ResourceSignaturesCount);
CI.ResourceSignaturesCount,
CI.NumSpecializationConstants);
if (CI.ppResourceSignatures != nullptr)
{
for (size_t i = 0; i < CI.ResourceSignaturesCount; ++i)
Expand All @@ -1191,6 +1192,17 @@ struct HashCombiner<HasherType, PipelineStateCreateInfo> : HashCombinerBase<Hash
{
VERIFY_EXPR(CI.ResourceSignaturesCount == 0);
}

if (CI.pSpecializationConstants != nullptr)
{
for (Uint32 i = 0; i < CI.NumSpecializationConstants; ++i)
{
const SpecializationConstant& SC = CI.pSpecializationConstants[i];
this->m_Hasher(SC.Name, SC.ShaderStages, SC.Size);
if (SC.pData != nullptr && SC.Size > 0)
this->m_Hasher.UpdateRaw(SC.pData, SC.Size);
}
}
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -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");
Expand Down Expand Up @@ -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);

Expand Down
36 changes: 35 additions & 1 deletion Graphics/GraphicsAccessories/src/GraphicsAccessories.cpp
Original file line number Diff line number Diff line change
@@ -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");
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions Graphics/GraphicsEngine.NET/Mapping.xml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@
<map field="EngineVkCreateInfo::IgnoreDebugMessageCount" relation="length(ppIgnoreDebugMessageNames)"/>

<map field="PipelineStateCreateInfo::ResourceSignaturesCount" relation="length(ppResourceSignatures)"/>
<map field="PipelineStateCreateInfo::NumSpecializationConstants" relation="length(pSpecializationConstants)"/>
<map field="PipelineStateCreateInfo::pInternalData" visibility="private"/>

<map field="RayTracingPipelineStateCreateInfo::GeneralShaderCount" relation="length(pGeneralShaders)"/>
Expand Down
18 changes: 15 additions & 3 deletions Graphics/GraphicsEngine/interface/GraphicsTypes.h
Original file line number Diff line number Diff line change
@@ -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");
Expand Down Expand Up @@ -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 {}

Expand Down Expand Up @@ -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
Expand Down
Loading
Loading