Search code examples
shaderdirectxhlsl

HLSL how to pass constant buffer as a whole to functions, instead of per member


Let's say I have multiple shaders that use the same functionality and I want to move it to a separate function. For example I have multiple vertex shaders that share the same matrix multiplications:

PS_INPUT main(VERTEX_INPUT vertex)
{
    PS_INPUT output = (PS_INPUT)0;

    output.Position = mul(vertex.Position, World);
    output.Position = mul(output.Position, View);
    output.Position = mul(output.Position, Projection);

    // other uncommon functionality...
   
    return output;
}

And the data comes from a constant buffer.

cbuffer Object : register(b0)
{
    float4x4 World;
}

cbuffer Camera : register(b1)
{
    float4x4 View;
    float4x4 Projection;
}

If I want to move the matrix multiplications into separate functions I would usually write something like this:

float4 process_position(float4 inputPosition, float4x4 world, float4x4 view, float4x4 projection)
{
    float4 outPosition = mul(inputPosition, world);
    outPosition = mul(outPosition, view);
    outPosition = mul(outPosition, projection);
    return outPosition;
}

This can easily get out of hand and become incredibly messy with more arguments. The arguments can be grouped in a struct, but still would require maintenance, if I change something in the constant buffer. Isn't there a way to automatically pass a whole cbuffer, bound to the current shader, to a function, instead of single members of it? Something like this would be great:

float4 process_position(float4 inputPosition, Object objBuf, Camera camBuf) // errors...
{
    float4 outPosition = mul(inputPosition, objBuf.World);
    outPosition = mul(outPosition, camBuf.View);
    outPosition = mul(outPosition, camBuf.Projection);
    return outPosition;
}

PS_INPUT main(VERTEX_INPUT vertex)
{
    PS_INPUT output = (PS_INPUT)0;

    output.Position = process_output(vertex.Position, Object, Camera); // errors...

    // other uncommon functionality...

    return output;
}

Note: working with shader model 5_0


Solution

  • In shader model 5, constant buffers are not object or variables, so no, you can't pass then as arguments to functions.

    Your closest way is indeed to work with structs (and make sure that you use the struct both in the cbuffer declaration and as function input.

    struct sObjectData
    {
        float4x4 World;
    };
    
    struct sCameraData
    {
        float4x4 View;
        float4x4 Projection;
    };
    
    cbuffer cbObject : register(b0)
    {
        sObjectData objectData;
    }
    
    cbuffer Camera : register(b1)
    {
        sCameraData cameraData;
    }
    
    float4 process_position(float4 inputPosition, sObjectData objBuf, sCameraData 
    camBuf)
    {
        float4 outPosition = mul(inputPosition, objBuf.World);
        outPosition = mul(outPosition, camBuf.View);
        outPosition = mul(outPosition, camBuf.Projection);
        return outPosition;
    }
    
    float4 test = process_position(input.pos, objectData, cameraData);
    

    Of course there can be some maintenance (if you rename a field or remove one, but that would also be the case if passing the whole cbuffer was allowed anyway), but at least adding new ones or reordering fields will go unaffected.