Last active
March 3, 2025 14:40
-
-
Save MerlinVR/2da80b29361588ddb556fd8d3f3f47b5 to your computer and use it in GitHub Desktop.
Basic global profiling scripts for Udon. Throw one of each script on a game object that has a TMP UI text somewhere in its children.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MIT License | |
// Copyright (c) 2021 Merlin | |
using UdonSharp; | |
using UnityEngine; | |
[DefaultExecutionOrder(1000000000)] | |
public class GlobalProfileHandler : UdonSharpBehaviour | |
{ | |
TMPro.TextMeshProUGUI timeText; | |
GlobalProfileKickoff kickoff; | |
private void Start() | |
{ | |
kickoff = GetComponent<GlobalProfileKickoff>(); | |
timeText = GetComponentInChildren<TMPro.TextMeshProUGUI>(); | |
} | |
int currentFrame = -1; | |
float elapsedTime = 0f; | |
private void FixedUpdate() | |
{ | |
if (currentFrame != Time.frameCount) | |
{ | |
elapsedTime = 0f; | |
currentFrame = Time.frameCount; | |
} | |
if (kickoff) | |
elapsedTime += (float)kickoff.stopwatch.Elapsed.TotalSeconds * 1000f; | |
} | |
private void Update() | |
{ | |
if (currentFrame != Time.frameCount) // FixedUpdate didn't run this frame, so reset the time | |
elapsedTime = 0f; | |
elapsedTime += (float)kickoff.stopwatch.Elapsed.TotalSeconds * 1000f; | |
} | |
private void LateUpdate() | |
{ | |
elapsedTime += (float)kickoff.stopwatch.Elapsed.TotalSeconds * 1000f; | |
timeText.text = $"Update time:\n{elapsedTime:F4}ms"; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MIT License | |
// Copyright (c) 2021 Merlin | |
using UdonSharp; | |
using UnityEngine; | |
[DefaultExecutionOrder(-1000000000)] | |
public class GlobalProfileKickoff : UdonSharpBehaviour | |
{ | |
[System.NonSerialized] | |
public System.Diagnostics.Stopwatch stopwatch; | |
private void Start() | |
{ | |
stopwatch = new System.Diagnostics.Stopwatch(); | |
} | |
private void FixedUpdate() | |
{ | |
stopwatch.Restart(); | |
} | |
private void Update() | |
{ | |
stopwatch.Restart(); | |
} | |
private void LateUpdate() | |
{ | |
stopwatch.Restart(); | |
} | |
} |
Here's an update with PostLateUpdate support as well:
// GlobalProfileHandler.cs
#define AVERAGE_OUTPUT
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using UdonSharp;
using UnityEngine;
[DefaultExecutionOrder(1000000000)]
public class GlobalProfileHandler : UdonSharpBehaviour
{
public TMPro.TextMeshPro _timeText;
private GlobalProfileKickoff _kickoff;
private void Start()
{
_kickoff = GetComponent<GlobalProfileKickoff>();
}
private int _currentFrame = -1;
private float _elapsedTime = 0f;
#if AVERAGE_OUTPUT
private float _measuredFrametimeTotal = 0f;
private float _measuredTimeTotal = 0f;
private int _measuredTimeFrameCount = 0;
private const int MEASURE_FRAME_AMOUNT = 45;
#endif
private void FixedUpdate()
{
if (_currentFrame != Time.frameCount)
{
_elapsedTime = 0f;
_currentFrame = Time.frameCount;
}
if (_kickoff)
_elapsedTime += (float)_kickoff.stopwatch.Elapsed.TotalSeconds * 1000f;
}
private void Update()
{
if (_currentFrame != Time.frameCount) // FixedUpdate didn't run this frame, so reset the time
_elapsedTime = 0f;
_elapsedTime += (float)_kickoff.stopwatch.Elapsed.TotalSeconds * 1000f;
}
private void LateUpdate()
{
_elapsedTime += (float)_kickoff.stopwatch.Elapsed.TotalSeconds * 1000f;
}
float lastFrame;
public override void PostLateUpdate()
{
_elapsedTime += (float)_kickoff.stopwatch.Elapsed.TotalSeconds * 1000f;
float now = Time.timeSinceLevelLoad;
_measuredFrametimeTotal += (now - lastFrame) * 1000f;
lastFrame = now;
#if AVERAGE_OUTPUT
if (_measuredTimeFrameCount >= MEASURE_FRAME_AMOUNT)
{
_timeText.text = $"Udon: {(_measuredTimeTotal / _measuredTimeFrameCount):F4}ms\nFrametime: {(_measuredFrametimeTotal/_measuredTimeFrameCount):F4}ms";
_measuredTimeTotal = 0f;
_measuredFrametimeTotal = 0f;
_measuredTimeFrameCount = 0;
}
_measuredTimeTotal += _elapsedTime;
_measuredTimeFrameCount += 1;
#else
_timeText.text = $"Update time:\n{_elapsedTime:F4}ms";
#endif
}
}
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
// MIT License
// Copyright (c) 2021 Merlin
using UdonSharp;
using UnityEngine;
[DefaultExecutionOrder(-1000000000)]
public class GlobalProfileKickoff : UdonSharpBehaviour
{
[System.NonSerialized]
public System.Diagnostics.Stopwatch stopwatch;
private void Start()
{
stopwatch = new System.Diagnostics.Stopwatch();
}
private void FixedUpdate()
{
stopwatch.Restart();
}
private void Update()
{
stopwatch.Restart();
}
private void LateUpdate()
{
stopwatch.Restart();
}
public override void PostLateUpdate()
{
stopwatch.Restart();
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is my version of GlobalProfileHandler.cs that averages the output, so that the frametime is more readable - updating a text field 90 times a second isn't easy to read after all :) Also I switched to Unity.UI.Text since I couldn't get TMP to work, maybe a prefab could be added to this here. You can simply switch to direct ouput on demand by uncommenting the first line.