Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -85,6 +85,12 @@ class PipelineStateWebGPUImpl final : public PipelineStateBase<EngineWebGPUImplT
ShaderWebGPUImpl* const pShader;
std::string PatchedWGSL;

// Per-stage specialization constant entries, built from user input
// and WGSL override reflection during InitializePipeline().
// Entries reference names in the shader resource name pool,
// which is kept alive by ShaderWebGPUImpl.
std::vector<WGPUConstantEntry> SpecConstEntries;

ShaderStageInfo(ShaderWebGPUImpl* _pShader) :
Type{_pShader->GetDesc().ShaderType},
pShader{_pShader}
Expand Down
2 changes: 1 addition & 1 deletion Graphics/GraphicsEngineWebGPU/src/EngineFactoryWebGPU.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?
Expand Down
143 changes: 141 additions & 2 deletions Graphics/GraphicsEngineWebGPU/src/PipelineStateWebGPUImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<double>(Val);
}

case SHADER_CODE_BASIC_TYPE_UINT:
{
Uint32 Val = 0;
memcpy(&Val, pData, std::min(DataSize, Uint32{4}));
return static_cast<double>(Val);
}

case SHADER_CODE_BASIC_TYPE_FLOAT:
{
float Val = 0;
memcpy(&Val, pData, std::min(DataSize, Uint32{4}));
return static_cast<double>(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<const WGSLShaderResources>& 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,
Expand Down Expand Up @@ -342,6 +457,15 @@ struct PipelineStateWebGPUImpl::AsyncPipelineBuilder : public ObjectBase<IObject
void PipelineStateWebGPUImpl::InitializePipeline(const GraphicsPipelineStateCreateInfo& CreateInfo)
{
TShaderStages ShaderStages = InitInternalObjects(CreateInfo);

// Build specialization constant entries while CreateInfo is still valid.
// The entries are stored per-stage in ShaderStageInfo::SpecConstEntries,
// which are moved into AsyncPipelineBuilder for async creation.
BuildSpecializationDataWebGPU(ShaderStages,
CreateInfo.NumSpecializationConstants,
CreateInfo.pSpecializationConstants,
m_Desc);

// NB: it is not safe to check m_AsyncInitializer here as, first, it is set after the async task is started,
// and second, it is not atomic or protected by mutex.
if ((CreateInfo.Flags & PSO_CREATE_FLAG_ASYNCHRONOUS) == 0)
Expand All @@ -358,6 +482,13 @@ void PipelineStateWebGPUImpl::InitializePipeline(const GraphicsPipelineStateCrea
void PipelineStateWebGPUImpl::InitializePipeline(const ComputePipelineStateCreateInfo& CreateInfo)
{
TShaderStages ShaderStages = InitInternalObjects(CreateInfo);

// Build specialization constant entries while CreateInfo is still valid.
BuildSpecializationDataWebGPU(ShaderStages,
CreateInfo.NumSpecializationConstants,
CreateInfo.pSpecializationConstants,
m_Desc);

// NB: it is not safe to check m_AsyncInitializer here as, first, it is set after the async task is started,
// and second, it is not atomic or protected by mutex.
if ((CreateInfo.Flags & PSO_CREATE_FLAG_ASYNCHRONOUS) == 0)
Expand Down Expand Up @@ -402,14 +533,18 @@ void PipelineStateWebGPUImpl::InitializeWebGPURenderPipeline(const TShaderStages
{
case SHADER_TYPE_VERTEX:
VERIFY(wgpuRenderPipelineDesc.vertex.module == nullptr, "Only one vertex shader is allowed");
wgpuRenderPipelineDesc.vertex.module = wgpuShaderModules[ShaderIdx].Get();
wgpuRenderPipelineDesc.vertex.entryPoint = GetWGPUStringView(Stage.pShader->GetEntryPoint());
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;

Expand Down Expand Up @@ -615,6 +750,10 @@ void PipelineStateWebGPUImpl::InitializeWebGPUComputePipeline(const TShaderStage
wgpuComputePipelineDesc.compute.entryPoint = GetWGPUStringView(pShaderWebGPU->GetEntryPoint());
wgpuComputePipelineDesc.layout = m_PipelineLayout.GetWebGPUPipelineLayout();

const std::vector<WGPUConstantEntry>& 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.
Expand Down
Loading
Loading