Post Process Mobile Performance : Alternatives To Graphics.Blit , OnRenderImage ?

I am creating an outline glow (aura) effect for a mobile game (android) and have noticed that the cost of a graphics.blit is quite high. Even only doing a “blit(source,dest)” and nothing else is slow (-5~-7fps).

I wanted to know if there are any methods / techniques that can reduce the decrease in frames ?
Any alternatives to using graphics.blit ? (Render to a full screen quad ?, Render MainCamera To RT, then do post work, then composite and blit to screen ?)

I can manage to get around 40 FPS, but its not an acceptable frame rate.
If there really is no way to do Post Render work on mobile, I will go a different route.

About my setup:
Testing on a Samsung Galaxy S5 with Android 5.
I have a post effect script attached to the main camera.
Main Camera does NOT render to an RT. It just renders normally.
I am doing my post effect work in “OnRenderImage”.
I am using 2 or 3 temporary render textures at runtime (RenderTexture.GetTemporary)
I have a color RenderTexture and a depth RenderTexture. The size is a quarter of the source RT.
I have some CommandBuffers set up to render the models that I want to create the outline glow effect for.
The models all get rendered to my temp RT, then the RT is blurred, then composited with the source RT.

For mobile you should to render all stuff to RenderTexture and then draw this texture with GUITexture (or other) with other camera, then make your magic in

void OnPreRender() {
Graphics.Blit(renderTexture, out, material);
}

2 Likes

Graphics.Blit is just a convenient call which render a full screen quad.It will not be the big problem.
I don’t use OnRenderImage(…)

void OnRenderImage(RenderTexture src, RenderTexture dest)
{
    Graphics.Blit(src,dest);
}

because if you did not supply a RenderTexture to the camera’s targetTexture, Unity will trigger CPU ReadPixel(get data back from GPU), which will stall the whole GPU until finish. Super slow, don’t do this.

What you can do is:

RenderTexture myRenderTexture;
void OnPreRender()
{
    myRenderTexture = RenderTexture.GetTemporary(width,height,16);
    camera.targetTexture = myRenderTexture;
}
void OnPostRender()
{
    camera.targetTexture = null; //null means framebuffer
    Graphics.Blit(myRenderTexture,null as RenderTexture, postProcessMaterial, postProcessMaterialPassNum);
    RenderTexture.ReleaseTemporary(myRenderTexture);
}

I can build combined bloom imageEffects working 60fps on galaxy S2 & Note2.(Build using Unity5.3.4)

So I guess your S5 can do better.

If you are still not reaching 60fps, try the following:
-using lower resolution RenderTextures
-lower shader precisions
-when sampling a texture in fragment shader, try to use the uv directly from vertex shader, because “dependent texture read” is slower.

19 Likes

could you post full monobehaviour script? or perhaps you have your project somewhere on github? I was trying your method and it works on PC but fails on android. Are there any specific deployment settings that I should consider? I have been struggling with this problem for several days now. please help! thank you

whats your result on android? android fail means fully black/pink screen?

@colin299 Could you please share your code or the project so that we could learn from you.

hi, sorry - i didnt see notification of your post.
The myRenderTexture buffer in the OnPreRender function was empty. In other words the camera image was black. The only thing that happens was that the shader effect in the OnPostRender() function is applied to black pixels. But then i solved it when i removed the OnRender function completely.

