Shader keyword limit

Hello folks!

Recently we seen some users experiencing issues with limited keyword count. As result we decided it might be a could time to revisit this problem again.

So for some who do not know about the global keywords limit, here is quick explanation.
Basically when you declare any new keyword in shader (etc. multi_compile, shader_feature) or with scripting API (etc. Shader.EnableKeyword, Material.EnableKeyword…) we actually assign unique index from 0 to 255 and store it as global dictionary between the name and index. This allows us to store enabled keywords as bitmask, this way increasing all keyword related operations significantly. However as some of you already aware it restricts project from having more than 256 unique keywords across all the shader. Usually it is not that easy to hit the cap, but that is not the case once you start to rely on third party packages.

For this reason we were thinking about possible solutions for this issue and we decided it would be great to get feedback from you guys.

1) Increase keyword count to 1024. Well this is actually the simplest solution and we already did this multiple times before.
Advantages - we do not break the old functionality.
Disadvantage - this affects keyword related performance, but we seen regression only if project actually uses more keywords.

2) Make keyword completely local. It means every shader can have 256 unique keywords. However functions that modify keywords globally (etc. Shader.EnableKeyword, CommandBuffer.EnableShaderKeyword), must be removed or changed to simulate old functionality by patching local map.
Advantages - we keep shader variant API intact, we get completly get rid of global keywords.
Disadvantage - functions that change keywords globally must be removed or has to get very heavy internal change (That might not work exactly same in all cases as it would be simulated with local keywords).

3) Add local keywords as additional functionality on current system. So basically hybrid mode to support local and global keywords at the same time. Idea is to leave 192 keywords for global and 64 for local per shader.
Advantages - we keep old functionality intact.
Disadvantage - we have to introduce new shader API that would allow specifying which keywords as local.
Current idea is to deprecate multi_compile/shader_feature and introduce new unified pragma generate_variants that would have some additional options.
For example:
#pragma multi_compile A B == #pragma generate_variants nostrip global A B == #pragma generate_variants nostrip A B
#pragma shader_feature A B == #pragma generate_variants strip global A B == #pragma generate_variants A B
#pragma generate_variants strip local A B
#pragma generate_variants nostrip local A B

We currently have option 3 implemented, but not publicly available. The option 2 still requires a bit investigation, because it would require fundamental changes of keyword system.

7 Likes

I suspect option 1 will be the option most people will choose, but I suspect that option will just lead to a post in a few years of “should we increase the keyword count to 2048?”.

Option 2 seems bonkers, global keywords are exceptionally useful.

Option 3 feels like the best option, though obviously won’t solve the problem for most people until assets get updated and people learn the new paradigm.
Also, why wouldn’t #pragma shader_feature AB == #pragma generate_variants strip **local** A B ? I’m not sure I’ve seen any assets that try and change shader feature keywords globally as, at least in my mind, I already treat them as “local” keywords. Granted this has the potentially to break old assets, but it’s already so dangerous to try to change potentially stripped keywords I would assume most people change to using multi_compile after finding shader_feature didn’t work in a standalone build, and most people don’t know about ShaderVariantCollections still.

Somehow I can still imagine a future where people ask for local and global 1024 keywords … we’ll ignore the multiple gigabyte of shader variants this option leads to, and that static branches are basically free on modern GPUs.

2 Likes

@bgolus , thanks for respond and those who voted in poll.

We come up with different approach for third option. What differs - global keyword limit will be left intact to 256 and we will add additional 64 for local and instead of introducing new user variant directive (etc. generate_variants, propose above), we will expose per shader in shader import settings if user wants to treat their shader_feature or/and multi_compile as local (Will be off by default).
This solution will be much safer choice as it would not only leave old functionality intact, but at the same time will allow some users to use it and isolate it per shader.

In the future we still thinking about new user variant directive that would encapsulate local/global/multi_compile/shader_feature functionality (As the naming for shader_feature and multi_compile was not ideal, especially when they both have very similar functionality), but we also want it to cover the unneeded variant striping (That is currently big issue of variant explosion)

5 Likes

@LukasCh ,

What ultimately happened with this?

We decided to drop this idea, as it will break consistency with our shader compilation. For this reason we decided to introduce just varations of shader directives shader_feature_local and multi_compile_local.

