Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice
  3. Dismiss Notice

Shader stops rendering when assets are updated

Discussion in 'General Graphics' started by I208iN, Feb 16, 2022.

  1. I208iN

    I208iN

    Joined:
    Mar 25, 2017
    Posts:
    3
    So I've been having this problem for the last few days and have been unable to find anything in the forums or elsewhere.

    Everything works fine until, at the slightest change in assets, my shader stops working entirely. There isn't even any wireframe showing up, so it's not just a texture problem. I assume that means my shader breaks when it's reloaded by the editor but I have no idea how. And if it is, I don't know whether I did something wrong and where.

    I have a custom instanced shader where I use SV_InstanceID and a structured buffer of a custom struct to retrieve my instances' data:
    Code (CSharp):
    1. StructuredBuffer<Properties> _Properties;
    2.  
    3. Properties InstanceProperties(uint instanceID)
    4. {
    5.     return _Properties[instanceID];
    6. }
    7.  
    8. Interpolator vert (MeshVertex mesh, uint instanceID: SV_InstanceID)
    9. {
    10.     Interpolator interpolator;
    11.    
    12.     // my custom instance struct
    13.     Properties properties = InstanceProperties(instanceID);
    14.    
    15.     // the transform matrix is computed in csharp
    16.     float4 pos = mul(properties.mat, mesh.position);
    17.     interpolator.position = UnityObjectToClipPos(pos); // : SV_Position
    18.  
    19.     // ... setting the interpolator up
    20.     return interpolator;
    21. }

    On the C# side, my ComputeBuffer is created once, and assigned to my material at the same time with the following code:
    Code (CSharp):
    1. propertiesBuffer = new ComputeBuffer(/* args */);
    2. material.SetBuffer("_Properties", propertiesBuffer);

    Then whenever my custom render pass needs to render, I update my buffer with ComputeBuffer.BeginWrite and ComputeBuffer.EndWrite. And render using CommandBuffer.DrawMeshInstancedIndirect. (Note: I had the same problem when I was using Graphics.DrawMeshInstancedIndirect in an Update so I don't think the render pass is the issue here).
    Code (CSharp):
    1. public void Draw(CommandBuffer cmd)
    2. {
    3.     cmd.DrawMeshInstancedIndirect(mesh, 0, material, -1, argsBuffer);
    4. }

    The material is created at runtime from the shader:
    Code (CSharp):
    1. material = new Material(shader);
    2. material.enableInstancing = true;

    And the shader is retrieved using addressables:
    Code (CSharp):
    1. Addressables.LoadAssetAsync<Shader>("Sprite shader").Completed += handle =>
    2. {
    3.     if (handle.Status == AsyncOperationStatus.Succeeded)
    4.     {
    5.         shader = handle.Result;
    6.     }
    7.     else
    8.     {
    9.         Debug.LogError("Failed to load sprite shader.");
    10.     }
    11. };

    I don't know what step is broken by the asset hot reload, does anyone have a clue ?
     
  2. I208iN

    I208iN

    Joined:
    Mar 25, 2017
    Posts:
    3
    I've managed to find a solution to my problem!

    It turns out compute buffers are broken by an AssetDatabse refresh, but their IsValid() method does not reflect that.

    What I ended up doing is creating an AssetPostprocessor so that I could implement the OnPostprocessAllAssets callback (this script is in an Editor folder).
    Code (CSharp):
    1. class ComputeBufferResetter : AssetPostprocessor
    2. {
    3.     static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
    4.     {
    5.         // GameObject static method
    6.         RenderManager.ResetComputeBuffers();
    7.     }
    8. }

    I then wait one frame before releasing and creating my compute buffers back. If I do it on the spot, it has no effect.
    Code (CSharp):
    1. public static void ResetComputeBuffers()
    2. {
    3.     instance?.StartCoroutine(instance.ResetComputeBuffersRoutine());
    4. }
    5.  
    6. private IEnumerator ResetComputeBuffersRoutine()
    7. {
    8.     yield return new WaitForEndOfFrame();
    9.     // this call resets the buffer in my code
    10.     InstancedMeshDrawer.ResetComputeBuffers();
    11. }
    The only drawback I've encountered so far is that there IS indeed a missing frame. Unless someone has a solution to this problem, I'll accommodate. I can't sink more days into this editor problem.
     
    c0d3_m0nk3y likes this.
  3. ArminRigo

    ArminRigo

    Joined:
    May 19, 2017
    Posts:
    20
    I'm seeing the same thing. Thanks for writing a follow-up! A few precisions from what I'm observing on my side: the ComputeBuffers seem to survive fine, but it seems only to be the `material.SetBuffer("_Properties", propertiesBuffer);` link that gets lost. I can fix it by calling `SetBuffer()` again (in the next frame, as you suggest).
     
  4. ArminRigo

    ArminRigo

    Joined:
    May 19, 2017
    Posts:
    20
    More precisely, it seems fine if I call `SetBuffer` again in the next `OnPreCull` event, which also avoids the missing frame problem. Doesn't it also work if you replace `yield return new WaitForEndOfFrame();` with `yield return null;` in your case? I would guess that the problem is that it must be done "later", but it doesn't necessarily have to be after the next frame was rendered.
     
  5. NightElfik

    NightElfik

    Joined:
    Oct 27, 2014
    Posts:
    28
    We have the same problem! Due to this, all instanced geometry disappears after any material, shader, or script is changed in play mode. We have hundreds of materials with buffers and it seems that the only solution is to call `SetBuffer` on each of them after asset refresh.

    If anyone from Unity sees this, can you please fix it? It would be highly appreciated.
     
  6. c0d3_m0nk3y

    c0d3_m0nk3y

    Joined:
    Oct 21, 2021
    Posts:
    702
    My understanding is that Unity has to serialize and deserialize temporary materials when reloading assets. All shader uniforms that don't have a corresponding Shaderlab property get lost. So one way to fix that would be to add a shaderlab property for the uniform. Unfortunately, there is no shaderlab property for buffers, as far as I know. That only leaves you with setting the buffer again after refreshing (or setting it every frame).