Search Unity

Question: Common Shader Naming Conventions and Performance Optimizations

Discussion in 'Shaders' started by Yoreki, Sep 18, 2019.

  1. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    Hi everyone,
    i've been learning about writing basic Shaders in Cg for a couple days now. However, i was not able to find a conclusive answer for commonly used naming/coding conventions. So my first question is, what naming conventions should i follow? From what i've seen, the default properties like _MainTex always start with an underscore followed by PascalCase. Functions and variables seem to be named in all lowercase, but they are also always single words. So would we go with camelCase, or snake_case for longer ones? Examples i found seem to contradict each other, with some people not using conventions at all or even changing their style while coding. And when i looked for naming conventions i did not really find anything that matched the above style.
    That left me a bit confused, since i assume there are some conventions most people follow and that are seen as "correct", right?.
    If there is a link covering the above (that everybody agrees on), feel free to just post that instead of explaining.

    And then i'd like to know if there is anything i should pay attention to in regards for performance. While not an expert, i have some decent understanding for how things work under the hood in C#, or on the CPU in general, or in Unity. But Shaders are new territory for me, since they a) run on the graphics card and b) use a completely different language.
    I'm sure a lot of knowledge can be directly applied to shaders, but is there anything i should pay more attention to? Like, for example, in C# using bitshift over multiplication over division (with numbers of power of 2) does not seem to make any difference at all anymore. But is this the same for shaders, or is it worth paying more attention to these things than i normally would?
    Is there anything shader-specific to pay attention to?
    And what about the interaction between C# and the Shader? Is it, for example, expensive to set tons of properties using SetFloat("something", something) each time, or is that pretty much just a normal assignment i should not worry about? I guess the string gets parsed somewhere, so i doubt it's super efficient. Not sure if it's expensive enough to have any noticable impact tho.
    Oh and while i'm at it, are unused functions compiled out, or should i comment them out or remove them when not needed (for the final product)?

    Anything else i should know or pay attention to? This is partly a pracitcal question, but also partly a curiosity based one, so even micro-optimizations are welcome answers (but should probably be marked as such).

    Thanks a lot in advance! :)
     
    Last edited: Sep 18, 2019
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Whatever you want. It's totally up to you. I think there's some CPU side code that expects material properties to start with an underscore, and for keywords to be uppercase, but that's about it.

    Unity's own code isn't consistent with its conventions for variable and function names. Heck, look at the insanity that is their matrix transform properties. UNITY_MATRIX_V, unity_ObjectToWorld, _CameraToWorld, etc. It's a mess.

    There are a ton of articles out there on GPU programming and optimization. I think the most important thing to think about is the impact of branching on a GPU ... or perhaps even more importantly, the lack of branching. That's not to say that modern GPUs can do dynamic branching, they can, but it's potentially expensive. Most of the time if you have a shader with an if function or some other kind of branch, assume all code on both sides of that branch are always going to be computed.

    If the branch is looking at is constant at compile time, the compiler will remove the unused code. If the branch is invariant, ie: it's based off of a value that's set as a material property or otherwise set using SetFloat() or similar shader-wide constant values, then the shader compiler may make it a real branch and the GPU can switch between the branches dynamically. If the value might change between pixels, then assume both sides are running 100% of the time. The reasons why have to do with SIMD computation. There are a ton of in depth articles on the topic, so I'm not going to delve into that here.

    Generally speaking, if you can solve something algebraically, or using bitwise ops (on desktop and gles 3.0 class GPUs) it's faster than using if statements. That said there's a lot of things people do to "avoid if statements", like using built in HLSL functions like step() or any() ... which are implemented as if statements. So they're not really that evil, just be aware that you're most likely not actually skipping that code.
     
    Yoreki likes this.
  3. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,745
    Wait, step is implemented as an if statement? Didn’t know that.

    It... I guess it makes sense.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    The most expensive part here is probably the mapping of the string to the actual property mapping. The assignment is negligible unless you're setting multiple hundreds of values, in which case a compute buffer / structured buffer might be more appropriate. Unity's Standard shader has something around 100 unique floats it sets for each object every frame. The way GPUs are designed, sending data from the CPU to the GPU is quite fast. Most of that data is getting bundled up into buffers by Unity's rendering systems and sent as batches of data rather than one at a time, so the API calls aren't that expensive overall.

    The way to make that cheaper is to cache the int value of a property ID using Shader.PropertyToID("_MyValue") and using the int instead of the string.

    I mentioned it briefly above, but unused code disappears from the compiled shader. The usual UnityCG.cginc file has a ton of code that it includes, and that all gets wiped away on compile. Unused properties too. You can assign them from c#, but Unity's rendering systems know the compiled shader doesn't use them and they just get skipped when actually sending data to the GPU.

    Code (csharp):
    1. // step function
    2. float val = step(foo, bar);
    3.  
    4. // inline comparison with implicit cast
    5. float val = bar >= foo;
    6.  
    7. // inline comparison with switch
    8. float val = bar >= foo ? 1.0 : 0.0;
    9.  
    10. // if statement
    11. float val = 0;
    12. if (bar >= foo)
    13.     val = 1;
    Those all compile to the exact same shader code.
    Code (csharp):
    1. ge r0.x, cb[0].x, cb[1].x
     
    Last edited: Sep 20, 2019
  5. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    Wow, there is already so much useful information here. Thank you a lot bgolus!
    This was very interresting to read, and i've got a lot of keywords to look into in relation to GPU programming now too.