4
\$\begingroup\$

I am trying to optimize my vertex and pixel shader code. Do you have any suggestions what could I possibly do to improve performance? I am using it to draw textured cuboids, and if there are many objects in the screen my framerate drops.

VERTEX SHADER

        float4x4 WorldViewProj                      : register(c0); 
        float4x3 WorldInversedMatrix                : register(c4);
        float4x4 World                              : register(c7);
        float3 viewPosition                         : register (c11);
        float3 dirLightDir                          : register(c12);
        float xCoordMultiplication                  : register (c13);
        float yCoordMultiplication                  : register (c14);

        struct VsInput
        {
            float4 position : POSITION;
            float3 normal   : NORMAL;
            float2 texCoord : TEXCOORD;
        };

        struct VsOutput
        {
            float4 position : POSITION;
            float3 dirLightPosition : COLOR;
            float2 texCoord : TEXCOORD0;
            float3 normal   : TEXCOORD1;
            float3 view : TEXCOORD2;
        };


        VsOutput main(VsInput IN)
        {
            VsOutput OUT;

            OUT.position = mul(IN.position, WorldViewProj);

            //if X/YCoordMultiplication != 1, then texture is multiplicated (for example when drawing tiled floor, and my texture is single tile
            OUT.texCoord = float2(IN.texCoord.x * xCoordMultiplication, 
                                  IN.texCoord.y * yCoordMultiplication);  
            OUT.normal = mul(WorldInversedMatrix, IN.normal);

            float3 worldPosition = mul(IN.position, World).xyz;
            OUT.view = viewPosition - worldPosition;

            OUT.dirLightPosition = mul(dirLightDir, World);
            return OUT;
        }

PIXEL SHADER

float4 filterColor              : register (c1);
float3 specularLightIntensity   : register (c2);


texture texTexture;
sampler textureSampler  : register(s0) = sampler_state {
    Texture = (texTexture);
    Filter = MIN_MAG_MIP_POINT;
    AddressU = Wrap;
    AddressV = Wrap;
};

struct VsOutput
{
    float4 position : POSITION;
    float3 dirLightPosition : COLOR;
    float2 texCoord : TEXCOORD0;
    float3 normal   : TEXCOORD1;
    float3 view : TEXCOORD2;
};

float4 main(VsOutput IN) : COLOR
{

    float4 texColor = tex2D(textureSampler, IN.texCoord);

    float3 specular = specularLightIntensity;
    float materialPower = 9.0f;

    float3 light = normalize(IN.dirLightPosition);
    float3 view = normalize(IN.view);
    float3 normal = normalize(IN.normal);
    float3 halfway = normalize(light + view);

    float3 processedSpecular = pow(saturate(dot(normal, halfway)), materialPower) 
                        * specular;

    float alphaChannel = texColor.a + filterColor.a;


    float4 color = 1.2f * (texColor + filterColor.a * filterColor   + float4(processedSpecular, 0.0f));

    return float4(color.r, color.g, color.b, filterColor.a + texColor.a);
}
\$\endgroup\$
3
  • \$\begingroup\$ Hi there, nice question! Could you edit your post to include the tags of the language you're using in this code? \$\endgroup\$ Oct 23 '15 at 9:00
  • \$\begingroup\$ @SuperBiasedMan Well, the language is hlsl i think :D \$\endgroup\$ Oct 23 '15 at 9:01
  • \$\begingroup\$ Well! Never mind me then, I'm a dummy haha. Hope you get good answers! \$\endgroup\$ Oct 23 '15 at 9:02
3
\$\begingroup\$

Late response to this, but all I can suggest is leveraging the per-component math of HLSL better in your shaders...

Vertex Shader

    float xCoordMultiplication                  : register (c13);
    float yCoordMultiplication                  : register (c14);

Make that a float2

    float2 xyCoordMultiplier   : register (c13) : register (c14);

I think that's how you tie two different registers to one global. Might want to double check MSDN HLSL reference to be sure, though.

With your xy multiplier now a float 2, you can change this...

        OUT.texCoord = float2(IN.texCoord.x * xCoordMultiplication, 
                              IN.texCoord.y * yCoordMultiplication); 

... to this ...

        OUT.texCoord = IN.texCoord.xy * xyCoordMultiplier.xy;

