Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Double buffering using Command Buffers

Discussion in 'Image Effects' started by VisionEEG, Oct 9, 2018.

  1. VisionEEG

    VisionEEG

    Joined:
    Aug 29, 2018
    Posts:
    28
    Hi,
    I am trying to achieve double buffering using command buffers (accumulation buffer in this example). I made it using 2 command buffers permuting them on the camera (AddCommandBuffer/RemoveCommandbuffer each frame), but I doubt this is the most efficient solution (it kind of break the profiler and give false results with lower averaged fps). I know it can be done without command buffers with temporary render targets using only Graphics.Blit; we need command buffers for other reasons not included here.

    Is there a way to update an existing command buffer to permute the source/destination instead of permuting the 2 command buffers on the camera?
    What would be the best way to implement double buffering with command buffers?

    EDIT: I removed the extra gbuffer3 copy using blend mode. The best solution yet is to do it with only one command buffer, recreating it every frame. I also tested versions using OnRenderImage setting render target to destination instead of
    BuiltinRenderTextureType.CameraTarget, but it was slightly slower.

    Thanks

    Best solution yet: put the code on the camera:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.Rendering;
    4.  
    5. [ImageEffectAllowedInSceneView]
    6. public class TestCBDoubleBuffering2 : MonoBehaviour
    7. {
    8.     protected CameraEvent m_eventCB = CameraEvent.AfterSkybox;
    9.  
    10.     private Material m_Material;
    11.     private Shader m_Shader;
    12.  
    13.     private Camera m_Camera;
    14.  
    15.     private RenderTexture m_AccumulationBuffer0;
    16.     private RenderTexture m_AccumulationBuffer1;
    17.  
    18.     protected bool m_IsCommandBufferBuilt = false;
    19.     protected CommandBuffer m_CommandBuffer;
    20.     protected bool m_IsFirstFrame = true;
    21.  
    22.     protected GPUProfiler gpuProfiler = new GPUProfiler( "TEST PROFILING CB0" );
    23.  
    24.     public static void BlitMRT(CommandBuffer cb, Material material, int pass)
    25.     {
    26.       cb.DrawMesh(BlitQuad, Matrix4x4.identity, material, 0, pass);
    27.     }
    28.     static Mesh s_BlitQuad;
    29.     public static Mesh BlitQuad
    30.     {
    31.       get
    32.       {
    33.           if (s_BlitQuad != null)
    34.               return s_BlitQuad;
    35.  
    36.           s_BlitQuad = new Mesh { name = "Fullscreen Quad" };
    37.  
    38.           s_BlitQuad = new Mesh();
    39.           s_BlitQuad.vertices = new Vector3[4] { new Vector3(-1, -1, 0), new Vector3(-1, 1, 0), new Vector3(1, 1, 0), new Vector3(1, -1, 0) };
    40.           s_BlitQuad.uv = new Vector2[4] { new Vector2(0, 1), new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1) };
    41.           s_BlitQuad.triangles = new int[6] { 0, 1, 2, 0, 2, 3 };
    42.           s_BlitQuad.UploadMeshData(false);
    43.           return s_BlitQuad;
    44.       }
    45.     }
    46.  
    47.     private void DestroyMaterial(Material mat)
    48.     {
    49.         if (mat)
    50.         {
    51.             DestroyImmediate(mat);
    52.             mat = null;
    53.         }
    54.     }
    55.  
    56.     private Material CreateMaterial(Shader shader)
    57.     {
    58.         if (!shader)
    59.             return null;
    60.         Material m = new Material(shader);
    61.         m.hideFlags = HideFlags.HideAndDontSave;
    62.         return m;
    63.     }
    64.  
    65.     private void CreateMaterials()
    66.     {
    67.         if (m_Shader == null)
    68.             m_Shader = Shader.Find("Hidden/TestDoubleBufferingCB");
    69.      
    70.         if (m_Material == null && m_Shader != null && m_Shader.isSupported)
    71.         {
    72.             m_Material = CreateMaterial(m_Shader);
    73.         }
    74.     }
    75.  
    76.     void OnEnable()
    77.     {
    78.         CreateMaterials();
    79.         m_Camera = GetComponent<Camera>();
    80.     }
    81.  
    82.     void OnDisable()
    83.     {
    84.         DestroyMaterial(m_Material); m_Material = null; m_Shader = null;
    85.         DestroyImmediate(m_AccumulationBuffer0); m_AccumulationBuffer0 = null;
    86.         DestroyImmediate(m_AccumulationBuffer1); m_AccumulationBuffer1 = null;
    87.     }
    88.  
    89.     void OnPreRender()
    90.     {
    91.         //  Clear accumulation buffer at first frame
    92.         if( m_IsFirstFrame || Time.frameCount % 100 == 0 )
    93.         {
    94.             Graphics.Blit( Texture2D.blackTexture, m_AccumulationBuffer0 );
    95.             Graphics.Blit( Texture2D.blackTexture, m_AccumulationBuffer1 );
    96.             m_IsFirstFrame = false;
    97.         }
    98.  
    99.         if( m_IsCommandBufferBuilt == false )
    100.         {
    101.             m_CommandBuffer = new CommandBuffer();
    102.             m_CommandBuffer.name = "Test Double Buffering CB reset each frame";
    103.             m_Camera.AddCommandBuffer( m_eventCB, m_CommandBuffer );
    104.  
    105.             m_AccumulationBuffer0 = new RenderTexture((int)(Screen.width), (int)(Screen.height), 0, RenderTextureFormat.ARGB32);
    106.             m_AccumulationBuffer0.name = "Accumulation0";
    107.  
    108.             m_AccumulationBuffer1 = new RenderTexture((int)(Screen.width), (int)(Screen.height), 0, RenderTextureFormat.ARGB32);
    109.             m_AccumulationBuffer1.name = "Accumulation1";
    110.  
    111.             m_IsCommandBufferBuilt = true;
    112.         } else
    113.         {
    114.             m_CommandBuffer.Clear();
    115.         }
    116.  
    117.         gpuProfiler.Begin( ref m_CommandBuffer );
    118.  
    119.         CreateMaterials();
    120.  
    121.         if( Time.frameCount % 2 == 0 )
    122.         {
    123.           m_Material.SetTexture( "_AccumulationBuffer", m_AccumulationBuffer1 );
    124.           m_Material.SetTexture( "_PrevAccumulationBuffer", m_AccumulationBuffer0 );
    125.         } else
    126.         {
    127.           m_Material.SetTexture( "_AccumulationBuffer", m_AccumulationBuffer0 );
    128.           m_Material.SetTexture( "_PrevAccumulationBuffer", m_AccumulationBuffer1 );
    129.         }
    130.  
    131.         //  Pass 0: accumulation
    132.         if( Time.frameCount % 2 == 0 )
    133.             m_CommandBuffer.SetRenderTarget( m_AccumulationBuffer1 );
    134.         else
    135.             m_CommandBuffer.SetRenderTarget( m_AccumulationBuffer0 );
    136.         BlitMRT(m_CommandBuffer, m_Material, 0);
    137.  
    138.  
    139.         //  Pass1: Final merge into scene
    140.         BuiltinRenderTextureType colorRTT = BuiltinRenderTextureType.CameraTarget;
    141.  
    142.         m_CommandBuffer.SetRenderTarget( colorRTT, BuiltinRenderTextureType.None );
    143.         BlitMRT(m_CommandBuffer, m_Material, 1);
    144.  
    145.         gpuProfiler.End( ref m_CommandBuffer );
    146.     }
    147.  
    148.     //[ImageEffectOpaque]
    149.     //void OnRenderImage(RenderTexture source, RenderTexture destination)
    150.     //{
    151.     //}
    152. }
    153.  
    154.  
    The shader:

    Code (CSharp):
    1.  
    2. Shader "Hidden/TestDoubleBufferingCB"
    3. {
    4.    Properties
    5.    {
    6.    }
    7.    SubShader
    8.    {
    9.        // No culling or depth
    10.        Cull Off ZWrite Off ZTest Always
    11.  
    12.        CGINCLUDE
    13.        #include "UnityCG.cginc"
    14.  
    15.        sampler2D _CameraGBufferTexture3;
    16.        sampler2D _GBuffer3Copy;
    17.        sampler2D _AccumulationBuffer;
    18.        sampler2D _PrevAccumulationBuffer;
    19.  
    20.        struct v2f
    21.        {
    22.            half4 pos : SV_POSITION;
    23.            half2 uv : TEXCOORD0;
    24.        };
    25.  
    26.        v2f_img vert(appdata_img v)
    27.        {
    28.            v2f_img OUT;
    29.            OUT.pos = float4(v.vertex.xyz, 1);
    30.            OUT.uv = ComputeScreenPos(OUT.pos).xy;
    31.            return OUT;
    32.        }
    33.        ENDCG
    34.  
    35.        //Pass 0 - Debug color accumulation
    36.        Pass
    37.        {
    38.             Blend One Zero
    39.  
    40.            CGPROGRAM
    41.  
    42.            #pragma vertex vert
    43.            #pragma fragment frag
    44.            #pragma target 5.0
    45.            #pragma only_renderers d3d11
    46.  
    47.            struct appdata
    48.            {
    49.                float4 vertex : SV_POSITION;
    50.                float2 uv : TEXCOORD0;
    51.            };
    52.  
    53.            fixed4 frag(appdata i) : SV_Target
    54.            {
    55.                 float2 uv = i.uv;
    56.  
    57.                 float4 colorTest = float4( 0.1, 0.01, 0.001, 1 );
    58.                 float4 colorPrev = tex2D( _PrevAccumulationBuffer, i.uv );
    59.  
    60.                 return colorTest + colorPrev;
    61.            }
    62.            ENDCG
    63.        }
    64.  
    65.         //  Pass 1: merge with the scene
    66.        Pass
    67.        {
    68.             Blend One OneMinusSrcColor
    69.  
    70.            CGPROGRAM
    71.  
    72.            #pragma vertex vert
    73.            #pragma fragment frag1
    74.            #pragma target 5.0
    75.            #pragma only_renderers d3d11
    76.  
    77.            struct appdata
    78.            {
    79.                float4 vertex : SV_POSITION;
    80.                float2 uv : TEXCOORD0;
    81.            };
    82.  
    83.            fixed4 frag1(appdata i) : SV_Target
    84.            {
    85.                 float2 uv = i.uv;
    86.  
    87.                 float4 colorAccumulation = tex2D(_AccumulationBuffer, i.uv);
    88.  
    89.                 float fRatio = 0.5;
    90.                 return float4( colorAccumulation.xyz * fRatio, 1);
    91.            }
    92.            ENDCG
    93.        }
    94.     }
    95. }
    96.  
    Custom profiler used along with Unity profiler (not sure why Unity profiler gives different results than the script profiler):
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Profiling;
    3. using UnityEngine.Rendering;
    4.  
    5. public class GPUProfiler
    6. {
    7.     protected string m_profilerName = "";
    8.     protected int m_NbFrames = 1;
    9.     protected float m_CumulativeProfilerMs = 0;
    10.     protected bool m_IsProfilerResetRequired = true;
    11.     protected int m_NbFramesMod = 60;
    12.  
    13.     public GPUProfiler( string name, int nbFramesMod = 60 )
    14.     {
    15.         m_profilerName = name;
    16.         m_NbFramesMod = nbFramesMod;
    17.     }
    18.  
    19.     public void Begin( ref CommandBuffer cb )
    20.     {
    21.         if( m_IsProfilerResetRequired )
    22.         {
    23.             cb.BeginSample( m_profilerName );
    24.             m_IsProfilerResetRequired = false;
    25.         }
    26.     }
    27.  
    28.     public void End( ref CommandBuffer cb )
    29.     {
    30.         //  End Profiling and display time in ms
    31.         if( Time.frameCount % m_NbFramesMod == 0 )
    32.         {
    33.             cb.EndSample( m_profilerName );
    34.             Sampler sampler = Sampler.Get( m_profilerName );
    35.             Recorder recorder = sampler.GetRecorder();
    36.  
    37.             //  Compute FPS cumulative moving average
    38.             m_NbFrames++;
    39.             float fTimeMs = recorder.elapsedNanoseconds / 1000000;
    40.             m_CumulativeProfilerMs = m_CumulativeProfilerMs + ( fTimeMs - m_CumulativeProfilerMs) / m_NbFrames;
    41.  
    42.             m_IsProfilerResetRequired = true;
    43.  
    44.             Debug.Log( "Profiler " + m_profilerName + "  Elapsed time: " + m_CumulativeProfilerMs + " ms;  frame " + Time.frameCount );
    45.         }
    46.     }
    47. }
     
    Last edited: Oct 10, 2018