Search Unity

  1. The 2022.1 beta is now available for testing. To find out what's new, have a look at our 2022.1 beta blog post.
    Dismiss Notice

CommandBuffer.Blit() with no custom shader != CommandBuffer.Blit() with Internal_BlitCopy.shader

Discussion in 'General Graphics' started by AaronBrownLM, Sep 23, 2016.

  1. AaronBrownLM


    Mar 6, 2014
    For those of you who have been following my posts as I've been trying (for 40+ hours) to get CommandBuffer.Blit() to work right, I believe I may have found one of the reasons why it took me so long. I think there is a bug in CommandBuffer.Blit().

    The CommandBuffer.Blit() function, when you specify a custom shader that does NOTHING except COPY the _MainTex (I tried using Internal_BlitCopy.shader, for example), fails to produce the same image that is produced by a call to CommandBuffer.Blit() where no custom shader is specified at all.

    For example, these two lines of code do not produce the same result:

    Code (CSharp):
    1. // Produces an image saved to texID:
    2. cmdBuffer.Blit(BuiltinRenderTextureType.CameraTarget, texID); //, Internal_BlitCopy);
    Code (CSharp):
    1. // Solid grey color is saved to texID:
    2. cmdBuffer.Blit(BuiltinRenderTextureType.CameraTarget, texID, Internal_BlitCopy);
    The shader for Internal_BlitCopy being used here, is an exact copy & paste of the shader found at builtin_shader-5.4.1f1\DefaultResourcesExtra\Internal-BlitCopy.shader

    This shader should, as indicated by its name, do nothing except copy.

    Here is that shader's code:

    Code (CSharp):
    1. Shader "Internal_BlitCopy" {  //Shader "Hidden/BlitCopy" {
    2.     Properties{ _MainTex("Texture", any) = "" {} }
    3.     SubShader {
    4.         Pass {
    5.             ZTest Always Cull Off ZWrite Off
    7.             CGPROGRAM
    8.             #pragma vertex vert
    9.             #pragma fragment frag
    10.             #pragma target 2.0
    12.             #include "UnityCG.cginc"
    14.             sampler2D _MainTex;
    15.             uniform float4 _MainTex_ST;
    17.             struct appdata_t {
    18.                 float4 vertex : POSITION;
    19.                 float2 texcoord : TEXCOORD0;
    20.             };
    22.             struct v2f {
    23.                 float4 vertex : SV_POSITION;
    24.                 float2 texcoord : TEXCOORD0;
    25.             };
    27.             v2f vert(appdata_t v)
    28.             {
    29.                 v2f o;
    30.                 o.vertex = UnityObjectToClipPos(v.vertex);
    31.                 o.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
    32.                 return o;
    33.             }
    35.             fixed4 frag(v2f i) : SV_Target
    36.             {
    37.                 return tex2D(_MainTex, i.texcoord);
    38.             }
    39.             ENDCG
    41.         }
    42.     }
    43.     Fallback Off
    44. }
    As you can see, it does nothing except copy _MainTex, as it should, seeing how it's the shader that Unity uses internally when doing a Blit().

    And yet, if you specify this or any other shader for copying the pixels, it results in a completely grey color with no picture. The copy fails to work right at all.

    But if you call CommandBuffer.Blit() and you do not pass in any custom shader, it copies the image just fine (except for the recent bug where it flips the picture up-side-down, but that's an unrelated issue).

    For anyone interested, I'm attaching two example scenes to demonstrate this issue.

    In the two attached scenes, it demonstrates the call to CommandBuffer.Blit() with and without passing in the Internal_BlitCopy.shader. There is only *one* line of code that's different between the two attached scenes, and that's the call to CommandBuffer.Blit(), one with the Internal_BlitCopy.shader, and one with no shader.

    To reproduce this using the example I'm attaching:

    Open the scene named "Blit_WILL_blit.unity" and click play, and observe that an image appears.

    Open the scene named "Blit_wont_blit.unity" and click play, and observe a grey screen appears.

    Open the source for both "Blit_WILL_blit.cs" and "Blit_wont_blit.cs" and observe the only difference is one line of code, the call to CommandBuffer.Blit(), wherein one uses the custom shader, and one doesn't.

    Open the custom shader, which is included in the attached package at Assets\Internal_BlitCopy.shader, and observe that it is an exact copy of Unity's builtin "Internal_BlitCopy.shader", and it does nothing except a simple copy of _MainTex, and it seems to me that there is no reason it should fail to produce the same results as would be expected if you didn't specify any custom shader at all.

    Attached Files:

    Euri and daneobyrd like this.
  2. AaronBrownLM


    Mar 6, 2014
    So far, in my quest to fix this issue, I tried these things:

    Fixing CommandBuffer.Blit():
    1) Read up on exactly how it's supposed to transfer the image data to the shader.
    Answer: "Source texture or render target will be passed to the material as "_MainTex" property."

    2) Using a BuiltinRenderTextureType with a custom shader doesn't fail, so see if I can get my texID to behave like a BuiltinRenderTextureType.
    Result: Tried, but couldn't. BuildinRenderTextureType is simply an enum, can't find or access their properties.

    3) Try to figure out how CommandBuffer.Blit() works when you do NOT include a custom shader, and then try to get my custom shader to do the same thing.
    Result: Tried, but couldn't. CommandBuffer.Blit() is hidden away in UnityEngine.Internal, which can't be accessed.

    So I'm still pretty stumped on this. :(
  3. AaronBrownLM


    Mar 6, 2014
    Welp I got it working, but I don't like my solution ;p

    My solution requires me to perform additional Blit()s to make a big circle around the bug.

    Here's how:

    Code (CSharp):
    1. cmdBuffer.Blit(BuiltinRenderTextureType.CameraTarget, texID);
    2. cmdBuffer.SetGlobalTexture("_OlderTexture", texID);
    3. cmdBuffer.Blit(BuiltinRenderTextureType.None, texID2, Modified_BlitCopy);
    4. cmdBuffer.SetGlobalTexture("_OlderTexture2", texID2);

    Let me explain what that does.

    1) It copies the camera picture to texID, with no custom shader.
    2) It sets texID as a global variable, accessible by any shader as "_OldTexture"
    3) It copies from "none" to texID2, this time WITH the custom shader.
    4) Modified_BlitCopy has been modified to read from _OldTexture instead of _MainTex.
    5) Finally, it sets TexID2 as a global texture, so I can use it later in the rendering pipeline.
    6) [Later in my rendering pipeline] I run a post-processing effect on it, turning it green color.

    It's basically making a wide circle around the fact that CommandBuffer.Blit() does not correctly pass anything to the shader's _MainTex property. The only way then to get it passed to the shader is to first do a Blit with no shader, then set that result as a global texture, then blit() again but this time with a shader, but tell the shader NOT to read from _MainTex (since that's bugged and empty), and instead to read from the global texture we set in step #2.

    So it requires an extra Blit(), and an extra temporary texture (texID2), but for now this is maybe the only workaround.

    Note: it still flips the picture up-side-down. That's an unrelated, but equally buggy bug.

    In case it helps anyone, I've attached a scene showing this workaround in action.

    Attached Files:

    Last edited: Sep 23, 2016
  4. AaronBrownLM


    Mar 6, 2014
    I guess I just won't let this go lol.

    I got it working better!

    The left side is the new version, and the right side is the old version from my previous post (above).

    In the new version, I got the picture right-side-up, and I was able to reduce the number of Blit() calls from 3 to 2, which appears to have removed a "Grab RenderTexture" according to the frame debugger.

    So how'd I do it?

    Well.... it's now more complicated.

    1) I'm now using two cameras instead of one.
    2) The main camera is now set to render to a RenderTexture instead of the screen.
    3) The second camera is rendering nothing at all (its culling mask is set to "nothing"). The sole purpose of this second camera is to give me a way to actually put a picture on the screen, since I can't figure out a way (using only CommandBuffers) to tell the main camera to stop rendering to the RenderTexture, and start rendering to the screen.
    4) It successfully Blits from the main camera's RenderTexture (set it step #2 above), WITH the custom shader! Yay. And as a bonus, the picture isn't flipped up-side-down for who-knows-why reason.
    5) Then it Blits directly to the second camera (the one that's not rendering anything), which the second camera then puts it directly onto the screen.

    I tried, really hard, to get the main camera to blit to the screen but I can't figure out a way to do it. Since it is set to render to a RenderTexture, instead of rendering to the screen, I can't find a way with any of the available CommandBuffer commands to tell it to stop rendering to the RenderTexture, and blit something directly to the screen. If anyone knows how to do that, I'd love to know... as that would save me the complexity of having to use a second camera just to gain a means of putting a picture on the screen.

    Here's the latest version in case anyone is interested:

    Attached Files:

  5. AaronBrownLM


    Mar 6, 2014
    Sigh. :(

    ... now the stencil buffer can't be accessed again. :(:(:(:(:(:(:(

    The fact that I'm rendering the main camera to a render texture, seems to make the stencil inaccessible by the shaders that are being fired by CommandBuffer.Blit(). I'm recalling how I just spent 40+ hours getting the stencil to work. Now I just spent half that long getting CommandBuffer.Blit() to wokr right. Now, the stencils are broken again. Ugh this is unbelievably pitiful.

    One problem is that I fundamentally don't understand where the heck the "stencil" buffer even is. If you render the camera to the "screen"... (what the heck is the "screen" anyway?)... the shaders seem to access the stencil because it is on the "screen", not because it is in the _MainTex of the shader. Am I correct about this?

    So therefore because the camera is rendering to a rendertexture, and not rendering to the screen, then there is no stencil buffer available in the... "screen". So therefore the shaders can't find it, because they don't seem to look for it in _MainTex, they look for it in some mysterious thing we call the "screen". You see how good I understand all this.

    When a shader is run by calling CommandBuffer.Blit(), the shader is passed a texture through _MainTex. Or so, that's what the documentation says anyway. But the shader doesn't seem to test for the stencil to be in _MainTex. Because if it did, then my stencil should work like a charm right now.

    So, I tried to see if it was possible to tell the camera to render only the COLOR to a rendertexture, but keep the DEPTH in the screen, so that my shader would be able to use the stencil buffer (which apparently it tries to read from the screen):

    Code (CSharp):
    1. Camera.main.SetTargetBuffers(MyRenderTex.colorBuffer, Graphics.activeDepthBuffer);
    Which promptly gave me this very hilarious error message:

    That's got to be one of the most satisfying error messages I've ever read.

    Yes, Unity, I'm trying to mix the buffers. NOW LET ME DO IT! lol.

    Anyone know how to access the stencil buffer for a camera that's rendering to a RenderTexture? :(

    Because otherwise all my solutions I posted above for getting the CommandBuffer.Blit()'s to work are useless to me, because yea I need all that to work, but I need stencils to work too.
  6. Simod


    Jan 29, 2014
    Hi Aaron,

    Eventually I got same issue like described in the first post. Not sure if you found solution already, but here is how I did it. Almost no workaround:
    Code (CSharp):
    1. Shader shader = Shader.Find ("Hidden/BlitCopy");
    2. Material material = new Material (shader);
    3. commandBuffer.SetGlobalTexture ("_MainTex", src);
    4. commandBuffer.Blit (src, dest, material, 0);
    I posted this also at:
    where I am trying to reproduce the same Blit but with quad as exampled by Tim-C in your other post:
    Last edited: Dec 19, 2016
    steven3Dim and SAM-tak like this.