Search Unity

Asset Bundles and Compiling Shaders

Discussion in 'Asset Bundles' started by mmkc, Sep 11, 2020.

  1. mmkc

    mmkc

    Joined:
    Sep 12, 2019
    Posts:
    49
    Our application loads an asset bundle based on user's selection and then immediately loads a scene asset bundle as well as a bundle that contains all of the assets for that scene. We notice there is always a lag when doing this and using the profiler I've traced it to compiling many shaders. To combat this, I've put many of the shaders into a ShaderVariantCollection (by loading the asset bundle in the Editor and gathering the shaders/variants using the tool).

    Now that we're doing that, I see when the application starts that we are compiling those shaders (not sure why there are logged twice):

    Compiled shader: HDRP/Lit, pass: ShadowCaster, stage: all, keywords <no keywords>
    Compiled shader: HDRP/Lit, pass: ShadowCaster, stage: all, keywords <no keywords>
    Compiled shader: HDRP/Lit, pass: ShadowCaster, stage: all, keywords _DOUBLESIDED_ON _NORMALMAP_TANGENT_SPACE
    Compiled shader: HDRP/Lit, pass: ShadowCaster, stage: all, keywords _DOUBLESIDED_ON _NORMALMAP_TANGENT_SPACE
    Compiled shader: HDRP/Lit, pass: ShadowCaster, stage: all, keywords INSTANCING_ON _ALPHATEST_ON _DOUBLESIDED_ON _NORMALMAP_TANGENT_SPACE
    Compiled shader: HDRP/Lit, pass: ShadowCaster, stage: all, keywords INSTANCING_ON _ALPHATEST_ON _DOUBLESIDED_ON _NORMALMAP_TANGENT_SPACE
    etc.

    However, when the asset bundle is loaded, it still has to compile those exact same shaders:
    Compiled shader: HDRP/Lit, pass: ShadowCaster, stage: all, keywords <no keywords>
    Compiled shader: HDRP/Lit, pass: ShadowCaster, stage: all, keywords <no keywords>
    etc.

    Is there something I'm doing wrong? This is critical for our application's performance.

    Unity 2019.4.2f1
    HDRP 7.2.1
     
    Last edited: Sep 11, 2020
  2. Ryanc_unity

    Ryanc_unity

    Unity Technologies

    Joined:
    Jul 22, 2015
    Posts:
    332
    ShaderVariantCollection is used to generate the list of minimum features to compile, but can still compile many more features than necessary. The HDRP implements the shader compiler callback: https://docs.unity3d.com/ScriptReference/Build.IPreprocessShaders.html and handles stripping out some variants there. You may want to implement your own version of this callback to strip out variants you know are not being used to reduce the loading stall.
     
  3. mmkc

    mmkc

    Joined:
    Sep 12, 2019
    Posts:
    49
    @Ryanc_unity Thank you very much for your reply. I have found the HDRP implementation for the callback. I'm not totally following how your solution will solve our problem.I get that ShaderVariantCollection could compile additional variants that aren't needed, and I'm fine with putting the lag on initial launch of application. What I don't understand is why it appears my application is compiling the same shader combinations multiple times (e.g. HDRP/Lit, pass: ShadowCaster, stage: all, keywords <no keywords>). Perhaps your reply is the route I need to fix this, I'm just not sure what you mean. Thanks!
     
  4. joeyipanimation

    joeyipanimation

    Joined:
    Apr 19, 2013
    Posts:
    1
    @Ryanc_unity Similar issue, and I've read a lot of your shader-in-bundles responses – they've been super helpful! (Thanks!)

    I've added my custom shaders to a "common" asset bundle with
    ShaderVariantCollection
    along with it call
    .WarmUp()
    at application start. The prefabs' materials all point to these shared shaders (dependencies verified via the AssetBundle Browser Tool).

    What I correctly see via [Android] logcat is during WarmUp() those shaders are compiled. However, when invoking the prefabs that need those shaders, Unity logs another "Compiled Shader: ..." trace.

    In Unity deep profile and taking a snapshot of memory usage shows multiple entries of the same shader, somewhat like this:
    • 1 Ref count – ShaderName (ref by common-bundle ShaderVariantCollection)
    • 1 Ref count – ShaderName (ref by bundleA prefab)
    • 30 Ref count – ShaderName (ref by bundleB prefabs)
    I would expect a single entry with a total of 32 Ref counts, so this behavior doesn't look like shader cache retrieval. I also read your post here, but didn't have access to
    BundleBuildParameters
    (I'm guess that's a later Unity feature) :(.

    All of these shaders have no keywords nor variants (fairly straightforward
    CGPROGRAM
    ). Note, all shader compiling always come in pairs, I'm sure you get this a lot... but why is that?

    Code (CSharp):
    1. 09-20 13:05:21.817 12262 12278 D Unity   : Compiled shader: ShaderName, pass: <unnamed>, stage: all, keywords <no keywords>
    2. 09-20 13:05:21.819 12262 12278 D Unity   : Compiled shader: ShaderName, pass: <unnamed>, stage: all, keywords <no keywords>
    Any pointers would be incredibly helpful! Thanks!

    Using Unity 2019.2.13f1 (macOS Mojave)
    Deploying to Android with Mali GPU
     
  5. mmkc

    mmkc

    Joined:
    Sep 12, 2019
    Posts:
    49
    @Ryanc_unity hello Ryan. Have you been able to look at this at all? If there are other avenues I can use for additional 1-on-1 help on this specific issue please let me know. Thanks!
     
  6. Ryanc_unity

    Ryanc_unity

    Unity Technologies

    Joined:
    Jul 22, 2015
    Posts:
    332
    Make sure to assign BOTH the ShaderVariantCollection & Shaders to the common bundle. If you assign just the SVC by itself, it will pull in the shaders as a dependency, but other bundles will not know they are pulled in and can't reference those shaders, thus those bundles will also pull in their own copy of the shader basically undoing your SVC work. I think this also applies to @kevin-wilson 's issue.
     
  7. mmkc

    mmkc

    Joined:
    Sep 12, 2019
    Posts:
    49
    @Ryanc_unity Thanks Ryan. So I need to go the "common bundle" route that contains the shaders and shader variant collection? Do I need to copy the shaders out of the HDRP library and ensure those copies get assigned to the materials instead of the shaders in the library? Are there instructions for doing all of this anywhere?

    We already have many asset bundles built and uploaded and it would be a large effort to redo all of them. Is it possible that I can reset the shaders after loading the asset bundle to the already compiled shader?
     
  8. Ryanc_unity

    Ryanc_unity

    Unity Technologies

    Joined:
    Jul 22, 2015
    Posts:
    332
    I'm not all up to date on the HDRP package, but if memory serves me correctly, all their shaders should be contained within the package folder for HDRP. Little annoying as package assets don't have the asset bundle assignment drop down. Now, just because they don't have the drop down, doesn't mean they can't be assigned to bundles, you just need to do so via script. And since we are talking just shaders here, there are a few ways to do this.

    First approach: So there are 2 build APIs we have with Scriptable Build Pipeline, one of which contains an input for AssetBundleBuilds[] for explicit asset to bundle assignment, and we have a handy native API here: https://docs.unity3d.com/ScriptRefe...BuildInterface.GenerateAssetBundleBuilds.html which asks the Asset Database for all existing asset bundle assignments via their drop downs and returns a prebuilt array for that data. So taking the return from that API we can then further modify it and add more data, such as package assets, before sending it off to SBP to build. So this is where you would create a new AssetBundleBuilds entry for some "common shaders" bundle, and add entries for all the shaders in HDRP.

    Second approach: Since we are dealing with just shaders, we can do this in a more auto-magic way, and there is already an example of this in SBP: CreateBuiltInShadersBundle.cs & UpdateBundleObjectLayout.cs where in CreateBuiltInShadersBundle we walk all the data being pulled into bundles, and check for shaders that are built-in ("Standard" shader for example) and assign that shader object to a new "BuiltInShaders" bundle using the BundleExplictObjectLayout struct. From there UpdateBundleObjectLayout task takes over and moves those object to the right bundle and fixes up references before final build. So using CreateBuiltInShadersBundle as an example, it would be possible to make a copy of it to just look for and assign all shaders instead of just built in shaders to a "CommonShaders" bundle instead.

    Let me know if this is enough info and links to possible code or examples. If not, I'll find some time to write up actual code examples of this later this week.

    Sadly no, part of the loading and integration process triggers that shader compiling if that asset points to a different shader than one that is already loaded. So the key is to make sure that assets point to the same set of shared shaders in bundles.
     
    Last edited: Sep 28, 2020
  9. mmkc

    mmkc

    Joined:
    Sep 12, 2019
    Posts:
    49
    @Ryanc_unity Thank you very much for all of this information.

    I think the first approach will be what we have to do since we don't build all of our asset bundles at once. We can just start by maintaining a list of shaders that we're using and ensure those get included. We'll continue to log shader compilation so if we see shaders getting compiled we know we need to add them to the list and rebuild. I will begin working on this and reach out if I run into issues.

    Regarding our existing bundles, is it theoretically possible to edit the bundle files (they are uncompressed) to point to the shader in the new common bundle? Or would that be a nightmare?
     
  10. Ryanc_unity

    Ryanc_unity

    Unity Technologies

    Joined:
    Jul 22, 2015
    Posts:
    332
    Theoretically possible? yes.
    Would I attempt it? no, potentially too many areas in the raw bytes would need to be touched to change a reference.
     
  11. mmkc

    mmkc

    Joined:
    Sep 12, 2019
    Posts:
    49
  12. Ryanc_unity

    Ryanc_unity

    Unity Technologies

    Joined:
    Jul 22, 2015
    Posts:
    332
    Hmm, that's a new one that hasn't reached me yet. This might be talking about the older native BuildPipeline.BuildAssetBundles api and not the Scriptable Build Pipeline api.
     
  13. mmkc

    mmkc

    Joined:
    Sep 12, 2019
    Posts:
    49
    I think you are absolutely right. Turns out I'm using the older pipeline as well. I'll look into switching to the new one and try it there. Thanks!
     
  14. mmkc

    mmkc

    Joined:
    Sep 12, 2019
    Posts:
    49
    I was able to get this working for the most part (thanks @Ryanc_unity )

    1. Put all of the shaders and the ShaderVariantCollection into an asset bundle
    2. When building asset bundles, inject the common shader asset bundle dependency.
    3. When the game starts, load the common shaders asset bundle and warm up the ShaderVariantCollection

    I still see lots of "Compiled Shader" entries in the logs after loading asset bundles that use the common shaders bundle but the performance is much better. I assume this is expected?

    Also, I'm only able to load the common shaders asset bundle by downloading it. If I include the bundle in the Assets folder and try to load it with AssetBundle.LoadFromFile, I get "Unable to open archive file", although it works just fine when playing from the editor. I looked up the error a bit and it suggested using the StreamingAssets folder. I tried that, but then the performance issues came back... I assume because the StreamingAssets folder is some special folder?

    I can go with the downloading route but I'd prefer to include the bundle in the build. Any ideas how to fix that?