Unable to Graphics.Blit() read and write to rendertexture in 2017.4; worked in 5.6

I have a multipass shadow rendering effect that broke transitioning from 5.6 to 2017.4 that worked like this:

  • Render a quad with a nebula graphic to RT_background rendertexture

  • Render the objects into RT_final_shadows rendertextures and process to make shadows

  • Combine the two, drawing RT_final_shadows over RT_background

  • Render the game on top of this background quad

In 2017.4, the background was rendered black.

I’ve finally identified the problem as an issue with reading+writing to the same rendertexture. Having rendered the background and shadows successfully, the composition code that fails is this:

        // Composition shader pass _0_BLEND_SHADOWS
            sampler2D _MainTex;
            sampler2D _BackgroundTex;
            sampler2D _FinalShadowTex;
            float _ShadowIntensity;

            fixed4 frag (v2f i) : SV_Target{
                fixed4 output = tex2D(_MainTex, i.uv);
                fixed4 secondary = tex2D(_FinalShadowTex, i.uv);
                output *= 1-(secondary.a*_ShadowIntensity);
                return output;
            }

      // Main code
      Graphics.Blit(RT_final_shadows, RT_background, compositionMaterial, (int)COMPOSITION_PASS._0_BLEND_SHADOWS);

The background in this case is drawn black in 2017, whether I sample _MainTex (background passed in as source) or _BackgroundTex.

However, if I output to another RT and then copy that to the background, it works…

        // Composition shader pass _0_BLEND_SHADOWS
            sampler2D _BackgroundTex;
            sampler2D _FinalShadowTex;
            float _ShadowIntensity;

            fixed4 frag (v2f i) : SV_Target{
                fixed4 output = tex2D(_BackgroundTex, i.uv);
                fixed4 secondary = tex2D(_FinalShadowTex, i.uv);
                output *= 1-(secondary.a*_ShadowIntensity);
                return output;
            }

      // Main code
      Graphics.Blit(RT_final_shadows, RT_shadowII, compositionMaterial, (int)COMPOSITION_PASS._0_BLEND_SHADOWS);
      Graphics.Blit(RT_shadowII, RT_background);

So where in Unity 5.6 I could pass my rendertexture into a Graphics.Blit and draw onto it directly, in 2017 I need an interim texture. This obviously adds a little overhead which is the last thing one wants in a mobile game.

Does anyone know any workaround for this, to be able to read and write to the same rendertexture in a Graphics.Blit?

It loks to me like a fixed bug.
Reading from and writing to the same texture in a single shader requires explicit synchronization, which is not provided in many graphics APIs. The result of doing this without synchronization is undefined.

Okay. Thanks. Could we have some clarification on the documentation to that effect? There’s nothing to say you shouldn’t write to a source texture and the result isn’t a compilation error but an unexplained black texture.

Also, is there any optimisations that can be used? I’m suddenly thinking I could swap the rendertexture on the background quad for example when rendering the game, and possibly ping-pong it between to reduce copies and draws a bit.

Double buffering is an option and quite a common one, yes. If you want a nice api for handling it you could do worse than investigate https://docs.unity3d.com/Manual/CustomRenderTextures.html

As for the manual point it’s a good one, as Unity has users from all walks of life and thus what is common knowledge for engine programmers is not for Unity’s typical users.

Thanks!

Yes. Thanks for the suggestion, I’ll update the documentation :slight_smile:

2 Likes

It is actually mentioned under the “Double Buffered Custom Textures” paragraph in https://docs.unity3d.com/2017.4/Documentation/Manual/CustomRenderTextures.html

Ironically with that double-buffering, it incurs a texture copy penalty. Better off manually using two RTs and swapping before them in code.

Indeed. But it explains, why and when double buffering is needed, so I think that there’s no need to update the docs :slight_smile:

The docs should still be updated. You don’t need to use custom render-ttextures to use blitting, so likely won’t ever encounter this page. The information about what buffers can be used when and where should be with the function that’s using those buffers, no? If we need to know about render-textures when using the Blit function, having it somewhere else is no use. It just needs a note something like…

Note : You cannot write to a render texture provided as a source. The result will be no action in the blit, but no error will be raised. If you need to copy a render-target’s contents in a blit, you need to either use a second render-texture for output, or use a double-buffered custom render-texture. Read more under the “Double Buffered Custom Textures” paragraph in https://docs.unity3d.com/2017.4/Documentation/Manual/CustomRenderTextures.html