DiligentCore icon indicating copy to clipboard operation
DiligentCore copied to clipboard

SetPushConstants support ?

Open hzqst opened this issue 10 months ago • 10 comments

would be useful on small amount of data that need to be changed on every draw

hzqst avatar May 03 '25 15:05 hzqst

The main question here is what to do on other backends

TheMostDiligent avatar May 03 '25 16:05 TheMostDiligent

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.

hzqst avatar May 03 '25 16:05 hzqst

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.

TheMostDiligent avatar May 04 '25 06:05 TheMostDiligent

I'm trying to add SetPushConstants to DiligentCore:

https://github.com/DiligentGraphics/DiligentCore/compare/master...hzqst:DiligentCore:pushconstants

the main problems are:

  1. How to get correct RootParameterIndex for SetGraphicsRoot32BitConstants?
  2. 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

hzqst avatar Oct 18 '25 08:10 hzqst

Some comments:

  • SetPushConstants method should be part of the IShaderResourceBinding as 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_CONSTANTS is 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.

TheMostDiligent avatar Oct 18 '25 15:10 TheMostDiligent

Some comments:

  • SetPushConstants method should be part of the IShaderResourceBinding as 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_CONSTANTS is 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

  1. Store in PipelineResourceSignatureD3D12Impl (similar to m_RootParams)
  2. Store in ShaderResourceBindingD3D12Impl
  3. Store in ShaderResourceCacheD3D12

Root parameter allocation for Push Constants

  1. Allocate as a separate D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS root parameter
  2. Do not occupy root parameter slots (since 32-bit constants are inlined directly in the root signature)

SetPushConstants call timing/behavior

  1. Immediately update and commit to command list each time it's called
  2. Cache the data and submit during CommitRootTables
  3. Independent submission that can be called before or after CommitRootTables

hzqst avatar Oct 19 '25 08:10 hzqst

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);

hzqst avatar Oct 19 '25 16:10 hzqst

I started working on this. The API will be:

  • A new PIPELINE_RESOURCE_FLAG_INLINE_CONSTANTS can be applied to SHADER_RESOURCE_TYPE_CONSTANT_BUFFER resources, which will make the constants in the buffer push/root constants
  • Inline constants will be set directly through IShaderResourceVariable::SetInlineConstants
  • SHADER_VARIABLE_FLAG_INLINE_CONSTANTS will allow using inline constants without specifying resource signature

TheMostDiligent avatar Oct 20 '25 03:10 TheMostDiligent

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 ?

hzqst avatar Nov 25 '25 13:11 hzqst

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.

TheMostDiligent avatar Nov 25 '25 15:11 TheMostDiligent