Search Unity

Question How to use GPU profiler stats?

Discussion in 'High Definition Render Pipeline' started by colinleet, Feb 16, 2021.

  1. colinleet

    colinleet

    Joined:
    Nov 20, 2019
    Posts:
    189
    Hello @MartinTilo (tagging you because of this very useful post, and I was wonder what might have changed since July),

    I'm using HDRP trying to get some GPU profiler stats at runtime but haven't been having any luck as of yet. (I'm using 2020.2.4, HDRP 10.3.1 on Windows.)

    When I turn off GraphicsJobs to allow GPU profiling I can see how long each step takes when by drilling down the hierarchy in the GPU profiler. But this takes a bunch of time to individually find/select each Post Processing step, and then I need to manually reselect it every time I shift frames... Which is why I'm trying to do it with ProfilerRecorders or possible the old Recorders.

    GPU Times.jpg

    I've set up a number of ProfilerRecorders which I have running in editor and in dev builds so I can see how much time the post processing stack is taking. The problem I'm getting is that I only seem to be able to get the CPU times for these calls.

    CPU Times.jpg

    I'm aware with the old profiler system there was a option for getting gpu time with gpuElapsedNanoseconds API. However when I try to use those I have no way for knowing what of the 3.5k+ available tags (name lists retrieved with this and this) are meant to work with a GPU.

    The only setting I can find around collecting GPU times is when creating a CustomSampler -- but I'm not sure how I theoretically could create one to cover already existing HDPR code... without editing the HDRP package directly...

    I was wondering if:

    1) Is it possible to determine if an already existing profiler sample records CPU vs GPU time? (Are all of the profiler names still just for CPU code or do only custom samplers support GPU timings still?)

    2) What if the profiler names are the same for the GPU as they are in the CPU? (E.g. CPU Bloom vs GPU Bloom) How would those samplers be differentiated?

    3) Is there (or will there eventually be) any ways to specifically try to get GPU times instead of CPU times with the ProfilerRecorders as there is with Recorders?

    4) Is there any ways to get the overall GPU time from ProfilerRecorder or Recorder -- or from any other method when using HDRP? (What is shown at the middle of the Profiler window next to CPU time.)

    Best regards,
    - Colin
     
    Last edited: Feb 17, 2021
    dog_funtom likes this.
  2. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,455
    That issue is addresses in 2021.1 and sadly is too big and risky a set of changes to backport it. But when profiling a build, you can totally use an editor to open an empty project and use it's Profiler to profile your 2020.2 build. Then it's: select once, see the selection in every view and frame.


    At this moment, only Recorder API can record GPU timings. In 2021.2, it can now do so on all platforms. Making ProfilerRecorder API also work with GPU timings is still in progress.

    The same marker can contain both CPU and GPU times. With Recorder API, CPU times are then reported via elapsedNanoseconds and GPU timings via gpuElapsedNanoseconds.


    Once ProfilerRecorder supports recording GPU timings, you'll be able to get all GPU timing markers via ProfilerRecorderHandle.GetAvailable().

    As of right now, this is still true:

    FrameTimingManager can provide some such measurements on some platforms and graphics APIs. Now that we got GPU measurements implemented on all of these and exposed through the Recorder API for 2021.2, aligning FrameTimingManager and ProfilerRecorder to use these new measurement capabilites is up next.
     
    dog_funtom and colinleet like this.
  3. colinleet

    colinleet

    Joined:
    Nov 20, 2019
    Posts:
    189
    Thank you for the great response!
     
  4. colinleet

    colinleet

    Joined:
    Nov 20, 2019
    Posts:
    189
    Ok I've made a testing script which goes through all of the available Reportes and determines which ones are currently report GPU times. So far I've tested it in 2020.2 (editor), 2020.2 (build) and 2021.1.b6 (editor): only in 2020.2 (editor) am I seeing any reported GPU times, and then just the following ten:

    WorkingGPURecorders.jpg

    This is actually a great improvement for me!

    Question)

    1) Is it expected to only get such a few number of (built in) working GPU tags in 2020.2?

    2) Is it likely that we'll be getting access to more working GPU profiler tags with subsequent releases of 2020.2 LTS or 2021.1 when it's out of beta?

    3) Is the lack of any currently working tags in 2021.1 with HDRP 11 because this is still a feature in development and it'll be filled in time? (Potentially a long time, as you said earlier like 2021.2?)

    Source code for my testing script (CCO)

    [Edit: For anyone wanting to use this code I posted an updated version of it below which works much better --> Returns 50+ recorders instead of around 10.]

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Profiling;
    5.  
    6. /*
    7. * Copyright 2021 By Colin Leet (https://leet.games/)
    8.  
    9. This is free and unencumbered software released into the public domain.
    10.  
    11. Anyone is free to copy, modify, publish, use, compile, sell, or
    12. distribute this software, either in source code form or as a compiled
    13. binary, for any purpose, commercial or non-commercial, and by any
    14. means.
    15.  
    16. In jurisdictions that recognize copyright laws, the author or authors
    17. of this software dedicate any and all copyright interest in the
    18. software to the public domain.We make this dedication for the benefit
    19. of the public at large and to the detriment of our heirs and
    20. successors.We intend this dedication to be an overt act of
    21. relinquishment in perpetuity of all present and future rights to this
    22. software under copyright law.
    23.  
    24.  
    25. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    26. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    27. MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    28. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
    29. OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
    30. ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    31. OTHER DEALINGS IN THE SOFTWARE.
    32.  
    33. For more information, please refer to<http://unlicense.org/>
    34. */
    35.  
    36. namespace LeetProfiling {
    37.     /// <summary>
    38.     /// This is a testing script for find out which of Unity's samplers can report GPU times on your given platform.
    39.     ///
    40.     /// This script creates recorders for all of available <see cref="Sampler.GetNames(List{string})"/>.
    41.     /// It then runs for a bit and checks which recorders report positive values for <see cref="Recorder.gpuElapsedNanoseconds"/>.
    42.     ///
    43.     /// Note: Only the editor and development builds can use Recorders as of Unity 2020.2
    44.     /// </summary>
    45.     public class TestGPURecorders : MonoBehaviour {
    46.  
    47.         [Tooltip("Enables the recorder GPU testing.")]
    48.         public bool RunTestInStart = true;
    49.  
    50.         [Tooltip("Seconds which will be waited before test for GPU profilers.")]
    51.         [Range(1, 30f)]
    52.         public float WaitTimeBeforeTest = 5f;
    53.  
    54.         [Header("Runtime Test Results")]
    55.         [Tooltip("Number of recorders which was retrieved with Sampler.GetNames")]
    56.         public int NumRecorders = 0;
    57.  
    58.         [Tooltip("Number of recorders which reported being valid.")]
    59.         public int ValidRecorders = 0;
    60.  
    61.         [Tooltip("Number of recorders which has positive GPU times.")]
    62.         public int ActiveGPURecorders = 0;
    63.  
    64.         /// <summary>
    65.         /// List of names generated from <see cref="Sampler.GetNames(List{string})"/>.
    66.         /// </summary>
    67.         private List<string> allNames = new List<string>();
    68.  
    69.         /// <summary>
    70.         /// List of all recorders generated from <see cref="Sampler.GetNames(List{string})"/>.
    71.         /// </summary>
    72.         private List<Recorder> allRecorders = new List<Recorder>();
    73.  
    74.         /// <summary>
    75.         /// List of the keys for <see cref="GpuRecorders"/>.
    76.         /// </summary>
    77.         public List<string> GpuNamesActive = new List<string>();
    78.  
    79.         /// <summary>
    80.         /// This is a dictionary of all of the activley recording GPU recorders.
    81.         /// </summary>
    82.         public Dictionary<string, Recorder> GpuRecorders = new Dictionary<string, Recorder>();
    83.  
    84.         private void Start() {
    85.             if ( RunTestInStart ) {
    86.                 // Init the recorder vars.
    87.                 InitiateAllRecorders();
    88.                 StartCoroutine(WaitBeforeTest());
    89.             }
    90.         }
    91.  
    92.         /// <summary>
    93.         /// Initiates all of the supported recorders.
    94.         /// </summary>
    95.         public void InitiateAllRecorders() {
    96.             // Only run in editor or debug builds.
    97.             if ( !( Application.isEditor || Debug.isDebugBuild ) ) return;
    98.  
    99.             allNames.Clear();
    100.             allRecorders.Clear();
    101.             GpuNamesActive.Clear();
    102.             GpuRecorders.Clear();
    103.  
    104.             Sampler.GetNames(allNames);
    105.             NumRecorders = allNames.Count;
    106.  
    107.             int lenRecorders = allNames.Count;
    108.             for ( int i = 0; i < lenRecorders; i++ ) {
    109.                 allRecorders.Add(Recorder.Get(allNames[i]));
    110.                 allRecorders[i].enabled = true;
    111.             }
    112.         }
    113.  
    114.         /// <summary>
    115.         /// Waits a period then runs the test, then logs the results.
    116.         /// </summary>
    117.         public IEnumerator WaitBeforeTest() {
    118.             // Only run in editor or debug builds.
    119.             if ( !( Application.isEditor || Debug.isDebugBuild ) ) yield break;
    120.  
    121.             // Wait for the scene to get going.
    122.             yield return new WaitForSecondsRealtime(WaitTimeBeforeTest);
    123.  
    124.             TestGPURecordingProfilers();
    125.  
    126.             yield return null;
    127.  
    128.             LogResults();
    129.         }
    130.  
    131.  
    132.         /// <summary>
    133.         /// Tests which GPU times have positive values.
    134.         /// This may be called multiple times outside of <see cref="WaitBeforeTest"/>.
    135.         /// </summary>
    136.         public void TestGPURecordingProfilers() {
    137.             ValidRecorders = 0;
    138.             ActiveGPURecorders = 0;
    139.  
    140.             // Determine all of the active gpu recorders
    141.             int lenRecorders = allNames.Count;
    142.             for ( int i = 0; i < lenRecorders; i++ ) {
    143.                 if ( allRecorders[i].isValid ) {
    144.  
    145.                     ValidRecorders++;
    146.  
    147.                     if ( allRecorders[i].gpuElapsedNanoseconds > 0 ) {
    148.                         if ( !GpuRecorders.ContainsKey(allNames[i]) ) {
    149.  
    150.                             GpuNamesActive.Add(allNames[i]);
    151.                             GpuRecorders[allNames[i]] = allRecorders[i];
    152.  
    153.                             ActiveGPURecorders++;
    154.                         }
    155.                     }
    156.                 }
    157.             }
    158.         }
    159.  
    160.         /// <summary>
    161.         /// Lists all of the active gpu recorders.
    162.         /// </summary>
    163.         public void LogResults() {
    164.             Debug.LogFormat("{0} Recorders are reporting positive times for the GPU... Listing <{0}>: ", ActiveGPURecorders);
    165.             int countActive = GpuNamesActive.Count;
    166.             for ( int i = 0; i < countActive; i++ ) {
    167.                 Debug.LogFormat("{0}: {1:N5} ms",
    168.                     GpuNamesActive[i],
    169.                     GpuRecorders[GpuNamesActive[i]].gpuElapsedNanoseconds * 1e-6);
    170.             }
    171.         }
    172.     }
    173. }
     
    Last edited: Feb 17, 2021
  5. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,455
    No, I suspect you need to wait some ~3 frames after startup for the GPU Samplers to have fully registered with the Profiler so that they'll actually show up when calling Sampler.GetNames. In the Editor, some markers might get used before you enter playmode or in a previous run in the same Editor session so they would already be known to the system.

    I don't see a likely chance for new GPU marker additions in the base Unity Releases for these versions. Also, the base version only has very few GPU markers to begin with. That said, the relevant releases for GPU markers aren't tied to the base editor & player Unity version but to the SRPs' version and I don't know what their plans are on that topic.

    As I mentioned above, the test scenario you set up might be missing some markers. Searching the HDRP packaged C# code for "CommandBuffer.BeginSample" usages might give you a fuller picture, also in cases where that particular Profiler Marker was just not hit in the frame you are checking against, as not all frames are necessarily using all features.
    That said, higher coverage of platforms and graphics APIs as well as linking stuff up with FrameTimingManager and the current focus we have on this might lead to further additions down the road, likely only for 2021.2 and up though as all other versions are already branched off and stabilizing. But that is a bit of conjecture on my part.
     
    colinleet likes this.
  6. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,455
    Also, ProfilerRecorderHandle.GetAvailable should also include the marker names for the GPU enabled samplers, same as Sampler.GetNames, but ProfilerRecorderHandle.GetAvailable also gives you the available Rendering Counters). Since the API the GPU markers are using doesn't allow for setting a category, they'd all get lumped into the Scripting category until we add GPU capabilities to ProfilerMarker API.
     
  7. colinleet

    colinleet

    Joined:
    Nov 20, 2019
    Posts:
    189
    Thanks for the fast and useful responses!

    Oh ok that makes a lot of sense! I thought the three frames delay was in relation to waiting till after a recorder was fully created and enabled from a script (not also in relation to needed to wait the frames for the names to be added to Sampler.GetNames). I'll modify my script to allow for that in debug builds.

    Also just to confirm)

    Does initiation in this context just mean waiting three(ish) frames after level launch for the Sampler.GetNames to fully populate? Or does it also include needing to wait the frames after setting a particular: "recorder.Enabled = true".

    And/or waiting three frames after first querying an already enabled recorder with: "Recorder.gpuElapsedNanoseconds".

    That's why I added a variable for the length of the delay after creating/enabling the recorders allow any start to behavior on other scripts to "warm up" before testing if their GPU recorders were active.
     
    Last edited: Feb 18, 2021
  8. colinleet

    colinleet

    Joined:
    Nov 20, 2019
    Posts:
    189
    Ok I've updated my testing script and now I'm seeing around 51 available profilers (when using VR), with near identical numbers of GPU profilers in editor and dev builds! This is SO AWSOME!

    Also it's probably worth noting that I did see an small increase (2-3) in the number of available profiler recorders between testing them at round 8 and 13 seconds after level load (I didn't bother to figure out which ones for now). For anyone reading, if your Recorder isn't reporting immediately you might just need to wait a few more seconds for it to become active.

    Here it the updated code:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Profiling;
    5.  
    6. /*
    7. * Copyright 2021 By Colin Leet (https://leet.games/)
    8.  
    9. This is free and unencumbered software released into the public domain.
    10.  
    11. Anyone is free to copy, modify, publish, use, compile, sell, or
    12. distribute this software, either in source code form or as a compiled
    13. binary, for any purpose, commercial or non-commercial, and by any
    14. means.
    15.  
    16. In jurisdictions that recognize copyright laws, the author or authors
    17. of this software dedicate any and all copyright interest in the
    18. software to the public domain.We make this dedication for the benefit
    19. of the public at large and to the detriment of our heirs and
    20. successors.We intend this dedication to be an overt act of
    21. relinquishment in perpetuity of all present and future rights to this
    22. software under copyright law.
    23.  
    24.  
    25. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    26. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    27. MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    28. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
    29. OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
    30. ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    31. OTHER DEALINGS IN THE SOFTWARE.
    32.  
    33. For more information, please refer to<http://unlicense.org/>
    34. */
    35.  
    36. namespace LeetProfiling {
    37.     /// <summary>
    38.     /// This is a testing script for find out which of Unity's samplers can report GPU times on your given platform.
    39.     ///
    40.     /// This script creates recorders for all of available <see cref="Sampler.GetNames(List{string})"/>.
    41.     /// It then runs for a bit and checks which recorders report positive values for <see cref="Recorder.gpuElapsedNanoseconds"/>.
    42.     ///
    43.     /// Note: Only the editor and development builds can use Recorders as of Unity 2020.2
    44.     /// </summary>
    45.     public class TestGPURecorders : MonoBehaviour {
    46.  
    47.         [Tooltip("Enables the recorder GPU testing.")]
    48.         public bool RunTestInStart = true;
    49.  
    50.         [Tooltip("Seconds which will be waited before test before initiating the profilers and allNames lists.")]
    51.         [Range(3, 9f)]
    52.         public float WaitTimeBeforeInitiation = 3f;
    53.  
    54.         [Tooltip("Seconds which will be waited before running the test for GPU profilers.")]
    55.         [Range(1, 30f)]
    56.         public float WaitTimeBeforeTest = 5f;
    57.  
    58.         [Tooltip("Runs the test twice...")]
    59.         public bool RunTestTwice = true;
    60.  
    61.         [Header("Runtime Test Results")]
    62.         [Tooltip("Number of recorders which was retrieved with Sampler.GetNames")]
    63.         public int NumRecorders = 0;
    64.  
    65.         [Tooltip("Number of recorders which reported being valid.")]
    66.         public int ValidRecorders = 0;
    67.  
    68.         [Tooltip("Number of recorders which have positive GPU times.")]
    69.         public int ActiveGPURecorders = 0;
    70.  
    71.         /// <summary>
    72.         /// List of names generated from <see cref="Sampler.GetNames(List{string})"/>.
    73.         /// </summary>
    74.         private List<string> allNames = new List<string>();
    75.  
    76.         /// <summary>
    77.         /// List of all recorders generated from <see cref="Sampler.GetNames(List{string})"/>.
    78.         /// </summary>
    79.         private List<Recorder> allRecorders = new List<Recorder>();
    80.  
    81.         /// <summary>
    82.         /// List of the keys for <see cref="GpuRecorders"/>.
    83.         /// </summary>
    84.         public List<string> GpuNamesActive = new List<string>();
    85.  
    86.         /// <summary>
    87.         /// This is a dictionary of all of the actively recording GPU recorders.
    88.         /// </summary>
    89.         public Dictionary<string, Recorder> GpuRecorders = new Dictionary<string, Recorder>();
    90.  
    91.         private void Start() {
    92.             if ( RunTestInStart ) {
    93.                 StartCoroutine(StartTestsWithDelays());
    94.             }
    95.         }
    96.  
    97.         /// <summary>
    98.         /// Initiates all of the supported recorders.
    99.         /// </summary>
    100.         public void InitiateAllRecorders() {
    101.             // Only run in editor or debug builds.
    102.             if ( !( Application.isEditor || Debug.isDebugBuild ) ) return;
    103.  
    104.             allNames.Clear();
    105.             allRecorders.Clear();
    106.             GpuNamesActive.Clear();
    107.             GpuRecorders.Clear();
    108.  
    109.             Sampler.GetNames(allNames);
    110.             NumRecorders = allNames.Count;
    111.             ValidRecorders = 0;
    112.             ActiveGPURecorders = 0;
    113.  
    114.             int lenRecorders = allNames.Count;
    115.             for ( int i = 0; i < lenRecorders; i++ ) {
    116.                 allRecorders.Add(Recorder.Get(allNames[i]));
    117.                 allRecorders[i].enabled = true;
    118.             }
    119.         }
    120.  
    121.         /// <summary>
    122.         /// Waits a period then runs the test, then logs the results.
    123.         /// </summary>
    124.         public IEnumerator StartTestsWithDelays() {
    125.             // Only run in editor or debug builds.
    126.             if ( !( Application.isEditor || Debug.isDebugBuild ) ) yield break;
    127.  
    128.             // Wait for the scene to get going.
    129.             yield return new WaitForSecondsRealtime(WaitTimeBeforeInitiation);
    130.  
    131.             // Init the recorder vars.
    132.             InitiateAllRecorders();
    133.  
    134.             // Wait for the scene to get going.
    135.             for ( int i = 0; i < 2; i++ ) {
    136.  
    137.                 yield return new WaitForSecondsRealtime(WaitTimeBeforeTest);
    138.  
    139.                 AddNewGPURecordingProfilers();
    140.  
    141.                 yield return null;
    142.  
    143.                 LogResults();
    144.  
    145.                 if ( !RunTestTwice ) yield break;
    146.             }
    147.         }
    148.  
    149.  
    150.         /// <summary>
    151.         /// Tests which GPU times have positive values.
    152.         /// This may be called multiple times outside of <see cref="StartTestsWithDelays"/>.
    153.         /// </summary>
    154.         public void AddNewGPURecordingProfilers() {
    155.             // Determine all of the active gpu recorders
    156.             int lenRecorders = allNames.Count;
    157.             for ( int i = 0; i < lenRecorders; i++ ) {
    158.                 if ( allRecorders[i].isValid ) {
    159.  
    160.                     ValidRecorders++;
    161.  
    162.                     if ( allRecorders[i].gpuElapsedNanoseconds > 0 ) {
    163.                         if ( !GpuRecorders.ContainsKey(allNames[i]) ) {
    164.  
    165.                             GpuNamesActive.Add(allNames[i]);
    166.                             GpuRecorders[allNames[i]] = allRecorders[i];
    167.  
    168.                             ActiveGPURecorders++;
    169.                         }
    170.                     }
    171.                 }
    172.             }
    173.         }
    174.  
    175.         /// <summary>
    176.         /// Lists all of the active gpu recorders.
    177.         /// </summary>
    178.         public void LogResults() {
    179.             Debug.LogFormat("{0} Recorders are reporting positive times for the GPU... Listing <{0}>: ", ActiveGPURecorders);
    180.             int countActive = GpuNamesActive.Count;
    181.             for ( int i = 0; i < countActive; i++ ) {
    182.                 Debug.LogFormat("{0}: {1:N5} ms",
    183.                     GpuNamesActive[i],
    184.                     GpuRecorders[GpuNamesActive[i]].gpuElapsedNanoseconds * 1e-6);
    185.             }
    186.         }
    187.     }
    188. }
     
    Last edited: Feb 18, 2021
    StaggartCreations and MartinTilo like this.
  9. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,455
    Both. First the Markers need to be hit by code execution, then there's the frame delay after turning recorders on these on.
    But it seems you already got that working :)
     
  10. projectorgames_unity

    projectorgames_unity

    Joined:
    Oct 15, 2018
    Posts:
    107
    Can I use these at runtime?
     
  11. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,455
    Yes, but there's probably only a very small set of them that are available in Non-Development builds.
     
  12. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,455
    Measuring GPU timings, e.g. via these recorders can have a bit of an impact on the general performance, depending also on the graphics APIs. For more info and also some metrics that are available in Release builds, check out this thread.
     
  13. THE2FUN

    THE2FUN

    Joined:
    Aug 25, 2015
    Posts:
    63