SetPushConstants support ?
would be useful on small amount of data that need to be changed on every draw
The main question here is what to do on other backends
as for OpenGL / GLES, what about fallback to ordinary uniform buffer ?
idk if : register(b1, space0) { sucks when being converted to glsl via HLSL2GLSLConverter. I am using spirv-cross to emit glsl instead of HLSL2GLSLConverter though. it won't be a problem to me.
The main problem is how to make the data available to the shader. In Vulkan/D3D12 it is set directly in the command buffer, but in other backends it will require some shenanigans with buffers. Given that generally there may be multiple push constants, that is quite non-trivial.
I'm trying to add SetPushConstants to DiligentCore:
https://github.com/DiligentGraphics/DiligentCore/compare/master...hzqst:DiligentCore:pushconstants
the main problems are:
- How to get correct RootParameterIndex for SetGraphicsRoot32BitConstants?
- We should probably leave SetPushConstants in D3D11/GL
UNSUPPORTED("SetPushConstants is not supported in DirectX 11/OpenGL");?
Wicked has something like this and NVRHI has this however I don't see anything similar in Diligent
Some comments:
-
SetPushConstantsmethod should be part of theIShaderResourceBindingas all resources are set through SRB - Pipeline resource signatures need to support push constants as they are part of the pipeline layout
-
SHADER_RESOURCE_TYPE_32_BIT_CONSTANTSis added - Push constants are reflected from the shaders and added to the signature
- They can also be added manually to the signature
-
- In D3D11/GL/WebGPU/Metal they should be somehow emulated through buffers. It will be really inconvenient if the feature is only supported in 2 of 6 backends.
Some comments:
SetPushConstantsmethod should be part of theIShaderResourceBindingas all resources are set through SRBPipeline resource signatures need to support push constants as they are part of the pipeline layout
SHADER_RESOURCE_TYPE_32_BIT_CONSTANTSis added- Push constants are reflected from the shaders and added to the signature
- They can also be added manually to the signature
In D3D11/GL/WebGPU/Metal they should be somehow emulated through buffers. It will be really inconvenient if the feature is only supported in 2 of 6 backends.
Some more questions need to be concerned carefully in D3D12:
Storage location for SetPushConstants data
- Store in
PipelineResourceSignatureD3D12Impl (similar to m_RootParams) - Store in
ShaderResourceBindingD3D12Impl - Store in
ShaderResourceCacheD3D12
Root parameter allocation for Push Constants
- Allocate as a separate
D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTSroot parameter - Do not occupy root parameter slots (since 32-bit constants are inlined directly in the root signature)
SetPushConstants call timing/behavior
- Immediately update and commit to command list each time it's called
- Cache the data and submit during
CommitRootTables - Independent submission that can be called before or after
CommitRootTables
SetPushConstants for D3D12 added (pretty buggy though): https://github.com/DiligentGraphics/DiligentCore/compare/master...hzqst:DiligentCore:D3D12_SetPushConstants
further investigation needed:
D3D12 ERROR: ID3D12Device::CreateGraphicsPipelineState: Root Signature doesn't match Vertex Shader: Shader CBV descriptor range (BaseShaderRegister=52685, NumDescriptors=1, RegisterSpace=205) is not fully bound in root signature
[ STATE_CREATION ERROR #688: CREATEGRAPHICSPIPELINESTATE_VS_ROOT_SIGNATURE_MISMATCH]
sample PSO creation:
PipelineResourceDesc PipelineResources[1];
PipelineResources[0].Name = "Constants";
PipelineResources[0].ShaderStages = SHADER_TYPE_VERTEX;
PipelineResources[0].ArraySize = sizeof(float4x4) / 4;
PipelineResources[0].ResourceType = SHADER_RESOURCE_TYPE_32_BIT_CONSTANTS;
PipelineResources[0].VarType = SHADER_RESOURCE_VARIABLE_TYPE_STATIC;
Uint32 NumPipelineResources = 1;
Diligent::PipelineResourceSignatureDesc PRSDesc;
PRSDesc.UseCombinedTextureSamplers = true;
PRSDesc.Resources = PipelineResources;
PRSDesc.NumResources = NumPipelineResources;
//PRSDesc.ImmutableSamplers = ImtblSamplers;
//PRSDesc.NumImmutableSamplers = NumImtblSamplers;
m_pDevice->CreatePipelineResourceSignature(PRSDesc, &m_pPRS);
IPipelineResourceSignature* ppSignatures[]{m_pPRS};
PSOCreateInfo.ppResourceSignatures = ppSignatures;
PSOCreateInfo.ResourceSignaturesCount = _countof(ppSignatures);
m_pDevice->CreateGraphicsPipelineState(PSOCreateInfo, &m_pPSO);
// Since we did not explicitly specify the type for 'Constants' variable, default
// type (SHADER_RESOURCE_VARIABLE_TYPE_STATIC) will be used. Static variables never
// change and are bound directly through the pipeline state object.
//m_pPSO->GetStaticVariableByName(SHADER_TYPE_VERTEX, "Constants")->Set(m_VSConstants);
// Create a shader resource binding object and bind all static resources in it
m_pPRS->CreateShaderResourceBinding(&m_pSRB, true);
I started working on this. The API will be:
- A new
PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTScan be applied toSHADER_RESOURCE_TYPE_CONSTANT_BUFFERresources, which will make the constants in the buffer push/root constants - Inline constants will be set directly through
IShaderResourceVariable::SetInlineConstants -
SHADER_VARIABLE_FLAG_INLINE_CONSTANTSwill allow using inline constants without specifying resource signature
As for vulkan, what if we have multiple constant buffers marked as inline constants?
let's say we have
//hlsl
struct pushconstant_t {
float4 color;
};
cbuffer g_PushConstants
{
pushconstant_t pushConsts;
}
struct pushconstant2_t {
float4 position;
};
cbuffer g_PushConstants2
{
pushconstant2_t pushConsts2;
}
We probably gonna merge those two cbuffers into one (since Vulkan spec does not allow more than one push_constants) and mark it as layout(push_constant) in spv (will introduce complexity to SPIRVShaderResources processing though, spirv_cross needed?):
//glsl
struct pushconstant_t {
float4 color;
};
struct pushconstant2_t {
float4 position;
};
layout(push_constant) uniform MergedPushConsts {
layout(offset = 0) pushconstant_t pushConsts;
layout(offset = 16) pushconstant2_t pushConsts2;
} pushConsts;
Or reject configurations with more than one inline constants as a temporary workaround ?
Yes, multiple inline constants in Vulkan is a major challenge. Merging multiple uniform buffers into one will require significant byte code patching, I don't think it is practical to try this. Disallowing multiple inline constants as certainly and option, but I am planning to emulate inline constant blocks with buffers, similar to D3D11. In practice, only one block should be recommended.