Currently local keywords landed in 2019.1 and will be available for alpha users. Here is more information about it https://docs.google.com/document/d/156MgqojKIgWgrpJLJ0WgD8pVzG0A0T42e3wr0d0oaLg/edit.

6 Likes

@LukasCh ,
Thanks for responding. I found a way to row back on the sheer number of shaders in my project in the meantime… :wink:

Where Do Those Shaders Come From?
I’ve found that many people do many things in many different ways to get what they are trying to express to work as they intend. As a test I’ve loaded over 555GB of asset packages into one Unity project while working in 3D. Doing that, I have also reached the 256 shader definition limit and at times have no idea of who added what to my shader list. That project only runs in Play-Mode on a fast Solid State Drive not a Disk Drive. I’ve tested that also. Even so, I’m pulling for the Asset Store creators/contributors creativity. I would opt for a 1024 shader definition limit as the way to go but also a way to see whom added which shaders from where. Having that shader info documented by the contributor might be a Best Practice, but maybe a set of log files built from the Unity Game Engine itself could automate that procedure. Thanks for the post.

I am dead excited to try this out! Didnt realise they were going to be added so soon!

why not make the keyword limit configurable while a good solution is implemented? That way we will at least get to test our projects quickly.

Could you elaborate? I’m guessing you mean changing global keyword limit between 1-256?

If you mean just plainly increasing keyword count while local keywords are developed. Well the biggest reason is that by increasing keyword count will result in performance decrease (It is not much, but it is static increase that will never go away). Even after the local keywords, we could not reduce keyword count as most of the user would depend on it by now (Even now, I would say 256 of global keywords is way too much, if we would had local keywords by now).

Hello, another batch of changes related to local keywords landed in 2019.1.0a14.
These includes:

  • Fix for subshaders not correctly working with local keywords
  • Fix for material properties copying
  • Other small fixes
  • Moved 38 of unity builtin keywords from global to local. In result unity now only occupies 38 global keywords by default instead of 79
  • Added new button in shader inspector to show what global and local keywords it uses
5 Likes

Does this improve surface shader compilation time as well? I have a big shader with many shader variants, that I’ve tested independently to be working, but if they are all added into the shader it compiles until it fails. It seems like I have to always disable one feature no matter which one it is. The strange thing is that it doesn’t tell me what the problem is, since the error message is very cryptic.

Well local keywords feature was not designed to shader compilation purposes, so it should no affect it in any way (If it is slower, it means it is a bug). Could you post the shader I would gladly look into it.

Oh I though it is related to local keywords, if it is really not - could you make new thread with this and poke me in there. I do not want to mix threads that would result only in confusion.

2 Likes

Thanks! I’ve deleted the original post and moved it here .

Sorry, been a while. I meant to make the keyword count a configurable property, perhaps in the project settings. The default value of the property would be 256. But the developers should be able to configure it to be anywhere between 0 to 1024 perhaps. the upper limit would be a hard limit with comments suggesting that its an interim solution and that it would be deprecated in favor of local keywords. Developers will have to go the way of local keywords to realize performance increases anyway(if they feel its significant). This will give developers some time to plan for localizing their keywords. I don’t know if its doable, but I think it will make it easier for us. Thanks.

@LucasCh,

Any comments on the above? Thanks.

Oh okey I understand what you mean now, we had some idea before the local keywords. However there is few reason why we decided to drop it:

  1. In unity all keyword variations are packed from strings array into 256 bit size integer (This is where limits comes) and in global map strings are stored. For configurable keyword limit we would had two solutions:
    a) In every code part make this integer size configurable, with this we would lose performance from build time optimisations.
    b) in every code part up this integer to maximum limit (etc. 1024), so in the end we would have some behaviour as just simply increasing the maximum.
  2. I fear that users would set it always to maximum to avoid headache in future (Even though if we had default like 256). In the end it would be simply another increase of global keyword count.
    As you mentioned before, to use this feature as a transition to local keywords. I believe it could bring more harm as we might end up supporting two ways and reducing limit back to 256 would be impossible.

No problem. Thanks for the info.

So…Whats the solution to all of us that reached the 256 limit? Any update soon? We can’t download more assets on the store and also I have to delete same of then because I get the error 256 limit very often, so basically it’s quite hard to keep working in our project.

On the other hand HDRP will have the same limit?

Thanks

1 Like