♪ [MUSIC] ♪
[BRONSON] Hi, everyone. This is Bronson Zgeb,
a content developer at Unity.
In this Unite Now session, I'm going to show you
how to capture profiler stats at runtime
using Unity's new runtime Profiler API.
This was added in Unity 2020.2
and it allows you to capture profiler stats
in the Editor in development builds
but also in release builds and on-device.
Let's get started.
I'm just going to jump right into my project.
You can see, I already have some profiler stats
set up here in the top left corner.
But I noticed I was having some performance issues on my phone
and I think it might be related to draw calls.
I'm going to add another line here to track my Draw Calls Count.
In order to start, I'm actually going to start
with a clean slate here.
This is a ProfilerController, and its job is going to be
to record the profiler stats,
and then display them to the screen.
I'm going to come over to the ProfilerRecorder documentation page,
and they have a nice little example script.
I'm just going to copy that directly into
my ProfilerController.
There's one little issue here. In the example script,
they use unsafe code to save unmanaged memory allocations.
You can do that, and if you're comfortable with unsafe code,
I recommend you do that.
But I just want to make it a bit simpler,
so I'm just going to use a List instead.
There's one other thing, in the example script,
they use OnGUI to display the stats.
Unfortunately, I don't think you'll be able to read this very well
in the video, so I'm going to display the stats on a Canvas.
I'm just going to create a TextMeshPro Text area.
Delete the OnGUI code.
And I just got to make sure to hook up...
a Text area, and then hit Play.
There we go.
Next, I wanted to show the draw calls.
I'm going to create a new ProfilerRecorder,
and that's going to record the draw calls.
onEnable, we initialize the ProfilerRecorder.
Here, you tell it which ProfilerCategory
the stat you're looking for is part of,
and then the name of the stat.
In our case, that's Draw Calls Count.
onDisable, we always want to dispose of our recorders
when we're done with them.
And then we're going to add a line on Update
to display the draw calls.
Here, we have some extra math.
This just converts a value from bytes into megabytes
to make it nicer to display.
In our case, we're getting a count back, not bytes
so we don't need that.
The two values above, those are bytes,
and then the top value here is actually in nanoseconds,
so we have some extra formatting just to convert from nanoseconds
into milliseconds.
Here you see the draw calls are being displayed.
Let me just recap what we did here.
We create a new ProfilerRecorder,
the drawCallsCountRecorder;
onEnable, we initialize the ProfilerRecorder,
we tell it which stat to track;
onDisable, always dispose of your recorders
when you're done with them;
and in Update, we added a line to our display string
that just outputs the Draw Calls Count.
You're probably wondering though,
how did I know what to put in here?
For that, we're going to go to the Documentation page,
and up here, "For a list of built-in Profiler markers"
you can see the documentation pages on the Memory Profiler,
Rendering Profiler, Virtual Texturing Profiler.
Draw calls is going to be part of the Rendering Profiler.
If we go down here,
see that there's a nice table of all the available stats
with their name, a description of the stat,
and finally, if they're available in the Release Player or not.
I mentioned earlier, this API works in release builds
as well as development builds or in the Editor,
but not all the stats are available in release.
That's why we have this extra column here
to tell you whether or not this particular stat
would be available in release or not.
Just so you know, I picked Draw Calls as my example,
but there a lot of other useful stats available here,
such as the SetPass Calls,
the Triangles Count, and the Vertex Count,
and these are all available in the Release Player.
Next up, I'm worried that my Player character
takes too long to update, so I wanted to add
another stat to track that.
I'm going to create a new ProfilerRecorder
and that's going to track
how long it takes for me to update my Player.
Since my Player character update isn't built in,
it's something I wrote, what can I pass into my StartNew
to let it know which block of code I'm profiling?
Well, for that, we can actually create a new ProfilerMarker.
When you create a ProfilerMarker, you have to give it a name.
You can call it whatever you want,
but it's a good idea to be descriptive.
I'm just going to call mine "Player.Update".
And now, in my game, I have a game manager
that calls Update on my Player,
so that makes it really easy to wrap my UpdatePlayer
with my new marker.
This is basically like a stopwatch. I'm just saying,
"Start timing here, and stop timing here,
and tell me how long it took to get from here to here."
In between, we're updating the Player;
start timing, update the Player;
stop timing, tell me how long that took.
With that, I can now just pass...
my marker directly into my StartNew
and that will work.
Always make sure to dispose of your recorders
when you're done with them.
And then we just need one more line
to our display string.
Except in this case, we're getting back nanoseconds.
I'm just going to copy the line from Frame Time
that converts a value from nanoseconds into milliseconds.
And I'm going to put that in here.
Just going to make sure to pass in the right recorder.
You probably noticed, we're using a function,
GetRecorderFrameAverage, that's available up here.
All this is doing is getting an average of the passed
bunch of frames that we've already recorded,
and then using that value instead,
and then converting it to milliseconds and showing that.
The reason why we do that is just because
the raw nanosecond value from frame to frame
is going to be too wild. It's not that easy to look at,
basically, to display.
And there you go. Now we can see our UpdatePlayer
is taking somewhere between 0.1 and 0.2 milliseconds.
You may not even see that it's stuttering in the video.
And just for the sake of completion,
I'm going to make a non-development build,
a release build, and show you
that this still works.
I'm in a release build, I can see the draw calls,
I can see the other stats that we got from the sample script,
and I can see that my Update Player actually takes
very little time at all in a release build, so that's great.
I'm just going to go back to the documentation real quick.
I just want to show...
The main meat of the new API is in this ProfilerRecorder.
This is what's going to do the recording
and you can grab the values and display it.
Then to know what you can actually track,
we can go to the different module pages,
like Rendering Profiler, et cetera.
Also, if you really want a full, exhaustive list
of all the available metrics,
you can go to this ProfilerRecorderHandle.GetAvailable.
And here we have another example script.
What this does is it actually enumerates through
every single metric that's available
and then outputs that.
I did that earlier, and I just output it to a file
so we could take a look.
Here's the file where I output all the available stats.
Each line in this file represents a ProfilerRecorderHandle,
which a ProfilerRecorder could hook into
and then gather metrics from.
There are a lot of stats available here.
If you ever wanted to know, let's say,
how many audio clips do I have in Memory,
there's a handle for that. Or how many bytes
are the audio clips taking in memory, there's a handle for that.
If you wanted to look at how long each Particle System module
was taking per frame,
there's a handle for each individual module there.
I made this list while I was running in the Editor,
so there are quite a few stats available.
Remember, when you make a development build
or a release build, there aren't going to be as many stats there.
I encourage you to try it out for yourself to see what's available.
By the way, if you try to hook into a stat
that isn't available,
you're not going to get an error, it's not going to crash,
the ProfilerRecorder just isn't going to return anything.
If you want to check if a stat is available
in the current configuration,
you can use ProfilerRecorder.Valid
to make sure that it's connected to a valid state.
If not, you could just remove it from your display.
Before I go, I just wanted to leave you with
some helpful resources. There are links to
the documentation pages that we looked at in this talk,
as well as the example scripts that we wrote in this talk.
Thanks for watching my Unite Now session.
I hope you learned something, and I just want to encourage you
to check out unity.com/unitenow
for other inspiring Unite Now sessions.
♪ [MUSIC] ♪