Search Unity

Callback when (compute) shader is reloaded

Discussion in 'Shaders' started by DrummerB, Aug 11, 2019.

  1. DrummerB

    DrummerB

    Joined:
    Dec 19, 2013
    Posts:
    41
    When you change the source of a shader during Play mode, Unity recompiles and hot swaps the shader. Unfortunately, it looks like compute shader properties are not restored after the hot swap. The textures that I set on my compute shader become null after the reload.

    Is there a way to be notified or detect a shader hot swap, so that I can reassign the shader properties? It seems like this is different from a full C# assembly hot swap. If you only change the shader code, the C# scripts are not reloaded and therefore, the usual solutions don't seem to work.

    Here's what I've tried so far:
     
  2. grizzly

    grizzly

    Joined:
    Dec 5, 2012
    Posts:
    262
    It's the editor losing focus when you switch to your IDE. See here.
     
  3. DrummerB

    DrummerB

    Joined:
    Dec 19, 2013
    Posts:
    41
    It only happens if I edit the shader source. Just alt-tabbing back and forth is no problem. OnApplicationFocus doesn't seem to be called in edit mode.
     
  4. grizzly

    grizzly

    Joined:
    Dec 5, 2012
    Posts:
    262
    Alt-tabbing or defocusing in any way will cause reference loss up until 2019.1.5f1 on Win in my experience. What version of Unity are you running?
    But it works in Play mode...
    Have you tried the solution at the bottom of that thread?
     
  5. DrummerB

    DrummerB

    Joined:
    Dec 19, 2013
    Posts:
    41
    Does that mean that on 2019.1.5 and newer, the references are restored? I'm on 2019.1.0, soon updating to 2019.2.0.

    I'm trying to set up a workflow that allows me to quickly iterate on a compute shader. You can do that with regular fragment shaders. You just assign it to a Material, put it on a Mesh and it updates in the Scene view any time you change the shader.

    I tried it, but couldn't get it to work reliably. I think the focus callback would anyway be too early, since the compute shader wouldn't have finished recompiling at that point.

    I now implemented a (not so elegant) workaround with an AssetPostprocessor that would allow me to reset the references after the shader has been reimported.

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. #if UNITY_EDITOR
    6.  
    7. class ComputeShaderPostprocessor : AssetPostprocessor
    8. {
    9.     public delegate void ComputeShaderEventHandler(ComputeShader shader);
    10.  
    11.     static Dictionary<ComputeShader, ComputeShaderEventHandler> Handlers =
    12.         new Dictionary<ComputeShader, ComputeShaderEventHandler>();
    13.  
    14.     public static void AddImportHandler(ComputeShader shader, ComputeShaderEventHandler handler)
    15.     {
    16.         Handlers[shader] = handler;
    17.     }
    18.  
    19.     public static void RemoveImportHandler(ComputeShader shader)
    20.     {
    21.         Handlers.Remove(shader);
    22.     }
    23.  
    24.     static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
    25.     {
    26.         foreach (string str in importedAssets)
    27.         {
    28.             if (str.EndsWith(".compute"))
    29.             {
    30.                 var shader = (ComputeShader)AssetDatabase.LoadAssetAtPath(str, typeof(ComputeShader));
    31.                 if (shader != null && Handlers.TryGetValue(shader, out var handler))
    32.                 {
    33.                     handler.Invoke(shader);
    34.                 }
    35.             }
    36.         }
    37.     }
    38. }
    39.  
    40. #endif
    Code (CSharp):
    1. void OnEnable()
    2. {
    3.     #if UNITY_EDITOR
    4.     ComputeShaderPostprocessor.AddImportHandler(myComputeShader, OnComputeShaderImported);
    5.     #endif
    6. }
    7.  
    8. void OnComputeShaderImported(ComputeShader shader)
    9. {
    10.     InitializeShader();
    11.     UpdateShaderData();
    12. }
     
  6. DrummerB

    DrummerB

    Joined:
    Dec 19, 2013
    Posts:
    41
    I just tried on 2019.2.0f1 and recompiling the shader still loses all texture references.
     
  7. grizzly

    grizzly

    Joined:
    Dec 5, 2012
    Posts:
    262
    It means that I've experienced this problem with all release versions upto and including 2019.1.5. Newer versions (which I haven't tested) may of behaved differently, but given what you've tried, that's evidently not the case :(
    It's related to resources living on the GPU, like RenderTexture or ComputeBuffer. Mesh is backed by a CPU copy and its reference appears to survive the context switch.
    What reliability issues were you seeing?
    I should clarify that the problem occurs on focus, not de-focus as described above. Recompilation happens here before the OnApplicationFocus event is called, so I've been able to restore references as per that solution.
     
  8. DrummerB

    DrummerB

    Joined:
    Dec 19, 2013
    Posts:
    41
    It's not being called in Edit mode at all. In Play mode it gets called sometimes but not always. I couldn't determine why it wasn't called sometimes.

    Doesn't a RenderTexture reference on a Material survive the reload? Or is that just because the Material serializes the reference?
     
  9. grizzly

    grizzly

    Joined:
    Dec 5, 2012
    Posts:
    262
    That's unusual - perhaps report a bug?
    Texture2D's backed by a CPU copy survive but RenderTexture references are lost here.