1
\$\begingroup\$

This is not a question but a confirmation if I'm correct about the behavior of HLSL arrays.

I'm working with a standard lighting vertex shader, HLSL plus C++, DirectX11.1 API. For storing my lights data I've setup a simple declaration, padded accordingly to match the packing requeriments of constant buffers:

  // C++ side   
struct LightBase {
            DirectX::XMFLOAT3   pos, color;
            DirectX::XMFLOAT3   color;
            float               intensity;
            float               isOn;
    };
    struct CBUFFER_LIGHT {
        LightBase   light[MAX_LIGHTS];
        float       numActiveLights;
        float       _padding[3];
    };

The shader declares a "matching" cbuffer as:

// HLSL vertex shader
    struct LightBase {
        float3 pos;
        float3 color;
        float  intensity;
        float  isOn;
    };
    cbuffer lights : register (b3) {
        LightBase light[MAX_LIGHTS];
        float numActiveLights;
    }

Now, as I expected, it didnt work due to the HLSL memory organization policy regarding arrays. e.g; if I set the buffer from C++ with the following data: enter image description here I get the following data when accessing struct members of light[0] from shader:

enter image description here

See how light[0].color and other struct members got messed up? Seems that HLSL will organize your arrays in Vector4 elements regardless of the real type of the array element.

So the array data passed from C++ which is { -7, -1, -100, 0.44, 0.66, 0.88, 1.0, 1.0 } will be organized by HLSL as follows:

A[0] = { -7, -1, -100, 0.44 }
A[1] = { 0.66, 0.88, 1.0, 1.0 }
A[2] = { ...}

So A[0] contains .xyz for position and .x for color struct member. A[1] contains .gb for color member, intensity and isOn data.

But calling light[0].color from the shader will still try to address &light + sizeof(float3)+sizeof(float3) as defined in the cbuffer, but you will get incorrect results due to array organization policy of HLSL.

If HLSL and DirectX are so fond of FLOAT4 values, it's not better to use all float4 variables and access members (.xyzw) as needed? Thanks.

\$\endgroup\$
3
\$\begingroup\$

The rules are a bit convoluted but the essence is that if a vector element would wrap into the next register, it's put at the beginning of the next register.

Your CPU-side and GPU-side definitions do not agree on actual member offsets, you need to manually pad your type on the CPU side according to the rules if you want to use a struct.

You can achieve a tighter packing by organizing your buffer elements such that they logically group up in chunks of 4.

float3 pos; float intensity;
float3 color; float isOn;

Encoding things in clunky float4 tends to be reserved for the case when you encode things in multi-component textures:

float4 pos3_and_intensity;
float4 color3_and_isOn;
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.