0
\$\begingroup\$

I noticed something strange while inspecting my compiled shader assemblies, that if I put some buffer loads inside an if statement or a loop, then the resulting assembly will be organized much differently then if outside a branch or loop.

Consider this example:

StructuredBuffer someBuffer<Type>:register(t0);
// ...
float4 pos;
// ...    
if (statementIsTrue)
{
  float4x4 world = someBuffer[index].matWorld;
  pos = mul(pos, world);
}

The mul gets compiled to something like this:

0x000000B8:   ld_structured_indexable(structured_buffer, stride=128)(mixed,mixed,mixed,mixed) r0.yzw, r0.x, l(4), t0.xxyz
0x000000E4:   ld_structured_indexable(structured_buffer, stride=128)(mixed,mixed,mixed,mixed) r1.xyz, r0.x, l(20), t0.xyzx
0x00000110:   ld_structured_indexable(structured_buffer, stride=128)(mixed,mixed,mixed,mixed) r2.xyz, r0.x, l(36), t0.xyzx
0x0000013C:   ld_structured_indexable(structured_buffer, stride=128)(mixed,mixed,mixed,mixed) r3.xyz, r0.x, l(52), t0.xyzx
0x00000168:   dp3 o0.x, v0.yzwy, r0.yzwy
0x00000184:   dp3 o0.y, v0.yzwy, r1.xyzx
0x000001A0:   dp3 o0.z, v0.yzwy, r2.xyzx
0x000001BC:   dp3 o0.w, v0.yzwy, r3.xyzx

Then when I leave the if branch, the whole thing just gets reorganized:

0x00000090: ld_structured_indexable(structured_buffer, stride=128)(mixed,mixed,mixed,mixed) r1.xyzw, r0.x, l(0), t0.xyzw
0x000000BC: dp4 o0.x, v0.xyzw, r1.xyzw
0x000000D8: ld_structured_indexable(structured_buffer, stride=128)(mixed,mixed,mixed,mixed) r1.xyzw, r0.x, l(16), t0.xyzw
0x00000104: dp4 o0.y, v0.xyzw, r1.xyzw
0x00000120: ld_structured_indexable(structured_buffer, stride=128)(mixed,mixed,mixed,mixed) r1.xyzw, r0.x, l(32), t0.xyzw
0x0000014C: ld_structured_indexable(structured_buffer, stride=128)(mixed,mixed,mixed,mixed) r0.xyzw, r0.x, l(48), t0.xyzw
0x00000178: dp4 o0.w, v0.xyzw, r0.xyzw
0x00000194: dp4 o0.z, v0.xyzw, r1.xyzw

Also notice how the dp3 instructions got changed to dp4s, which is also confusing to me.

This also happens with loops. Unrolled loops however act like the second case without the brach.

What could be the reason behind it all and does it matter?

\$\endgroup\$
2
\$\begingroup\$

The HLSL compiler like any compiler tries to optimize code by applying various kinds of code analysis, peep hole optimization, etc. You can try playing around with optimization settings, but fundamentally the compiler is trying to make the best code it can.

Remember that branches can have a huge impact on the efficiency of shaders, particularly dynamic branching. HLSL has some attributes you can use to control how conditional branches are handled: [branch] if or [flatten] if. See MSDN.

In loops, you can use [loop] for or [unroll] for. See MSDN

When doing matrix multiplies, you can do them either with dp3 or dp4 depending on the memory layout. Generally for column-major (the default for HLSL), it can use fewer dp4 instructions.

Code-generation will also depend on your target shader profile.

In short, it does matter but you shouldn't worry about it.

If you really want to get into the nitty-gritty of the HLSL compiler, you might want to check out this GitHub project for the new LLVM/Clang-based Shader Model 6.1 HLSL compiler for DirectX 12.

\$\endgroup\$
  • \$\begingroup\$ Hey thanks for answering. I know about those attributes, I was specifically interested in the above mentioned compiler behaviour. I had an idea that maybe it tries to optimize for balancing ALU/TEX load (or buffer load) when there is no dynamic flow control? \$\endgroup\$ – János Turánszki Feb 14 '17 at 17:49

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.