My biggest problem is that this solution does not work when used in the stereo mode with vuforia or GVRViewer from google :(.
When using GVRViewer - the image is displayed with no shader applied.

you mean something like i.texcoord?

sorry, I don’t have experience in VR (only mobile phones and tablet), so I can’t help.
I guess postprocess is specially handled in VR.

yes, you need to use the texture coord directly from interpolation from vertex shader.

even i.texcoord.zw will count as dependent texture read.

1 Like

Do you know if this works in deferred rendering path, since it works only in forward path for me. (Onrenderimage works in both)?

Any fix?

Genuis! I used to use direct framebuffer to do the PE though I know it’s less effeciency than using rendertarget, because it’s easier to handle PE swiching on/off on different mobile hardwares. This totally solved that!

@colin299

Sorry for re-opening the thread, but… are you sure? From my experience, the dependent texture read is any modification of texture coordinates in pixel shader.

So tex2D(_Tex, i.texcoord + 0.0h) IS, in fact dependent texture read, while tex2D(_Tex, i.texcoord.xy) is not.

Am I wrong?

1 Like

According to apple a swizzle could be a dependent texture read. Not sure about opengl es 3.0. They mention an example of packing 2 uvs into a float4.

1 Like

Hm…it’s odd.
When I tested that, I simply forced #pragma target 2.0 and performed texture reads with different arguments. Shader model 2.0 has a hard limit: 4 dependent texure reads as maximum.
So if I can perform, let’s say, 8 texture reads and the shader compiles successfully - it means all of them count as regular texture reads, not as dependent. Otherwise you’ll get a compilation error saying: “The maximum of 4 texture indirections is reached” or something like that.
I got no error when I performed a swizzle, while I did get the error if I do type cast (which is bad for fragment shader anyway).

But these tests were a long time ago, and UT have changed shader compiler a few times since then. So I may need to re-check it on the up-to-date version of Unity.

Is there a difference between dependent texture reads vs texture indirections? Maybe they are counted differently.

So far I didn’t manage to find a good clarification of differences between texture indirection and dependent read.
Some people say one thing, other use these two terms as equivalents in their books.
Since even Apple call swizzling a “dependent read” (which definitely has nothing to do with sampling texture A by values from B), I assume “texture indirection” = “dependent read”. Which also makes sense.

But I may be wrong and would appreciate if someone correct me.

There’s been a few different circumstances described by “dependant read” that I’ve come across. My understanding is it’s anything that breaks texture prefetching (going into vertex & pixel shaders) in the graphics card. So if you modify the UVs in the pixel shader, it’s a dependant read. If you modify them in the vertex shader it’s not, unless you use them there rather than just passing to pixel shader. In that case, you break texture prefetching for the vertex shader. Basically it’s doing anything that means the prefetched texture values aren’t correct when you use them, causing another read.

What should i select as targetTexture? I changed in BlurOptimized.cs. Is everything right in my script ? Also, Hidden/Fastblur is choosen as Blur Shader. Is this right ?

 public class BlurOptimized : PostEffectsBase
     {
         RenderTexture myRenderTexture;
         public RenderTexture targetTexture;
         Camera camera;
         private int width, height, postProcessMaterialPassNum;
         private Material postProcessMaterial;
         [Range(0, 2)]
         public int downsample = 1;
         public enum BlurType {
             StandardGauss = 0,
             SgxGauss = 1,
         }
         [Range(0.0f, 10.0f)]
         public float blurSize;
         [Range(1, 4)]
         public int blurIterations = 2;
         public BlurType blurType= BlurType.StandardGauss;
         public Shader blurShader = null;
         private Material blurMaterial = null;

         public override bool CheckResources () {
             CheckSupport (false);
             blurMaterial = CheckShaderAndCreateMaterial (blurShader, blurMaterial);
             if (!isSupported)
                 ReportAutoDisable ();
             return isSupported;
         }
         public void OnDisable () {
             if (blurMaterial)
                 DestroyImmediate (blurMaterial);
         }
         void OnPreRender()
         {
             myRenderTexture = RenderTexture.GetTemporary(width,height,16);
             camera.targetTexture = myRenderTexture;
         }
         void OnPostRender()
         {
             camera.targetTexture = null; //null means framebuffer
             Graphics.Blit(myRenderTexture,null as RenderTexture, postProcessMaterial, postProcessMaterialPassNum);
             RenderTexture.ReleaseTemporary(myRenderTexture);
         }
     }
1 Like

@colin299 what filter are you using for your bloom shader Box Filter, Gaussian
or Kawase