You save yourself having to cast another float2 (for the x & y math operations), and you leverage .xy per-component math which is faster then doing it each separately.

In your pixel shader...

float3 processedSpecular = pow(saturate(dot(normal, halfway)), materialPower) * specular;

... HLSL has an intrinsic function called "lit" that you can read more about here... http://developer.download.nvidia.com/cg/lit.html

Basically you hand it a sun luminence dot (NDotL ... dot ( normal, light)), a specular reflection dot (NDotH ... dot ( normal, halfway)) and a specular pow value (your materialPower) it does all the pow, saturate, etc.. basically creates your float specular luminence to * by your float3 specularLightIntensity.

While you still have to calculate the dots in the shader, you'd use the lit function, b/c it replaces the pow,saturate,etc you have with a built-in function doing it more efficiently then you coding it in the shader yourself.

I would also change this stuff around...

float alphaChannel = texColor.a + filterColor.a;

float4 color = 1.2f * (texColor + filterColor.a * filterColor   + float4(processedSpecular, 0.0f));

return float4(color.r, color.g, color.b, filterColor.a + texColor.a);

... you're not using alphaChannel; you're adding filterColor.a + texColor.a in the return again.. so just make your return be...

float4 color;
       color.rgb = 1.2f * (texColor.rgb + filterColor.a * filterColor.rgb   + processedSpecular);
       color.a = texColor.a + filterColor.a;

return color;

... that way you declare 1 float4 return value, and do the math for .rgb and .a separately. B/c as you're doing it in your initial way, you're wasting calculations a) generating a float alphaChannel var that never gets used, b) calculating color.rgba when you just replace color.a in the return anyways (by re-calculating alphaChannel), c) you're casting another float4 in your return, and inefficiently comma-delim'ing your color.r, color.g, color.b values when you can just color.rgb them together.

The idea is to reduce the number of variables casts you make... so while you can "float4 ( somevalue.r, somevalue.g, somevalue.b, somevalue.a )"... if you're already creating a return value, then just use it as the return value instead of recasting it.

Sometimes your return value you're dealing with may just be a float3, eg: if you're only pulling the rgb of a texture, working with it, and the alpha .a will always be 1. Even then.. just cast a float4 from the start, do all your work with the .rgb float3 parts of it, and then set the .a part of it to 1 and return it...

eg:

float4 color;
       color.a = 1;
       color.rgb = tex2d (sample, uv).rgb;
       color.rgb = (complex code that alters it)
       ... ... ...
return color;

You create a single float4, and then just work with it's .rgb through the shader, but return the float4 whole at the end.

You can do this in other places, too... often people will pack HLSL vars full.. eg: they'll take 2 float4's and use the .rgb parts for colors and then pack a float2 in the .a alphas of them...

eg:

struct someObject
{
    float4 sunU; // .rgb = sun vec * light matrix // .a = UV.x
    float4 skyV; // .rgb = sky vec * light matrix // .a = UV.y
}

void vertex_shader ( in blah i, out someObject o)
{
    o.sunU.rgb = suncolor.rgb;
    o.sunU.a = uv.x;
    o.skyV.rgb = skycolor.rgb;
    o.skyV.a = uv.y;
}

I prefer casting a variable once, and then loading it like I did above. But, I see folks double-casting vars all the time, like so...

o.sunU = float4 ( suncolor.rgb, uv.x );
o.skyV = float4 ( skycolor.rgb, uv.y );

.. I mean... it does the same thing: loads the float4's with their values. But, it casts a second float4 on the right-side of the equation to do so. We're casting 4 float4's there.. the sunU and skyV, and then the right-side equations to piece together what we're packing in them. The way I wrote it eariler, you only cast 2 float4's in the struct, then use swizzling to tell them what goes in what parts without having to cast more vars to do so en-masse.

It seems like such a minor thing, and overall it probably is, but when you have something like that in a pixel shader firing off for every pixel... it's just unnecessary overhead ... X many pixels times Y number of frames per second... it adds up.

\$\endgroup\$
1
  • \$\begingroup\$ Very elaborate for a first post! (There's one thing nagging me: did you try out the changes suggested?) \$\endgroup\$
    – greybeard
    Mar 18 '18 at 12:19

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.