Search Unity

Question ComputeShader output not rendered until texture copied?

Discussion in 'Shaders' started by NNSkelly, Sep 23, 2020.

  1. NNSkelly

    NNSkelly

    Joined:
    Nov 16, 2015
    Posts:
    35
    Top-line question: What does Graphics.CopyTexture change, under the hood, in the source texture and how it's flagged for rendering use? I have a ComputeShaded RenderTexture that does not update in the Unity scene until and unless I run it through Graphics.CopyTexture at least once.

    Backstory:
    I'm using a compute shader to crunch a large volume of numeric data into a visual representation.
    Because I discovered that it's still not possible to flag a RenderTexture asset as random-write enabled (and equally impossible to change enableRandomWrite after creation), I fell back on duping up a new texture at runtime on the basis of a source RT asset configured in the editor:

    Code (CSharp):
    1. RenderTextureDescriptor desc = new RenderTextureDescriptor();
    2. desc.autoGenerateMips = false;
    3. desc.bindMS = false;
    4. desc.colorFormat = silhouetteTexMirror != null ? silhouetteTexMirror.format : RenderTextureFormat.ARGB32;
    5. desc.depthBufferBits = 0;
    6. desc.dimension = UnityEngine.Rendering.TextureDimension.Tex2D;
    7. desc.enableRandomWrite = true; // this is the ONLY reason we need to go through all this bother. Can't create a RT asset with it pre-set.
    8. //desc.flags = RenderTextureCreationFlags.bleh; // why is this even when all the flags are represented by other descriptor params??
    9. desc.graphicsFormat = silhouetteTexMirror != null ? silhouetteTexMirror.graphicsFormat : UnityEngine.Experimental.Rendering.GraphicsFormat.R8G8B8A8_UNorm;
    10. desc.height = silhouetteTexMirror != null ? silhouetteTexMirror.height : 4096;
    11. desc.memoryless = RenderTextureMemoryless.None;
    12. desc.mipCount = 0;
    13. desc.msaaSamples = 1; // must be 1, 2, 4 or 8
    14. desc.shadowSamplingMode = UnityEngine.Rendering.ShadowSamplingMode.None;
    15. desc.sRGB = false; // documentation ENTIRELY unhelpful.
    16. desc.stencilFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.None;
    17. desc.useDynamicScale = false;
    18. desc.useMipMap = false;
    19. desc.volumeDepth = 1; // "must be greater than 0" even for no-3D textures??
    20. desc.vrUsage = VRTextureUsage.None;
    21. desc.width = silhouetteTexMirror != null ? silhouetteTexMirror.width : 4096;
    22. silhouetteTex = new RenderTexture(desc);
    23.  
    24. silhouetteCompute.SetTexture(processKernel, "Result", silhouetteTex);
    25. silhouetteCompute.SetTexture(clearKernel, "Result", silhouetteTex);
    26.  
    But because I wanted to keep eyes on the actual texture content even when I didn't have it rendering to anything in the scene yet, from the outset, I did a Graphics.CopyTexture back to the reference RT. The world was good; everything worked.

    One of the next things I added to the scene was a RawImage on which to optionally display the processed output:
    Code (CSharp):
    1. if (shadowImg != null)
    2. {
    3.     shadowImg.texture = silhouetteTex;
    4. }
    Scene ran, RawImage displayed the processed output, life still good.

    I then decided that hey, the copy-back to the reference RT was probably entirely wasted overhead and should probably be removed, or at least made conditional on whether or not it is actually needed:
    Code (CSharp):
    1. silhouetteCompute.Dispatch(clearKernel, silhouetteTex.width / 8, silhouetteTex.height / 8, 1);
    2. silhouetteCompute.Dispatch(processKernel, frame.DepthFrameSource.Width, frame.DepthFrameSource.Height, 1);
    3.                    
    4. // something about doing the CopyTexture causes the in-use render texture to update
    5. // when it doesn't by default...
    6. // and after writeback has happened once, render picks up on all updates??
    7. if (silhouetteTexMirror != null && writeBackMirror) Graphics.CopyTexture(silhouetteTex, silhouetteTexMirror);
    8. else if (silhouetteTex.updateCount == 0)
    9. {
    10.     RenderTexture dummy = new RenderTexture(silhouetteTex);
    11.     Graphics.CopyTexture(silhouetteTex, dummy);
    12. }
    13. // so... update count is 0 when no action taken
    14. // is 1 after the first CopyTexture
    15. // and then doesn't increase further.
    16. // No, IncreaseUpdateCount() does not cause the texture update to be acknowledged.
    17. Debug.Log(silhouetteTex.updateCount);
    The results are... as commented. First, I discovered that when I stopped calling CopyTexture, the RawImage ceased to update.
    Then I discovered that I could turn on the CopyTexture for exactly one frame and the RawImage would update for the remainder of the run.
    Then I tested and found that the copy target was immaterial; I could literally copy the ComputeShader written texture into any random thowaway texture, exactly once, and it would render correctly in the scene for the remainder of the run.
    The only remote clue I've observed so far is that the first call to CopyTexture (and not any subsequent call) increases the source texture's updateCount, so CopyTexture is presumably doing something else fiddly under the hood.
    Simply increasing the updateCount manually is not enough to get the render pipeline to acknowledge the changes. Neither is e.g. MarkRestoreExpected. And RenderTextures do not have an Apply() function to explicitly force a refresh (and rightly so, in this case, since the changes are purely GPU side).
    Graphics Fences sound like maybe the right direction- be sure that the Dispatch calls finish before letting the CPU & render code proceed?- but since the GraphicsFence documentation all seems to assume prior familiarity with the GraphicsFence mechanisms, I'm hesitant to go blindly plumbing that angle without some sample code.

    So what am I missing here? "Issue is resolved by copying the texture once" seems like a really ugly hacky fix that relies upon a side-effect of the copy rather than properly identifying and remedying the root cause.
     
  2. Santa

    Santa

    Joined:
    Dec 4, 2009
    Posts:
    42
    Have the same issue. My Unity is not up-to-date though. It is 2020.1.11.

    RenderTexture require some kind of "awake" after update inside ComputeShader. I've found that click on it in Editor also works.
    Thank you for CopyTexture variant.

    Is it fixed in newly Unity versions? (I need older one for moment).
     
  3. poka_

    poka_

    Joined:
    Feb 12, 2017
    Posts:
    1
    Here's the solution for those that will stumble on this thread:
    The render texture must be initialized once using the RenderTexture.Create() method.

    I don't understand why Unity allows to actually use the render texture if Create() isn't called. There's no warning message and it's very misleading.