这是indexloc提供的服务,不要输入任何密码
Skip to content

Investigation : Push Constants #75

@shaoboyan

Description

@shaoboyan

Intro

In many situations, like dynamic indexing, developers may pass a small amount of read-only information to pipeline and update them frequently.

Metal, D3D12 and Vulkan all provide optimizations for such kind of usages.

It is reasonable to bring these features to WebGPU.

Native APIs

Metal

In Metal, SetBytes(), SetVertexBytes() and SetFragmentBytes() APIs are potential candidates to support this feature.

Here are the related descriptions in the documents of Metal:

Using these methods is equivalent to creating a new MTLBuffer object from the specified data and then binding it to the compute function with setBuffer() | setVertexBuffer() | setFragmentBuffer() method.

These methods avoid the possible performance overhead of creating an intermediary MTLBuffer Object.

To use these APIs, developers need to call SetBytes() for compute function, call SetVertexBytes() for vertex function and SetFragmentBytes() for fragment function to ensure the special data buffers are set to corresponding shader stages.

Metal requests the data buffers set by these APIs less than 4KB and must be one-time-use data buffer in each render encoder. Metal doesn’t support updating a subset of the values in these special data buffers.

D3D12

In D3D12, Root Constants is potential candidate to support this feature.

According to MSDN, Root Constants are

  • inline 32-bit values
  • Applications can define root constants in the root signature, each as a set of 32-bit values. They appear in High Level Shading Language (HLSL) as a constant buffer.

Actually, in Microsoft’s DirectX-Graphics-Sample, root constants are used in dynamic indexing.

To use root constants, developers need to register root constants into root signatures through InitAsConstant() and set the visibilities of the root constants for each shader stage. After that, it is linked to the command lists. Developers can update any subset of values in root constants through APIs like setGraphicsRoot32BitConstants() by providing the slot number, memory address of new data and range.

Here are the limitations of root constants :

  • Each set of user constants is treated as a scalar array of 32-bit values, dynamically indexable and read-only from the shader. Note that constant buffers for historical reasons are viewed as sets of 4x32-bit values.

  • Arrays are not permitted in constant buffers that get mapped onto root constants since dynamic indexing in the root signature space is not supported.

  • The maximum size of a root signature is 64 DWORDs. Root constants cost 1 DWORD each, since they are 32-bit values.

Vulkan

In Vulkan, Push Constants is ideal candidate to support this feature.

According to Vulkan SPEC:

Push constants represent a high speed path to modify constant data in pipelines that is expected to outperform memory-backed resource updates.

So push constants in Vulkan is a high speed way to handle small amount of read-only data which needs to updated frequently.

But in Vulkan, as is different from D3D12 and Metal, all shader stages access the same push constants block. This means, even if values in push constants are updated with one shader stage flag, all of the shader stages in the pipeline will be affected.

To use push constants in Vulkan, developers need to fill the information in vkPipelineLayout to describe the ranges of the push constants that the pipeline wants to access and their the shader stage visibility. After that, these ranges are allocated. Developer can update any subset of values in ranges of push constants through API vkCmdPushConstants() with shader stage bits, memory address of new data, offset in bytes and length of updated range in bytes.

Here are the limitations of Vulkan push constants:

  • Push constants has length limitation.

  • Vulkan use std430 layout in GLSL shader by default. Push constants block in shader may be padded.

  • Push constant variables with same offset cannot have different values in different shader stages.

Shaders

MSL

The SetBytes() family of functions generate implicit constant buffers in MSL.

Example:

struct PushConstants {
 ...
};


// bindingNum is the same as index from set*Bytes()
Foo(constant PushConstants& push_constants [[buffer(bindingNum)]] ) {
...
} 

HLSL

ConstantBuffer is used in HLSL with register number to support RootConstants.

Example:

struct DrawConstants
{
    uint foo;
    float2 bar;
    int moo;
};
ConstantBuffer<DrawConstants> myDrawConstants : register(b1, space0); 

Spirv

Storage Class Has PushConstant type.

Example:

%_ptr_PushConstant_a_block = OpTypePointer PushConstant %a_block
          %a = OpVariable %_ptr_PushConstant_a_block PushConstant
          %b = OpVariable %_ptr_PushConstant_a_block PushConstant
          %c = OpVariable %_ptr_PushConstant_a_block PushConstant
       %void = OpTypeVoid
          %7 = OpTypeFunction %void
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
%_ptr_PushConstant_int = OpTypePointer PushConstant %int
     %uses_a = OpFunction %void None %7
         %10 = OpLabel
         %14 = OpAccessChain %_ptr_PushConstant_int %a %uint_0
         %15 = OpLoad %int %14
               OpReturn
               OpFunctionEnd

Proposal

There are common requests in Metal, D3D12 and Vulkan to support this feature. And we propose push constants in WebGPU should follow these common requests :

WebGPU API

  • Before each render pass, all push constants need to be set to zero. So we don't have to validate it was set before a shader uses push constatns. And it aligns to Metal’s behavior that special data buffers become invalid after the encoder is submitted to the queue.

  • The offset of push constants blocks in shader must be a multiple of 4, as required by both Vulkan and D3D12.

  • a single push constant range that’s shared between all shader stages in the same pipeline. It means variables with same offset have identical content in all shader stages in the same pipeline.

  • We propose a new attribute pushConstantSize in GPUPipelineLayoutDescriptor for developers to describe push constants size :

    dictionary GPUPipelineLayoutDescriptor {
            sequence<WebGPUBindingGroupLayout> bindGroupLayouts;
            uint32_t pushConstantsSize = 0;
    };

    And two pipeline layouts are defined to be “compatible for push constants” if they were created with identical push constant size. It means push constants values can share between pipeline layouts that are compatible for push constants.

  • The proposed APIs to update push constants are

    interface GPUCommandEncoder {
            void setPushConstantU32(uint32_t offset, uint32_t value);
            void setPushConstantI32(uint32_t offset, int32_t value);
            void setPushConstantF32(uin32_t offset, float32_t value);
            void setPushConstants(uint32_t offset, uint32_t count, ArrayBuffer data);
    }

WGSL

Add push_constant in address spaces and requires
each entrypoint can statically use at most one such variable.
Details:

In Variable and Value Declarations. Details:

Metadata

Metadata

Assignees

No one assigned

    Labels

    apiWebGPU APIapi-milestone-2-202502api issues that were in milestone 2 before we triaged milestone 1 on 2025-02-19investigationprogramming modelwgslWebGPU Shading Language Issues

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions